public static void AddPnLHoldTime(this Plotter plotter, SimulatorCore sim) { var tradeLog = LogAnalysis .GroupPositions(sim.Log, true) .OrderBy(i => i.Entry.BarOfExecution.Time); plotter.SelectChart(Plotter.SheetNames.PNL_HOLD_TIME, "Days Held"); foreach (var trade in tradeLog) { var pnl = (trade.Quantity > 0 ? 100.0 : -100.0) * (trade.Exit.FillPrice / trade.Entry.FillPrice - 1.0); var label = pnl > 0.0 ? "Profit" : "Loss"; plotter.SetX((trade.Exit.BarOfExecution.Time - trade.Entry.BarOfExecution.Time).TotalDays); plotter.Plot(label, pnl); } }
public static void AddMfeMae(this Plotter plotter, SimulatorCore sim) { var tradeLog = LogAnalysis .GroupPositions(sim.Log, true) .OrderBy(i => i.Entry.BarOfExecution.Time); plotter.SelectChart(Plotter.SheetNames.PNL_MFE_MAE, "Max Excursion"); foreach (var trade in tradeLog) { var pnl = 100.0 * (trade.Exit.FillPrice / trade.Entry.FillPrice - 1.0); var label = pnl > 0.0 ? "Profit" : "Loss"; plotter.SetX(100.0 * (trade.HighestHigh / trade.Entry.FillPrice - 1.0)); plotter.Plot(label, pnl); plotter.SetX(100.0 * (trade.LowestLow / trade.Entry.FillPrice - 1.0)); plotter.Plot(label, pnl); } }
public static void AddPositionLog(this Plotter plotter, SimulatorCore sim) { var tradeLog = LogAnalysis .GroupPositions(sim.Log, true) .OrderBy(i => i.Entry.BarOfExecution.Time); plotter.SelectChart("Position Log", "entry date"); foreach (var trade in tradeLog) { plotter.SetX(string.Format("{0:MM/dd/yyyy}", trade.Entry.BarOfExecution.Time)); plotter.Plot("exit date", string.Format("{0:MM/dd/yyyy}", trade.Exit.BarOfExecution.Time)); plotter.Plot("days held", (trade.Exit.BarOfExecution.Time - trade.Entry.BarOfExecution.Time).TotalDays); plotter.Plot("Symbol", trade.Symbol); plotter.Plot("Quantity", trade.Quantity); plotter.Plot("% Profit", Math.Round((trade.Quantity > 0 ? 100.0 : -100.0) * (trade.Exit.FillPrice / trade.Entry.FillPrice - 1.0), 2)); plotter.Plot("Exit", trade.Exit.OrderTicket.Comment ?? ""); //plotter.Plot("$ Profit", trade.Quantity * (trade.Exit.FillPrice - trade.Entry.FillPrice)); } }
public override void Run() { //========== initialization ========== StartTime = DateTime.Parse("01/01/2008"); EndTime = DateTime.Now.Date - TimeSpan.FromDays(5); foreach (var n in UNIVERSE) { AddDataSource(n); } AddDataSource(BENCHMARK); Deposit(INITIAL_CAPITAL); CommissionPerShare = 0.015; //========== simulation loop ========== foreach (var s in SimTimes) { //----- find instruments _benchmark = _benchmark ?? FindInstrument(BENCHMARK); var universe = Instruments .Where(i => i != _benchmark) .ToList(); //----- calculate indicators // make sure to calculate indicators for the // full universe on every single bar var indicators = universe .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 candidates by ROC to find entries var entries = filtered .OrderByDescending(i => indicators[i].roc[0]) .Take(MAX_ENTRIES) .ToList(); foreach (var i in universe) { int targetShares = entries.Contains(i) ? (int)Math.Floor(NetAssetValue[0] / MAX_ENTRIES / i.Close[0]) : 0; i.Trade(targetShares - i.Position); } } //----- output if (!IsOptimizing) { // plot to chart _plotter.SelectChart(Name + ": " + OptimizerParamsAsString, "date"); _plotter.SetX(SimTime[0]); _plotter.Plot("nav", NetAssetValue[0]); _plotter.Plot(_benchmark.Symbol, _benchmark.Close[0]); // placeholder, to make sure positions land on sheet 2 _plotter.SelectChart(Name + " positions", "entry date"); // additional indicators _plotter.SelectChart(Name + " extra", "date"); _plotter.SetX(SimTime[0]); _plotter.Plot("leverage", Instruments.Sum(i => i.Position * i.Close[0]) / NetAssetValue[0]); } } //========== post processing ========== //----- print position log, grouped as LIFO if (!IsOptimizing) { var tradeLog = LogAnalysis .GroupPositions(Log, true) .OrderBy(i => i.Entry.BarOfExecution.Time); _plotter.SelectChart(Name + " positions", "entry date"); foreach (var trade in tradeLog) { _plotter.SetX(trade.Entry.BarOfExecution.Time); _plotter.Plot("exit date", trade.Exit.BarOfExecution.Time); _plotter.Plot("Symbol", trade.Symbol); _plotter.Plot("Quantity", trade.Quantity); _plotter.Plot("% Profit", (trade.Quantity > 0 ? 1.0 : -1.0) * (trade.Exit.FillPrice / trade.Entry.FillPrice - 1.0)); _plotter.Plot("Exit", trade.Exit.OrderTicket.Comment ?? ""); //_plotter.Plot("$ Profit", trade.Quantity * (trade.Exit.FillPrice - trade.Entry.FillPrice)); } } //----- optimization objective double cagr = Math.Exp(252.0 / Math.Max(1, TradingDays) * Math.Log(NetAssetValue[0] / INITIAL_CAPITAL)) - 1.0; FitnessValue = cagr / Math.Max(1e-10, Math.Max(0.01, NetAssetValueMaxDrawdown)); if (!IsOptimizing) { Output.WriteLine("CAGR = {0:P2}, DD = {1:P2}, Fitness = {2:F4}", cagr, NetAssetValueMaxDrawdown, FitnessValue); } }
public override void Run() { //========== initialization ========== StartTime = DateTime.Parse("01/01/2008"); EndTime = DateTime.Now.Date - TimeSpan.FromDays(5); foreach (var n in UNIVERSE) { AddDataSource(n); } AddDataSource(BENCHMARK); Deposit(INITIAL_CAPITAL); CommissionPerShare = 0.015; var entryParameters = Enumerable.Empty <Instrument>() .ToDictionary( i => i, i => new { entryDate = default(DateTime), entryPrice = default(double), stopLoss = default(double), profitTarget = default(double), }); //========== simulation loop ========== foreach (var s in SimTimes) { //----- find instruments _benchmark = _benchmark ?? FindInstrument(BENCHMARK); var universe = Instruments .Where(i => i != _benchmark) .ToList(); //----- calculate indicators // make sure to calculate indicators for the // full universe on every single bar var indicators = universe .ToDictionary( i => i, i => new { sma150 = i.Close.SMA(SMA_DAYS), adx7 = i.ADX(7), atr10 = i.TrueRange().Divide(i.Close).SMA(10), rsi3 = i.Close.RSI(3), }); // filter universe to potential candidates var filtered = universe .Where(i => (ENTRY_DIR > 0 ? i.Close[0] > indicators[i].sma150[0] // long: above sma : i.Close[0] > i.Close[1] && i.Close[1] > i.Close[2]) && // short: 2 up-days indicators[i].adx7[0] > MIN_ADX && indicators[i].atr10[0] > MIN_ATR / 10000.0 && (ENTRY_DIR > 0 ? indicators[i].rsi3[0] < MINMAX_RSI // long: maximum : indicators[i].rsi3[0] > MINMAX_RSI)) // short: minimum .ToList(); //----- manage existing positions int numOpenPositions = Positions.Keys.Count(); foreach (var pos in Positions.Keys) { // time-based exit if (entryParameters[pos].entryDate <= SimTime[MAX_HOLD_DAYS - 1]) { pos.Trade(-pos.Position, OrderType.closeThisBar).Comment = "time exit"; numOpenPositions--; } else if (ENTRY_DIR > 0 ? pos.Close[0] >= entryParameters[pos].profitTarget // long : pos.Close[0] <= entryParameters[pos].profitTarget) // short { pos.Trade(-pos.Position, OrderType.openNextBar) .Comment = "profit target"; numOpenPositions--; } else { pos.Trade(-pos.Position, OrderType.stopNextBar, entryParameters[pos].stopLoss) .Comment = "stop loss"; } } //----- open new positions if (NextSimTime.DayOfWeek < SimTime[0].DayOfWeek) // open positions on Monday { // sort candidates by RSI to find entries var entries = ENTRY_DIR > 0 ? filtered // long .Where(i => i.Position == 0) .OrderBy(i => indicators[i].rsi3[0]) .Take(MAX_ENTRIES - numOpenPositions) .ToList() : filtered // short .Where(i => i.Position == 0) .OrderByDescending(i => indicators[i].rsi3[0]) .Take(MAX_ENTRIES - numOpenPositions) .ToList(); foreach (var i in entries) { // save our entry parameters, so that we may access // them later to manage exits double entryPrice = ENTRY_DIR > 0 ? i.Close[0] * (1.0 - MIN_ATR / 10000.0) // long : i.Close[0]; // short double stopLoss = ENTRY_DIR > 0 ? entryPrice * (1.0 - STOP_LOSS / 100.0 * indicators[i].atr10[0]) // long : entryPrice * (1.0 + STOP_LOSS / 100.0 * indicators[i].atr10[0]); // short double profitTarget = ENTRY_DIR > 0 ? entryPrice * (1.0 + PROFIT_TARGET / 10000.0) // long : entryPrice * (1.0 - PROFIT_TARGET / 10000.0); // short entryParameters[i] = new { entryDate = NextSimTime, entryPrice, stopLoss, profitTarget, }; // calculate target shares in two ways: // * fixed-fractional risk (with entry - stop-loss = "risk"), and // * fixed percentage of total equity double riskPerShare = ENTRY_DIR > 0 ? Math.Max(0.10, entryPrice - stopLoss) // long : Math.Max(0.10, stopLoss - entryPrice); // short int sharesRiskLimited = (int)Math.Floor(MAX_RISK / 100.0 / MAX_ENTRIES * NetAssetValue[0] / riskPerShare); int sharesCapLimited = (int)Math.Floor(MAX_CAP / 100.0 / MAX_ENTRIES * NetAssetValue[0] / entryParameters[i].entryPrice); int targetShares = (ENTRY_DIR > 0 ? 1 : -1) * Math.Min(sharesRiskLimited, sharesCapLimited); // enter positions with limit order i.Trade(targetShares, OrderType.limitNextBar, entryParameters[i].entryPrice); } } //----- output if (!IsOptimizing) { // plot to chart _plotter.SelectChart(Name + ": " + OptimizerParamsAsString, "date"); _plotter.SetX(SimTime[0]); _plotter.Plot("nav", NetAssetValue[0]); _plotter.Plot(_benchmark.Symbol, _benchmark.Close[0]); // placeholder, to make sure positions land on sheet 2 _plotter.SelectChart(Name + " positions", "entry date"); // additional indicators _plotter.SelectChart(Name + " extra", "date"); _plotter.SetX(SimTime[0]); _plotter.Plot("leverage", Instruments.Sum(i => i.Position * i.Close[0]) / NetAssetValue[0]); } } //========== post processing ========== //----- print position log, grouped as LIFO if (!IsOptimizing) { var tradeLog = LogAnalysis .GroupPositions(Log, true) .OrderBy(i => i.Entry.BarOfExecution.Time); _plotter.SelectChart(Name + " positions", "entry date"); foreach (var trade in tradeLog) { _plotter.SetX(trade.Entry.BarOfExecution.Time); _plotter.Plot("exit date", trade.Exit.BarOfExecution.Time); _plotter.Plot("Symbol", trade.Symbol); _plotter.Plot("Quantity", trade.Quantity); _plotter.Plot("% Profit", (ENTRY_DIR > 0 ? 1.0 : -1.0) * (trade.Exit.FillPrice / trade.Entry.FillPrice - 1.0)); _plotter.Plot("Exit", trade.Exit.OrderTicket.Comment ?? ""); //_plotter.Plot("$ Profit", trade.Quantity * (trade.Exit.FillPrice - trade.Entry.FillPrice)); } } //----- optimization objective double cagr = Math.Exp(252.0 / Math.Max(1, TradingDays) * Math.Log(NetAssetValue[0] / INITIAL_CAPITAL)) - 1.0; FitnessValue = cagr / Math.Max(1e-10, Math.Max(0.01, NetAssetValueMaxDrawdown)); if (!IsOptimizing) { Output.WriteLine("CAGR = {0:P2}, DD = {1:P2}, Fitness = {2:F4}", cagr, NetAssetValueMaxDrawdown, FitnessValue); } }
public override void Run() { //---------- initialization // set simulation time frame WarmupStartTime = DateTime.Parse("01/01/2005", CultureInfo.InvariantCulture); StartTime = DateTime.Parse("01/01/2008", CultureInfo.InvariantCulture); EndTime = DateTime.Now.Date - TimeSpan.FromDays(5); // set account value Deposit(INITIAL_FUNDS); CommissionPerShare = 0.015; // add instruments AddDataSource(BENCHMARK); foreach (string nickname in UNIVERSE) { AddDataSource(nickname); } //---------- simulation // loop through all bars foreach (DateTime simTime in SimTimes) { // determine active instruments var activeInstruments = Instruments .Where(i => UNIVERSE.Contains(i.Nickname) && i.Time[0] == simTime) .ToList(); // calculate indicators // store to list, to make sure indicators are evaluated // exactly once per bar var instrumentEvaluation = activeInstruments .Select(i => new { instrument = i, regression = i.Close.LogRegression(MOM_PERIOD), maxMove = i.Close.LogReturn().AbsValue().Highest(MOM_PERIOD), avg100 = i.Close.EMA(INSTR_FLT), atr20 = i.AverageTrueRange(ATR_PERIOD), }) .ToList(); // 1) rank instruments by volatility-adjusted momentum // 2) determine position size with risk equal to 10-basis points // of NAV per instrument, based on 20-day ATR // 3) disqualify instruments, when // - trading below 100-day moving average // - negative momentum // - maximum move > 15% var instrumentRanking = instrumentEvaluation .OrderByDescending(e => e.regression.Slope[0] * e.regression.R2[0]) .Select(e => new { instrument = e.instrument, positionSize = (e.maxMove[0] < Math.Log(MAX_MOVE / 100.0) && e.instrument.Close[0] > e.avg100[0] && e.regression.Slope[0] > 0.0) ? 0.001 / e.atr20[0] * e.instrument.Close[0] : 0.0, }) .ToList(); // assign equity, until we run out of cash, // or we are no longer in the top-ranking 20% var instrumentEquity = Enumerable.Range(0, instrumentRanking.Count) .ToDictionary( i => instrumentRanking[i].instrument, i => i <= TOP_PCNT / 100.0 * instrumentRanking.Count ? Math.Min( instrumentRanking[i].positionSize, Math.Max(0.0, 1.0 - instrumentRanking.Take(i).Sum(r => r.positionSize))) : 0.0); // index filter: only buy any shares, while S&P-500 is trading above its 200-day moving average var indexFilter = FindInstrument(BENCHMARK).Close .Divide(FindInstrument(BENCHMARK).Close.EMA(INDEX_FLT)); // trade once per week // this is a slight simplification from Clenow's suggestion to change positions // every week, and adjust position sizes only every other week if (SimTime[0].DayOfWeek >= DayOfWeek.Wednesday && SimTime[1].DayOfWeek < DayOfWeek.Wednesday) { foreach (Instrument instrument in instrumentEquity.Keys) { int currentShares = instrument.Position; int targetShares = (int)Math.Round(NetAssetValue[0] * instrumentEquity[instrument] / instrument.Close[0]); if (indexFilter[0] < 0.0) { targetShares = Math.Min(targetShares, currentShares); } instrument.Trade(targetShares - currentShares); } string message = instrumentEquity .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) { Output.WriteLine(message); } } // create plots on Sheet 1 if (TradingDays > 0) { _plotter.SelectChart(Name, "date"); _plotter.SetX(SimTime[0]); _plotter.Plot("NAV", NetAssetValue[0]); _plotter.Plot(BENCHMARK, FindInstrument(BENCHMARK).Close[0]); } } //----- post processing // print position log, grouped as LIFO var tradeLog = LogAnalysis .GroupPositions(Log, true) .OrderBy(i => i.Entry.BarOfExecution.Time); _plotter.SelectChart(Name + " positions", "entry date"); foreach (var trade in tradeLog) { _plotter.SetX(trade.Entry.BarOfExecution.Time.Date); _plotter.Plot("exit date", trade.Exit.BarOfExecution.Time.Date); _plotter.Plot("Symbol", trade.Symbol); _plotter.Plot("Quantity", trade.Quantity); _plotter.Plot("% Profit", trade.Exit.FillPrice / trade.Entry.FillPrice - 1.0); _plotter.Plot("Exit", trade.Exit.OrderTicket.Comment ?? ""); //_plotter.Plot("$ Profit", trade.Quantity * (trade.Exit.FillPrice - trade.Entry.FillPrice)); } }
public override void Run() { //========== initialization ========== StartTime = DateTime.Parse("01/01/1990"); EndTime = DateTime.Now.Date - TimeSpan.FromDays(5); AddDataSource(MARKET); Deposit(INITIAL_CAPITAL); CommissionPerShare = 0.015; //========== simulation loop ========== var entryPrices = new Dictionary <Instrument, double>(); foreach (var s in SimTimes) { _market = _market ?? FindInstrument(MARKET); double percentToBuySell = Rules(_market); //----- entries if (_market.Position == 0 && percentToBuySell != 0.0 || _market.Position > 0 && percentToBuySell > 0 || _market.Position < 0 && percentToBuySell < 0) { int sharesToBuySell = (int)(Math.Sign(percentToBuySell) * Math.Floor( Math.Abs(percentToBuySell) * NetAssetValue[0] / _market.Close[0])); _market.Trade(sharesToBuySell, OrderType.closeThisBar); } //----- exits if (_market.Position > 0 && percentToBuySell < 0 || _market.Position < 0 && percentToBuySell > 0) { // none of the algorithms attempt to gradually // exit positions, so this is good enough _market.Trade(-_market.Position, OrderType.closeThisBar); } //----- output if (!IsOptimizing) { // plot to chart _plotter.SelectChart(Name, "date"); _plotter.SetX(SimTime[0]); _plotter.Plot("nav", NetAssetValue[0]); _plotter.Plot(_market.Symbol, _market.Close[0]); // placeholder for 2nd sheet _plotter.SelectChart(Name + " positions", "entry date"); _plotter.SelectChart(Name + " % invested", "entry date"); _plotter.SetX(SimTime[0]); _plotter.Plot("% long", Positions.Keys.Sum(i => Math.Max(0, i.Position) * i.Close[0]) / NetAssetValue[0]); _plotter.Plot("% short", Positions.Keys.Sum(i => - Math.Min(0, i.Position) * i.Close[0]) / NetAssetValue[0]); _plotter.Plot("% total", Positions.Keys.Sum(i => Math.Abs(i.Position) * i.Close[0]) / NetAssetValue[0]); } } //========== post processing ========== //----- print position log, grouped as LIFO if (!IsOptimizing) { var tradeLog = LogAnalysis .GroupPositions(Log, true) .OrderBy(i => i.Entry.BarOfExecution.Time); _plotter.SelectChart(Name + " positions", "entry date"); foreach (var trade in tradeLog) { _plotter.SetX(trade.Entry.BarOfExecution.Time); _plotter.Plot("exit date", trade.Exit.BarOfExecution.Time); _plotter.Plot("Symbol", trade.Symbol); _plotter.Plot("Quantity", trade.Quantity); _plotter.Plot("% Profit", (trade.Quantity > 0 ? 1.0 : -1.0) * (trade.Exit.FillPrice / trade.Entry.FillPrice - 1.0)); _plotter.Plot("Exit", trade.Exit.OrderTicket.Comment ?? ""); //_plotter.Plot("$ Profit", trade.Quantity * (trade.Exit.FillPrice - trade.Entry.FillPrice)); } } //----- optimization objective double cagr = Math.Exp(252.0 / Math.Max(1, TradingDays) * Math.Log(NetAssetValue[0] / INITIAL_CAPITAL)) - 1.0; FitnessValue = cagr / Math.Max(1e-10, Math.Max(0.01, NetAssetValueMaxDrawdown)); if (!IsOptimizing) { Output.WriteLine("CAGR = {0:P2}, DD = {1:P2}, Fitness = {2:F4}", cagr, NetAssetValueMaxDrawdown, FitnessValue); } }
public override void Run() { //========== initialization ========== StartTime = DateTime.Parse("01/01/1990"); EndTime = DateTime.Now.Date - TimeSpan.FromDays(5); AddDataSource(MARKET); AddDataSource(VOLATILITY); #if INCLUDE_TRIN_STRATEGY AddDataSource(TRIN); #endif Deposit(INITIAL_CAPITAL); CommissionPerShare = 0.015; //========== simulation loop ========== foreach (var s in SimTimes) { _market = _market ?? FindInstrument(MARKET); _volatility = _volatility ?? FindInstrument(VOLATILITY); #if INCLUDE_TRIN_STRATEGY _trin = _trin ?? FindInstrument(TRIN); #endif int buySell = Rules(); //----- enter positions if (_market.Position == 0 && buySell != 0) { int numShares = buySell * (int)Math.Floor(NetAssetValue[0] / _market.Close[0]); _market.Trade(numShares, OrderType.closeThisBar); } //----- exit positions else if (_market.Position != 0 && buySell != 0) { _market.Trade(-_market.Position, OrderType.closeThisBar); } //----- output if (!IsOptimizing) { // plot to chart _plotter.SelectChart(Name + ": " + OptimizerParamsAsString, "date"); _plotter.SetX(SimTime[0]); _plotter.Plot("nav", NetAssetValue[0]); _plotter.Plot(_market.Symbol, _market.Close[0]); } } //========== post processing ========== //----- print position log, grouped as LIFO if (!IsOptimizing) { var tradeLog = LogAnalysis .GroupPositions(Log, true) .OrderBy(i => i.Entry.BarOfExecution.Time); _plotter.SelectChart(Name + " positions", "entry date"); foreach (var trade in tradeLog) { _plotter.SetX(trade.Entry.BarOfExecution.Time); _plotter.Plot("exit date", trade.Exit.BarOfExecution.Time); _plotter.Plot("Symbol", trade.Symbol); _plotter.Plot("Quantity", trade.Quantity); _plotter.Plot("% Profit", (trade.Quantity > 0 ? 1.0 : -1.0) * (trade.Exit.FillPrice / trade.Entry.FillPrice - 1.0)); _plotter.Plot("Exit", trade.Exit.OrderTicket.Comment ?? ""); //_plotter.Plot("$ Profit", trade.Quantity * (trade.Exit.FillPrice - trade.Entry.FillPrice)); } } //----- optimization objective double cagr = Math.Exp(252.0 / Math.Max(1, TradingDays) * Math.Log(NetAssetValue[0] / INITIAL_CAPITAL)) - 1.0; FitnessValue = cagr / Math.Max(1e-10, Math.Max(0.01, NetAssetValueMaxDrawdown)); if (!IsOptimizing) { Output.WriteLine("CAGR = {0:P2}, DD = {1:P2}, Fitness = {2:F4}", cagr, NetAssetValueMaxDrawdown, FitnessValue); } }