Overall Statistics |
Total Trades 512 Average Win 0.71% Average Loss -0.42% Compounding Annual Return 10.961% Drawdown 9.000% Expectancy 0.820 Net Profit 150.244% Sharpe Ratio 1.154 Probabilistic Sharpe Ratio 70.157% Loss Rate 32% Win Rate 68% Profit-Loss Ratio 1.66 Alpha 0.072 Beta 0.022 Annual Standard Deviation 0.064 Annual Variance 0.004 Information Ratio -0.117 Tracking Error 0.134 Treynor Ratio 3.435 Total Fees $512.00 |
using System.Collections.Generic; using QuantConnect.Data; using System; using System.Collections; using QuantConnect.Orders; using System.Globalization; using QuantConnect.Data.Market; using QuantConnect.Algorithm; using QuantConnect.Securities; using QuantConnect.Brokerages; namespace QuantConnect { public class PortfolioOfStrategies : QCAlgorithm { // Algorithm parameters private int mStartYear = 2011; public int mStartMonth = 1; public int mStartDay = 28; private decimal mStartingCash = 10000; private Resolution mResolution = Resolution.Minute; // Hourly or more frequent. Daily and up bugs. private decimal mLeverage = 0.95m; private bool mRebalanceOnLaunch = true; // Whether the algorithms should trigger a rebalance when launching the algorithm (true) or wait until the next rebalance period (false) protected decimal mAvoidRebalancesBelowThisAmount = 500.0m; // 500 is a good value. Value, in dollars, under which small rebalances will be ignored to cut on fees. private decimal mAllocationRatioUIS = 1.0m; private decimal mAllocationRatioUISZIV = 0.0m; private decimal mAllocationRatioBRS = 0.0m; private decimal mAllocationRatioGLDUSD = 0.0m; private decimal mAllocationRatioMYRS = 0.0m; private decimal mAllocationRatioNASDAQ_LI_SIGNALS = 0.0m; private decimal mAllocationRatioGLD_LI_SIGNALS = 0.0m; private decimal mAllocationRatioUIS_LI_SIGNALS = 0.0m; private decimal mAllocationRatioFFA = 0.0m; private decimal mAllocationRatioEMPTYALGO = 0.0m; private decimal mAllocationRatioSRCTEST = 0.0m; //Other variables public bool mBacktestDatesOverriden = false; private List<SubAlgorithm> mSubAlgorithms = null; EquityExchange mEquityMarket = new EquityExchange(); public override void Initialize() { Portfolio.MarginCallModel = MarginCallModel.Null; SetCash(mStartingCash); SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage); // Request SPY and USDCAD data AddSecurity(SecurityType.Equity, "SPY", mResolution, true, 3, false); AddSecurity(SecurityType.Forex, "USDCAD", mResolution, true, 3, false); // Instantiate our subalgorithm classes mSubAlgorithms = new List<SubAlgorithm>(); mSubAlgorithms.Add(new UIS (this, mResolution, mAllocationRatioUIS, mStartYear, mRebalanceOnLaunch)); mSubAlgorithms.Add(new UISZIV (this, mResolution, mAllocationRatioUISZIV, mStartYear, mRebalanceOnLaunch)); mSubAlgorithms.Add(new MYRS (this, mResolution, mAllocationRatioMYRS, mStartYear, mRebalanceOnLaunch)); mSubAlgorithms.Add(new BRS (this, mResolution, mAllocationRatioBRS, mStartYear, mRebalanceOnLaunch)); mSubAlgorithms.Add(new GLDUSD (this, mResolution, mAllocationRatioGLDUSD, mStartYear, mRebalanceOnLaunch)); mSubAlgorithms.Add(new NASDAQ_LI_SIGNALS(this, mResolution, mAllocationRatioNASDAQ_LI_SIGNALS, mStartYear, mRebalanceOnLaunch)); mSubAlgorithms.Add(new GLD_LI_SIGNALS (this, mResolution, mAllocationRatioGLD_LI_SIGNALS, mStartYear, mRebalanceOnLaunch)); mSubAlgorithms.Add(new UIS_LI_SIGNALS (this, mResolution, mAllocationRatioUIS_LI_SIGNALS, mStartYear, mRebalanceOnLaunch)); mSubAlgorithms.Add(new FFA (this, mResolution, mAllocationRatioFFA, mStartYear, mRebalanceOnLaunch)); mSubAlgorithms.Add(new EMPTYALGO (this, mResolution, mAllocationRatioEMPTYALGO, mStartYear, mRebalanceOnLaunch)); mSubAlgorithms.Add(new Test_SRC (this, mResolution, mAllocationRatioSRCTEST, mStartYear, mRebalanceOnLaunch)); // Initialize the subalgorithms foreach(SubAlgorithm wSubAlgorithm in mSubAlgorithms) { if(wSubAlgorithm.mAllocationRatio > 0.01m) { wSubAlgorithm.Initialize(mLeverage * wSubAlgorithm.mAllocationRatio * (Portfolio.TotalPortfolioValue - GetUnconvertedCadValueInUsd())); } } // Set up our analysis span (unless a strategy has overridden them) if (!mBacktestDatesOverriden) { SetStartDate(mStartYear, mStartMonth, mStartDay); SetEndDate(DateTime.Today.AddDays(-1)); } } public void OnData(TradeBars data) { if(mEquityMarket.DateTimeIsOpen(Time)) { // Set allocation of money in the different subalgorithms, and dispatch them bool wAllAlgosCommunicatedDesiredAllocation = true; foreach(SubAlgorithm wSubAlgorithm in mSubAlgorithms) { if(wSubAlgorithm.mAllocationRatio > 0.01m) { wSubAlgorithm.SetAllocationAmount(mLeverage * wSubAlgorithm.mAllocationRatio * (Portfolio.TotalPortfolioValue - GetUnconvertedCadValueInUsd())); wSubAlgorithm.OnData(data); wAllAlgosCommunicatedDesiredAllocation &= wSubAlgorithm.CommunicatedDesiredAllocation; } } // Process the reallocation requests from all sub-algorithms, if all algos ran at least once and gave their required symbols if (wAllAlgosCommunicatedDesiredAllocation) ProcessReallocations(data); } // If there is more than 1000 USD worth of CAD, convert it to USD ConvertCadToUsd(); } private decimal GetUnconvertedCadValueInCad() { Cash wUnconvertedCAD; Portfolio.CashBook.TryGetValue("CAD",out wUnconvertedCAD); return wUnconvertedCAD.Amount; } private decimal GetUnconvertedCadValueInUsd() { Cash wUnconvertedCAD; Portfolio.CashBook.TryGetValue("CAD",out wUnconvertedCAD); return wUnconvertedCAD.ValueInAccountCurrency; } private void ConvertCadToUsd() { decimal wUnconvertedCADValueInUSD = GetUnconvertedCadValueInUsd(); if (wUnconvertedCADValueInUSD >= 1000) { decimal wHowManyUSDToBuy = Math.Floor(wUnconvertedCADValueInUSD/1000)*1000; Log("Converting CAD to USD: Buying " + wHowManyUSDToBuy + " USD"); MarketOrder("USDCAD", wHowManyUSDToBuy); } } public SharpeRatioCalculator SRC(List<decimal> iSymbolsRatios, int period, decimal volatilityFactor) { var src = new SharpeRatioCalculator(iSymbolsRatios, period, volatilityFactor); return src; } public SharpeRatioCalculator SRC(int period, decimal volatilityFactor) { var src = new SharpeRatioCalculator(period, volatilityFactor); return src; } private void ProcessReallocations(TradeBars data) { Dictionary<string,int> wDesiredEquityAmounts = new Dictionary<string,int>(); foreach (KeyValuePair<Symbol,Security> wSymbolSecurity in Securities) { if (wSymbolSecurity.Key == "USDCAD") continue; wDesiredEquityAmounts.Add(wSymbolSecurity.Key,0); } foreach (SubAlgorithm wSubAlgorithm in mSubAlgorithms) { foreach (KeyValuePair<string,int> wSymbolAmount in wSubAlgorithm.mDesiredEquityAmounts) { if (!wDesiredEquityAmounts.ContainsKey(wSymbolAmount.Key)) { wDesiredEquityAmounts.Add(wSymbolAmount.Key, 0); } wDesiredEquityAmounts[wSymbolAmount.Key] = wDesiredEquityAmounts[wSymbolAmount.Key] + wSymbolAmount.Value; } } SetHoldingsSellFirst(data, wDesiredEquityAmounts); } private void SetHoldingsSellFirst(TradeBars data, Dictionary<string,int> wSymbolsAndAmounts) { Dictionary<string,int> wTransactions = new Dictionary<string,int>(); int wDesiredAmount = 0; int wActualAmount = 0; int wDelta = 0; foreach (KeyValuePair<string,int> wSymbolAndAmount in wSymbolsAndAmounts) { wDesiredAmount = wSymbolAndAmount.Value; wActualAmount = (int)Securities[wSymbolAndAmount.Key].Holdings.Quantity; wDelta = wDesiredAmount - wActualAmount; if (wDelta != 0 && data.ContainsKey(wSymbolAndAmount.Key)) { if ((Math.Abs(wDelta) * data[wSymbolAndAmount.Key].Close) > mAvoidRebalancesBelowThisAmount) { wTransactions.Add(wSymbolAndAmount.Key, wDelta); } } } foreach (KeyValuePair<string,int> wTransaction in wTransactions) { if (wTransaction.Value < 0) { MarketOrder(wTransaction.Key, wTransaction.Value); } } foreach (KeyValuePair<string,int> wTransaction in wTransactions) { if (wTransaction.Value > 0) { MarketOrder(wTransaction.Key, wTransaction.Value); } } } // Helper function to get week of year public int GetIso8601WeekOfYear(DateTime time) { // Seriously cheat. If its Monday, Tuesday or Wednesday, then it'll // be the same week# as whatever Thursday, Friday or Saturday are, // and we always get those right DayOfWeek day = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(time); if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday) { time = time.AddDays(3); } // Return the week of our adjusted day return CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday); } } }
using QuantConnect.Data.Market; namespace QuantConnect { /// <summary> /// /// Empty algorithm. To be used to run live to only monitor IB account without trading. /// /// </summary> public class EMPTYALGO : SubAlgorithm { public EMPTYALGO(PortfolioOfStrategies iAlgorithm, Resolution iResolution, decimal iAllocationRatio, int iStartYear, bool iRebalanceOnLaunch) : base(iAlgorithm, iResolution, iAllocationRatio, iStartYear, iRebalanceOnLaunch) { mDataFeedFrequency = "Weekly"; mRebalanceFrequency = "Weekly"; mName = "EMPTYALGO"; } public override void Initialize_Specific() { } public override void OnData_Specific(TradeBars data) { mCommunicatedDesiredAllocation = true; } protected override bool IsDataValid(TradeBars iData) { return true; } } }
using System.Net; using System.Text.RegularExpressions; using System; using System.Collections; using QuantConnect.Data.Market; using System.Collections.Generic; using QuantConnect.Securities; using QuantConnect.Orders; using System.Globalization; using System.Linq; namespace QuantConnect { /// <summary> /// /// Universal Investment Strategy (UIS) as described here: http://seekingalpha.com/article/2714185-the-spy-tlt-universal-investment-strategy /// /// </summary> public abstract class SubAlgorithm { // Strategy parameters protected bool EnableLogs = false; protected string mDataFeedFrequency = "Daily"; // Minute, Hour, Daily, Weekly, Monthly. If shorter than algo resolution, resolution will be used. protected string mRebalanceFrequency = "Daily"; // Must be a longer timeframe, or equal, to mDataFeedFrequency private bool mRebalanceOnLaunch = true; protected decimal mLastAllocationAmount; protected bool mFirstPass = true; public decimal mAllocationRatio = 0.0m; protected decimal mAllocationAmount = 0.0m; protected DateTime mPrevious; protected PortfolioOfStrategies mAlgorithm; protected Resolution mResolution; protected int mStartYear; protected bool mKeepFeedingData = false; // If, for some reason, a sub-algorithm needs to keep receiving data (more than just once a day/week/month), toggle this to true. public Dictionary<string,int> mDesiredEquityAmounts; protected Dictionary<DateTime,Dictionary<string,string>> mHistoryDictionary; protected string mName = ""; protected bool mCommunicatedDesiredAllocation = false; protected DateTime Time { get { return this.mAlgorithm.Time; } } protected SecurityManager Securities { get { return this.mAlgorithm.Securities; } } public bool RanOnce { get { return !mFirstPass; } } public bool CommunicatedDesiredAllocation { get { return mCommunicatedDesiredAllocation; } } public string Name { get { return mName; } } protected bool LiveMode { get { return this.mAlgorithm.LiveMode; } } public bool RebalanceOnLaunch { get { return this.mRebalanceOnLaunch; } } public SubAlgorithm(PortfolioOfStrategies iAlgorithm, Resolution iResolution, decimal iAllocationRatio, int iStartYear, bool iRebalanceOnLaunch) { mAlgorithm = iAlgorithm; mResolution = iResolution; mAllocationRatio = iAllocationRatio; mStartYear = iStartYear; mRebalanceOnLaunch = iRebalanceOnLaunch; mDesiredEquityAmounts = new Dictionary<string,int>(); mHistoryDictionary = new Dictionary<DateTime,Dictionary<string,string>>(); } public void Initialize(decimal iAllocationAmount) { mAllocationAmount = iAllocationAmount; mLastAllocationAmount = mAllocationAmount; mDesiredEquityAmounts = new Dictionary<string,int>(); Initialize_Specific(); } abstract public void Initialize_Specific(); public void SetAllocationAmount(decimal iAllocationAmount) { mAllocationAmount = iAllocationAmount; } public void OnData(TradeBars data) { if (!IsDataValid(data)) return; if (!mKeepFeedingData) // If the algo requested to keep feeding data more frequently, do so. { if (mDataFeedFrequency == "Minute") { // Only once per minute if ((mPrevious.Minute == mAlgorithm.Time.Minute) && (mPrevious.Hour == mAlgorithm.Time.Hour) && (mPrevious.Date == mAlgorithm.Time.Date)) return; } else if (mDataFeedFrequency == "Hour" || mDataFeedFrequency == "Hourly") { // Only once per hour if ((mPrevious.Hour == mAlgorithm.Time.Hour) && (mPrevious.Date == mAlgorithm.Time.Date)) return; } else if (mDataFeedFrequency == "Daily") { // Only once per day if (mPrevious.Date == mAlgorithm.Time.Date) return; } else if (mDataFeedFrequency == "Weekly") { // only once per week if ((GetIso8601WeekOfYear(mPrevious) == GetIso8601WeekOfYear(mAlgorithm.Time)) && (mPrevious.Year == mAlgorithm.Time.Year)) return; // Added year check, because at start of algo, month = Jan 1997, so if we start in jan 2010 for example, the first month used to be skipped. } else { // only once per month if ((mPrevious.Month == mAlgorithm.Time.Month) && (mPrevious.Year == mAlgorithm.Time.Year)) return; // Added year check, because at start of algo, month = Jan 1997, so if we start in jan 2010 for example, the first month used to be skipped. } } // Dispatch the algorithm OnData_Specific(data); mPrevious = mAlgorithm.Time; mLastAllocationAmount = mAllocationAmount; mFirstPass = false; } abstract public void OnData_Specific(TradeBars data); // Helper function to get week of year public int GetIso8601WeekOfYear(DateTime time) { // Seriously cheat. If its Monday, Tuesday or Wednesday, then it'll // be the same week# as whatever Thursday, Friday or Saturday are, // and we always get those right DayOfWeek day = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(time); if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday) { time = time.AddDays(3); } // Return the week of our adjusted day return CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday); } // Helper function to initialize a SharpeRatioCalculator indicator public SharpeRatioCalculator SRC(List<decimal> iSymbolsRatios, int period, decimal volatilityFactor) { var src = new SharpeRatioCalculator(iSymbolsRatios, period, volatilityFactor); return src; } public SharpeRatioCalculator SRC(int period, decimal volatilityFactor) { var src = new SharpeRatioCalculator(period, volatilityFactor); return src; } protected void Reallocate(TradeBars data, List<KeyValuePair<string,decimal>> wSymbolsAndRatios) { mDesiredEquityAmounts = new Dictionary<string,int>(); foreach (KeyValuePair<string,decimal> wSymbolAndRatio in wSymbolsAndRatios) { mDesiredEquityAmounts.Add(wSymbolAndRatio.Key, (int)(wSymbolAndRatio.Value * mAllocationAmount / data[wSymbolAndRatio.Key].Close)); } mCommunicatedDesiredAllocation = true; } protected abstract bool IsDataValid(TradeBars iData); protected bool IsTimeToRebalance() { if (!mRebalanceOnLaunch && mFirstPass) { return false; } else if (mRebalanceFrequency == "Daily") { // Only once per day return (mPrevious.Date != mAlgorithm.Time.Date); } else if (mRebalanceFrequency == "Weekly") { // only once per week return ((GetIso8601WeekOfYear(mPrevious) != GetIso8601WeekOfYear(mAlgorithm.Time)) || (mPrevious.Year != mAlgorithm.Time.Year)); // Added year check, because at start of algo, month = Jan 1997, so if we start in jan 2010 for example, the first month used to be skipped. } else { // only once per month return ((mPrevious.Month != mAlgorithm.Time.Month) || (mPrevious.Year != mAlgorithm.Time.Year)); // Added year check, because at start of algo, month = Jan 1997, so if we start in jan 2010 for example, the first month used to be skipped. } } protected void ReadHistoryFromLogicalInvest(string iStrategyPrefix, string iHistoryURL) { WebClient wc = new System.Net.WebClient(); byte[] raw = null; string webData = ""; try { raw = wc.DownloadData(iHistoryURL); } catch { Log("Could not retrieve " + mName + " history from Logical Invest, at " + iHistoryURL); return; } if (raw == null) { Log("Data retrieved from " + iHistoryURL + " is null."); return; } webData = System.Text.Encoding.UTF8.GetString(raw).Replace("</td><td>",",").Replace("<tr><td>","").Replace("</td></tr>",""); var lines = webData.Split(new[] { Environment.NewLine },StringSplitOptions.None); DateTime wPreviousDate = new DateTime(); DateTime wDate = new DateTime(); int wValuesCounter = 1; string wSymbol = ""; string wRatio = ""; foreach (string line in lines) { Dictionary<string,string> wEntry = new Dictionary<string,string>(); if(line.Contains("table")) continue; if(line.Contains("###")) continue; List<string> values = line.Split(',').ToList(); if (values.Count < 4) continue; if (values[0] == "") continue; wDate = DateTime.Parse(values[1]); wDate = new DateTime(wDate.Year, wDate.Month, 1); if (wDate != wPreviousDate) { wValuesCounter = 1; mHistoryDictionary.Add(wDate,new Dictionary<string,string>()); wPreviousDate = wDate; } wSymbol = values[2]; wRatio = (values[3].ToDecimal()/100).ToString(); mHistoryDictionary[wDate].Add(iStrategyPrefix + "_Symbol" + wValuesCounter, wSymbol); mHistoryDictionary[wDate].Add(iStrategyPrefix + "_Symbol" + wValuesCounter + "_Ratio", wRatio); wValuesCounter += 1; } } protected void Trade(TradeBars data, List<string> iSymbolsList, List<decimal> iSymbolsRatios) { if (iSymbolsList.Count() != iSymbolsRatios.Count()) {Log("Error in " + Name + ", Trade: Symbols list count must match symbols ratios list count. Number of symbols = " + iSymbolsList.Count() + ", number of ratios = " + iSymbolsRatios.Count()); return;} List<KeyValuePair<string,decimal>> wSymbolsAndRatios = new List<KeyValuePair<string,decimal>>(); for (int i = 0; i < iSymbolsList.Count; i++) { wSymbolsAndRatios.Add(new KeyValuePair<string, decimal>(iSymbolsList[i], iSymbolsRatios[i])); } Reallocate(data, wSymbolsAndRatios); } protected void Trade(TradeBars data, string iSymbol1, decimal iSymbol1Ratio) { List<string> wSymbolsList = new List<string>(); wSymbolsList.Add(iSymbol1); List<decimal> wSymbolRatiosList = new List<decimal>(); wSymbolRatiosList.Add(iSymbol1Ratio); Trade(data, wSymbolsList, wSymbolRatiosList); } protected void Trade(TradeBars data, string iSymbol1, decimal iSymbol1Ratio, string iSymbol2, decimal iSymbol2Ratio) { List<string> wSymbolsList = new List<string>(); wSymbolsList.Add(iSymbol1); wSymbolsList.Add(iSymbol2); List<decimal> wSymbolRatiosList = new List<decimal>(); wSymbolRatiosList.Add(iSymbol1Ratio); wSymbolRatiosList.Add(iSymbol2Ratio); Trade(data, wSymbolsList, wSymbolRatiosList); } protected void Trade(TradeBars data, string iSymbol1, decimal iSymbol1Ratio, string iSymbol2, decimal iSymbol2Ratio, string iSymbol3, decimal iSymbol3Ratio) { List<string> wSymbolsList = new List<string>(); wSymbolsList.Add(iSymbol1); wSymbolsList.Add(iSymbol2); wSymbolsList.Add(iSymbol3); List<decimal> wSymbolRatiosList = new List<decimal>(); wSymbolRatiosList.Add(iSymbol1Ratio); wSymbolRatiosList.Add(iSymbol2Ratio); wSymbolRatiosList.Add(iSymbol3Ratio); Trade(data, wSymbolsList, wSymbolRatiosList); } protected void Trade(TradeBars data, string iSymbol1, decimal iSymbol1Ratio, string iSymbol2, decimal iSymbol2Ratio, string iSymbol3, decimal iSymbol3Ratio, string iSymbol4, decimal iSymbol4Ratio) { List<string> wSymbolsList = new List<string>(); wSymbolsList.Add(iSymbol1); wSymbolsList.Add(iSymbol2); wSymbolsList.Add(iSymbol3); wSymbolsList.Add(iSymbol4); List<decimal> wSymbolRatiosList = new List<decimal>(); wSymbolRatiosList.Add(iSymbol1Ratio); wSymbolRatiosList.Add(iSymbol2Ratio); wSymbolRatiosList.Add(iSymbol3Ratio); wSymbolRatiosList.Add(iSymbol4Ratio); Trade(data, wSymbolsList, wSymbolRatiosList); } protected void Trade(TradeBars data, string iSymbol1, decimal iSymbol1Ratio, string iSymbol2, decimal iSymbol2Ratio, string iSymbol3, decimal iSymbol3Ratio, string iSymbol4, decimal iSymbol4Ratio, string iSymbol5, decimal iSymbol5Ratio) { List<string> wSymbolsList = new List<string>(); wSymbolsList.Add(iSymbol1); wSymbolsList.Add(iSymbol2); wSymbolsList.Add(iSymbol3); wSymbolsList.Add(iSymbol4); wSymbolsList.Add(iSymbol5); List<decimal> wSymbolRatiosList = new List<decimal>(); wSymbolRatiosList.Add(iSymbol1Ratio); wSymbolRatiosList.Add(iSymbol2Ratio); wSymbolRatiosList.Add(iSymbol3Ratio); wSymbolRatiosList.Add(iSymbol4Ratio); wSymbolRatiosList.Add(iSymbol5Ratio); Trade(data, wSymbolsList, wSymbolRatiosList); } protected void SetStartDate(int iYear, int iMonth, int iDay) { mAlgorithm.mBacktestDatesOverriden = true; mAlgorithm.SetStartDate(iYear, iMonth, iDay); } protected void SetEndDate(DateTime iEndDate) { mAlgorithm.mBacktestDatesOverriden = true; mAlgorithm.SetEndDate(iEndDate); } protected void Log(string iString) { mAlgorithm.Log(iString); } protected void Log(int iString) { mAlgorithm.Log(iString.ToString()); } protected void Log(decimal iString) { mAlgorithm.Log(iString.ToString()); } protected void Log(TradeBars iString) { mAlgorithm.Log(iString.ToString()); } protected void AddSecurity(SecurityType securityType, string symbol, Resolution resolution, bool fillDataForward, decimal leverage, bool extendedMarketHours) { mAlgorithm.AddSecurity(securityType, symbol, resolution, fillDataForward, leverage, extendedMarketHours); } protected void SetHoldings(Symbol symbol, decimal percentage) { mAlgorithm.SetHoldings(symbol, percentage); } protected void Plot(string chart, string series, decimal value) { mAlgorithm.Plot(chart, series, value); } protected void Plot(string chart, string series, double value) { mAlgorithm.Plot(chart, series, value); } protected void Plot(string chart, string series, Int32 value) { mAlgorithm.Plot(chart, series, value); } protected void Plot(string chart, string series, Single value) { mAlgorithm.Plot(chart, series, value); } protected IEnumerable<TradeBar> History(Symbol symbol,int numberOfBars, Nullable<Resolution> resolution) { return mAlgorithm.History(symbol, numberOfBars, resolution); } protected OrderTicket MarketOrder(Symbol symbol, int quantity) { return mAlgorithm.MarketOrder(symbol, quantity); } protected OrderTicket MarketOrder(Symbol symbol, decimal quantity) { return mAlgorithm.MarketOrder(symbol, quantity); } } }
using QuantConnect.Data.Market; using System; using System.Collections.Generic; using System.Linq; namespace QuantConnect { /// <summary> /// /// Same strategy as UIS, but with addition of ZIV, similar to MYRS strategy explained here: https://seekingalpha.com/article/1698412-how-to-build-an-etf-rotation-strategy-with-50-percent-annualized-returns /// /// Generally outperformed UIS for 2012-2017, a bit more stable /// /// UIS return UIS Sharpe UISZIV return UISZIV Sharpe /// 2011 93,10% 1,981 /// 2012 14,54% 0,727 36,37% 1,66 /// 2013 50,17% 1,609 48,60% 1,67 /// 2014 56,53% 2,395 58,66% 2,59 /// 2015 -9,64% -0,11 -2,94% 0,03 /// 2016 42,29% 1,488 53,84% 1,87 /// 2017 57,51% 2,898 56,18% 2,83 /// /// </summary> public class UISZIV : SubAlgorithm { // Strategy parameters private string mSymbol1 = "SPY"; private string mSymbol2 = "TLT"; private string mSymbol3 = "SPXU"; private string mSymbol4 = "TMV"; private string mSymbol5 = "ZIV"; private bool mUseShort = false; private bool mUseCorrelationExit = true; // Cash exit when the correlation between UPRU and TMF is positive, for a 1, 2, and 3 months lookback period. Does not exit ZIV positions. private int mMinimumRebalancePeriod = 1; private int mSharpeLookback = 63; // 63 private decimal mSharpeVolatilityFactor = 2.8m; // 2.8 private SharpeRatioCalculator mMaxSharpeCombination; private CorrelationCalculator mCorrel2Weeks; private CorrelationCalculator mCorrel1Month; private CorrelationCalculator mCorrel2Month; private CorrelationCalculator mCorrel3Month; private int mDaysCounter = 0; private List<SharpeRatioCalculator> mSrcList; private decimal mLastRatio1 = 0; private decimal mLastRatio2 = 0; private decimal mLastRatio5 = 0; bool mCashExit = false; bool mCashExit_previous = false; bool mBuyBackIn = false; DateTime mLastHistoryCallsDate = DateTime.MinValue; List<TradeBar> mTradeBarHistorySymbol1; List<TradeBar> mTradeBarHistorySymbol2; List<TradeBar> mTradeBarHistorySymbol5; public UISZIV(PortfolioOfStrategies iAlgorithm, Resolution iResolution, decimal iAllocationRatio, int iStartYear, bool iRebalanceOnLaunch) : base(iAlgorithm, iResolution, iAllocationRatio, iStartYear, iRebalanceOnLaunch) { if (LiveMode) { mDataFeedFrequency = "Minute"; } else { mDataFeedFrequency = "Daily"; // Needs to stay daily or faster, for correlation exits and historical performances calculation } mRebalanceFrequency = "Weekly"; mName = "UISZIV"; } public override void Initialize_Specific() { if (EnableLogs) { Log("UISZIV Strategy: Backtesting with following parameters:"); Log("\tSymbol1 >> " + mSymbol1); Log("\tSymbol2 >> " + mSymbol2); if (mUseShort) { Log("\tSymbol3 >> " + mSymbol3); Log("\tSymbol4 >> " + mSymbol4); } Log("\tUseShort >> " + mUseShort); Log("\tMinimum Rebalance Period >> " + mMinimumRebalancePeriod); Log("\tStart year >> " + mStartYear); Log("\tSharpe lookback >> " + mSharpeLookback); Log("\tSharpe volatility factor >> " + mSharpeVolatilityFactor); } AddSecurity(SecurityType.Equity, mSymbol1, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol2, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol5, mResolution, true, 3, false); if (mUseShort) { AddSecurity(SecurityType.Equity, mSymbol3, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol4, mResolution, true, 3, false); } mTradeBarHistorySymbol1 = new List<TradeBar>(); mTradeBarHistorySymbol2 = new List<TradeBar>(); mTradeBarHistorySymbol5 = new List<TradeBar>(); RecalculateSharpeRatios(mSharpeLookback, mSharpeVolatilityFactor, ref mSrcList); } public override void OnData_Specific(TradeBars data) { mDaysCounter = mDaysCounter - 1; RecalculateSharpeRatios(mSharpeLookback, mSharpeVolatilityFactor, ref mSrcList); // For backtesting purposes only, exclude the discontinuity that occured in the data on Aug 22-25 2016 if (Time.Date >= DateTime.Parse("08/22/2016") && Time.Date <= DateTime.Parse("08/26/2016")) { if (mUseShort) { Trade(data, mSymbol3, 0, mSymbol4, 0, mSymbol5, 0); } else { Trade(data, mSymbol1, 0, mSymbol2, 0, mSymbol5, 0); } return; } decimal wRatio1 = 0.0m; decimal wRatio2 = 0.0m; decimal wRatio5 = 0.0m; if (mSrcList[0].IsReady) { mMaxSharpeCombination = mSrcList.Select( (value, index) => new { Value = value, Index = index } ).Aggregate( (a, b) => (a.Value > b.Value) ? a : b ).Value; wRatio1 = mMaxSharpeCombination.mSymbolsRatios[0]; wRatio2 = mMaxSharpeCombination.mSymbolsRatios[1]; wRatio5 = mMaxSharpeCombination.mSymbolsRatios[2]; } if (mFirstPass == true || mDaysCounter <= 0 || mLastRatio1 != wRatio1 || mLastRatio2 != wRatio2 || mLastRatio5 != wRatio5) { mDaysCounter = mMinimumRebalancePeriod; if (EnableLogs) { Log(" "); Log("Rebalancing:"); } if (mSrcList[0].IsReady) { mMaxSharpeCombination = mSrcList.Select( (value, index) => new { Value = value, Index = index } ).Aggregate( (a, b) => (a.Value > b.Value) ? a : b ).Value; if (EnableLogs) { Log(" Max Sharpe Combination >> " + mSymbol1 + " " + (mMaxSharpeCombination.mSymbolsRatios[0] * 100).ToString ("#.#") + "%, " + mSymbol2 + " " + (mMaxSharpeCombination.mSymbolsRatios[1] * 100).ToString ("#.#") + "%."); } decimal wSymbol1Ratio = mMaxSharpeCombination.mSymbolsRatios[0]; decimal wSymbol2Ratio = mMaxSharpeCombination.mSymbolsRatios[1]; decimal wSymbol5Ratio = mMaxSharpeCombination.mSymbolsRatios[2]; if (mUseCorrelationExit) { // Cash exit when the correlation between UPRU and TMF is positive, for a 1, 2, and 3 months lookback period. Does not exit ZIV positions. if (mCorrel1Month.Correlation > 0 && mCorrel2Month.Correlation > 0 && mCorrel3Month.Correlation > 0) { wSymbol1Ratio = 0; wSymbol2Ratio = 0; mCashExit = true; if (EnableLogs) { Log("Cash exit: Correlation between UPRO and TMF is positive for 1, 2, and 3 months lookback period. Keeping ZIV positions."); } } else if (mCashExit == true && mBuyBackIn == false) { mCashExit = false; mBuyBackIn = true; } } // Trade if it it time to trade, or if there is cash exit toggle or buy back in toggle if ((mCashExit && !mCashExit_previous) || mBuyBackIn || IsTimeToRebalance()) { if (mUseShort) { Trade(data, mSymbol3, -wSymbol1Ratio, mSymbol4, -wSymbol2Ratio, mSymbol5, wSymbol5Ratio); } else { Trade(data, mSymbol1, wSymbol1Ratio, mSymbol2, wSymbol2Ratio, mSymbol5, wSymbol5Ratio); } mBuyBackIn = false; } mCashExit_previous = mCashExit; } else { if (EnableLogs) Log(" Sharpe not ready >> " + mSymbol1 + " 50%, " + mSymbol2 + " 50%."); if (IsTimeToRebalance()) { // If sharpe ratio not ready yet, use a 50%-50% split between UPRO and TMF if (mUseShort) { Trade(data, mSymbol3, -0.5m, mSymbol4, -0.5m, mSymbol5, 0.0m); } else { Trade(data, mSymbol1, 0.5m, mSymbol2, 0.5m, mSymbol5, 0.0m); } } } mLastAllocationAmount = mAllocationAmount; mLastRatio1 = wRatio1; mLastRatio2 = wRatio2; mLastRatio5 = wRatio5; } decimal wSymbol1Percentage; decimal wSymbol2Percentage; decimal wSymbol5Percentage; if (mSrcList[0].IsReady && mMaxSharpeCombination != null) { wSymbol1Percentage = mMaxSharpeCombination.mSymbolsRatios[0] * 100; wSymbol2Percentage = mMaxSharpeCombination.mSymbolsRatios[1] * 100; wSymbol5Percentage = mMaxSharpeCombination.mSymbolsRatios[2] * 100; } else { wSymbol1Percentage = 50; wSymbol2Percentage = 50; wSymbol5Percentage = 0; } Plot(mSymbol1 + "-" + mSymbol2 + "-" + mSymbol5 + " Split", mSymbol1 + " (%)", wSymbol1Percentage); Plot(mSymbol1 + "-" + mSymbol2 + "-" + mSymbol5 + " Split", mSymbol2 + " (%)", wSymbol2Percentage); Plot(mSymbol1 + "-" + mSymbol2 + "-" + mSymbol5 + " Split", mSymbol5 + " (%)", wSymbol5Percentage); Plot(mSymbol1 + "-" + mSymbol2 + " Correlation", "Correl 2 Weeks", mCorrel2Weeks.Correlation*100); Plot(mSymbol1 + "-" + mSymbol2 + " Correlation", "Correl 1 Month", mCorrel1Month.Correlation*100); Plot(mSymbol1 + "-" + mSymbol2 + " Correlation", "Correl 2 Months", mCorrel2Month.Correlation*100); Plot(mSymbol1 + "-" + mSymbol2 + " Correlation", "Correl 3 Months", mCorrel3Month.Correlation*100); Plot(mSymbol1 + "-" + mSymbol2 + " Correlation", "0", 0); } protected override bool IsDataValid(TradeBars iData) { bool wValid = true; if (!mUseShort) { wValid &= iData.ContainsKey(mSymbol1); wValid &= iData.ContainsKey(mSymbol2); wValid &= iData.ContainsKey(mSymbol5); } if (mUseShort) { wValid &= iData.ContainsKey(mSymbol3); wValid &= iData.ContainsKey(mSymbol4); wValid &= iData.ContainsKey(mSymbol5); } return wValid; } private void RecalculateSharpeRatios(int iLookback, decimal iVolatilityFactor, ref List<SharpeRatioCalculator> iSrcList) { iSrcList = new List<SharpeRatioCalculator>(); mCorrel2Weeks = new CorrelationCalculator(10); mCorrel1Month = new CorrelationCalculator(21); mCorrel2Month = new CorrelationCalculator(42); mCorrel3Month = new CorrelationCalculator(63); int granularity = 10; for (decimal i = 0.0m; i <= 1.0m; i+=(decimal)(1.0m/granularity)) { for (decimal j = 0.0m; j <= 1.0m-i; j+=(decimal)(1.0m/granularity)) { List<decimal> wSymbolsRatios = new List<decimal>(); wSymbolsRatios.Add(j); wSymbolsRatios.Add(1.0m-i-j); wSymbolsRatios.Add(i); iSrcList.Add(SRC(wSymbolsRatios, iLookback, iVolatilityFactor)); } } // get the last 100 daily bars, once a day if (mAlgorithm.Time.Date > mLastHistoryCallsDate.Date) { mLastHistoryCallsDate = mAlgorithm.Time; mTradeBarHistorySymbol1 = History(mSymbol1, 100, Resolution.Daily).ToList(); mTradeBarHistorySymbol2 = History(mSymbol2, 100, Resolution.Daily).ToList(); mTradeBarHistorySymbol5 = History(mSymbol5, 100, Resolution.Daily).ToList(); } for (int i = 0; i < mTradeBarHistorySymbol5.Count(); i++) { for (int j = 0; j < iSrcList.Count; j++) { List<TradeBar> wData = new List<TradeBar>(); wData.Add(mTradeBarHistorySymbol1[i]); wData.Add(mTradeBarHistorySymbol2[i]); wData.Add(mTradeBarHistorySymbol5[i]); iSrcList[j].AddObservation(wData); } mCorrel2Weeks.AddObservation((double)mTradeBarHistorySymbol1[i].Close, (double)mTradeBarHistorySymbol2[i].Close); mCorrel1Month.AddObservation((double)mTradeBarHistorySymbol1[i].Close, (double)mTradeBarHistorySymbol2[i].Close); mCorrel2Month.AddObservation((double)mTradeBarHistorySymbol1[i].Close, (double)mTradeBarHistorySymbol2[i].Close); mCorrel3Month.AddObservation((double)mTradeBarHistorySymbol1[i].Close, (double)mTradeBarHistorySymbol2[i].Close); } } } }
using QuantConnect.Data.Market; using QuantConnect.Util; using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace QuantConnect { /// <summary> /// /// Bond rotation strategy (BRS) as described here: https://seekingalpha.com/article/2936206-the-new-enhanced-bond-rotation-strategy-with-adaptive-bond-allocation /// /// Optimization results: /// lookback volatilityfactor CAGR Sharpe /// 40 1.5 10.32 1.407 /// 50 1.5 10.07 1.365 /// 60 1.5 9.595 1.302 /// 70 1.5 10.41 1.402 /// 80 1.5 9.214 1.225 /// 40 1.75 10.508 1.453 /// 50 1.75 10.356 1.437 /// 60 1.75 9.335 1.278 /// 70 1.75 10.499 1.422 /// 80 1.75 9.634 1.291 /// 40 2 10.417 1.443 /// 50 2 10.203 1.428 /// 60 2 9.528 1.311 /// 70 2 10.295 1.403 /// 80 2 9.835 1.326 /// 40 2.25 10.155 1.422 /// 50 2.25 10.956 1.53 /// 60 2.25 9.362 1.296 /// 70 2.25 9.674 1.367 /// 80 2.25 9.024 1.25 /// 40 2.5 10.284 1.449 /// 50 2.5 10.941 1.536 /// 60 2.5 9.388 1.305 /// 70 2.5 9.478 1.35 /// 80 2.5 8.98 1.261 /// 40 2.75 10.055 1.457 /// 50 2.75 10.987 1.545 /// 60 2.75 9.492 1.319 /// 70 2.75 9.492 1.353 /// 80 2.75 9.263 1.306 /// 40 3 /// 50 3 11.225 1.583 /// /// </summary> public class BRS : SubAlgorithm { // Strategy parameters private string mSymbol1 = "CWB"; private string mSymbol2 = "JNK"; private string mSymbol3 = "TLT"; private string mSymbol4 = "PCY"; private int mMinimumRebalancePeriod = 1; private int mSharpeLookback = 50; //50 private decimal mSharpeVolatilityFactor = 3.0m; //3.0 private decimal SharpeThresholdForCash = 0; // Threshold for max sharpe ratio under which the algo will go 50% cash. Needs to be tuned when changes to volatility factor are made. private SharpeRatioCalculator mMaxSharpeCombination; private int mDaysCounter = 0; private List<SharpeRatioCalculator> mSrcList; private decimal mLastRatio1 = 0; private decimal mLastRatio2 = 0; private decimal mLastRatio3 = 0; private decimal mLastRatio4 = 0; DateTime mLastHistoryCallsDate = DateTime.MinValue; List<TradeBar> mTradeBarHistorySymbol1; List<TradeBar> mTradeBarHistorySymbol2; List<TradeBar> mTradeBarHistorySymbol3; List<TradeBar> mTradeBarHistorySymbol4; public BRS(PortfolioOfStrategies iAlgorithm, Resolution iResolution, decimal iAllocationRatio, int iStartYear, bool iRebalanceOnLaunch) : base(iAlgorithm, iResolution, iAllocationRatio, iStartYear, iRebalanceOnLaunch) { if (LiveMode) { mDataFeedFrequency = "Minute"; } else { mDataFeedFrequency = "Monthly"; } mRebalanceFrequency = "Monthly"; mName = "BRS"; } public override void Initialize_Specific() { if (EnableLogs) { Log("BRS Strategy: Backtesting with following parameters:"); Log("\tSymbol1 >> " + mSymbol1); Log("\tSymbol2 >> " + mSymbol2); Log("\tSymbol3 >> " + mSymbol3); Log("\tSymbol4 >> " + mSymbol4); Log("\tMinimum Rebalance Period >> " + mMinimumRebalancePeriod); Log("\tStart year >> " + mStartYear); Log("\tSharpe lookback >> " + mSharpeLookback); Log("\tSharpe volatility factor >> " + mSharpeVolatilityFactor); } AddSecurity(SecurityType.Equity, mSymbol1, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol2, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol3, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol4, mResolution, true, 3, false); mTradeBarHistorySymbol1 = new List<TradeBar>(); mTradeBarHistorySymbol2 = new List<TradeBar>(); mTradeBarHistorySymbol3 = new List<TradeBar>(); mTradeBarHistorySymbol4 = new List<TradeBar>(); RecalculateSharpeRatios(mSharpeLookback, mSharpeVolatilityFactor, ref mSrcList); } public override void OnData_Specific(TradeBars data) { mDaysCounter = mDaysCounter - 1; RecalculateSharpeRatios(mSharpeLookback, mSharpeVolatilityFactor, ref mSrcList); decimal wRatio1 = 0.0m; decimal wRatio2 = 0.0m; decimal wRatio3 = 0.0m; decimal wRatio4 = 0.0m; if (mSrcList[0].IsReady) { mMaxSharpeCombination = mSrcList.Select( (value, index) => new { Value = value, Index = index } ).Aggregate( (a, b) => (a.Value > b.Value) ? a : b ).Value; // If the maximum sharpe ratio combination yields a sharpe ratio below SharpeThresholdForCash, then reduce ratios by 50% and invest 50% in cash if (mMaxSharpeCombination < SharpeThresholdForCash) { for (int i=0; i<mMaxSharpeCombination.mSymbolsRatios.Count(); i++) { mMaxSharpeCombination.mSymbolsRatios[i] = mMaxSharpeCombination.mSymbolsRatios[i] / 2; } } wRatio1 = mMaxSharpeCombination.mSymbolsRatios[0]; wRatio2 = mMaxSharpeCombination.mSymbolsRatios[1]; wRatio3 = mMaxSharpeCombination.mSymbolsRatios[2]; wRatio4 = mMaxSharpeCombination.mSymbolsRatios[3]; } if (mFirstPass == true || mDaysCounter <= 0 || mLastRatio1 != wRatio1 || mLastRatio2 != wRatio2 || mLastRatio3 != wRatio3 || mLastRatio4 != wRatio4) { mDaysCounter = mMinimumRebalancePeriod; if (EnableLogs) { Log(" "); Log("Rebalancing:"); } if (mSrcList[0].IsReady) { mMaxSharpeCombination = mSrcList.Select( (value, index) => new { Value = value, Index = index } ).Aggregate( (a, b) => (a.Value > b.Value) ? a : b ).Value; if (EnableLogs) Log(" Max Sharpe Combination >> " + mSymbol1 + " " + (mMaxSharpeCombination.mSymbolsRatios[0] * 100).ToString ("#.#") + "%, " + mSymbol2 + " " + (mMaxSharpeCombination.mSymbolsRatios[1] * 100).ToString ("#.#") + "%, " + mSymbol3 + " " + (mMaxSharpeCombination.mSymbolsRatios[2] * 100).ToString ("#.#") + "%, " + mSymbol4 + " " + (mMaxSharpeCombination.mSymbolsRatios[3] * 100).ToString ("#.#") + "%."); if (IsTimeToRebalance()) { Trade(data, mSymbol1, mMaxSharpeCombination.mSymbolsRatios[0], mSymbol2, mMaxSharpeCombination.mSymbolsRatios[1], mSymbol3, mMaxSharpeCombination.mSymbolsRatios[2], mSymbol4, mMaxSharpeCombination.mSymbolsRatios[3]); } } else { if (EnableLogs) Log(" Sharpe not ready >> " + mSymbol1 + " 50%, " + mSymbol2 + " 50%, " + mSymbol3 + " 0%, " + mSymbol4 + " 0%."); if (IsTimeToRebalance()) { // If sharpe ratio not ready yet, use a 50%-50% split Trade(data, mSymbol1, 0.5m, mSymbol2, 0.5m, mSymbol3, 0.0m, mSymbol4, 0.0m); } } mLastAllocationAmount = mAllocationAmount; mLastRatio1 = wRatio1; } decimal wSymbol1Percentage; decimal wSymbol2Percentage; decimal wSymbol3Percentage; decimal wSymbol4Percentage; if (mSrcList[0].IsReady && mMaxSharpeCombination != null) { wSymbol1Percentage = mMaxSharpeCombination.mSymbolsRatios[0] * 100; wSymbol2Percentage = mMaxSharpeCombination.mSymbolsRatios[1] * 100; wSymbol3Percentage = mMaxSharpeCombination.mSymbolsRatios[2] * 100; wSymbol4Percentage = mMaxSharpeCombination.mSymbolsRatios[3] * 100; } else { wSymbol1Percentage = 50; wSymbol2Percentage = 50; wSymbol3Percentage = 0; wSymbol4Percentage = 0; } Plot("BRS Assets Split", mSymbol1 + " (%)", wSymbol1Percentage); Plot("BRS Assets Split", mSymbol2 + " (%)", wSymbol2Percentage); Plot("BRS Assets Split", mSymbol3 + " (%)", wSymbol3Percentage); Plot("BRS Assets Split", mSymbol4 + " (%)", wSymbol4Percentage); } protected override bool IsDataValid(TradeBars iData) { bool wValid = true; wValid &= iData.ContainsKey(mSymbol1); wValid &= iData.ContainsKey(mSymbol2); wValid &= iData.ContainsKey(mSymbol3); wValid &= iData.ContainsKey(mSymbol4); return wValid; } private void RecalculateSharpeRatios(int iLookback, decimal iVolatilityFactor, ref List<SharpeRatioCalculator> iSrcList) { iSrcList = new List<SharpeRatioCalculator>(); for (int i=0; i<4; i++) { for (int j=i+1; j<4; j++) { for (decimal k=0.4m; k<=0.6m; k+=0.05m) { List<decimal> wSymbolsRatios = new List<decimal> {0.0m,0.0m,0.0m,0.0m}; wSymbolsRatios[i] = k; wSymbolsRatios[j] = 1-k; iSrcList.Add(SRC(wSymbolsRatios,iLookback,iVolatilityFactor)); } } } // get the last 100 daily bars, once a day if (mAlgorithm.Time.Date > mLastHistoryCallsDate.Date) { mLastHistoryCallsDate = mAlgorithm.Time; mTradeBarHistorySymbol1 = History(mSymbol1, 100, Resolution.Daily).ToList(); mTradeBarHistorySymbol2 = History(mSymbol2, 100, Resolution.Daily).ToList(); mTradeBarHistorySymbol3 = History(mSymbol3, 100, Resolution.Daily).ToList(); mTradeBarHistorySymbol4 = History(mSymbol4, 100, Resolution.Daily).ToList(); } for (int i = 0; i < mTradeBarHistorySymbol1.Count(); i++) { for (int j = 0; j < iSrcList.Count; j++) { List<TradeBar> wData = new List<TradeBar>(); wData.Add(mTradeBarHistorySymbol1[i]); wData.Add(mTradeBarHistorySymbol2[i]); wData.Add(mTradeBarHistorySymbol3[i]); wData.Add(mTradeBarHistorySymbol4[i]); iSrcList[j].AddObservation(wData); } } } } }
namespace QuantConnect { public class SharpeRatioCalculator { public SharpeRatioCalculator(List<decimal> iSymbolsRatios, int period, decimal volatilityFactor) { mSymbolsRatios = iSymbolsRatios; mVolatilityFactor = volatilityFactor; mPreviousTradeBars = new List<TradeBar>(); for (int i = 0; i<iSymbolsRatios.Count(); i++) { mPreviousTradeBars.Add(null); } SharpeRatio = 0.0m; MAC = new MovingAverageCalculator(period); } public SharpeRatioCalculator(int period, decimal volatilityFactor) { mSymbolsRatios = new List<decimal>(); mSymbolsRatios.Add(1.0m); mVolatilityFactor = volatilityFactor; mPreviousTradeBars = new List<TradeBar>(); mPreviousTradeBars.Add(null); SharpeRatio = 0.0m; MAC = new MovingAverageCalculator(period); } public double Average { get { return MAC.Average; } } public double StandardDeviation { get { return MAC.StandardDeviation; } } public double Variance { get { return MAC.Variance; } } public bool IsReady { get { return MAC.HasFullPeriod; } } public bool HasFullPeriod { get { return MAC.HasFullPeriod; } } public IEnumerable<double> Observations { get { return MAC.Observations; } } public int N { get { return MAC.N; } } public void AddObservation(List<TradeBar> iData) { for (int i = 0; i < iData.Count; i++) { if (mPreviousTradeBars[i] == null) mPreviousTradeBars[i] = iData[i]; } List<decimal> wSymbolSplitFactorList = new List<decimal>(); for (int i = 0; i < iData.Count; i++) { decimal wSymbolSplitFactor = 1.0m; if (mPreviousTradeBars[i].Close > iData[i].Close) { wSymbolSplitFactor = Math.Round(mPreviousTradeBars[i].Close / iData[i].Close); } else { wSymbolSplitFactor = 1.0m / Math.Round(iData[i].Close / mPreviousTradeBars[i].Close); } wSymbolSplitFactorList.Add(wSymbolSplitFactor); } List<decimal> wDailyReturns = new List<decimal>(); for (int i = 0; i < iData.Count; i++) { decimal wDailyReturn = ((iData[i].Close * wSymbolSplitFactorList[i]) - mPreviousTradeBars[i].Close) / mPreviousTradeBars[i].Close; wDailyReturns.Add(wDailyReturn); } decimal wTotalDailyReturn = 0.0m; for (int i = 0; i < iData.Count; i++) { wTotalDailyReturn = wTotalDailyReturn + (wDailyReturns[i] * mSymbolsRatios[i]); } MAC.AddObservation((double) wTotalDailyReturn); if (MAC.HasFullPeriod && MAC.StandardDeviation > 0) { SharpeRatio = (decimal) (MAC.Average / Math.Pow(MAC.StandardDeviation, (double) mVolatilityFactor)); } for (int i = 0; i < iData.Count; i++) { mPreviousTradeBars[i] = iData[i]; } } public void AddObservation(List<decimal> iCloseData) { List<TradeBar> wTradeBarsData = new List<TradeBar>(); foreach (decimal wCloseData in iCloseData) { wTradeBarsData.Add(new TradeBar(new DateTime(),"Portfolio",wCloseData,wCloseData,wCloseData,wCloseData,wCloseData,null)); } AddObservation(wTradeBarsData); } public void AddObservation(decimal iCloseData) { List<TradeBar> wTradeBarsData = new List<TradeBar>(); wTradeBarsData.Add(new TradeBar(new DateTime(),"Portfolio",iCloseData,iCloseData,iCloseData,iCloseData,iCloseData,null)); AddObservation(wTradeBarsData); } public MovingAverageCalculator MAC; private decimal SharpeRatio; public List<decimal> mSymbolsRatios; public List<TradeBar> mPreviousTradeBars; public decimal mVolatilityFactor; /// <summary> /// Returns the current value of this instance /// </summary> /// <param name="instance">The indicator instance</param> /// <returns>The current value of the indicator</returns> public static implicit operator decimal(SharpeRatioCalculator instance) { return instance.SharpeRatio; } /// <summary> /// Determines if the indicator's current value is greater than the specified value /// </summary> public static bool operator >(SharpeRatioCalculator left, double right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio > (decimal)right; } /// <summary> /// Determines if the indicator's current value is less than the specified value /// </summary> public static bool operator <(SharpeRatioCalculator left, double right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio < (decimal)right; } /// <summary> /// Determines if the specified value is greater than the indicator's current value /// </summary> public static bool operator >(double left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return (decimal)left > right.SharpeRatio; } /// <summary> /// Determines if the specified value is less than the indicator's current value /// </summary> public static bool operator <(double left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return (decimal)left < right.SharpeRatio; } /// <summary> /// Determines if the indicator's current value is greater than or equal to the specified value /// </summary> public static bool operator >=(SharpeRatioCalculator left, double right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio >= (decimal)right; } /// <summary> /// Determines if the indicator's current value is less than or equal to the specified value /// </summary> public static bool operator <=(SharpeRatioCalculator left, double right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio <= (decimal)right; } /// <summary> /// Determines if the specified value is greater than or equal to the indicator's current value /// </summary> public static bool operator >=(double left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return (decimal)left >= right.SharpeRatio; } /// <summary> /// Determines if the specified value is less than or equal to the indicator's current value /// </summary> public static bool operator <=(double left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return (decimal)left <= right.SharpeRatio; } /// <summary> /// Determines if the indicator's current value is equal to the specified value /// </summary> public static bool operator ==(SharpeRatioCalculator left, double right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio == (decimal)right; } /// <summary> /// Determines if the indicator's current value is not equal to the specified value /// </summary> public static bool operator !=(SharpeRatioCalculator left, double right) { if (ReferenceEquals(left, null)) return true; return left.SharpeRatio != (decimal)right; } /// <summary> /// Determines if the specified value is equal to the indicator's current value /// </summary> public static bool operator ==(double left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return (decimal)left == right.SharpeRatio; } /// <summary> /// Determines if the specified value is not equal to the indicator's current value /// </summary> public static bool operator !=(double left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return true; return (decimal)left != right.SharpeRatio; } /// <summary> /// Determines if the indicator's current value is greater than the specified value /// </summary> public static bool operator >(SharpeRatioCalculator left, float right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio > (decimal)right; } /// <summary> /// Determines if the indicator's current value is less than the specified value /// </summary> public static bool operator <(SharpeRatioCalculator left, float right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio < (decimal)right; } /// <summary> /// Determines if the specified value is greater than the indicator's current value /// </summary> public static bool operator >(float left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return (decimal)left > right.SharpeRatio; } /// <summary> /// Determines if the specified value is less than the indicator's current value /// </summary> public static bool operator <(float left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return (decimal)left < right.SharpeRatio; } /// <summary> /// Determines if the indicator's current value is greater than or equal to the specified value /// </summary> public static bool operator >=(SharpeRatioCalculator left, float right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio >= (decimal)right; } /// <summary> /// Determines if the indicator's current value is less than or equal to the specified value /// </summary> public static bool operator <=(SharpeRatioCalculator left, float right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio <= (decimal)right; } /// <summary> /// Determines if the specified value is greater than or equal to the indicator's current value /// </summary> public static bool operator >=(float left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return (decimal)left >= right.SharpeRatio; } /// <summary> /// Determines if the specified value is less than or equal to the indicator's current value /// </summary> public static bool operator <=(float left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return (decimal)left <= right.SharpeRatio; } /// <summary> /// Determines if the indicator's current value is equal to the specified value /// </summary> public static bool operator ==(SharpeRatioCalculator left, float right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio == (decimal)right; } /// <summary> /// Determines if the indicator's current value is not equal to the specified value /// </summary> public static bool operator !=(SharpeRatioCalculator left, float right) { if (ReferenceEquals(left, null)) return true; return left.SharpeRatio != (decimal)right; } /// <summary> /// Determines if the specified value is equal to the indicator's current value /// </summary> public static bool operator ==(float left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return (decimal)left == right.SharpeRatio; } /// <summary> /// Determines if the specified value is not equal to the indicator's current value /// </summary> public static bool operator !=(float left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return true; return (decimal)left != right.SharpeRatio; } /// <summary> /// Determines if the indicator's current value is greater than the specified value /// </summary> public static bool operator >(SharpeRatioCalculator left, int right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio > right; } /// <summary> /// Determines if the indicator's current value is less than the specified value /// </summary> public static bool operator <(SharpeRatioCalculator left, int right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio < right; } /// <summary> /// Determines if the specified value is greater than the indicator's current value /// </summary> public static bool operator >(int left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return left > right.SharpeRatio; } /// <summary> /// Determines if the specified value is less than the indicator's current value /// </summary> public static bool operator <(int left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return left < right.SharpeRatio; } /// <summary> /// Determines if the indicator's current value is greater than or equal to the specified value /// </summary> public static bool operator >=(SharpeRatioCalculator left, int right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio >= right; } /// <summary> /// Determines if the indicator's current value is less than or equal to the specified value /// </summary> public static bool operator <=(SharpeRatioCalculator left, int right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio <= right; } /// <summary> /// Determines if the specified value is greater than or equal to the indicator's current value /// </summary> public static bool operator >=(int left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return left >= right.SharpeRatio; } /// <summary> /// Determines if the specified value is less than or equal to the indicator's current value /// </summary> public static bool operator <=(int left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return left <= right.SharpeRatio; } /// <summary> /// Determines if the indicator's current value is equal to the specified value /// </summary> public static bool operator ==(SharpeRatioCalculator left, int right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio == right; } /// <summary> /// Determines if the indicator's current value is not equal to the specified value /// </summary> public static bool operator !=(SharpeRatioCalculator left, int right) { if (ReferenceEquals(left, null)) return true; return left.SharpeRatio != right; } /// <summary> /// Determines if the specified value is equal to the indicator's current value /// </summary> public static bool operator ==(int left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return left == right.SharpeRatio; } /// <summary> /// Determines if the specified value is not equal to the indicator's current value /// </summary> public static bool operator !=(int left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return true; return left != right.SharpeRatio; } /// <summary> /// Determines if the indicator's current value is greater than the specified value /// </summary> public static bool operator >(SharpeRatioCalculator left, long right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio > right; } /// <summary> /// Determines if the indicator's current value is less than the specified value /// </summary> public static bool operator <(SharpeRatioCalculator left, long right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio < right; } /// <summary> /// Determines if the specified value is greater than the indicator's current value /// </summary> public static bool operator >(long left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return left > right.SharpeRatio; } /// <summary> /// Determines if the specified value is less than the indicator's current value /// </summary> public static bool operator <(long left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return left < right.SharpeRatio; } /// <summary> /// Determines if the indicator's current value is greater than or equal to the specified value /// </summary> public static bool operator >=(SharpeRatioCalculator left, long right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio >= right; } /// <summary> /// Determines if the indicator's current value is less than or equal to the specified value /// </summary> public static bool operator <=(SharpeRatioCalculator left, long right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio <= right; } /// <summary> /// Determines if the specified value is greater than or equal to the indicator's current value /// </summary> public static bool operator >=(long left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return left >= right.SharpeRatio; } /// <summary> /// Determines if the specified value is less than or equal to the indicator's current value /// </summary> public static bool operator <=(long left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return left <= right.SharpeRatio; } /// <summary> /// Determines if the indicator's current value is equal to the specified value /// </summary> public static bool operator ==(SharpeRatioCalculator left, long right) { if (ReferenceEquals(left, null)) return false; return left.SharpeRatio == right; } /// <summary> /// Determines if the indicator's current value is not equal to the specified value /// </summary> public static bool operator !=(SharpeRatioCalculator left, long right) { if (ReferenceEquals(left, null)) return true; return left.SharpeRatio != right; } /// <summary> /// Determines if the specified value is equal to the indicator's current value /// </summary> public static bool operator ==(long left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return false; return left == right.SharpeRatio; } /// <summary> /// Determines if the specified value is not equal to the indicator's current value /// </summary> public static bool operator !=(long left, SharpeRatioCalculator right) { if (ReferenceEquals(right, null)) return true; return left != right.SharpeRatio; } public override bool Equals(Object right) { // If parameter is null return false. if (right == null) { return false; } // Return true if the fields match: return (this == right); } public override int GetHashCode() { int hash = 13; foreach (decimal wSymbolRatio in mSymbolsRatios) { hash = (hash * 7) + wSymbolRatio.GetHashCode(); } hash = (hash * 7) + mVolatilityFactor.GetHashCode(); hash = (hash * 7) + SharpeRatio.GetHashCode(); return hash; } } }
using QuantConnect.Data.Market; using System; namespace QuantConnect { // // Test for SharpeRatioCalculator // /// <summary> /// /// This test verifies that the SharpeRatioCalculator returns values that are at least proportional to /// test runs from the real algorithm, for different time periods, with the same parameters. /// /// How to test it: /// - In Main.cs, change the allocation to 100% SharpeRatioCalculatorTest (mAllocationRatioSRCTEST = 1.0m) /// - Run the test /// - In the backtests statistics section, copy the 3-months SharpeRatio values in an Excel file /// - Open the graph "Sharpe Ratio three months" /// - For about 2 years of data, manually copy in the Excel file the values of the calculated sharpe ratios, at the same dates as the algo statistics /// - See if there is a correlation /// /// Results so far: quite poor (analysis done from April 2015 to December 2016) /// Correlation = 0.268 /// /// Data results: /// /// Date 3 Months, algo 3 Months, SRC calculator /// 04/30/2015 -0.014354245 -0.0454 /// 05/31/2015 -0.013994576 -0.0341 /// 06/30/2015 -0.045307418 -0.1322 /// 07/31/2015 -0.002708537 -0.0005 /// 08/31/2015 -0.007749081 -0.0385 /// 09/30/2015 -0.003662463 -0.0183 /// 10/31/2015 -0.014735746 0.0008 /// 11/30/2015 0.011244831 0.0962 /// 12/31/2015 -0.007871732 0.0992 /// 01/31/2016 -0.013966913 -0.0727 /// 02/29/2016 -0.001043126 0.0296 /// 03/31/2016 -0.01398189 0.1413 /// 04/30/2016 -0.018236248 0.2148 /// 05/31/2016 -0.009374347 0.1655 /// 06/30/2016 -0.008349114 0.1858 /// 07/31/2016 -0.014002565 0.3271 /// 08/31/2016 -0.010672807 0.2293 /// 09/30/2016 -0.003444261 0.0323 /// 10/31/2016 -0.007270203 -0.0989 /// 11/30/2016 -0.015016703 -0.1928 /// 12/31/2016 -0.020562391 -0.1895 /// /// À faire: Faire les calculs de sharpe ratio et moving average, manuellement, dans Excel. /// Pour faire ça, faire logger la valeur du portfolio à chaque jour. /// /// Ok, j'ai complété le test manuellement dans Excel. Le calcul du Sharpe ratio manuel donne bien le résultat de l'indicateur. /// Possiblement que c'est parce que QC calcule le sharpe ratio basé sur des gains de close - open, et moi close - past close. /// /// </summary> public class Test_SRC : SubAlgorithm { // Strategy parameters private string mSymbol1 = "SPY"; private string mSymbol2 = "TLT"; private SharpeRatioCalculator testSharpeRatioCalculator; public Test_SRC(PortfolioOfStrategies iAlgorithm, Resolution iResolution, decimal iAllocationRatio, int iStartYear, bool iRebalanceOnLaunch) : base(iAlgorithm, iResolution, iAllocationRatio, iStartYear, iRebalanceOnLaunch) { } public override void Initialize_Specific() { // Override start and end date for this test only SetStartDate(2015,01,01); SetEndDate(DateTime.Today.AddDays(-1)); mDataFeedFrequency = "Daily"; AddSecurity(SecurityType.Equity, mSymbol1, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol2, mResolution, true, 3, false); testSharpeRatioCalculator = SRC(63, 1); } public override void OnData_Specific(TradeBars data) { mLastAllocationAmount = mAllocationAmount; if (mFirstPass) { SetHoldings(mSymbol1,0.5m); SetHoldings(mSymbol2,0.5m); } testSharpeRatioCalculator.AddObservation(mAllocationAmount); Plot("SharpeRatio three months", "Sharpe Ratio", testSharpeRatioCalculator); Plot("SharpeRatio three months", "Moving average", testSharpeRatioCalculator); } protected override bool IsDataValid(TradeBars iData) { bool wValid = true; wValid &= iData.ContainsKey(mSymbol1); wValid &= iData.ContainsKey(mSymbol2); return wValid; } } }
namespace QuantConnect { public class CorrelationCalculator { public CorrelationCalculator(int period) { _period = period; _window1 = new double[period]; _window2 = new double[period]; } public double Correlation { get { return _correlation; } } public bool HasFullPeriod { get { return _num_added >= _period; } } public int N { get { return Math.Min(_num_added, _period); } } public void AddObservation(double iValue1, double iValue2) { // Window is treated as a circular buffer. var ndx = _num_added % _period; _window1[ndx] = iValue1; // add new observation _window2[ndx] = iValue2; // add new observation _num_added++; // Update correlation coefficient int wNumElements = N; double[] array_xy = new double[wNumElements]; double[] array_xp2 = new double[wNumElements]; double[] array_yp2 = new double[wNumElements]; double sum_xy = 0; double sum_xpow2 = 0; double sum_ypow2 = 0; for (int i = 0; i < wNumElements; i++) array_xy[i] = _window1[i] * _window2[i]; for (int i = 0; i < wNumElements; i++) array_xp2[i] = Math.Pow(_window1[i], 2.0); for (int i = 0; i < wNumElements; i++) array_yp2[i] = Math.Pow(_window2[i], 2.0); double sum_x = 0; double sum_y = 0; for (int i = 0; i < wNumElements; i++) { sum_x += _window1[i]; sum_y += _window2[i]; sum_xy += array_xy[i]; sum_xpow2 += array_xp2[i]; sum_ypow2 += array_yp2[i]; } double Ex2 = Math.Pow(sum_x, 2.00); double Ey2 = Math.Pow(sum_y, 2.00); _correlation = (_window1.Length * sum_xy - sum_x * sum_y) / Math.Sqrt((_window1.Length * sum_xpow2 - Ex2) * (_window1.Length * sum_ypow2 - Ey2)); } private readonly int _period; private readonly double[] _window1; private readonly double[] _window2; private int _num_added; private double _correlation; } }
namespace QuantConnect { public class MovingAverageCalculator { public MovingAverageCalculator(int period) { _period = period; _window = new double[period]; } public double Average { get { return _average; } } public double StandardDeviation { get { var variance = Variance; if (variance >= double.Epsilon) { var sd = Math.Sqrt(variance); return double.IsNaN(sd) ? 0.0 : sd; } return 0.0; } } public double Variance { get { var n = N; return n > 1 ? _variance_sum / (n - 1) : 0.0; } } public bool HasFullPeriod { get { return _num_added >= _period; } } public IEnumerable<double> Observations { get { return _window.Take(N); } } public int N { get { return Math.Min(_num_added, _period); } } public void AddObservation(double observation) { // Window is treated as a circular buffer. var ndx = _num_added % _period; var old = _window[ndx]; // get value to remove from window _window[ndx] = observation; // add new observation in its place. _num_added++; // Update average and standard deviation using deltas var old_avg = _average; if (_num_added <= _period) { var delta = observation - old_avg; _average += delta / _num_added; _variance_sum += (delta * (observation - _average)); } else // use delta vs removed observation. { var delta = observation - old; _average += delta / _period; _variance_sum += (delta * ((observation - _average) + (old - old_avg))); } } private readonly int _period; private readonly double[] _window; private int _num_added; private double _average; private double _variance_sum; } }
using QuantConnect.Data.Market; using System; using System.Collections.Generic; using System.Linq; namespace QuantConnect { /// <summary> /// /// Universal Investment Strategy (UIS) as described here: http://seekingalpha.com/article/2714185-the-spy-tlt-universal-investment-strategy /// /// Best config so far (for long): /// Log("Backtesting with following parameters:"); /// Log("Symbol1 >> " + UPRO); /// Log("Symbol2 >> " + TMF); /// Log("Symbol3 >> " + SPXU); /// Log("Symbol4 >> " + TMV); /// Log("Use short >> " + false); /// Log("Leverage >> " + 0.98); /// Log("Minimum Rebalance Period >> " + 1); /// Log("Start year >> " + 2010); /// Log("Sharpe lookback >> " + 63); /// Log("Sharpe volatility factor >> " + 2.8); /// /// Best config so far (for short): /// Log("Backtesting with following parameters:"); /// Log("Symbol1 >> " + UPRO); /// Log("Symbol2 >> " + TMF); /// Log("Symbol3 >> " + SPXU); /// Log("Symbol4 >> " + TMV); /// Log("Use short >> " + true); /// Log("Leverage >> " + 0.98); /// Log("Minimum Rebalance Period >> " + 21); /// Log("Start year >> " + 2010); /// Log("Sharpe lookback >> " + 63); /// Log("Sharpe volatility factor >> " + 2.8); /// /// </summary> public class UIS : SubAlgorithm { // Strategy parameters private string mSymbol1 = "SPY"; private string mSymbol2 = "TLT"; private string mSymbol3 = "SPXU"; private string mSymbol4 = "TMV"; private bool mUseShort = false; private bool mUseCorrelationExit = true; // Cash exit when the correlation between UPRU and TMF is positive, for a 1, 2, and 3 months lookback period. Does not exit ZIV positions. private int mMinimumRebalancePeriod = 1; private int mSharpeLookback = 63; // 63 private decimal mSharpeVolatilityFactor = 2.8m; // 2.8 private SharpeRatioCalculator mMaxSharpeCombination; private CorrelationCalculator mCorrel2Weeks; private CorrelationCalculator mCorrel1Month; private CorrelationCalculator mCorrel2Month; private CorrelationCalculator mCorrel3Month; private int mDaysCounter = 0; private List<SharpeRatioCalculator> mSrcList; private decimal mLastRatio1 = 0; private decimal mLastRatio2 = 0; bool mCashExit = false; bool mCashExit_previous = false; bool mBuyBackIn = false; DateTime mLastHistoryCallsDate = DateTime.MinValue; List<TradeBar> mTradeBarHistorySymbol1; List<TradeBar> mTradeBarHistorySymbol2; public UIS(PortfolioOfStrategies iAlgorithm, Resolution iResolution, decimal iAllocationRatio, int iStartYear, bool iRebalanceOnLaunch) : base(iAlgorithm, iResolution, iAllocationRatio, iStartYear, iRebalanceOnLaunch) { if (LiveMode) { mDataFeedFrequency = "Minute"; } else { mDataFeedFrequency = "Daily"; // Needs to stay daily or faster, for correlation exits and historical performances calculation } mRebalanceFrequency = "Weekly"; mName = "UIS"; } public override void Initialize_Specific() { if (EnableLogs) { Log("UIS Strategy: Backtesting with following parameters:"); Log("\tSymbol1 >> " + mSymbol1); Log("\tSymbol2 >> " + mSymbol2); if (mUseShort) { Log("\tSymbol3 >> " + mSymbol3); Log("\tSymbol4 >> " + mSymbol4); } Log("\tUseShort >> " + mUseShort); Log("\tMinimum Rebalance Period >> " + mMinimumRebalancePeriod); Log("\tStart year >> " + mStartYear); Log("\tSharpe lookback >> " + mSharpeLookback); Log("\tSharpe volatility factor >> " + mSharpeVolatilityFactor); } AddSecurity(SecurityType.Equity, mSymbol1, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol2, mResolution, true, 3, false); if (mUseShort) { AddSecurity(SecurityType.Equity, mSymbol3, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol4, mResolution, true, 3, false); } mTradeBarHistorySymbol1 = new List<TradeBar>(); mTradeBarHistorySymbol2 = new List<TradeBar>(); RecalculateSharpeRatios(mSharpeLookback, mSharpeVolatilityFactor, ref mSrcList); } public override void OnData_Specific(TradeBars data) { mDaysCounter = mDaysCounter - 1; RecalculateSharpeRatios(mSharpeLookback, mSharpeVolatilityFactor, ref mSrcList); // For backtesting purposes only, exclude the discontinuity that occured in the data on Aug 22-25 2016 if (Time.Date >= DateTime.Parse("08/22/2016") && Time.Date <= DateTime.Parse("08/26/2016")) { if (mUseShort) { Trade(data, mSymbol3, 0, mSymbol4, 0); } else { Trade(data, mSymbol1, 0, mSymbol2, 0); } return; } decimal wRatio1 = 0.5m; decimal wRatio2 = 0.5m; if (mSrcList[0].IsReady) { mMaxSharpeCombination = mSrcList.Select( (value, index) => new { Value = value, Index = index } ).Aggregate( (a, b) => (a.Value > b.Value) ? a : b ).Value; wRatio1 = mMaxSharpeCombination.mSymbolsRatios[0]; wRatio2 = mMaxSharpeCombination.mSymbolsRatios[1]; } if (mFirstPass == true || mDaysCounter <= 0 || mLastRatio1 != wRatio1 || mLastRatio2 != wRatio2) { mDaysCounter = mMinimumRebalancePeriod; if (EnableLogs) { Log(" "); Log("Rebalancing:"); } if (mSrcList[0].IsReady) { mMaxSharpeCombination = mSrcList.Select( (value, index) => new { Value = value, Index = index } ).Aggregate( (a, b) => (a.Value > b.Value) ? a : b ).Value; if (EnableLogs) { Log(" Max Sharpe Combination >> " + mSymbol1 + " " + (mMaxSharpeCombination.mSymbolsRatios[0] * 100).ToString ("#.#") + "%, " + mSymbol2 + " " + (mMaxSharpeCombination.mSymbolsRatios[1] * 100).ToString ("#.#") + "%."); } decimal wSymbol1Ratio = mMaxSharpeCombination.mSymbolsRatios[0]; decimal wSymbol2Ratio = mMaxSharpeCombination.mSymbolsRatios[1]; if (mUseCorrelationExit) { // Cash exit when the correlation between UPRU and TMF is positive, for a 1, 2, and 3 months lookback period. if (mCorrel1Month.Correlation > 0 && mCorrel2Month.Correlation > 0 && mCorrel3Month.Correlation > 0) { wSymbol1Ratio = 0; wSymbol2Ratio = 0; mCashExit = true; if (EnableLogs) { Log("Cash exit: Correlation between UPRO and TMF is positive for 1, 2, and 3 months lookback period."); } } else if (mCashExit == true && mBuyBackIn == false) { mCashExit = false; mBuyBackIn = true; } } // Trade if it it time to trade, or if there is cash exit toggle or buy back in toggle if ((mCashExit && !mCashExit_previous) || mBuyBackIn || IsTimeToRebalance()) { if (mUseShort) { Trade(data, mSymbol3, -wSymbol1Ratio, mSymbol4, -wSymbol2Ratio); } else { Trade(data, mSymbol1, wSymbol1Ratio, mSymbol2, wSymbol2Ratio); } mBuyBackIn = false; } mCashExit_previous = mCashExit; } else { if (EnableLogs) Log(" Sharpe not ready >> " + mSymbol1 + " 50%, " + mSymbol2 + " 50%."); if (IsTimeToRebalance()) { // If sharpe ratio not ready yet, use a 50%-50% split between UPRO and TMF if (mUseShort) { Trade(data, mSymbol3, -0.5m, mSymbol4, -0.5m); } else { Trade(data, mSymbol1, 0.5m, mSymbol2, 0.5m); } } } mLastAllocationAmount = mAllocationAmount; mLastRatio1 = wRatio1; mLastRatio2 = wRatio2; } decimal wSymbol1Percentage; decimal wSymbol2Percentage; if (mSrcList[0].IsReady && mMaxSharpeCombination != null) { wSymbol1Percentage = mMaxSharpeCombination.mSymbolsRatios[0] * 100; wSymbol2Percentage = mMaxSharpeCombination.mSymbolsRatios[1] * 100; } else { wSymbol1Percentage = 50; wSymbol2Percentage = 50; } Plot(mSymbol1 + "-" + mSymbol2 + " Split", mSymbol1 + " (%)", wSymbol1Percentage); Plot(mSymbol1 + "-" + mSymbol2 + " Split", mSymbol2 + " (%)", wSymbol2Percentage); Plot(mSymbol1 + "-" + mSymbol2 + " Correlation", "Correl 2 Weeks", mCorrel2Weeks.Correlation*100); Plot(mSymbol1 + "-" + mSymbol2 + " Correlation", "Correl 1 Month", mCorrel1Month.Correlation*100); Plot(mSymbol1 + "-" + mSymbol2 + " Correlation", "Correl 2 Months", mCorrel2Month.Correlation*100); Plot(mSymbol1 + "-" + mSymbol2 + " Correlation", "Correl 3 Months", mCorrel3Month.Correlation*100); Plot(mSymbol1 + "-" + mSymbol2 + " Correlation", "0", 0); } protected override bool IsDataValid(TradeBars iData) { bool wValid = true; if (!mUseShort) { wValid &= iData.ContainsKey(mSymbol1); wValid &= iData.ContainsKey(mSymbol2); } if (mUseShort) { wValid &= iData.ContainsKey(mSymbol3); wValid &= iData.ContainsKey(mSymbol4); } return wValid; } private void RecalculateSharpeRatios(int iLookback, decimal iVolatilityFactor, ref List<SharpeRatioCalculator> iSrcList) { iSrcList = new List<SharpeRatioCalculator>(); mCorrel2Weeks = new CorrelationCalculator(10); mCorrel1Month = new CorrelationCalculator(21); mCorrel2Month = new CorrelationCalculator(42); mCorrel3Month = new CorrelationCalculator(63); int granularity = 15; for (int i = 0; i <= granularity; i++) { List<decimal> wSymbolsRatios = new List<decimal>(); wSymbolsRatios.Add(1/(decimal)granularity * i); wSymbolsRatios.Add(1.0m - (1/(decimal)granularity * i)); iSrcList.Add(SRC(wSymbolsRatios, iLookback, iVolatilityFactor)); } // get the last 100 daily bars, once a day if (mAlgorithm.Time.Date > mLastHistoryCallsDate.Date) { mLastHistoryCallsDate = mAlgorithm.Time; mTradeBarHistorySymbol1 = History(mSymbol1, 100, Resolution.Daily).ToList(); mTradeBarHistorySymbol2 = History(mSymbol2, 100, Resolution.Daily).ToList(); } for (int i = 0; i < mTradeBarHistorySymbol1.Count(); i++) { for (int j = 0; j < iSrcList.Count; j++) { List<TradeBar> wData = new List<TradeBar>(); wData.Add(mTradeBarHistorySymbol1[i]); wData.Add(mTradeBarHistorySymbol2[i]); iSrcList[j].AddObservation(wData); } mCorrel2Weeks.AddObservation((double)mTradeBarHistorySymbol1[i].Close, (double)mTradeBarHistorySymbol2[i].Close); mCorrel1Month.AddObservation((double)mTradeBarHistorySymbol1[i].Close, (double)mTradeBarHistorySymbol2[i].Close); mCorrel2Month.AddObservation((double)mTradeBarHistorySymbol1[i].Close, (double)mTradeBarHistorySymbol2[i].Close); mCorrel3Month.AddObservation((double)mTradeBarHistorySymbol1[i].Close, (double)mTradeBarHistorySymbol2[i].Close); } } } }
using QuantConnect.Data.Market; using System; using System.Collections.Generic; using System.Net; using System.Text.RegularExpressions; using System.Linq; namespace QuantConnect { /// <summary> /// /// See here for the strategy: https://logical-invest.com/portfolio-items/the-gold-etf-currency-strategy/ /// /// This is a semi-automated algorithm that uses signals from Logical Invest, stored in a csv file on Dropbox. /// /// </summary> public class GLD_LI_SIGNALS : SubAlgorithm { // Strategy parameters private bool mRebalanceOnChange = true; // Other variables private string mSymbol1 = ""; private string mSymbol2 = ""; private decimal mSymbol1Ratio = 0.0m; private decimal mSymbol2Ratio = 0.0m; private bool mReadyToTrade = false; private Dictionary<string, string> mValuesDictionary; private string mDropboxFileDirectDownloadLink = ""; public GLD_LI_SIGNALS(PortfolioOfStrategies iAlgorithm, Resolution iResolution, decimal iAllocationRatio, int iStartYear, bool iRebalanceOnLaunch) : base(iAlgorithm, iResolution, iAllocationRatio, iStartYear, iRebalanceOnLaunch) { if (LiveMode) { mDataFeedFrequency = "Minute"; } else { mDataFeedFrequency = "Daily"; } mRebalanceFrequency = "Monthly"; mDropboxFileDirectDownloadLink = "https://dropbox.com/s/ko9exryrs1ckojz/Algo%20Values%20Updater.csv?dl=1"; mName = "GLD_LI_SINGALS"; } public override void Initialize_Specific() { mValuesDictionary = new Dictionary<string, string>(); ReadHistoryFromLogicalInvest("GLD", "https://logical-invest.com/wp-content/csvrepository/GLD-USD_return.html"); } public override void OnData_Specific(TradeBars data) { UpdateDictionary(Time, ref mValuesDictionary); if (!mKeepFeedingData) { bool wSomeValueHasChanged = false; bool wSuccess = RetrieveValuesFromDictionary(ref wSomeValueHasChanged); if (!wSuccess) return; if (!Securities.ContainsKey(mSymbol1)) AddSecurity(SecurityType.Equity, mSymbol1, mResolution, true, 3, false); if (!Securities.ContainsKey(mSymbol2)) AddSecurity(SecurityType.Equity, mSymbol2, mResolution, true, 3, false); if (IsTimeToRebalance() || (mRebalanceOnChange && wSomeValueHasChanged)) { mKeepFeedingData = true; mReadyToTrade = true; } } if (!IsDataValid(data)) return; if (mReadyToTrade) { Trade(data, mSymbol1, mSymbol1Ratio, mSymbol2, mSymbol2Ratio); mKeepFeedingData = false; mReadyToTrade = false; } // TODO: Add plotting of symbols } protected override bool IsDataValid(TradeBars iData) { if (mSymbol1 == "") { return true; // If the required data is not yet initialized, skip this. } else { bool wValid = true; wValid &= iData.ContainsKey(mSymbol1); wValid &= iData.ContainsKey(mSymbol2); return wValid; } } // // Reads the desired values from the web and stores it in oValuesDictionary (Dictionary of variable name:value) // If in backtest mode and dates are in the past month or earlier, reads from LogicalInvest webpage directly. // If the date is in the current month, reads from the CSV file manually maintained on Dropbox. // private void UpdateDictionary(DateTime iTime, ref Dictionary<string, string> oValuesDictionary) { DateTime wRealTime = mAlgorithm.Time; if ((iTime.Year != wRealTime.Year) || (iTime.Month != wRealTime.Month)) { oValuesDictionary = mHistoryDictionary[new DateTime(iTime.Year, iTime.Month, 1)]; } else { using (var client = new WebClient()) { string file = ""; try { // fetch the file from dropbox file = client.DownloadString(mDropboxFileDirectDownloadLink); } catch { if (EnableLogs) Log("Error reading file from Dropbox: " + mDropboxFileDirectDownloadLink); return; } var lines = file.Replace("\r", String.Empty).Split(new[] { Environment.NewLine },StringSplitOptions.None); foreach (string line in lines) { List<string> wKeyVal = line.Split(',').ToList(); if (wKeyVal[0] == "") continue; string wKey = wKeyVal[0]; string wValue = ""; if (wKeyVal.Count > 1) { wValue = wKeyVal[1]; } if(!oValuesDictionary.ContainsKey(wKey)) { oValuesDictionary.Add(wKey, wValue); } else { oValuesDictionary[wKey] = wValue; } } } } } // // Gets the symbol values from mValuesDictionary, and stores them in mSymbol1, mSymbol2, ... , mSymbol1Ratio, mSymbol2Ratio, ... // Needs to have called UpdateDictionary() prior to this. // private bool RetrieveValuesFromDictionary(ref bool oSomeValueHasChanged) { if (!mValuesDictionary.ContainsKey("GLD_Symbol1")) Log("Error: Could not retrieve parameter GLD_Symbol1 from Dropbox."); if (!mValuesDictionary.ContainsKey("GLD_Symbol1_Ratio")) Log("Error: Could not retrieve parameter GLD_Symbol1_Ratio from Dropbox."); if (!mValuesDictionary.ContainsKey("GLD_Symbol2")) Log("Error: Could not retrieve parameter GLD_Symbol2 from Dropbox."); if (!mValuesDictionary.ContainsKey("GLD_Symbol2_Ratio")) Log("Error: Could not retrieve parameter GLD_Symbol2_Ratio from Dropbox."); if (!mValuesDictionary.ContainsKey("GLD_Symbol1") || !mValuesDictionary.ContainsKey("GLD_Symbol1_Ratio") || !mValuesDictionary.ContainsKey("GLD_Symbol2") || !mValuesDictionary.ContainsKey("GLD_Symbol2_Ratio")) { return false; } oSomeValueHasChanged = mSymbol1 != mValuesDictionary["GLD_Symbol1"] || mSymbol2 != mValuesDictionary["GLD_Symbol2"] || mSymbol1Ratio != Convert.ToDecimal(mValuesDictionary["GLD_Symbol1_Ratio"]) || mSymbol2Ratio != Convert.ToDecimal(mValuesDictionary["GLD_Symbol2_Ratio"]); mSymbol1 = mValuesDictionary["GLD_Symbol1"]; mSymbol2 = mValuesDictionary["GLD_Symbol2"]; mSymbol1Ratio = Convert.ToDecimal(mValuesDictionary["GLD_Symbol1_Ratio"]); mSymbol2Ratio = Convert.ToDecimal(mValuesDictionary["GLD_Symbol2_Ratio"]); return true; } } }
using QuantConnect.Data.Market; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text.RegularExpressions; namespace QuantConnect { /// <summary> /// /// See here for the strategy: https://logical-invest.com/portfolio-items/3x-leveraged-universal-investment-strategy/ /// /// This is a fully automated algorithm that uses signals from Logical Invest. /// Signal emails from Logical Invest are automatically stored as text files in Dropbox, and read by this algorithm. /// /// </summary> public class NASDAQ_LI_SIGNALS : SubAlgorithm { // Strategy parameters private bool mRebalanceOnChange = true; // Other variables private string mSymbol1 = ""; private string mSymbol2 = ""; private string mSymbol3 = ""; private string mSymbol4 = ""; private string mSymbol5 = ""; private decimal mSymbol1Ratio = 0.0m; private decimal mSymbol2Ratio = 0.0m; private decimal mSymbol3Ratio = 0.0m; private decimal mSymbol4Ratio = 0.0m; private decimal mSymbol5Ratio = 0.0m; private bool mReadyToTrade = false; private bool mReadFromCvsInsteadOfEmail = false; private Dictionary<string, string> mValuesDictionary; private string mDropboxFileDirectDownloadLink = ""; public NASDAQ_LI_SIGNALS(PortfolioOfStrategies iAlgorithm, Resolution iResolution, decimal iAllocationRatio, int iStartYear, bool iRebalanceOnLaunch) : base(iAlgorithm, iResolution, iAllocationRatio, iStartYear, iRebalanceOnLaunch) { if (LiveMode) { mDataFeedFrequency = "Minute"; // Needs to stay daily or faster, for historical performances calculation } else { mDataFeedFrequency = "Daily"; // Needs to stay daily or faster, for historical performances calculation } mRebalanceFrequency = "Monthly"; mName = "NASDAQ_LI_SIGNALS"; if (mReadFromCvsInsteadOfEmail) { // To read from the manual csv file, in "Dropbox\Public\Algo Values Updater.csv" mDropboxFileDirectDownloadLink = "https://www.dropbox.com/s/ko9exryrs1ckojz/Algo%20Values%20Updater.csv?dl=1"; } else { // To read from the logical invest email text file, in "Dropbox\Public\Logical Invest Rebalance Emails\Nasdaq100SignalEmail.txt" // Thie text file is automatically dropped there everytime a new email comes in, using Zapier.com to automate it mDropboxFileDirectDownloadLink = "https://www.dropbox.com/s/gwf88v90h4pq5ho/Nasdaq100SignalEmail.txt?dl=1"; } } public override void Initialize_Specific() { mValuesDictionary = new Dictionary<string, string>(); ReadHistoryFromLogicalInvest("NASDAQ", "https://logical-invest.com/wp-content/csvrepository/NASDAQ100_return.html"); } public override void OnData_Specific(TradeBars data) { UpdateDictionary(Time, ref mValuesDictionary); if (!mKeepFeedingData) { bool wSomeValueHasChanged = false; bool wSuccess = RetrieveValuesFromDictionary(ref wSomeValueHasChanged); if (!wSuccess) return; if (!Securities.ContainsKey(mSymbol1)) AddSecurity(SecurityType.Equity, mSymbol1, mResolution, true, 3, false); if (!Securities.ContainsKey(mSymbol2)) AddSecurity(SecurityType.Equity, mSymbol2, mResolution, true, 3, false); if (!Securities.ContainsKey(mSymbol3)) AddSecurity(SecurityType.Equity, mSymbol3, mResolution, true, 3, false); if (!Securities.ContainsKey(mSymbol4)) AddSecurity(SecurityType.Equity, mSymbol4, mResolution, true, 3, false); if (!Securities.ContainsKey(mSymbol5)) AddSecurity(SecurityType.Equity, mSymbol5, mResolution, true, 3, false); if (IsTimeToRebalance() || (mRebalanceOnChange && wSomeValueHasChanged)) { mKeepFeedingData = true; mReadyToTrade = true; } } if (!IsDataValid(data)) return; if (mReadyToTrade) { Trade(data, mSymbol1, mSymbol1Ratio, mSymbol2, mSymbol2Ratio, mSymbol3, mSymbol3Ratio, mSymbol4, mSymbol4Ratio, mSymbol5, mSymbol5Ratio); mKeepFeedingData = false; mReadyToTrade = false; } // TODO: Add plotting of symbols } protected override bool IsDataValid(TradeBars iData) { if (mSymbol1 == "") { return true; // If the required data is not yet initialized, skip this. } else { bool wValid = true; wValid &= iData.ContainsKey(mSymbol1); wValid &= iData.ContainsKey(mSymbol2); wValid &= iData.ContainsKey(mSymbol3); wValid &= iData.ContainsKey(mSymbol4); wValid &= iData.ContainsKey(mSymbol5); return wValid; } } // // Reads the desired values from the web and stores it in oValuesDictionary (Dictionary of variable name:value) // If in backtest mode and dates are in the past month or earlier, reads from LogicalInvest webpage directly. // If the date is in the current month, reads from Dropbox, the email received from LogicalInvest. // private void UpdateDictionary(DateTime iTime, ref Dictionary<string, string> oValuesDictionary) { DateTime wRealTime = mAlgorithm.Time; if ((iTime.Year != wRealTime.Year) || (iTime.Month != wRealTime.Month)) { oValuesDictionary = mHistoryDictionary[new DateTime(iTime.Year, iTime.Month, 1)]; } else { using (var client = new WebClient()) { string file = ""; try { // fetch the file from dropbox file = client.DownloadString(mDropboxFileDirectDownloadLink); } catch { if (EnableLogs) Log("Error reading file from Dropbox: " + mDropboxFileDirectDownloadLink); return; } if (mReadFromCvsInsteadOfEmail) { var lines = file.Replace("\r", String.Empty).Split(new[] { Environment.NewLine },StringSplitOptions.None); foreach (string line in lines) { List<string> wKeyVal = line.Split(',').ToList(); if (wKeyVal[0] == "") continue; string wKey = wKeyVal[0]; string wValue = ""; if (wKeyVal.Count > 1) { wValue = wKeyVal[1]; } if(!oValuesDictionary.ContainsKey(wKey)) { oValuesDictionary.Add(wKey, wValue); } else { oValuesDictionary[wKey] = wValue; } } } else { Regex r = new Regex("New allocations for your portfolio by ETF(.|\r\n)*?</table>", RegexOptions.IgnoreCase); string wAllocationsSection = r.Match(file).Value; // For debugging // Log("wAllocationsSection = " + wAllocationsSection); // Each match in wSymbolMatches will be of this format: ">FOXA - " // Each symbol in wSymbols will be the actual ticker (Ex: "FOXA") r = new Regex(">[A-Z]*? - "); MatchCollection wSymbolMatches = r.Matches(wAllocationsSection); var wSymbols = new List<string>(); foreach (Match match in wSymbolMatches) { string wSymbol = match.Value.Replace(">","").Replace(" - ",""); wSymbols.Add(wSymbol); } // Each match in wPercentMatches will be of this format: "<td>30.0%</td>" // Each value in wAllocations will be the actual decimal allocation, as a string (Ex: "0.010") r = new Regex("<td>.*?%</td>"); MatchCollection wPercentMatches = r.Matches(wAllocationsSection); var wAllocations = new List<string>(); foreach (Match match in wPercentMatches) { string wAllocationPercentage = match.Value.Replace("<td>","").Replace("%","").Replace("</td>",""); string wAllocationDecimal = "0." + wAllocationPercentage.Replace(".",""); wAllocations.Add(wAllocationDecimal); } // Remove the last percentage entry, which is the total percentage allocation if(wAllocations.Any()) { wAllocations.RemoveAt(wAllocations.Count - 1); } // Each line in wLines2 will be of this format: "NASDAQ_Symbol1,AMZN" and "NASDAQ_Symbol1_Ratio,0.10" var wLines2 = new List<string>(); for (int i = 0; i<wSymbols.Count(); i++) { // For debugging // Log("wSymbols["+i+"] = " + wSymbols[i]); // Log("wAllocations["+i+"] = " + wAllocations[i]); string wSymbol = wSymbols[i]; // Ex: AMZN string wAllocationDecimal = wAllocations[i]; // Ex: 0.105 wLines2.Add("NASDAQ_Symbol" + (i+1) + "," + wSymbol); // Ex: NASDAQ_Symbol1,AMZN wLines2.Add("NASDAQ_Symbol" + (i+1) + "_Ratio," + wAllocationDecimal); // Ex: NASDAQ_Symbol1_Ratio,0.105 } foreach (string line in wLines2) { List<string> wKeyVal = line.Split(',').ToList(); if (wKeyVal[0] == "") continue; string wKey = wKeyVal[0]; string wValue = ""; if (wKeyVal.Count > 1) { wValue = wKeyVal[1]; } if(!oValuesDictionary.ContainsKey(wKey)) { oValuesDictionary.Add(wKey, wValue); } else { oValuesDictionary[wKey] = wValue; } } // For debugging // foreach (KeyValuePair<string,string> wKeyValue in oValuesDictionary) // { // Log("Key = " + wKeyValue.Key + " - Value = " + wKeyValue.Value); // } } } } } // // Gets the symbol values from mValuesDictionary, and stores them in mSymbol1, mSymbol2, ... , mSymbol1Ratio, mSymbol2Ratio, ... // Needs to have called UpdateDictionary() prior to this. // private bool RetrieveValuesFromDictionary(ref bool oSomeValueHasChanged) { if (!mValuesDictionary.ContainsKey("NASDAQ_Symbol1")) Log("Error: Could not retrieve parameter NASDAQ_Symbol1 from Dropbox."); if (!mValuesDictionary.ContainsKey("NASDAQ_Symbol1_Ratio")) Log("Error: Could not retrieve parameter NASDAQ_Symbol1_Ratio from Dropbox."); if (!mValuesDictionary.ContainsKey("NASDAQ_Symbol2")) Log("Error: Could not retrieve parameter NASDAQ_Symbol2 from Dropbox."); if (!mValuesDictionary.ContainsKey("NASDAQ_Symbol2_Ratio")) Log("Error: Could not retrieve parameter NASDAQ_Symbol2_Ratio from Dropbox."); if (!mValuesDictionary.ContainsKey("NASDAQ_Symbol3")) Log("Error: Could not retrieve parameter NASDAQ_Symbol3 from Dropbox."); if (!mValuesDictionary.ContainsKey("NASDAQ_Symbol3_Ratio")) Log("Error: Could not retrieve parameter NASDAQ_Symbol3_Ratio from Dropbox."); if (!mValuesDictionary.ContainsKey("NASDAQ_Symbol4")) Log("Error: Could not retrieve parameter NASDAQ_Symbol4 from Dropbox."); if (!mValuesDictionary.ContainsKey("NASDAQ_Symbol4_Ratio")) Log("Error: Could not retrieve parameter NASDAQ_Symbol4_Ratio from Dropbox."); if (!mValuesDictionary.ContainsKey("NASDAQ_Symbol5")) Log("Error: Could not retrieve parameter NASDAQ_Symbol5 from Dropbox."); if (!mValuesDictionary.ContainsKey("NASDAQ_Symbol5_Ratio")) Log("Error: Could not retrieve parameter NASDAQ_Symbol5_Ratio from Dropbox."); if (!mValuesDictionary.ContainsKey("NASDAQ_Symbol1") || !mValuesDictionary.ContainsKey("NASDAQ_Symbol1_Ratio") || !mValuesDictionary.ContainsKey("NASDAQ_Symbol2") || !mValuesDictionary.ContainsKey("NASDAQ_Symbol2_Ratio") || !mValuesDictionary.ContainsKey("NASDAQ_Symbol3") || !mValuesDictionary.ContainsKey("NASDAQ_Symbol3_Ratio") || !mValuesDictionary.ContainsKey("NASDAQ_Symbol4") || !mValuesDictionary.ContainsKey("NASDAQ_Symbol4_Ratio") || !mValuesDictionary.ContainsKey("NASDAQ_Symbol5") || !mValuesDictionary.ContainsKey("NASDAQ_Symbol5_Ratio")) { return false; } oSomeValueHasChanged = mSymbol1 != mValuesDictionary["NASDAQ_Symbol1"] || mSymbol2 != mValuesDictionary["NASDAQ_Symbol2"] || mSymbol3 != mValuesDictionary["NASDAQ_Symbol3"] || mSymbol4 != mValuesDictionary["NASDAQ_Symbol4"] || mSymbol5 != mValuesDictionary["NASDAQ_Symbol5"] || mSymbol1Ratio != Convert.ToDecimal(mValuesDictionary["NASDAQ_Symbol1_Ratio"]) || mSymbol2Ratio != Convert.ToDecimal(mValuesDictionary["NASDAQ_Symbol2_Ratio"]) || mSymbol3Ratio != Convert.ToDecimal(mValuesDictionary["NASDAQ_Symbol3_Ratio"]) || mSymbol4Ratio != Convert.ToDecimal(mValuesDictionary["NASDAQ_Symbol4_Ratio"]) || mSymbol5Ratio != Convert.ToDecimal(mValuesDictionary["NASDAQ_Symbol5_Ratio"]); mSymbol1 = mValuesDictionary["NASDAQ_Symbol1"]; mSymbol2 = mValuesDictionary["NASDAQ_Symbol2"]; mSymbol3 = mValuesDictionary["NASDAQ_Symbol3"]; mSymbol4 = mValuesDictionary["NASDAQ_Symbol4"]; mSymbol5 = mValuesDictionary["NASDAQ_Symbol5"]; mSymbol1Ratio = Convert.ToDecimal(mValuesDictionary["NASDAQ_Symbol1_Ratio"]); mSymbol2Ratio = Convert.ToDecimal(mValuesDictionary["NASDAQ_Symbol2_Ratio"]); mSymbol3Ratio = Convert.ToDecimal(mValuesDictionary["NASDAQ_Symbol3_Ratio"]); mSymbol4Ratio = Convert.ToDecimal(mValuesDictionary["NASDAQ_Symbol4_Ratio"]); mSymbol5Ratio = Convert.ToDecimal(mValuesDictionary["NASDAQ_Symbol5_Ratio"]); return true; } } }
using QuantConnect.Data.Market; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text.RegularExpressions; namespace QuantConnect { /// <summary> /// /// See here for the strategy: https://logical-invest.com/portfolio-items/the-gold-etf-currency-strategy/ /// /// This is a semi-automated algorithm that uses signals from Logical Invest, stored in a csv file on Dropbox. /// /// </summary> public class UIS_LI_SIGNALS : SubAlgorithm { // Strategy parameters private bool mRebalanceOnChange = true; // Other variables private string mSymbol1 = ""; private string mSymbol2 = ""; private decimal mSymbol1Ratio = 0.0m; private decimal mSymbol2Ratio = 0.0m; private bool mReadyToTrade = false; private Dictionary<string, string> mValuesDictionary; private string mDropboxFileDirectDownloadLink = ""; public UIS_LI_SIGNALS(PortfolioOfStrategies iAlgorithm, Resolution iResolution, decimal iAllocationRatio, int iStartYear, bool iRebalanceOnLaunch) : base(iAlgorithm, iResolution, iAllocationRatio, iStartYear, iRebalanceOnLaunch) { if (LiveMode) { mDataFeedFrequency = "Minute"; } else { mDataFeedFrequency = "Daily"; } mRebalanceFrequency = "Monthly"; mName = "UIS_LI_SIGNALS"; mDropboxFileDirectDownloadLink = "https://dropbox.com/s/ko9exryrs1ckojz/Algo%20Values%20Updater.csv?dl=1"; } public override void Initialize_Specific() { mValuesDictionary = new Dictionary<string, string>(); ReadHistoryFromLogicalInvest("UIS", "https://logical-invest.com/wp-content/csvrepository/UIS-SPXL-TMF_return.html"); } public override void OnData_Specific(TradeBars data) { UpdateDictionary(Time, ref mValuesDictionary); if (!mKeepFeedingData) { bool wSomeValueHasChanged = false; bool wSuccess = RetrieveValuesFromDictionary(ref wSomeValueHasChanged); if (!wSuccess) return; if (!Securities.ContainsKey(mSymbol1)) AddSecurity(SecurityType.Equity, mSymbol1, mResolution, true, 3, false); if (!Securities.ContainsKey(mSymbol2)) AddSecurity(SecurityType.Equity, mSymbol2, mResolution, true, 3, false); if (IsTimeToRebalance() || (mRebalanceOnChange && wSomeValueHasChanged)) { mKeepFeedingData = true; mReadyToTrade = true; } } if (!IsDataValid(data)) return; if (mReadyToTrade) { Trade(data, mSymbol1, mSymbol1Ratio, mSymbol2, mSymbol2Ratio); mKeepFeedingData = false; mReadyToTrade = false; } // TODO: Add plotting of symbols } protected override bool IsDataValid(TradeBars iData) { if (mSymbol1 == "") { return true; // If the required data is not yet initialized, skip this. } else { bool wValid = true; wValid &= iData.ContainsKey(mSymbol1); wValid &= iData.ContainsKey(mSymbol2); return wValid; } } // // Reads the desired values from the web and stores it in oValuesDictionary (Dictionary of variable name:value) // If in backtest mode and dates are in the past month or earlier, reads from LogicalInvest webpage directly. // If the date is in the current month, reads from the CSV file manually maintained on Dropbox. // private void UpdateDictionary(DateTime iTime, ref Dictionary<string, string> oValuesDictionary) { DateTime wRealTime = mAlgorithm.Time; if ((iTime.Year != wRealTime.Year) || (iTime.Month != wRealTime.Month)) { oValuesDictionary = mHistoryDictionary[new DateTime(iTime.Year, iTime.Month, 1)]; } else { using (var client = new WebClient()) { string file = ""; try { // fetch the file from dropbox file = client.DownloadString(mDropboxFileDirectDownloadLink); } catch { if (EnableLogs) Log("Error reading file from Dropbox: " + mDropboxFileDirectDownloadLink); return; } var lines = file.Replace("\r", String.Empty).Split(new[] { Environment.NewLine },StringSplitOptions.None); foreach (string line in lines) { List<string> wKeyVal = line.Split(',').ToList(); if (wKeyVal[0] == "") continue; string wKey = wKeyVal[0]; string wValue = ""; if (wKeyVal.Count > 1) { wValue = wKeyVal[1]; } if(!oValuesDictionary.ContainsKey(wKey)) { oValuesDictionary.Add(wKey, wValue); } else { oValuesDictionary[wKey] = wValue; } } } } } // // Gets the symbol values from mValuesDictionary, and stores them in mSymbol1, mSymbol2, ... , mSymbol1Ratio, mSymbol2Ratio, ... // Needs to have called UpdateDictionary() prior to this. // private bool RetrieveValuesFromDictionary(ref bool oSomeValueHasChanged) { if (!mValuesDictionary.ContainsKey("UIS_Symbol1")) Log("Error: Could not retrieve parameter UIS_Symbol1 from Dropbox."); if (!mValuesDictionary.ContainsKey("UIS_Symbol1_Ratio")) Log("Error: Could not retrieve parameter UIS_Symbol1_Ratio from Dropbox."); if (!mValuesDictionary.ContainsKey("UIS_Symbol2")) Log("Error: Could not retrieve parameter UIS_Symbol2 from Dropbox."); if (!mValuesDictionary.ContainsKey("UIS_Symbol2_Ratio")) Log("Error: Could not retrieve parameter UIS_Symbol2_Ratio from Dropbox."); if (!mValuesDictionary.ContainsKey("UIS_Symbol1") || !mValuesDictionary.ContainsKey("UIS_Symbol1_Ratio") || !mValuesDictionary.ContainsKey("UIS_Symbol2") || !mValuesDictionary.ContainsKey("UIS_Symbol2_Ratio")) { return false; } oSomeValueHasChanged = mSymbol1 != mValuesDictionary["UIS_Symbol1"] || mSymbol2 != mValuesDictionary["UIS_Symbol2"] || mSymbol1Ratio != Convert.ToDecimal(mValuesDictionary["UIS_Symbol1_Ratio"]) || mSymbol2Ratio != Convert.ToDecimal(mValuesDictionary["UIS_Symbol2_Ratio"]); mSymbol1 = mValuesDictionary["UIS_Symbol1"]; mSymbol2 = mValuesDictionary["UIS_Symbol2"]; mSymbol1Ratio = Convert.ToDecimal(mValuesDictionary["UIS_Symbol1_Ratio"]); mSymbol2Ratio = Convert.ToDecimal(mValuesDictionary["UIS_Symbol2_Ratio"]); return true; } } }
using QuantConnect.Data.Market; using System; using System.Collections.Generic; using System.Linq; namespace QuantConnect { /// <summary> /// /// /// /// </summary> public class GLDUSD : SubAlgorithm { // Strategy parameters private string mSymbol1 = "GLD"; private string mSymbol2 = "EUO"; private string mSymbol3 = "CROC"; private string mSymbol4 = "YCS"; private int mMinimumRebalancePeriod = 1; private int mSharpeLookback = 30; //30 private decimal mSharpeVolatilityFactor = 2.0m; //1.0 or 2.0 private decimal SharpeThresholdForCash = 0; // Threshold for max sharpe ratio under which the algo will go 50% cash. Needs to be tuned when changes to volatility factor are made. private SharpeRatioCalculator mMaxSharpeCombination; private int mDaysCounter = 0; private List<SharpeRatioCalculator> mSrcList; private decimal mLastRatio1 = 0; private decimal mLastRatio2 = 0; private decimal mLastRatio3 = 0; private decimal mLastRatio4 = 0; DateTime mLastHistoryCallsDate = DateTime.MinValue; List<TradeBar> mTradeBarHistorySymbol1; List<TradeBar> mTradeBarHistorySymbol2; List<TradeBar> mTradeBarHistorySymbol3; List<TradeBar> mTradeBarHistorySymbol4; public GLDUSD(PortfolioOfStrategies iAlgorithm, Resolution iResolution, decimal iAllocationRatio, int iStartYear, bool iRebalanceOnLaunch) : base(iAlgorithm, iResolution, iAllocationRatio, iStartYear, iRebalanceOnLaunch) { if (LiveMode) { mDataFeedFrequency = "Minute"; } else { mDataFeedFrequency = "Monthly"; } mRebalanceFrequency = "Monthly"; mName = "GLDUSD"; } public override void Initialize_Specific() { if (EnableLogs) { Log("BRS Strategy: Backtesting with following parameters:"); Log("\tSymbol1 >> " + mSymbol1); Log("\tSymbol2 >> " + mSymbol2); Log("\tSymbol3 >> " + mSymbol3); Log("\tSymbol4 >> " + mSymbol4); Log("\tMinimum Rebalance Period >> " + mMinimumRebalancePeriod); Log("\tStart year >> " + mStartYear); Log("\tSharpe lookback >> " + mSharpeLookback); Log("\tSharpe volatility factor >> " + mSharpeVolatilityFactor); } AddSecurity(SecurityType.Equity, mSymbol1, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol2, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol3, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol4, mResolution, true, 3, false); mTradeBarHistorySymbol1 = new List<TradeBar>(); mTradeBarHistorySymbol2 = new List<TradeBar>(); mTradeBarHistorySymbol3 = new List<TradeBar>(); mTradeBarHistorySymbol4 = new List<TradeBar>(); RecalculateSharpeRatios(mSharpeLookback, mSharpeVolatilityFactor, ref mSrcList); } public override void OnData_Specific(TradeBars data) { mDaysCounter = mDaysCounter - 1; RecalculateSharpeRatios(mSharpeLookback, mSharpeVolatilityFactor, ref mSrcList); decimal wRatio1 = 0.0m; decimal wRatio2 = 0.0m; decimal wRatio3 = 0.0m; decimal wRatio4 = 0.0m; if (mSrcList[0].IsReady) { mMaxSharpeCombination = mSrcList.Select( (value, index) => new { Value = value, Index = index } ).Aggregate( (a, b) => (a.Value > b.Value) ? a : b ).Value; // If the maximum sharpe ratio combination yields a sharpe ratio below SharpeThresholdForCash, then reduce ratios by 50% and invest 50% in cash if (mMaxSharpeCombination < SharpeThresholdForCash) { for (int i=0; i<mMaxSharpeCombination.mSymbolsRatios.Count(); i++) { mMaxSharpeCombination.mSymbolsRatios[i] = mMaxSharpeCombination.mSymbolsRatios[i] / 2; } } wRatio1 = mMaxSharpeCombination.mSymbolsRatios[0]; wRatio2 = mMaxSharpeCombination.mSymbolsRatios[1]; wRatio3 = mMaxSharpeCombination.mSymbolsRatios[2]; wRatio4 = mMaxSharpeCombination.mSymbolsRatios[3]; } if (mFirstPass == true || mDaysCounter <= 0 || mLastRatio1 != wRatio1 || mLastRatio2 != wRatio2 || mLastRatio3 != wRatio3 || mLastRatio4 != wRatio4) { mDaysCounter = mMinimumRebalancePeriod; if (EnableLogs) { Log(" "); Log("Rebalancing:"); } if (mSrcList[0].IsReady) { mMaxSharpeCombination = mSrcList.Select( (value, index) => new { Value = value, Index = index } ).Aggregate( (a, b) => (a.Value > b.Value) ? a : b ).Value; if (EnableLogs) Log(" Max Sharpe Combination >> " + mSymbol1 + " " + (mMaxSharpeCombination.mSymbolsRatios[0] * 100).ToString ("#.#") + "%, " + mSymbol2 + " " + (mMaxSharpeCombination.mSymbolsRatios[1] * 100).ToString ("#.#") + "%, " + mSymbol3 + " " + (mMaxSharpeCombination.mSymbolsRatios[2] * 100).ToString ("#.#") + "%, " + mSymbol4 + " " + (mMaxSharpeCombination.mSymbolsRatios[3] * 100).ToString ("#.#") + "%."); if (IsTimeToRebalance()) { Trade(data, mSymbol1, mMaxSharpeCombination.mSymbolsRatios[0], mSymbol2, mMaxSharpeCombination.mSymbolsRatios[1], mSymbol3, mMaxSharpeCombination.mSymbolsRatios[2], mSymbol4, mMaxSharpeCombination.mSymbolsRatios[3]); } } else { if (EnableLogs) Log(" Sharpe not ready >> " + mSymbol1 + " 50%, " + mSymbol2 + " 50%, " + mSymbol3 + " 0%, " + mSymbol4 + " 0%."); if (IsTimeToRebalance()) { // If sharpe ratio not ready yet, use a 50%-50% split Trade(data, mSymbol1, 0.5m, mSymbol2, 0.5m, mSymbol3, 0.0m, mSymbol4, 0.0m); } } mLastAllocationAmount = mAllocationAmount; mLastRatio1 = wRatio1; } decimal wSymbol1Percentage; decimal wSymbol2Percentage; decimal wSymbol3Percentage; decimal wSymbol4Percentage; if (mSrcList[0].IsReady && mMaxSharpeCombination != null) { wSymbol1Percentage = mMaxSharpeCombination.mSymbolsRatios[0] * 100; wSymbol2Percentage = mMaxSharpeCombination.mSymbolsRatios[1] * 100; wSymbol3Percentage = mMaxSharpeCombination.mSymbolsRatios[2] * 100; wSymbol4Percentage = mMaxSharpeCombination.mSymbolsRatios[3] * 100; } else { wSymbol1Percentage = 50; wSymbol2Percentage = 50; wSymbol3Percentage = 0; wSymbol4Percentage = 0; } Plot("GLDUSD Assets Split", mSymbol1 + " (%)", wSymbol1Percentage); Plot("GLDUSD Assets Split", mSymbol2 + " (%)", wSymbol2Percentage); Plot("GLDUSD Assets Split", mSymbol3 + " (%)", wSymbol3Percentage); Plot("GLDUSD Assets Split", mSymbol4 + " (%)", wSymbol4Percentage); } protected override bool IsDataValid(TradeBars iData) { bool wValid = true; wValid &= iData.ContainsKey(mSymbol1); wValid &= iData.ContainsKey(mSymbol2); wValid &= iData.ContainsKey(mSymbol3); wValid &= iData.ContainsKey(mSymbol4); return wValid; } private void RecalculateSharpeRatios(int iLookback, decimal iVolatilityFactor, ref List<SharpeRatioCalculator> iSrcList) { iSrcList = new List<SharpeRatioCalculator>(); int granularity = 15; for (int i=1; i<4; i++) { for (int j = 0; j <= granularity; j++) { List<decimal> wSymbolsRatios = new List<decimal> {0.0m,0.0m,0.0m,0.0m}; wSymbolsRatios[0] = 1/(decimal)granularity * j; wSymbolsRatios[i] = 1.0m - (1/(decimal)granularity * j); iSrcList.Add(SRC(wSymbolsRatios,iLookback,iVolatilityFactor)); } } // get the last 100 daily bars, once a day if (mAlgorithm.Time.Date > mLastHistoryCallsDate.Date) { mLastHistoryCallsDate = mAlgorithm.Time; mTradeBarHistorySymbol1 = History(mSymbol1, 100, Resolution.Daily).ToList(); mTradeBarHistorySymbol2 = History(mSymbol2, 100, Resolution.Daily).ToList(); mTradeBarHistorySymbol3 = History(mSymbol3, 100, Resolution.Daily).ToList(); mTradeBarHistorySymbol4 = History(mSymbol4, 100, Resolution.Daily).ToList(); } for (int i = 0; i < mTradeBarHistorySymbol1.Count(); i++) { for (int j = 0; j < iSrcList.Count; j++) { List<TradeBar> wData = new List<TradeBar>(); wData.Add(mTradeBarHistorySymbol1[i]); wData.Add(mTradeBarHistorySymbol2[i]); wData.Add(mTradeBarHistorySymbol3[i]); wData.Add(mTradeBarHistorySymbol4[i]); iSrcList[j].AddObservation(wData); } } } } }
using QuantConnect.Data.Market; using System; using System.Collections.Generic; using System.Linq; namespace QuantConnect { /// <summary> /// /// Maximum yield rotation strategy (MYRS) as described here: https://seekingalpha.com/article/1698412-how-to-build-an-etf-rotation-strategy-with-50-percent-annualized-returns /// /// </summary> public class MYRS : SubAlgorithm { // Strategy parameters private string mSymbol1 = "ZIV"; private string mSymbol2 = "MDY"; private string mSymbol3 = "EDV"; private string mSymbol4 = "SHY"; private int mMinimumRebalancePeriod = 1; private int mSharpeLookback = 63; private decimal mSharpeVolatilityFactor = 0.0m; private SharpeRatioCalculator mMaxSharpeCombination; private List<SharpeRatioCalculator> mSrcList; private decimal mLastRatio1 = 0; private decimal mLastRatio2 = 0; private decimal mLastRatio3 = 0; private decimal mLastRatio4 = 0; DateTime mLastHistoryCallsDate = DateTime.MinValue; List<TradeBar> mTradeBarHistorySymbol1; List<TradeBar> mTradeBarHistorySymbol2; List<TradeBar> mTradeBarHistorySymbol3; List<TradeBar> mTradeBarHistorySymbol4; public MYRS(PortfolioOfStrategies iAlgorithm, Resolution iResolution, decimal iAllocationRatio, int iStartYear, bool iRebalanceOnLaunch) : base(iAlgorithm, iResolution, iAllocationRatio, iStartYear, iRebalanceOnLaunch) { if (LiveMode) { mDataFeedFrequency = "Minute"; } else { mDataFeedFrequency = "Weekly"; } mRebalanceFrequency = "Weekly"; mName = "MYRS"; } public override void Initialize_Specific() { if (EnableLogs) { Log("MYRS Strategy: Backtesting with following parameters:"); Log("\tSymbol1 >> " + mSymbol1); Log("\tSymbol2 >> " + mSymbol2); Log("\tSymbol3 >> " + mSymbol3); Log("\tSymbol4 >> " + mSymbol4); Log("\tMinimum Rebalance Period >> " + mMinimumRebalancePeriod); Log("\tStart year >> " + mStartYear); Log("\tSharpe lookback >> " + mSharpeLookback); Log("\tSharpe volatility factor >> " + mSharpeVolatilityFactor); } AddSecurity(SecurityType.Equity, mSymbol1, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol2, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol3, mResolution, true, 3, false); AddSecurity(SecurityType.Equity, mSymbol4, mResolution, true, 3, false); mTradeBarHistorySymbol1 = new List<TradeBar>(); mTradeBarHistorySymbol2 = new List<TradeBar>(); mTradeBarHistorySymbol3 = new List<TradeBar>(); mTradeBarHistorySymbol4 = new List<TradeBar>(); RecalculateSharpeRatios(mSharpeLookback, mSharpeVolatilityFactor, ref mSrcList); } public override void OnData_Specific(TradeBars data) { RecalculateSharpeRatios(mSharpeLookback, mSharpeVolatilityFactor, ref mSrcList); decimal wRatio1 = 0.0m; decimal wRatio2 = 0.0m; decimal wRatio3 = 0.0m; decimal wRatio4 = 0.0m; if (mSrcList[0].IsReady) { mMaxSharpeCombination = mSrcList.Select( (value, index) => new { Value = value, Index = index } ).Aggregate( (a, b) => (a.Value > b.Value) ? a : b ).Value; wRatio1 = mMaxSharpeCombination.mSymbolsRatios[0]; wRatio2 = mMaxSharpeCombination.mSymbolsRatios[1]; wRatio3 = mMaxSharpeCombination.mSymbolsRatios[2]; wRatio4 = mMaxSharpeCombination.mSymbolsRatios[3]; } if (mFirstPass == true || mLastRatio1 != wRatio1 || mLastRatio2 != wRatio2 || mLastRatio3 != wRatio3 || mLastRatio4 != wRatio4) { if (EnableLogs) { Log(" "); Log("Rebalancing:"); } if (mSrcList[0].IsReady) { mMaxSharpeCombination = mSrcList.Select( (value, index) => new { Value = value, Index = index } ).Aggregate( (a, b) => (a.Value > b.Value) ? a : b ).Value; if (EnableLogs) Log(" Max Sharpe Combination >> " + mSymbol1 + " " + (mMaxSharpeCombination.mSymbolsRatios[0] * 100).ToString ("#.#") + "%, " + mSymbol2 + " " + (mMaxSharpeCombination.mSymbolsRatios[1] * 100).ToString ("#.#") + "%, " + mSymbol3 + " " + (mMaxSharpeCombination.mSymbolsRatios[2] * 100).ToString ("#.#") + "%, " + mSymbol4 + " " + (mMaxSharpeCombination.mSymbolsRatios[3] * 100).ToString ("#.#") + "%."); if (IsTimeToRebalance()) { Trade(data, mSymbol1, mMaxSharpeCombination.mSymbolsRatios[0], mSymbol2, mMaxSharpeCombination.mSymbolsRatios[1], mSymbol3, mMaxSharpeCombination.mSymbolsRatios[2], mSymbol4, mMaxSharpeCombination.mSymbolsRatios[3]); } } else { if (EnableLogs) Log(" Sharpe not ready >> " + mSymbol1 + " 50%, " + mSymbol2 + " 50%, " + mSymbol3 + " 0%, " + mSymbol4 + " 0%."); if (IsTimeToRebalance()) { // If sharpe ratio not ready yet, use a 50%-50% split between ZIV and MDY Trade(data, mSymbol1, 0.5m, mSymbol2, 0.5m, mSymbol3, 0.0m, mSymbol4, 0.0m); } } mLastAllocationAmount = mAllocationAmount; mLastRatio1 = wRatio1; } decimal wSymbol1Percentage; decimal wSymbol2Percentage; decimal wSymbol3Percentage; decimal wSymbol4Percentage; if (mSrcList[0].IsReady && mMaxSharpeCombination != null) { wSymbol1Percentage = mMaxSharpeCombination.mSymbolsRatios[0] * 100; wSymbol2Percentage = mMaxSharpeCombination.mSymbolsRatios[1] * 100; wSymbol3Percentage = mMaxSharpeCombination.mSymbolsRatios[2] * 100; wSymbol4Percentage = mMaxSharpeCombination.mSymbolsRatios[3] * 100; } else { wSymbol1Percentage = 50; wSymbol2Percentage = 50; wSymbol3Percentage = 0; wSymbol4Percentage = 0; } Plot("MYRS Assets Split", mSymbol1 + " (%)", wSymbol1Percentage); Plot("MYRS Assets Split", mSymbol2 + " (%)", wSymbol2Percentage); Plot("MYRS Assets Split", mSymbol3 + " (%)", wSymbol3Percentage); Plot("MYRS Assets Split", mSymbol4 + " (%)", wSymbol4Percentage); } protected override bool IsDataValid(TradeBars iData) { bool wValid = true; wValid &= iData.ContainsKey(mSymbol1); wValid &= iData.ContainsKey(mSymbol2); wValid &= iData.ContainsKey(mSymbol3); wValid &= iData.ContainsKey(mSymbol4); return wValid; } private void RecalculateSharpeRatios(int iLookback, decimal iVolatilityFactor, ref List<SharpeRatioCalculator> iSrcList) { iSrcList = new List<SharpeRatioCalculator>(); for (int i=0; i<4; i++) { List<decimal> wSymbolsRatios = new List<decimal> {0.0m,0.0m,0.0m,0.0m}; wSymbolsRatios[i] = 1.0m; iSrcList.Add(SRC(wSymbolsRatios,iLookback,iVolatilityFactor)); } // get the last 100 daily bars, once a day if (mAlgorithm.Time.Date > mLastHistoryCallsDate.Date) { mLastHistoryCallsDate = mAlgorithm.Time; mTradeBarHistorySymbol1 = History(mSymbol1, 100, Resolution.Daily).ToList(); mTradeBarHistorySymbol2 = History(mSymbol2, 100, Resolution.Daily).ToList(); mTradeBarHistorySymbol3 = History(mSymbol3, 100, Resolution.Daily).ToList(); mTradeBarHistorySymbol4 = History(mSymbol4, 100, Resolution.Daily).ToList(); } for (int i = 0; i < mTradeBarHistorySymbol1.Count(); i++) { for (int j = 0; j < iSrcList.Count; j++) { List<TradeBar> wData = new List<TradeBar>(); wData.Add(mTradeBarHistorySymbol1[i]); wData.Add(mTradeBarHistorySymbol2[i]); wData.Add(mTradeBarHistorySymbol3[i]); wData.Add(mTradeBarHistorySymbol4[i]); iSrcList[j].AddObservation(wData); } } } } }
using QuantConnect.Data.Market; using System; using System.Collections.Generic; using System.Linq; namespace QuantConnect { /// <summary> /// /// Re-coding of this python strategy: https://www.quantconnect.com/forum/discussion/3377/momentum-strategy-with-market-cap-and-ev-ebitda /// /// </summary> public class FFA : SubAlgorithm { protected int holding_months = 1; protected int num_screener = 100; protected int num_stocks = 10; protected int formation_days = 200; protected bool lowmom = false; protected int month_count = 1; private bool mReadyToTrade = false; // rebalance the universe selection once a month protected bool redo_symbol_selection_flag = false; // make sure to run the universe selection at the start of the algorithm even it's not the month start protected bool first_month_symbol_selection_flag = true; protected int trade_flag = 0; protected List<Symbol> symbols = null; protected List<string> chosen_symbols = null; protected List<decimal> ratios_list = null; protected TradeBars mData = null; public FFA(PortfolioOfStrategies iAlgorithm, Resolution iResolution, decimal iAllocationRatio, int iStartYear, bool iRebalanceOnLaunch) : base(iAlgorithm, iResolution, iAllocationRatio, iStartYear, iRebalanceOnLaunch) { if (LiveMode) { mDataFeedFrequency = "Minute"; } else { mDataFeedFrequency = "Daily"; } mRebalanceFrequency = "Monthly"; mName = "FundamentalFactorAlgorithm"; } public override void Initialize_Specific() { month_count = holding_months; symbols = new List<Symbol>(); chosen_symbols = new List<string>(); ratios_list = new List<decimal>(); AddSecurity(SecurityType.Equity, "SPY", mResolution, true, 3, false); AddSecurity(SecurityType.Equity, "TLT", mResolution, true, 3, false); mAlgorithm.UniverseSettings.Resolution = Resolution.Daily; mAlgorithm.AddUniverse(CoarseSelectionFunction, FineSelectionFunction); mAlgorithm.Schedule.On(mAlgorithm.DateRules.MonthEnd("SPY"), mAlgorithm.TimeRules.At(23, 0), monthly_rebalance); //mAlgorithm.Schedule.On(mAlgorithm.DateRules.MonthStart("SPY"), mAlgorithm.TimeRules.At(10, 0), rebalance); } public override void OnData_Specific(TradeBars data) { mData = data; rebalance(); } protected override bool IsDataValid(TradeBars iData) { bool wValid = true; wValid &= iData.ContainsKey("SPY"); wValid &= iData.ContainsKey("TLT"); foreach (string symbol in chosen_symbols) { wValid &= iData.ContainsKey(symbol); } wValid &= symbols.Count() > 0; return wValid; } public IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse) { if (redo_symbol_selection_flag || first_month_symbol_selection_flag) { List<CoarseFundamental> selected = new List<CoarseFundamental>(); List<Symbol> filtered = new List<Symbol>(); foreach (CoarseFundamental x in coarse) { if (x.HasFundamentalData && x.Price > 5) { selected.Add(x); } } selected = selected.OrderByDescending(o=>o.DollarVolume).ToList(); foreach (CoarseFundamental x in selected.Take(200)) { filtered.Add(x.Symbol); } return filtered; } else { return symbols; } } public IEnumerable<Symbol> FineSelectionFunction(IEnumerable<FineFundamental> fine) { if (redo_symbol_selection_flag || first_month_symbol_selection_flag) { List<FineFundamental> filtered_fine = new List<FineFundamental>(); List<FineFundamental> top = new List<FineFundamental>(); try { foreach (FineFundamental x in fine) { if (x.ValuationRatios.EVToEBITDA > 0 && x.EarningReports.BasicAverageShares.ThreeMonths > 0 && x.EarningReports.BasicAverageShares.ThreeMonths * (x.EarningReports.BasicEPS.TwelveMonths*x.ValuationRatios.PERatio) > 2000000000) { filtered_fine.Add(x); } } } catch (Exception e) { filtered_fine.Clear(); foreach (FineFundamental x in fine) { if (x.ValuationRatios.EVToEBITDA > 0 && x.EarningReports.BasicAverageShares.ThreeMonths > 0) { filtered_fine.Add(x); } } } top = filtered_fine.OrderByDescending(o=>o.ValuationRatios.EVToEBITDA).ToList().Take(num_screener).ToList(); symbols.Clear(); foreach (FineFundamental x in top) { symbols.Add(x.Symbol); } redo_symbol_selection_flag = false; if(symbols.Count() > 0) { first_month_symbol_selection_flag = false; } } return symbols; } protected void monthly_rebalance() { redo_symbol_selection_flag = true; } protected void rebalance() { if (IsTimeToRebalance() && !mKeepFeedingData) { IEnumerable<TradeBar> hist = mAlgorithm.History("SPY", 120, Resolution.Daily); List<decimal> spy_hist = new List<decimal>(); foreach (TradeBar tradebar in hist) { spy_hist.Add(tradebar.Close); } decimal average = spy_hist.Count > 0 ? spy_hist.Average() : 0.0m; if (mData["SPY"].Price < average) { Trade(mData, "TLT", 1.0m); chosen_symbols.Clear(); return; } if (symbols.Count() == 0) return; chosen_symbols = calc_return(symbols).Take(num_stocks).ToList(); foreach (string symbol in chosen_symbols) { Log("rebalance: " + symbol); } decimal wRatio = 1.0m/num_stocks; ratios_list = new List<decimal>(); for (int i=0; i<num_stocks; i++) { ratios_list.Add(wRatio); } foreach (string symbol in chosen_symbols) { AddSecurity(SecurityType.Equity, symbol, mResolution, true, 3, false); } mKeepFeedingData = true; mReadyToTrade = true; } if (!IsDataValid(mData)) { foreach (string symbol in chosen_symbols) { if(mData.ContainsKey(symbol)) { Log(" - " + symbol + " ok. Price = " + mData[symbol].Close); } else { Log(" - " + symbol + " not in mData."); } } return; } if (mReadyToTrade) { Trade(mData,chosen_symbols,ratios_list); mKeepFeedingData = false; mReadyToTrade = false; } } protected List<string> calc_return(List<Symbol> stocks) { IEnumerable<Slice> hist = mAlgorithm.History(stocks, formation_days, Resolution.Daily); IEnumerable<Slice> current = mAlgorithm.History(stocks, 1, Resolution.Minute); Dictionary<string,List<decimal>> price = new Dictionary<string,List<decimal>>(); Dictionary<string,decimal> ret = new Dictionary<string,decimal>(); foreach (Symbol symbol in stocks) { if (symbol.Value == "UPL") continue; // For some weird reason, symbol UPL messes up the data in backtest mode, in 2004 and 2005. So ignore this symbol. price[symbol.Value] = new List<decimal>(); foreach(Slice slice in hist) { if(slice.ContainsKey(symbol)) { price[symbol.Value].Add(slice[symbol].Close); } } foreach(Slice slice in current) { if(slice.ContainsKey(symbol)) { price[symbol.Value].Add(slice[symbol].Close); } } } int priceCount; foreach (string symbol in price.Keys) { priceCount = price[symbol].Count; ret[symbol] = (price[symbol][priceCount - 1] - price[symbol][0]) / price[symbol][0]; } var sort_return = ret.ToList(); if (lowmom) { sort_return = sort_return.OrderBy(o=>o.Value).ToList(); } else { sort_return = sort_return.OrderByDescending(o=>o.Value).ToList(); } List<string> selectedSymbols = new List<string>(); foreach (KeyValuePair<string,decimal> keyVal in sort_return) { selectedSymbols.Add(keyVal.Key); } return selectedSymbols; } } }