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 IEnumerable <Bar> Run(DateTime?startTime, DateTime?endTime) { //========== 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 = startTime ?? Globals.START_TIME; EndTime = endTime ?? 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 (_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 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 ========== 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); if (_alloc.LastUpdate == SimTime[0]) { _plotter.AddTargetAllocationRow(_alloc); } } } //========== 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; 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); if (_alloc.LastUpdate == SimTime[0]) { _plotter.AddTargetAllocationRow(_alloc); } } } //========== 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; // 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))); if (_alloc.LastUpdate == SimTime[0]) { _plotter.AddTargetAllocationRow(_alloc); } } } //----- 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; // Faber does not consider commissions var ASSETS = ASSET_CLASSES .SelectMany(c => c.assets) .Distinct() .ToList(); var benchmark = AddDataSource(BENCHMARK); var assets = 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; } // execute trades once per month if (SimTime[0].Month != NextSimTime.Month) { // create empty structure for instrument weights Dictionary <Instrument, double> instrumentWeights = assets .ToDictionary(ds => ds.Instrument, ds => 0.0); // loop through all asset classes and accumulate asset weights 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; } } double totalWeight = ASSET_CLASSES .Sum(a => a.weight); double equityUnit = NetAssetValue[0] / totalWeight; // create orders _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 < 63) { Output.WriteLine(message); } } // plotter output if (TradingDays > 0) { _plotter.AddNavAndBenchmark(this, FindInstrument(BENCHMARK)); _plotter.AddStrategyHoldings(this, ASSETS.Select(nick => FindInstrument(nick))); if (_alloc.LastUpdate == SimTime[0]) { _plotter.AddTargetAllocationRow(_alloc); } } } //========== 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(); }