Example #1
0
        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();
        }
        override public void Run()
        {
            //---------- initialization

            //WarmupStartTime = DateTime.Parse("06/01/2011", CultureInfo.InvariantCulture);
#if FAKE_OPTIONS
            // data range for fake data
            StartTime = DateTime.Parse("06/01/2011", CultureInfo.InvariantCulture);
            EndTime   = DateTime.Parse("04/09/2020", CultureInfo.InvariantCulture);
#endif
#if SPX_OPTIONS
            // SPX date range
            StartTime = DateTime.Parse("02/01/2007", CultureInfo.InvariantCulture);
            EndTime   = DateTime.Parse("11/28/2018", CultureInfo.InvariantCulture);
#endif
#if XSP_OPTIONS
            // XSP date range
            //StartTime = DateTime.Parse("08/01/2006", CultureInfo.InvariantCulture);
            StartTime = DateTime.Parse("11/19/2007", CultureInfo.InvariantCulture); // availability of monthlies
            EndTime   = DateTime.Parse("08/01/2018", CultureInfo.InvariantCulture);
#endif
#if SPY_OPTIONS
            StartTime = DateTime.Parse("11/19/2007", CultureInfo.InvariantCulture); // availability of monthlies
            EndTime   = DateTime.Parse("05/31/2020", CultureInfo.InvariantCulture);
#endif

            Deposit(Globals.INITIAL_CAPITAL);
            CommissionPerShare = Globals.COMMISSION;

            var spy = AddDataSource(UNDERLYING_NICK);
            AddDataSource(OPTION_NICK);
            //var vix = AddDataSource(VIX_1Y);
            var bench = AddDataSource(BENCHMARK);

            //---------- simulation

            foreach (var simTime in SimTimes)
            {
                if (!HasInstrument(spy) || !HasInstrument(OPTION_NICK) /*|| !HasInstrument(vix)*/ || !HasInstrument(bench))
                {
                    continue;
                }

                _underlying = _underlying ?? spy.Instrument;
                //_vix1y = _vix1y ?? vix.Instrument;

                MaintainMainPosition();
                MaintainMainHedge();
                MaintainIncomePosition();
                MaintainIncomeHedge();

                if (TradingDays > 0 && !IsOptimizing)
                {
                    _plotter.AddNavAndBenchmark(this, bench.Instrument);

#if true
                    _plotter.SelectChart("Strikes", "Date");
                    _plotter.SetX(SimTime[0]);
                    _plotter.Plot("Underlying", spy.Instrument.Close[0]);
                    _plotter.Plot("Main Position", _mainPosition != null ? _mainPosition.OptionStrike : 0.0);
                    _plotter.Plot("Main Hedge", _mainHedge != null ? _mainHedge.OptionStrike : 0.0);
                    _plotter.Plot("Income Position", _incomePosition != null ? _incomePosition.OptionStrike : 0.0);
                    _plotter.Plot("Income Hedge", _incomeHedge != null ? _incomeHedge.OptionStrike : 0.0);
#endif

#if false
                    _plotter.SelectChart("Hedge Strikes", "Date");
                    _plotter.SetX(SimTime[0]);
                    _plotter.Plot("Underlying", spy.Instrument.Close[0]);
                    _plotter.Plot("Main Hedge", _mainHedge != null ? _mainHedge.OptionStrike : 0.0);
                    _plotter.Plot("Income Hedge", _incomeHedge != null ? _incomeHedge.OptionStrike : 0.0);
#endif
#if true
                    _plotter.SelectChart("Position Breakdown", "Date");
                    _plotter.SetX(SimTime[0]);
                    _plotter.Plot("NAV", NetAssetValue[0]);
                    _plotter.Plot("Main Position", _mainPosition != null
                        ? (_mainPosition.IsOption
                            ? 100.0 * _mainPosition.Position * _mainPosition.Bid[0]
                            : _mainPosition.Position * _mainPosition.Close[0])
                        : 0.0);
                    _plotter.Plot("Main Hedge", _mainHedge != null ? 100.0 * _mainHedgeCurrentLots * _mainHedge.Bid[0] : 0.0);
                    _plotter.Plot("Income Position", _incomePosition != null ? 100.0 * _incomePositionCurrentLots * _incomePosition.Ask[0] : 0.0);
                    _plotter.Plot("Income Hedge", _incomeHedge != null ? 100.0 * _incomeHedgeCurrentLots * _incomeHedge.Bid[0] : 0.0);
                    _plotter.Plot("Cash", Cash);
#endif
                }
            }

            //---------- post-processing

            _plotter.AddOrderLog(this);
            _plotter.AddParameters(this);

            FitnessValue = this.CalcFitness();
        }
Example #3
0
        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 ==========

#if USE_BOOK_RANGE
            StartTime = SubclassedStartTime ?? DateTime.Parse("01/01/2003", CultureInfo.InvariantCulture);
            EndTime   = SubclassedEndTime ?? DateTime.Parse("12/31/2018", CultureInfo.InvariantCulture);
#else
            StartTime = SubclassedStartTime ?? Globals.START_TIME;
            EndTime   = SubclassedEndTime ?? Globals.END_TIME;
#endif
            WarmupStartTime = StartTime - TimeSpan.FromDays(100);

            Deposit(Globals.INITIAL_CAPITAL);
            //CommissionPerShare = Globals.COMMISSION; // Connors does not consider commissions

            var universe  = AddDataSources(UNIVERSE.Constituents);
            var idle      = AddDataSource(IDLE_CASH);
            var spx       = AddDataSource(SPX);
            var benchmark = AddDataSource(BENCHMARK);

            //========== simulation loop ==========

            var weeklyBars  = new Dictionary <Instrument, TimeSeries <double> >();
            var entryPrices = new Dictionary <Instrument, double>();

            foreach (var s in SimTimes)
            {
                if (!HasInstrument(benchmark) || !HasInstrument(idle))
                {
                    continue;
                }

                // determine universe constituents
                var constituents = Instruments
                                   .Where(i => UNIVERSE.IsConstituent(i.Nickname, SimTime[0]))
                                   .ToList();

                // calculate daily indicators
                var dailyIndicators = Instruments
                                      .ToDictionary(
                    i => i,
                    i => new
                {
                    volatility = i.Close.Volatility(100)[0],
                });

                // start with weights representing current positions
                const int NUM_POS = 10;
                var       weights = Instruments
                                    .ToDictionary(
                    i => i,
                    i => constituents.Contains(i) && i.Position > 0
                            ? 1.0 / NUM_POS
                            : 0.0);

                // sell, if stock closes 10% below entry price
                foreach (var i in Instruments)
                {
                    if (i != idle.Instrument && i.Position > 0.0 && i.Close[0] < 0.90 * entryPrices[i])
                    {
                        weights[i] = 0.0;
                    }
                }

                // weekly logic on last business day of the week
                if (SimTime[0].DayOfWeek > NextSimTime.DayOfWeek)
                {
                    foreach (var i in Instruments)
                    {
                        if (!weeklyBars.ContainsKey(i))
                        {
                            weeklyBars[i] = new TimeSeries <double>();
                        }
                    }

                    // advance weekly bars
                    foreach (var i in Instruments)
                    {
                        weeklyBars[i].Value = i.Close[0];
                    }

                    // calculate weekly indicators
                    var weeklyIndicators = Instruments
                                           .ToDictionary(
                        i => i,
                        i => new
                    {
                        rsi = weeklyBars[i].RSI(2)[0],
                    });

                    // sell, if RSI is above 80
                    foreach (var i in Instruments)
                    {
                        if (weeklyIndicators[i].rsi > 80)
                        {
                            weights[i] = 0.0;
                        }
                    }

                    // determine number of new entries
                    var numCurrentEntries = weights
                                            .Where(w => w.Value > 0.0)
                                            .Count();
                    var numNewEntries = NUM_POS - numCurrentEntries;

                    // buy, if
                    // - we don't have a position
                    // - trend filter is positive
                    // - RSI is below 20
                    var newEntries = constituents
                                     .Where(i => i.Position == 0)
                                     .Where(i => spx.Instrument.Close[0] > spx.Instrument.Close[126])
                                     .Where(i => weeklyIndicators[i].rsi < 20)
                                     .OrderBy(i => dailyIndicators[i].volatility)
                                     .Take(numNewEntries)
                                     .ToList();

                    foreach (var i in newEntries)
                    {
                        weights[i]     = 1.0 / NUM_POS;
                        entryPrices[i] = i.Close[0];
                    }

                    // invest in the idle instrument, if we are not planning to
                    // fill all slots for mean-reversion instruments
                    double idleWeight = (numNewEntries - newEntries.Count()) / NUM_POS;
                    weights[idle.Instrument] = idleWeight;
                }

                foreach (var i in Instruments)
                {
                    int targetShares = (int)Math.Floor(weights[i] * NetAssetValue[0] / i.Close[0]);
                    // don't rebalance our mean-reversion instruments
                    if (i != idle.Instrument && i.Position > 0 && targetShares > 0)
                    {
                        targetShares = i.Position;
                    }
                    i.Trade(targetShares - i.Position, OrderType.closeThisBar);
                }

                if (TradingDays > 0 && !IsOptimizing)
                {
                    _plotter.AddNavAndBenchmark(this, benchmark.Instrument);
                    //_plotter.AddStrategyHoldings(this, universe.Select(u => u.Instrument));
                }

                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();
        }
Example #5
0
        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();
        }
        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; // 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 ==========

            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();
        }
Example #9
0
        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();
        }
Example #10
0
        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();
        }
Example #11
0
        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 ==========

#if USE_BOOK_RANGE
            StartTime = SubclassedStartTime ?? DateTime.Parse("01/01/2005", CultureInfo.InvariantCulture);
            EndTime   = SubclassedEndTime ?? DateTime.Parse("12/31/2018", CultureInfo.InvariantCulture);
#else
            StartTime = SubclassedStartTime ?? Globals.START_TIME;
            EndTime   = SubclassedEndTime ?? Globals.END_TIME;
#endif
            WarmupStartTime = StartTime - TimeSpan.FromDays(252);

            Deposit(Globals.INITIAL_CAPITAL);
            //CommissionPerShare = Globals.COMMISSION; // Connors does not consider commissions

            var universe  = AddDataSources(UNIVERSE);
            var benchmark = AddDataSource(BENCHMARK);

            //========== simulation loop ==========

            foreach (var s in SimTimes)
            {
                if (!HasInstruments(universe) || !HasInstrument(benchmark))
                {
                    continue;
                }

                // calculate momentum and volatility
                var indicators = universe
                                 .ToDictionary(
                    d => d.Instrument,
                    d => new
                {
                    momentum   = MOMENTUM(d.Instrument),
                    volatility = d.Instrument.Close.Volatility(63)[0],
                });

                // take the top 5 assets with the highest momentum
                var top5 = indicators.Keys
                           .OrderByDescending(i => indicators[i].momentum)
                           .Take(5)
                           .ToList();

                // place orders on last business day of the month
                if (SimTime[0].Month != NextSimTime.Month)
                {
                    // determine sum of the inverse volatilities
                    var sumInverseVol = top5
                                        .Sum(i => 1.0 / Math.Max(1e-10, indicators[i].volatility));

                    foreach (var d in universe)
                    {
                        double weight = top5.Contains(d.Instrument)
                            ? 1.0 / indicators[d.Instrument].volatility / sumInverseVol
                            : 0.0;

                        int targetShares = (int)Math.Floor(weight * NetAssetValue[0] / d.Instrument.Close[0]);
                        d.Instrument.Trade(targetShares - d.Instrument.Position);
                    }
                }

                if (TradingDays > 0 && !IsOptimizing)
                {
                    _plotter.AddNavAndBenchmark(this, benchmark.Instrument);
                    _plotter.AddStrategyHoldings(this, universe.Select(u => u.Instrument));
                }

                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 void Run()
        {
            //========== initialization ==========

#if USE_BOOK_RANGE
            StartTime = SubclassedStartTime ?? DateTime.Parse("01/01/2007", CultureInfo.InvariantCulture);
            EndTime   = SubclassedEndTime ?? DateTime.Parse("12/31/2018", CultureInfo.InvariantCulture);
#else
            StartTime = SubclassedStartTime ?? Globals.START_TIME;
            EndTime   = SubclassedEndTime ?? Globals.END_TIME;
#endif
            WarmupStartTime = StartTime - TimeSpan.FromDays(252);

            Deposit(Globals.INITIAL_CAPITAL);
            //CommissionPerShare = Globals.COMMISSION; // Connors does not consider commissions

            var universe  = AddDataSources(UNIVERSE);
            var idle      = AddDataSource(IDLE_CASH);
            var benchmark = AddDataSource(BENCHMARK);

            //========== simulation loop ==========

            foreach (var s in SimTimes)
            {
                if (!HasInstruments(universe) || !HasInstrument(benchmark))
                {
                    continue;
                }

                var indicators = Instruments
                                 .ToDictionary(
                    i => i,
                    i => new
                {
                    longTermTrend         = i.Close.Momentum(252)[0],
                    intermediateTermTrend = i.Close.Momentum(21)[0],
                    rsi        = i.Close.RSI(2)[0],
                    volatility = i.Close.Volatility(100)[0]
                });

                // start with weights representing current positions
                // note that the idle instrument is not included here
                const int NUM_POS = 5;
                var       weights = Instruments
                                    .ToDictionary(
                    i => i,
                    i => universe.Contains(i.DataSource) && i.Position < 0
                            ? -1.0 / NUM_POS
                            : 0.0);

                // exit, if RSI is below 15
                foreach (var i in Instruments)
                {
                    if (i.Position < 0.0 && indicators[i].rsi < 15)
                    {
                        weights[i] = 0.0;
                    }
                }

                // exit, if intermediate-term momentum turns positive
                foreach (var i in Instruments)
                {
                    if (i.Position < 0.0 && indicators[i].intermediateTermTrend > 0.0)
                    {
                        weights[i] = 0.0;
                    }
                }

                // determine number of new entries
                var numCurrentEntries = weights
                                        .Where(w => w.Value < 0.0)
                                        .Count();
                var numNewEntries = NUM_POS - numCurrentEntries;

                // sell short, if
                // - we don't have a position
                // - trend filters are both negative
                // - RSI is above 70
                var newEntries = universe
                                 .Select(ds => ds.Instrument)
                                 .Where(i => i.Position == 0)
                                 .Where(i => indicators[i].longTermTrend < 0.0 && indicators[i].intermediateTermTrend < 0.0)
                                 .Where(i => indicators[i].rsi > 70)
                                 .OrderByDescending(i => indicators[i].volatility)
                                 .Take(numNewEntries)
                                 .ToList();

                foreach (var i in newEntries)
                {
                    weights[i] = -1.0 / NUM_POS;
                }

                // invest in the idle instrument, if we are not planning to
                // fill all slots for mean-reverting instruments
                double idleWeight = ((double)numNewEntries - newEntries.Count()) / NUM_POS;
                weights[idle.Instrument] = idleWeight;

                // place orders
                foreach (var i in Instruments)
                {
                    int targetShares = (int)(Math.Sign(weights[i]) * Math.Floor(Math.Abs(weights[i]) * NetAssetValue[0] / i.Close[0]));

                    if (i != idle.Instrument)
                    {
                        // note: we don't rebalance our mean-reverting instruments

                        // enter positions w/ limit order
                        if (i.Position == 0 && targetShares != 0)
                        {
                            i.Trade(targetShares, OrderType.limitNextBar, i.Close[0] * 1.03);
                        }

                        // exit positions w/ market order
                        if (i.Position != 0 && targetShares == 0)
                        {
                            i.Trade(-i.Position);
                        }
                    }
                    else
                    {
                        // idle instrument
                        i.Trade(targetShares - i.Position);
                    }
                }

                if (TradingDays > 0 && !IsOptimizing)
                {
                    _plotter.AddNavAndBenchmark(this, benchmark.Instrument);
                    _plotter.AddStrategyHoldings(this, universe.Select(u => u.Instrument).Concat(new List <Instrument> {
                        idle.Instrument
                    }));
                }

                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 void Run()
        {
            //========== initialization ==========

#if USE_BOOK_RANGE
            StartTime = SubclassedStartTime ?? DateTime.Parse("01/01/2005", CultureInfo.InvariantCulture);
            EndTime   = SubclassedEndTime ?? DateTime.Parse("12/31/2018", CultureInfo.InvariantCulture);
#else
            StartTime = SubclassedStartTime ?? Globals.START_TIME;
            EndTime   = SubclassedEndTime ?? Globals.END_TIME;
#endif
            WarmupStartTime = StartTime - TimeSpan.FromDays(105);

            Deposit(Globals.INITIAL_CAPITAL);
            //CommissionPerShare = Globals.COMMISSION; // Connors does not consider commissions

            var universe  = AddDataSources(UNIVERSE);
            var fallback  = AddDataSource(FALLBACK);
            var benchmark = AddDataSource(BENCHMARK);

            //========== simulation loop ==========

            foreach (var s in SimTimes)
            {
                if (!HasInstruments(universe) || !HasInstrument(fallback) || !HasInstrument(benchmark))
                {
                    continue;
                }

                if (SimTime[0].DayOfWeek > NextSimTime.DayOfWeek)
                {
                    var weights = new Dictionary <Instrument, double>();

                    foreach (var ds in universe)
                    {
                        weights[ds.Instrument] = 0.0;

                        foreach (var l in LOOKBACKS)
                        {
                            weights[ds.Instrument] += ds.Instrument.Close[0] > ds.Instrument.Close[l]
                                ? 0.05
                                : 0.0;
                        }
                    }

                    weights[fallback.Instrument] = 1.0 - weights.Sum(w => w.Value);

                    foreach (var i in weights.Keys)
                    {
                        int targetShares = (int)Math.Floor(weights[i] * NetAssetValue[0] / i.Close[0]);
                        i.Trade(targetShares - i.Position);
                    }
                }

                if (TradingDays > 0 && !IsOptimizing)
                {
                    _plotter.AddNavAndBenchmark(this, benchmark.Instrument);
                    _plotter.AddStrategyHoldings(this, Instruments.Where(i => universe.Contains(i.DataSource) || fallback == i.DataSource));
                }

                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 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();
        }
Example #16
0
        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 ==========

#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

            AddDataSources(UNIVERSE.Constituents);
            AddDataSource(BENCHMARK);

            Deposit(Globals.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.IsConstituent(UNIVERSE))
#if false
                               // we don't like to have these filter rules here. Instead,
                               // this should be solved by proper universe selection
                               .Where(i =>
                                      i.Close[0] > 1.00 &&
                                      i.Volume.ToDouble().SMA(50)[0] > 0.5e6 &&
                                      i.Close.Multiply(i.Volume.ToDouble()).SMA(50)[0] > 2.5e6)
#endif
                               .ToList();

                //----- calculate indicators

                // make sure to calculate indicators for all
                // known instruments, as they may enter the universe
                // at any time
                var indicators = Instruments
                                 .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 = ENTRY_DIR > 0
                    ? (universe
                       .Where(i =>                                        // - long -
                              i.Close[0] > indicators[i].sma150[0] &&     // close above 150-day SMA
                              indicators[i].adx7[0] > MIN_ADX &&          // 7-day ADX above 45
                              indicators[i].atr10[0] > MIN_ATR / 10000.0 && // 10-day ATR above 4%
                              indicators[i].rsi3[0] < MINMAX_RSI)         // 3-day RSI below 30
                       .ToList())
                    : (universe
                       .Where(i =>                                             // - short -
                              i.Close[0] > i.Close[1] && i.Close[1] > i.Close[2] && // 2 up-days
                              indicators[i].adx7[0] > MIN_ADX &&               // 7-day ADX above 50
                              indicators[i].atr10[0] > MIN_ATR / 10000.0 &&    // 10-day ATR above 5%
                              indicators[i].rsi3[0] > MINMAX_RSI)              // 3-day RSI above 85
                       .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

                // 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 && 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(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();
        }
Example #18
0
        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();
        }
Example #19
0
        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();
        }
Example #20
0
        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();
        }
Example #21
0
        public override void Run()
        {
            //========== initialization ==========

            StartTime = Globals.START_TIME;
            EndTime   = Globals.END_TIME;

            var universe       = AddDataSources(UNIVERSE);
            var safeInstrument = AddDataSource(SAFE_INSTRUMENT);
            var benchmark      = AddDataSource(BENCHMARK);

            Deposit(Globals.INITIAL_CAPITAL);
            CommissionPerShare = Globals.COMMISSION;

            //========== simulation loop ==========

            foreach (var s in SimTimes)
            {
                //----- skip until all required instruments are valid
                if (!HasInstruments(universe) ||
                    !HasInstrument(safeInstrument) ||
                    !HasInstrument(benchmark))
                {
                    continue;
                }


                //----- memorize our momentum
                // its good practice to do this, to make sure
                // indicators are only evaluated once
                var momentum1 = universe
                                .ToDictionary(
                    ds => ds.Instrument,
                    ds => ds.Instrument.Close.Momentum(RANK1_DAYS)[0]);

                var momentum2 = universe
                                .ToDictionary(
                    ds => ds.Instrument,
                    ds => ds.Instrument.Close.Momentum(RANK2_DAYS)[0]);

                //----- rank universe by momentum
                var rank1 = universe
                            .OrderByDescending(ds => momentum1[ds.Instrument])
                            .Select((ds, n) => new { instr = ds.Instrument, rank = n, mom = momentum1[ds.Instrument] })
                            .ToDictionary(
                    i => i.instr,
                    i => i);

                var rank2 = universe
                            .OrderByDescending(ds => momentum2[ds.Instrument])
                            .Select((ds, n) => new { instr = ds.Instrument, rank = n, mom = momentum2[ds.Instrument] })
                            .ToDictionary(
                    i => i.instr,
                    i => i);

                var rank3 = universe
                            .OrderBy(ds => 1.001 * rank1[ds.Instrument].rank + rank2[ds.Instrument].rank) // use rank1 as tie break
                            .Select((ds, n) => new { instr = ds.Instrument, rank = n, sum = 1.001 * rank1[ds.Instrument].rank + rank2[ds.Instrument].rank })
                            .ToDictionary(
                    i => i.instr,
                    i => i);

                //----- select our 2 top ranking instruments
#if true
                // this is what Cesar Alvarez seems to be describing
                // in his blog post. however, the results are nowhere close
                // to what he  published.
                var top2 = rank3
                           .OrderBy(i => i.Value.rank)
                           .Take(2)
                           .ToDictionary(
                    i => i.Key,
                    i => i.Value);
#else
                // this is probably what Cesar Alvarez has simulated,
                // as the results seem to match those published
                // on the blog closely.
                // this is chosing the 2 _worst_ ranked sectors,
                // making this a mean-reversion strategy
                var top2 = rank3
                           .OrderByDescending(i => i.Value.rank)
                           .Take(2)
                           .ToDictionary(
                    i => i.Key,
                    i => i.Value);
#endif

                //----- assign weights
                var weights = universe
                              .ToDictionary(
                    ds => ds.Instrument,
                    ds => top2.ContainsKey(ds.Instrument)
                            ? (ds.Instrument.Close[0] > ds.Instrument.Close[252] ? 0.5 : 0.0)
                            : 0.0);

                weights[safeInstrument.Instrument] = 1.0 - weights.Sum(i => i.Value);

                //----- trade instruments
                if (SimTime[0].Month != SimTime[1].Month)
                {
                    foreach (var i in weights.Keys)
                    {
                        var targetShares = (int)Math.Floor(weights[i] * NetAssetValue[0] / i.Close[0]);
                        i.Trade(targetShares - i.Position);
                    }
                }

                //---- plot output
                _plotter.AddNavAndBenchmark(this, benchmark.Instrument);
                _plotter.AddStrategyHoldings(this, universe.Select(ds => ds.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 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();
        }