public override void Run() { //========== initialization ========== WarmupStartTime = Globals.WARMUP_START_TIME; StartTime = Globals.START_TIME; EndTime = Globals.END_TIME; Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = Globals.COMMISSION; var risky = RISKY_PORTFOLIO .Select(a => Tuple.Create(AddDataSource(a.Item1), a.Item2)) .ToList(); var cash = CASH_PORTFOLIO .Select(a => Tuple.Create(AddDataSource(a.Item1), a.Item2)) .ToList(); var universe = risky .Select(a => a.Item1) .Concat(cash .Select(a => a.Item1)) .Distinct() .ToList(); var economy = AddDataSource(ECONOMY); var market = AddDataSource(MARKET); var benchmark = AddDataSource(BENCHMARK); //========== simulation loop ========== foreach (var simTime in SimTimes) { // skip if there are any instruments missing from our universe if (!HasInstruments(universe) || !HasInstrument(benchmark) || !HasInstrument(economy)) { continue; } // calculate indicators var economyLagged = economy.Instrument.Close.Delay(25); // 1 month publication lag: March observation published April 03 var economySMA = economyLagged.SMA(252); var economyGrowing = economyLagged[0] < economySMA[0]; var marketSMA = market.Instrument.Close.SMA(200); // 10-months moving average var marketRising = market.Instrument.Close[0] > marketSMA[0]; // trigger monthly rebalancing if (SimTime[0].Month != NextSimTime.Month) { // determine target allocation: cash, if economy shrinking _and_ markets declining var allocation = economyGrowing || marketRising ? risky : cash; // determine weights var weights = universe .Select(ds => ds.Instrument) .ToDictionary( i => i, i => allocation .Where(a => a.Item1 == i.DataSource) .Sum(a => a.Item2)); // submit orders _alloc.LastUpdate = SimTime[0]; foreach (var i in weights.Keys) { _alloc.Allocation[i] = weights[i]; var shares = (int)Math.Floor(weights[i] * NetAssetValue[0] / i.Close[0]); i.Trade(shares - i.Position); } } // plotter output if (!IsOptimizing && TradingDays > 0) { _plotter.AddNavAndBenchmark(this, FindInstrument(BENCHMARK)); _plotter.AddStrategyHoldings(this, universe.Select(ds => ds.Instrument)); if (_alloc.LastUpdate == SimTime[0]) { _plotter.AddTargetAllocationRow(_alloc); } #if true // additional plotter output _plotter.SelectChart("Unemployment Trend", "Date"); _plotter.SetX(SimTime[0]); _plotter.Plot(economy.Instrument.Name, economyLagged[0]); _plotter.Plot(economy.Instrument.Name + "-SMA", economySMA[0]); _plotter.SelectChart("Market Trend", "Date"); _plotter.SetX(SimTime[0]); _plotter.Plot(market.Instrument.Name, market.Instrument.Close[0]); _plotter.Plot(market.Instrument.Name + "-SMA", marketSMA[0]); #endif } } //========== post processing ========== if (!IsOptimizing) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); _plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }
public override IEnumerable <Bar> Run(DateTime?startTime, DateTime?endTime) { //========== initialization ========== WarmupStartTime = Globals.WARMUP_START_TIME; StartTime = Globals.START_TIME; EndTime = Globals.END_TIME; Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = Globals.COMMISSION; // the book does not deduct commissions var menu = AddDataSources(ETF_MENU).ToList(); var bench = AddDataSource(BENCHMARK); //========== simulation loop ========== foreach (DateTime simTime in SimTimes) { // calculate momentum w/ algorithm-specific helper function var evaluation = Instruments .ToDictionary( i => i, i => MOMENTUM(i)); // skip, if there are any missing instruments if (!HasInstruments(menu) || !HasInstrument(bench)) { continue; } // rank, and select top-3 instruments var top3 = menu .Select(ds => ds.Instrument) .OrderByDescending(i => evaluation[i]) .Take(NUM_PICKS); // calculate target percentage and how far we are off double targetPercentage = 1.0 / NUM_PICKS; double maxOff = menu .Select(ds => ds.Instrument) .Max(i => (top3.Count() > 0 && top3.Contains(i) ? 1.0 : 0.0) * Math.Abs(i.Position * i.Close[0] / NetAssetValue[0] - targetPercentage) / targetPercentage); // rebalance once per month, and only if we need adjustments exceeding 20% if (REBAL_TODAY(maxOff)) { _alloc.LastUpdate = SimTime[0]; foreach (var i in menu.Select(ds => ds.Instrument)) { _alloc.Allocation[i] = top3.Contains(i) ? targetPercentage : 0.0; // determine current and target shares per instrument... double targetEquity = (top3.Contains(i) ? targetPercentage : 0.0) * NetAssetValue[0]; int targetShares = (int)Math.Floor(targetEquity / i.Close[0]); int currentShares = i.Position; // ... and trade the delta Order newOrder = i.Trade(targetShares - currentShares, ORDER_TYPE); // add a comment, to make the trading log easier to read if (newOrder != null) { if (currentShares == 0) { newOrder.Comment = "Open"; } else if (targetShares == 0) { newOrder.Comment = "Close"; } else { newOrder.Comment = "Rebalance"; } } } } // plotter output if (!IsOptimizing && TradingDays > 0) { _plotter.AddNavAndBenchmark(this, FindInstrument(BENCHMARK)); _plotter.AddStrategyHoldings(this, ETF_MENU.Select(nick => FindInstrument(nick))); if (_alloc.LastUpdate == SimTime[0]) { _plotter.AddTargetAllocationRow(_alloc); } if (IsDataSource) { var v = 10.0 * NetAssetValue[0] / Globals.INITIAL_CAPITAL; yield return(Bar.NewOHLC( this.GetType().Name, SimTime[0], v, v, v, v, 0)); } } } //========== post processing ========== if (!IsOptimizing) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); _plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }
public override void Run() { //========== initialization ========== // set simulation time frame #if USE_CLENOWS_RANGE // matching Clenow's charts StartTime = DateTime.Parse("01/01/1999", CultureInfo.InvariantCulture); WarmupStartTime = StartTime - TimeSpan.FromDays(180); EndTime = DateTime.Parse("12/31/2014", CultureInfo.InvariantCulture); #else WarmupStartTime = Globals.WARMUP_START_TIME; StartTime = Globals.START_TIME; EndTime = Globals.END_TIME; #endif // set account value Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = Globals.COMMISSION; // Clenow is not considering commissions // add instruments AddDataSource(BENCHMARK); AddDataSources(UNIVERSE.Constituents); //========== simulation loop ========== Instrument benchmark = null; double? benchmark0 = null; // loop through all bars foreach (DateTime simTime in SimTimes) { benchmark = benchmark ?? FindInstrument(BENCHMARK); if (benchmark0 == null && TradingDays == 1) { benchmark0 = benchmark.Open[0]; } // calculate indicators // store to list, to make sure indicators are evaluated // exactly once per bar // do this on all available instruments, to make sure we // have valid data available when instruments become // constituents of our universe var instrumentIndicators = Instruments .Select(i => new { instrument = i, regression = i.Close.LogRegression(MOM_PERIOD), maxMove = i.TrueRange().Divide(i.Close).Highest(MOM_PERIOD), avg100 = i.Close.SMA(INSTR_FLT), atr20 = i.AverageTrueRange(ATR_PERIOD), }) .ToList(); // index filter: only buy any shares, while S&P-500 is trading above its 200-day moving average // NOTE: the 10-day SMA on the benchmark is _not_ mentioned in // the book. We added it here, to compensate for the // simplified re-balancing schedule. bool allowNewEntries = FindInstrument(BENCHMARK).Close.SMA(10)[0] > FindInstrument(BENCHMARK).Close.SMA(INDEX_FLT)[0]; // trade once per week // this is a slight simplification from Clenow's suggestion to adjust positions // every week, and adjust position sizes only every other week // CAUTION: no indicator calculations within this block! if (SimTime[0].DayOfWeek <= DayOfWeek.Wednesday && NextSimTime.DayOfWeek > DayOfWeek.Wednesday) { // select our universe constituents var universeConstituents = instrumentIndicators .Where(e => e.instrument.IsConstituent(UNIVERSE)) .ToList(); // rank by volatility-adjusted momentum and pick top 20% var topRankedInstruments = universeConstituents .OrderByDescending(e => (Math.Exp(252.0 * e.regression.Slope[0]) - 1.0) * e.regression.R2[0]) .Take((int)Math.Round(TOP_PCNT / 100.0 * universeConstituents.Count)) .ToList(); // disqualify // - trading below 100-day moving average // - maximum move > 15% var availableInstruments = topRankedInstruments .Where(e => e.instrument.Close[0] > e.avg100[0] && e.maxMove[0] < MAX_MOVE / 100.0) .ToList(); // calculate position sizes var positionSizes = availableInstruments .Select(e => new { instrument = e.instrument, positionSize = RISK_PER_STOCK * 0.0001 / e.atr20[0] * e.instrument.Close[0], }) .ToList(); // assign equity, until we run out of cash var instrumentRelativeEquity = Instruments.ToDictionary(i => i, i => 0.0); double availableEquity = 1.0; foreach (var i in positionSizes) { if (i.positionSize <= availableEquity) { instrumentRelativeEquity[i.instrument] = i.positionSize; availableEquity -= i.positionSize; } else { break; } } // loop through all instruments and submit trades _alloc.LastUpdate = SimTime[0]; _alloc.Allocation.Clear(); foreach (Instrument instrument in instrumentRelativeEquity.Keys) { if (instrumentRelativeEquity[instrument] > 0.005) { _alloc.Allocation[instrument] = instrumentRelativeEquity[instrument]; } int currentShares = instrument.Position; int targetSharesPreFilter = (int)Math.Round(NetAssetValue[0] * instrumentRelativeEquity[instrument] / instrument.Close[0]); int targetShares = allowNewEntries ? targetSharesPreFilter : Math.Min(currentShares, targetSharesPreFilter); instrument.Trade(targetShares - currentShares, OrderType.openNextBar); } string message = instrumentRelativeEquity .Where(i => i.Value != 0.0) .Aggregate(string.Format("{0:MM/dd/yyyy}: ", SimTime[0]), (prev, next) => prev + string.Format("{0}={1:P2} ", next.Key.Symbol, next.Value)); if (!IsOptimizing && (EndTime - SimTime[0]).TotalDays < 30) { Output.WriteLine(message); } } // create plots on Sheet 1 if (TradingDays > 0) { #if REPRODUCE_CLENOWS_CHART _plotter.SelectChart(Name, "date"); _plotter.SetX(SimTime[0]); _plotter.Plot(Name, NetAssetValue[0] / INITIAL_FUNDS); _plotter.Plot(benchmark.Name, benchmark.Close[0] / benchmark0); _plotter.Plot(benchmark.Name + " 200-day moving average", benchmark.Close.SMA(200)[0] / benchmark0); _plotter.Plot("Cash", Cash / NetAssetValue[0]); #else _plotter.AddNavAndBenchmark(this, benchmark); //_plotter.AddStrategyHoldings(this, universeConstituents); // plot strategy exposure _plotter.SelectChart("Strategy Exposure", "Date"); _plotter.SetX(SimTime[0]); _plotter.Plot("Exposure", Instruments.Sum(i => i.Position * i.Close[0]) / NetAssetValue[0]); _plotter.Plot("Number of Stocks", (double)Positions.Count); #endif } } //========== post processing ========== if (!IsOptimizing) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); _plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }
public override void Run() { //========== initialization ========== #if USE_BENSDORPS_RANGE // matching range in the book StartTime = SubclassedStartTime ?? DateTime.Parse("01/02/1995", CultureInfo.InvariantCulture); EndTime = SubclassedEndTime ?? DateTime.Parse("11/23/2016", CultureInfo.InvariantCulture); WarmupStartTime = StartTime - TimeSpan.FromDays(365); #else StartTime = SubclassedStartTime ?? Globals.START_TIME; EndTime = SubclassedEndTime ?? Globals.END_TIME; WarmupStartTime = Globals.WARMUP_START_TIME; #endif Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = Globals.COMMISSION; AddDataSources(UNIVERSE.Constituents); var spx = AddDataSource(SPX); var benchmark = AddDataSource(BENCHMARK); //========== simulation loop ========== double nn = 0.0; foreach (var s in SimTimes) { var universe = Instruments .Where(i => i.IsConstituent(UNIVERSE)) .ToList(); //----- calculate indicators // calculate indicators for all known instruments, // as they might enter the universe any time var indicators = Instruments .ToDictionary( i => i, i => new { rsi = i.Close.RSI(3), roc = i.Close.Momentum(200), }); if (!HasInstrument(benchmark)) { continue; } var smaBand = spx.Instrument.Close.SMA(200).Multiply(0.98); // 2% below 200-day SMA // open positions on Monday if (NextSimTime.DayOfWeek < SimTime[0].DayOfWeek) // open positions on Monday { // we are not entirely sure how Bensdorp wants this strategy to work. // we see three alternatives #if false // solution A // create one list of 10 stocks, none of which are overbought, // ranked by momentum // good: new entries are guaranteed to be on keep-list on day 1 // also, we always hold 10 stocks // bad: we might exit stocks with top momentum, as soon as they become overbought // => this strategy seems to never hold stocks longer than 60 days, // conflicting with the statements made in the book var nextHoldings = universe .Where(i => spx.Instrument.Close[0] > smaBand[0]) .Where(i => indicators[i].rsi[0] < MAX_RSI) .OrderByDescending(i => indicators[i].roc[0]) .Take(MAX_ENTRIES) .ToList(); #endif #if false // solution B // create separate list for new entries and for keepers // good: this makes sure that we almost always hold 10 stocks // bad: a new stock entered might not meet the hold requirements, // as it might not have top-10 momentum. this adds somewhat of // a mean-reversion component to the strategy // => this strategy seems to work very well over the book's backtesting period. // overall, higher return and higher drawdown than C, worse Sharpe ratio var keep = universe .Where(i => spx.Instrument.Close[0] > smaBand[0]) .OrderByDescending(i => indicators[i].roc[0]) .Take(MAX_ENTRIES) .Where(i => i.Position != 0) .ToList(); var enter = universe .Where(i => spx.Instrument.Close[0] > smaBand[0]) .Where(i => i.Position == 0 && indicators[i].rsi[0] < MAX_RSI) .OrderByDescending(i => indicators[i].roc[0]) .Take(MAX_ENTRIES - keep.Count) .ToList(); var nextHoldings = keep .Concat(enter) .ToList(); #endif #if true // solution C // draw new entries and keeps both from top-10 ranked stocks // good: new entries are guaranteed to be on the keep list on day 1 // bad: the enter list might be empty, if all top-10 stocks are overbought // driving down our exposure and therewith return var top10 = universe .Where(i => spx.Instrument.Close[0] > smaBand[0]) .OrderByDescending(i => indicators[i].roc[0]) .Take(MAX_ENTRIES) .ToList(); var keep = top10 .Where(i => i.Position != 0) .ToList(); var enter = top10 .Where(i => i.Position == 0 && indicators[i].rsi[0] < MAX_RSI) .ToList(); var nextHoldings = keep .Concat(enter) .ToList(); #endif _alloc.LastUpdate = SimTime[0]; _alloc.Allocation.Clear(); foreach (var i in Instruments) { double targetPercentage = nextHoldings.Contains(i) ? 1.0 / MAX_ENTRIES : 0.0; int targetShares = (int)Math.Floor(NetAssetValue[0] * targetPercentage / i.Close[0]); if (targetPercentage != 0.0) { _alloc.Allocation[i] = targetPercentage; } i.Trade(targetShares - i.Position); } nn = nextHoldings.Count / 10.0; } //----- output if (!IsOptimizing && TradingDays > 0) { _plotter.AddNavAndBenchmark(this, FindInstrument(BENCHMARK)); //_plotter.AddStrategyHoldings(this, universe); // plot strategy exposure _plotter.SelectChart("Exposure Chart", "Date"); _plotter.SetX(SimTime[0]); _plotter.Plot("Exposure", Instruments.Sum(i => i.Position * i.Close[0]) / NetAssetValue[0]); //_plotter.Plot("Choices", nn); if (IsSubclassed) { AddSubclassedBar(10.0 * NetAssetValue[0] / Globals.INITIAL_CAPITAL); } } } //========== post processing ========== if (!IsOptimizing) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); _plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }
public override IEnumerable <Bar> Run(DateTime?startTime, DateTime?endTime) { //========== initialization ========== WarmupStartTime = Globals.WARMUP_START_TIME; StartTime = Globals.START_TIME; EndTime = Globals.END_TIME; Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = Globals.COMMISSION; // our universe consists of risky & safe assets var riskyAssets = AddDataSources(RISKY_ASSETS); var safeAssets = AddDataSources(SAFE_ASSETS); var universe = riskyAssets.Concat(safeAssets); var bench = AddDataSource(BENCHMARK); //========== simulation loop ========== foreach (var simTime in SimTimes) { // calculate indicators on overy bar Dictionary <Instrument, double> momentum = Instruments .ToDictionary( i => i, i => (1.0 * i.Close.Momentum(21)[0] + 3.0 * i.Close.Momentum(63)[0] + 6.0 * i.Close.Momentum(126)[0] + 12.0 * i.Close.Momentum(252)[0]) / 22.0); // skip if there are any instruments missing from our universe if (!HasInstruments(universe) || !HasInstrument(bench)) { continue; } // trigger rebalancing if (SimTime[0].Month != NextSimTime.Month) // monthly { // calculate covariance var covar = new PortfolioSupport.Covariance(Instruments, 12, 21); // 12 monthly bars // calculate efficient frontier for universe // note how momentum and covariance are annualized here var cla = new PortfolioSupport.MarkowitzCLA( universe.Select(ds => ds.Instrument), i => 252.0 * momentum[i], (i, j) => 252.0 / covar.BarSize * covar[i, j], i => 0.0, i => safeAssets.Contains(i.DataSource) ? 1.0 : MAX_RISKY_ALLOC); // find portfolio with specified risk var pf = cla.TargetVolatility(TVOL); Output.WriteLine("{0:MM/dd/yyyy}: {1}", SimTime[0], pf.ToString()); // adjust all positions _alloc.LastUpdate = SimTime[0]; foreach (var i in pf.Weights.Keys) { _alloc.Allocation[i] = pf.Weights[i]; int targetShares = (int)Math.Floor(NetAssetValue[0] * pf.Weights[i] / i.Close[0]); int currentShares = i.Position; var ticket = i.Trade(targetShares - currentShares); if (ticket != null) { if (i.Position == 0) { ticket.Comment = "open"; } else if (targetShares == 0) { ticket.Comment = "close"; } else { ticket.Comment = "rebalance"; } } } } // plotter output if (!IsOptimizing && TradingDays > 0) { _plotter.AddNavAndBenchmark(this, FindInstrument(BENCHMARK)); _plotter.AddStrategyHoldings(this, universe.Select(ds => ds.Instrument)); if (_alloc.LastUpdate == SimTime[0]) { _plotter.AddTargetAllocationRow(_alloc); } if (IsDataSource) { var v = 10.0 * NetAssetValue[0] / Globals.INITIAL_CAPITAL; yield return(Bar.NewOHLC( this.GetType().Name, SimTime[0], v, v, v, v, 0)); } } } //========== post processing ========== if (!IsOptimizing) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); _plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }
public override void Run() { //========== initialization ========== StartTime = SubclassedStartTime ?? START_TIME; EndTime = SubclassedEndTime ?? END_TIME; Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = 0.0; // lazy portfolios w/o commissions var universe = AddDataSources(ALLOCATION.Select(u => u.Item1)); var bench = AddDataSource(BENCH); //========== simulation loop ========== foreach (var s in SimTimes) { if (!HasInstruments(universe) || !HasInstrument(bench)) { continue; } //if (SimTime[0].Date.DayOfWeek > NextSimTime.Date.DayOfWeek) if (SimTime[0].Date.Month != NextSimTime.Date.Month) { _alloc.LastUpdate = SimTime[0]; foreach (var a in ALLOCATION) { var w = a.Item2 != 0.0 ? a.Item2 : 1.0 / ALLOCATION.Count; var i = FindInstrument(a.Item1); _alloc.Allocation[i] = w; int targetShares = (int)Math.Floor(NetAssetValue[0] * w / i.Close[0]); i.Trade(targetShares - i.Position); } } AddSubclassedBar(10.0 * NetAssetValue[0] / Globals.INITIAL_CAPITAL); // plotter output if (!IsOptimizing && TradingDays > 0) { _plotter.AddNavAndBenchmark(this, bench.Instrument); _plotter.AddStrategyHoldings(this, universe.Select(ds => ds.Instrument)); } #if false if (_nav.Count() == 0) { Output.WriteLine("First simulation timestamp {0:MM/dd/yyyy}", SimTime[0]); foreach (var ds in universe) { Output.WriteLine(" {0}: start at {1:MM/dd/yyyy}", ds.Instrument.Name, ds.FirstTime); } } _nav.Add(NetAssetValue[0]); foreach (var d in _cagrPeriods) { if (_nav.Count > d) { var cagr = Math.Exp(252.0 / d * Math.Log(_nav.Last() / _nav[_nav.Count() - 1 - d])) - 1.0; if (!_minCagr.ContainsKey(d)) { _minCagr[d] = cagr; _maxCagr[d] = cagr; } else { _minCagr[d] = Math.Min(_minCagr[d], cagr); _maxCagr[d] = Math.Max(_maxCagr[d], cagr); } } } #endif } //========== post processing ========== #if false foreach (var d in _minCagr.Keys) { Output.WriteLine("{0}-year return: min = {1:P2}, max = {2:P2}", d / 252, _minCagr[d], _maxCagr[d]); } #endif if (!IsOptimizing) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); //_plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }
public override void Run() { //========== initialization ========== WarmupStartTime = Globals.WARMUP_START_TIME; StartTime = Globals.START_TIME; EndTime = Globals.END_TIME; Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = Globals.COMMISSION; // Faber does not consider commissions var assets = ASSET_CLASSES .SelectMany(c => c.assets) .Distinct() .ToList(); AddDataSource(BENCHMARK); AddDataSources(assets); //========== simulation loop ========== foreach (DateTime simTime in SimTimes) { // evaluate instrument momentum for all known instruments, // we need to make sure to evaluate every instrument only once! Dictionary <Instrument, double> instrumentMomentum = Instruments .ToDictionary(i => i, i => SCORING_FUNC(i)); // skip if there are any missing instruments, // we want to make sure our strategy has all instruemnts available if (!HasInstruments(assets) || !HasInstrument(BENCHMARK)) { continue; } // create empty structure for instrument weights Dictionary <Instrument, double> instrumentWeights = assets .ToDictionary(nick => FindInstrument(nick), nick => 0.0); // loop through all asset classes foreach (AssetClass assetClass in ASSET_CLASSES) { List <Instrument> assetClassInstruments = assetClass.assets .Select(n => FindInstrument(n)) .ToList(); var bestInstruments = assetClassInstruments .OrderByDescending(i => instrumentMomentum[i]) .Take(assetClass.numpicks); foreach (Instrument bestInstrument in bestInstruments) { instrumentWeights[bestInstrument] += assetClass.weight / assetClass.numpicks; } } // execute trades once per month if (SimTime[0].Month != SimTime[1].Month) { double totalWeight = ASSET_CLASSES .Sum(a => a.weight); double equityUnit = NetAssetValue[0] / totalWeight; _alloc.LastUpdate = SimTime[0]; string message = string.Format("{0:MM/dd/yyyy}: ", SimTime[0]); foreach (var i in instrumentWeights.Keys) { _alloc.Allocation[i] = instrumentWeights[i] / totalWeight; message += string.Format("{0} = {1:P2}, ", i.Symbol, instrumentWeights[i]); int targetShares = (int)Math.Floor(instrumentWeights[i] * equityUnit / i.Close[0]); int currentShares = i.Position; Order newOrder = i.Trade(targetShares - currentShares); if (newOrder != null) { if (currentShares == 0) { newOrder.Comment = "open"; } else if (targetShares == 0) { newOrder.Comment = "close"; } else { newOrder.Comment = "rebalance"; } } } if (TradingDays > 0 && !IsOptimizing && (EndTime - SimTime[0]).TotalDays < 31) { Output.WriteLine(message); } } // plotter output if (TradingDays > 0) { _plotter.AddNavAndBenchmark(this, FindInstrument(BENCHMARK)); _plotter.AddStrategyHoldings(this, assets.Select(nick => FindInstrument(nick))); } } //========== post processing ========== if (!IsOptimizing) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); _plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }
public override IEnumerable <Bar> Run(DateTime?startTime, DateTime?endTime) { //========== initialization ========== #if DATE_RANGES_FROM_PAPER #if DATA_RANG_IS StartTime = DateTime.Parse("01/03/2005", CultureInfo.InvariantCulture); EndTime = DateTime.Parse("12/11/2012, 4pm", CultureInfo.InvariantCulture); #endif #if DATE_RANGE_OS StartTime = DateTime.Parse("01/02/1998", CultureInfo.InvariantCulture); EndTime = DateTime.Parse("12/31/2004, 4pm", CultureInfo.InvariantCulture); #endif #if DATE_RANGE_FULL StartTime = DateTime.Parse("01/02/1998", CultureInfo.InvariantCulture); EndTime = DateTime.Parse("12/14/2012, 4pm", CultureInfo.InvariantCulture); #endif WarmupStartTime = StartTime - TimeSpan.FromDays(126); #else WarmupStartTime = Globals.WARMUP_START_TIME; StartTime = Globals.START_TIME; EndTime = Globals.END_TIME; #endif Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = Globals.COMMISSION; var universe = AddDataSources(U); var benchmark = AddDataSource(BENCHMARK); //========== simulation loop ========== foreach (var simTime in SimTimes) { // skip if there are any instruments missing from our universe if (!HasInstruments(universe) || !HasInstrument(benchmark)) { continue; } // calculate indicators var indicators = universe .Select(ds => ds.Instrument) .ToDictionary( i => i, i => new indicatorValues { r = i.Close[0] / i.Close[LOOKBACK_R] - 1.0, a = i.Close[0] / i.Close[LOOKBACK_A] - 1.0, v = i.Close.Volatility(LOOKBACK_C)[0], c = universe .Select(ds => ds.Instrument) .Where(i2 => i != i2) // FIXME: how exactly are we calculating correlation here? .Average(i2 => i.Close.Correlation(i2.Close, LOOKBACK_C)[0]) //.Average(i2 => i.Close.Return().Correlation(i2.Close.Return(), LOOKBACK_C)[0]) //.Average(i2 => i.Close.LogReturn().Correlation(i2.Close.LogReturn(), LOOKBACK_C)[0]) }); // trigger monthly rebalancing if (SimTime[0].Month != NextSimTime.Month) { // calculate loss function for universe var L = LossFunction(indicators); // Keller is adamant to filter for absolute momentum // after ranking for the loss function var top = L.Keys .OrderBy(i => L[i]) .Take(N) .Where(i => indicators[i].a > RMIN / 100.0) .ToList(); // initialize all weights to zero var weights = indicators.Keys .ToDictionary( i => i, i => 0.0); // assign weights for top instruments for (int n = 0; n < top.Count; n++) { // N = 3, a = 0.5: weights = 2.5/6, 2.0/6, 1.5/6 // 0.417 0.333 0.250 weights[top[n]] += (1.0 - A / 100.0) / N + A / 100.0 * (N - n) / ((N + 1.0) * N / 2.0); } // assign any leftover weight to safe instrument weights[FindInstrument(U_SAFE)] += 1.0 - weights.Sum(kv => kv.Value); //weights[FindInstrument(U_SAFE)] += (N - top.Count) / (double)N; // submit orders foreach (var i in weights.Keys) { var shares = (int)Math.Floor(weights[i] * NetAssetValue[0] / i.Close[0]); i.Trade(shares - i.Position); } } // plotter output if (!IsOptimizing && TradingDays > 0) { _plotter.AddNavAndBenchmark(this, FindInstrument(BENCHMARK)); _plotter.AddStrategyHoldings(this, universe.Select(ds => ds.Instrument)); #if true // additional plotter output _plotter.SelectChart("Factor R", "Date"); _plotter.SetX(SimTime[0]); foreach (var i in indicators.Keys) { _plotter.Plot(i.Symbol, indicators[i].r); } _plotter.SelectChart("Factor V", "Date"); _plotter.SetX(SimTime[0]); foreach (var i in indicators.Keys) { _plotter.Plot(i.Symbol, indicators[i].v); } _plotter.SelectChart("Factor C", "Date"); _plotter.SetX(SimTime[0]); foreach (var i in indicators.Keys) { _plotter.Plot(i.Symbol, indicators[i].c); } #endif if (IsDataSource) { var v = 10.0 * NetAssetValue[0] / Globals.INITIAL_CAPITAL; yield return(Bar.NewOHLC( this.GetType().Name, SimTime[0], v, v, v, v, 0)); } } } //========== post processing ========== if (!IsOptimizing) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); _plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }
override public void Run() { //========== initialization ========== WarmupStartTime = Globals.WARMUP_START_TIME; StartTime = Globals.START_TIME; EndTime = Globals.END_TIME; Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = Globals.COMMISSION; // the book does not deduct commissions AddDataSource(BENCHMARK); foreach (string nick in ETF_MENU) { AddDataSource(nick); } //========== simulation loop ========== foreach (DateTime simTime in SimTimes) { // calculate momentum w/ algorithm-specific helper function var evaluation = Instruments .ToDictionary( i => i, i => MOMENTUM(i)); // skip, if there are any missing instruments // we want to make sure our strategy has all instruments available if (!HasInstruments(ETF_MENU)) { continue; } // find our trading instruments var instruments = Instruments .Where(i => ETF_MENU.Contains(i.Nickname)); // rank, and select top-3 instruments const int numHold = 3; var top3 = instruments .OrderByDescending(i => evaluation[i]) .Take(numHold); // calculate target percentage and how far we are off double targetPercentage = 1.0 / numHold; double maxOff = instruments .Max(i => (top3.Count() > 0 && top3.Contains(i) ? 1.0 : 0.0) * Math.Abs(i.Position * i.Close[0] / NetAssetValue[0] - targetPercentage) / targetPercentage); // rebalance once per month, and only if we need adjustments exceeding 20% if (SimTime[0].Month != SimTime[1].Month && maxOff > REBAL_TRIGGER) { _alloc.LastUpdate = SimTime[0]; foreach (Instrument i in instruments) { _alloc.Allocation[i] = top3.Contains(i) ? targetPercentage : 0.0; // determine current and target shares per instrument... double targetEquity = (top3.Contains(i) ? targetPercentage : 0.0) * NetAssetValue[0]; int targetShares = (int)Math.Floor(targetEquity / i.Close[0]); int currentShares = i.Position; // ... and trade the delta Order newOrder = i.Trade(targetShares - currentShares); // add a comment, to make the trading log easier to read if (newOrder != null) { if (currentShares == 0) { newOrder.Comment = "Open"; } else if (targetShares == 0) { newOrder.Comment = "Close"; } else { newOrder.Comment = "Rebalance"; } } } } // plotter output if (!IsOptimizing && TradingDays > 0) { _plotter.AddNavAndBenchmark(this, FindInstrument(BENCHMARK)); _plotter.AddStrategyHoldings(this, ETF_MENU.Select(nick => FindInstrument(nick))); if (IsSubclassed) { AddSubclassedBar(); } } } //========== post processing ========== if (!IsOptimizing) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); _plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }
public override void Run() { //----- initialization WarmupStartTime = Globals.WARMUP_START_TIME; StartTime = Globals.START_TIME; EndTime = Globals.END_TIME; Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = Globals.COMMISSION; // paper does not consider trade commissions AddDataSources(riskyUniverse); AddDataSources(cashUniverse); AddDataSources(protectiveUniverse); AddDataSource(BENCHMARK); //----- simulation loop foreach (DateTime simTime in SimTimes) { // calculate 13612W momentum for all instruments Dictionary <Instrument, double> momentum13612W = Instruments .ToDictionary( i => i, i => 0.25 * (12.0 * (i.Close[0] / i.Close[21] - 1.0) + 4.0 * (i.Close[0] / i.Close[63] - 1.0) + 2.0 * (i.Close[0] / i.Close[126] - 1.0) + 1.0 * (i.Close[0] / i.Close[252] - 1.0))); // skip if there are any missing instruments // we want to make sure our strategy has all instruments available if (!HasInstruments(riskyUniverse) || !HasInstruments(cashUniverse) || !HasInstruments(protectiveUniverse)) { continue; } // rebalance once per month // CAUTION: no indicator calculations within this block! if (SimTime[0].Month != SimTime[1].Month) { // find T top risky assets IEnumerable <Instrument> topInstruments = Instruments .Where(i => riskyUniverse.Contains(i.Nickname)) .OrderByDescending(i => momentum13612W[i]) .Take(T); // find single cash/ bond asset Instrument cashInstrument = Instruments .Where(i => cashUniverse.Contains(i.Nickname)) .OrderByDescending(i => momentum13612W[i]) .First(); // determine number of bad assets in canary universe double b = Instruments .Where(i => protectiveUniverse.Contains(i.Nickname)) .Sum(i => momentum13612W[i] < 0.0 ? 1.0 : 0.0); // calculate cash fraction //double CF = Math.Min(1.0, b / B) // standard calculation double CF = Math.Min(1.0, 1.0 / T * Math.Floor(b * T / B)); // Easy Trading // set instrument weights Dictionary <Instrument, double> weights = Instruments .ToDictionary(i => i, i => 0.0); weights[cashInstrument] = CF; foreach (Instrument i in topInstruments) { weights[i] += (1.0 - CF) / T; } _alloc.LastUpdate = SimTime[0]; foreach (Instrument i in Instruments) { if (riskyUniverse.Contains(i.Nickname) || cashUniverse.Contains(i.Nickname)) { _alloc.Allocation[i] = weights[i]; } int targetShares = (int)Math.Floor(weights[i] * NetAssetValue[0] / i.Close[0]); Order newOrder = i.Trade(targetShares - i.Position); if (newOrder != null) { if (i.Position == 0) { newOrder.Comment = "open"; } else if (targetShares == 0) { newOrder.Comment = "close"; } else { newOrder.Comment = "rebalance"; } } } } // plotter output if (!IsOptimizing && TradingDays > 0) { _plotter.AddNavAndBenchmark(this, FindInstrument(BENCHMARK)); _plotter.AddStrategyHoldings(this, Instruments .Where(i => riskyUniverse.Contains(i.Nickname) || cashUniverse.Contains(i.Nickname))); } } //----- post processing if (!IsOptimizing) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); _plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }
public override void Run() { //========== initialization ========== WarmupStartTime = Globals.WARMUP_START_TIME; StartTime = Globals.START_TIME; EndTime = Globals.END_TIME; Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = Globals.COMMISSION; var market = AddDataSource(MARKET); var volatility = AddDataSource(VOLATILITY); #if INCLUDE_TRIN_STRATEGY AddDataSource(TRIN); #endif //========== simulation loop ========== foreach (var s in SimTimes) { if (!HasInstrument(market) || !HasInstrument(volatility)) { continue; } if (!_alloc.Allocation.ContainsKey(market.Instrument)) { _alloc.Allocation[market.Instrument] = 0.0; } int buySell = Rules(market.Instrument); _alloc.LastUpdate = SimTime[0]; //----- enter positions if (market.Instrument.Position == 0 && buySell != 0) { int numShares = buySell * (int)Math.Floor(NetAssetValue[0] / market.Instrument.Close[0]); _alloc.Allocation[market.Instrument] += buySell; market.Instrument.Trade(numShares, OrderType.closeThisBar); } //----- exit positions else if (market.Instrument.Position != 0 && buySell != 0) { _alloc.Allocation[market.Instrument] = 0.0; market.Instrument.Trade(-market.Instrument.Position, ORDER_TYPE); } //----- output if (!IsOptimizing && TradingDays > 0) { _plotter.AddNavAndBenchmark(this, market.Instrument); _plotter.AddStrategyHoldings(this, market.Instrument); } } //========== post processing ========== if (!IsOptimizing && TradingDays > 0) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); _plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }
public override void Run() { //========== initialization ========== WarmupStartTime = Globals.WARMUP_START_TIME; StartTime = Globals.START_TIME; EndTime = Globals.END_TIME; Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = Globals.COMMISSION; var market = AddDataSource(MARKET); //========== simulation loop ========== var entryPrices = new Dictionary <Instrument, double>(); foreach (var s in SimTimes) { if (!_alloc.Allocation.ContainsKey(market.Instrument)) { _alloc.Allocation[market.Instrument] = 0.0; } double percentToBuySell = Rules(market.Instrument); _alloc.LastUpdate = SimTime[0]; //----- entries if (market.Instrument.Position >= 0 && percentToBuySell > 0 || market.Instrument.Position <= 0 && percentToBuySell < 0) { int sharesToBuySell = (int)(Math.Sign(percentToBuySell) * Math.Floor( Math.Abs(percentToBuySell) * NetAssetValue[0] / market.Instrument.Close[0])); _alloc.Allocation[market.Instrument] += percentToBuySell; market.Instrument.Trade(sharesToBuySell, ORDER_TYPE); } //----- exits if (market.Instrument.Position > 0 && percentToBuySell < 0 || market.Instrument.Position < 0 && percentToBuySell > 0) { // none of the algorithms attempt to gradually // exit positions, so this is good enough _alloc.Allocation[market.Instrument] = 0.0; market.Instrument.Trade(-market.Instrument.Position, OrderType.closeThisBar); } //----- output if (!IsOptimizing && TradingDays > 0) { _plotter.AddNavAndBenchmark(this, market.Instrument); _plotter.AddStrategyHoldings(this, market.Instrument); } } //========== post processing ========== if (!IsOptimizing) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); _plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }
public override IEnumerable <Bar> Run(DateTime?startTime, DateTime?endTime) { //========== initialization ========== StartTime = START_TIME; EndTime = END_TIME; WarmupStartTime = StartTime - TimeSpan.FromDays(365); Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = Globals.COMMISSION; // it is unclear, if Antonacci considers commissions // assets we can trade List <string> ASSETS = ASSET_CLASSES .SelectMany(c => c.assets) .Distinct() .Where(nick => nick != ABS_MOMENTUM) .ToList(); ASSETS.Add(SAFE_INSTR); var assets = AddDataSources(ASSETS); var safe = AddDataSource(SAFE_INSTR); // we just need the data source var absMom = AddDataSource(ABS_MOMENTUM); var benchmark = AddDataSource(BENCHMARK); double totalWeights = ASSET_CLASSES.Sum(a => a.weight); //========== simulation loop ========== foreach (DateTime simTime in SimTimes) { // skip if there are any missing instruments if (!HasInstruments(assets) || !HasInstrument(benchmark) || !HasInstrument(absMom)) { continue; } // evaluate momentum for all known instruments Dictionary <Instrument, double> instrumentMomentum = Instruments .ToDictionary(i => i, i => MOMENTUM(i)); // execute trades once per month // CAUTION: do not calculate indicators within this block! if (SimTime[0].Month != NextSimTime.Month) { // create empty structure for instrument weights Dictionary <Instrument, double> instrumentWeights = Instruments .ToDictionary(i => i, i => 0.0); // loop through all asset classes, and find the top-ranked one Instrument safeInstrument = safe.Instrument; foreach (AssetClass assetClass in ASSET_CLASSES) { // find the instrument with the highest momentum // in each asset class var bestInstrument = assetClass.assets .Select(nick => FindInstrument(nick)) .OrderByDescending(i => instrumentMomentum[i]) .Take(1) .First(); // sum up the weights (because instrument is duplicated) instrumentWeights[bestInstrument] += assetClass.weight / totalWeights; if (assetClass.setSafeInstrument) { safeInstrument = bestInstrument; } } // if momentum of any instrument drops below that of a T-Bill, // we use the safe instrument instead // therefore, we swap T-Bills for the safe instrument: double pcntTbill = instrumentWeights[absMom.Instrument]; instrumentWeights[absMom.Instrument] = 0.0; instrumentWeights[safeInstrument] += pcntTbill; // submit orders _alloc.LastUpdate = SimTime[0]; foreach (var ds in assets) { _alloc.Allocation[ds.Instrument] = instrumentWeights[ds.Instrument]; int targetShares = (int)Math.Floor(instrumentWeights[ds.Instrument] * NetAssetValue[0] / ds.Instrument.Close[0]); int currentShares = ds.Instrument.Position; Order newOrder = ds.Instrument.Trade(targetShares - currentShares); if (newOrder != null) { if (currentShares == 0) { newOrder.Comment = "open"; } else if (targetShares == 0) { newOrder.Comment = "close"; } else { newOrder.Comment = "rebalance"; } } } } // plotter output if (!IsOptimizing && TradingDays > 0) { _plotter.AddNavAndBenchmark(this, benchmark.Instrument); _plotter.AddStrategyHoldings(this, assets.Select(ds => ds.Instrument)); if (_alloc.LastUpdate == SimTime[0]) { _plotter.AddTargetAllocationRow(_alloc); } if (IsDataSource) { var v = 10.0 * NetAssetValue[0] / Globals.INITIAL_CAPITAL; yield return(Bar.NewOHLC( this.GetType().Name, SimTime[0], v, v, v, v, 0)); } } } //========== post processing ========== if (!IsOptimizing) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); //_plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }
public override void Run() { //----- initialization WarmupStartTime = Globals.WARMUP_START_TIME; StartTime = Globals.START_TIME; EndTime = Globals.END_TIME; Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = Globals.COMMISSION; // paper does not consider trade commissions var riskyUniverse = AddDataSources(RISKY_UNIVERSE); var cashUniverse = AddDataSources(CASH_UNIVERSE); var protectiveUniverse = AddDataSources(PROTECTIVE_UNIVERSE); var benchmark = AddDataSource(BENCHMARK); //----- simulation loop var monthlyBars = new Dictionary <Instrument, TimeSeries <double> >(); foreach (DateTime simTime in SimTimes) { // skip if there are any missing instruments // we want to make sure our strategy has all instruments available if (!HasInstruments(riskyUniverse) || !HasInstruments(cashUniverse) || !HasInstruments(protectiveUniverse) || !HasInstrument(benchmark) || ASSET_SUB != null && !HasInstruments(ASSET_SUB.Values)) { continue; } // rebalance once per month // CAUTION: no indicator calculations within this block! if (SimTime[0].Month != NextSimTime.Month) { // calculate 13612W momentum for all instruments foreach (var i in Instruments) { if (!monthlyBars.ContainsKey(i)) { monthlyBars[i] = new TimeSeries <double>(); } } foreach (var i in Instruments) { monthlyBars[i].Value = i.Close[0]; } Dictionary <Instrument, double> momentum13612W = Instruments .ToDictionary( i => i, i => 0.25 * (12.0 * (monthlyBars[i][0] / monthlyBars[i][1] - 1.0) + 4.0 * (monthlyBars[i][0] / monthlyBars[i][3] - 1.0) + 2.0 * (monthlyBars[i][0] / monthlyBars[i][6] - 1.0) + 1.0 * (monthlyBars[i][0] / monthlyBars[i][12] - 1.0))); // determine number of bad assets in canary universe double b = protectiveUniverse .Select(ds => ds.Instrument) .Sum(i => momentum13612W[i] < 0.0 ? 1.0 : 0.0); // calculate cash fraction //double CF = Math.Min(1.0, b / B) // standard calculation double CF = Math.Min(1.0, 1.0 / T * Math.Floor(b * T / B)); // Easy Trading // as part of Easy Trading, we scale back the number of // top assets as CF increases int t = (int)Math.Round((1.0 - CF) * T); // find T top risky assets IEnumerable <Instrument> topInstruments = riskyUniverse .Select(ds => ds.Instrument) .OrderByDescending(i => momentum13612W[i]) .Take(t); // find single cash/ bond asset Instrument cashInstrument = cashUniverse .Select(ds => ds.Instrument) .OrderByDescending(i => momentum13612W[i]) .First(); // set instrument weights Dictionary <Instrument, double> weights = Instruments .ToDictionary(i => i, i => 0.0); weights[cashInstrument] = CF; foreach (Instrument i in topInstruments) { weights[i] += (1.0 - CF) / t; } _alloc.LastUpdate = SimTime[0]; foreach (Instrument i in Instruments) { // skip instruments not in our relevant universes if (!riskyUniverse.Contains(i.DataSource) && !cashUniverse.Contains(i.DataSource)) { continue; } // for the 'on steroids' versions, we run the signals // as usual, but substitute some assets with leveraged // counterparts for the actual trading var i2 = AssetSub(i); // calculate target allocations _alloc.Allocation[i2] = weights[i]; int targetShares = (int)Math.Floor(weights[i] * NetAssetValue[0] / i2.Close[0]); Order newOrder = i2.Trade(targetShares - i2.Position); if (newOrder != null) { if (i.Position == 0) { newOrder.Comment = "open"; } else if (targetShares == 0) { newOrder.Comment = "close"; } else { newOrder.Comment = "rebalance"; } } } } // plotter output if (!IsOptimizing && TradingDays > 0) { _plotter.AddNavAndBenchmark(this, FindInstrument(BENCHMARK)); _plotter.AddStrategyHoldings(this, Instruments .Where(i => riskyUniverse.Contains(i.DataSource) || cashUniverse.Contains(i.DataSource)) .Select(i => AssetSub(i))); } } //----- post processing if (!IsOptimizing) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); _plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }
public override void Run() { //========== initialization ========== #if USE_BENSDORPS_RANGE // matching range in the book StartTime = DateTime.Parse("01/02/1995", CultureInfo.InvariantCulture); WarmupStartTime = StartTime - TimeSpan.FromDays(365); EndTime = DateTime.Parse("11/23/2016", CultureInfo.InvariantCulture); #else WarmupStartTime = Globals.WARMUP_START_TIME; StartTime = Globals.START_TIME; EndTime = Globals.END_TIME; #endif Deposit(Globals.INITIAL_CAPITAL); CommissionPerShare = Globals.COMMISSION; AddDataSources(UNIVERSE.Constituents); AddDataSource(BENCHMARK); //========== simulation loop ========== foreach (var s in SimTimes) { //----- find instruments _benchmark = _benchmark ?? FindInstrument(BENCHMARK); var universe = Instruments .Where(i => i.IsConstituent(UNIVERSE)) .ToList(); //----- calculate indicators // calculate indicators for all known instruments, // as they might enter the universe any time var indicators = Instruments .ToDictionary( i => i, i => new { rsi = i.Close.RSI(3), roc = i.Close.Momentum(200), }); var smaBand = _benchmark.Close.SMA(200).Multiply(0.98); // 2% below 200-day SMA // filter universe to potential candidates var filtered = universe .Where(i => _benchmark.Close[0] > smaBand[0] && indicators[i].rsi[0] < MAX_RSI) .ToList(); if (NextSimTime.DayOfWeek < SimTime[0].DayOfWeek) // open positions on Monday { // sort by momentum var ranked = universe .Where(i => _benchmark.Close[0] > smaBand[0]) .OrderByDescending(i => indicators[i].roc[0]) .ToList(); // enter: top-ranked momentum and low RSI var entry = ranked .Where(i => indicators[i].rsi[0] < MAX_RSI) .Take(MAX_ENTRIES) .ToList(); // hold: top-ranked momentum var hold = ranked .Take(MAX_ENTRIES) .ToList(); // keep those we have identified as 'hold' var nextHoldings = Instruments .Where(i => i.Position != 0 && hold.Contains(i)) .ToList(); // fill up, until we reach MAX_ENTRIES nextHoldings = nextHoldings //.Concat(entry.Take(MAX_ENTRIES - nextHoldings.Count)) .Concat(entry.Where(i => !nextHoldings.Contains(i)).Take(MAX_ENTRIES - nextHoldings.Count)) .ToList(); _alloc.LastUpdate = SimTime[0]; _alloc.Allocation.Clear(); foreach (var i in Instruments) { double targetPercentage = nextHoldings.Contains(i) ? 1.0 / MAX_ENTRIES : 0.0; int targetShares = (int)Math.Floor(NetAssetValue[0] * targetPercentage / i.Close[0]); if (targetPercentage != 0.0) { _alloc.Allocation[i] = targetPercentage; } i.Trade(targetShares - i.Position); } } //----- output if (!IsOptimizing && TradingDays > 0) { _plotter.AddNavAndBenchmark(this, FindInstrument(BENCHMARK)); //_plotter.AddStrategyHoldings(this, universe); // plot strategy exposure _plotter.SelectChart("Exposure Chart", "Date"); _plotter.SetX(SimTime[0]); _plotter.Plot("Exposure", Instruments.Sum(i => i.Position * i.Close[0]) / NetAssetValue[0]); if (IsSubclassed) { AddSubclassedBar(); } } } //========== post processing ========== if (!IsOptimizing) { _plotter.AddTargetAllocation(_alloc); _plotter.AddOrderLog(this); _plotter.AddPositionLog(this); _plotter.AddPnLHoldTime(this); _plotter.AddMfeMae(this); _plotter.AddParameters(this); } FitnessValue = this.CalcFitness(); }