Overall Statistics |
Total Trades 93 Average Win 3.36% Average Loss -1.59% Compounding Annual Return 4.763% Drawdown 16.600% Expectancy 0.504 Net Profit 100.858% Sharpe Ratio 0.697 Loss Rate 52% Win Rate 48% Profit-Loss Ratio 2.11 Alpha 0.049 Beta 0.001 Annual Standard Deviation 0.07 Annual Variance 0.005 Information Ratio -0.06 Tracking Error 0.216 Treynor Ratio 36.021 Total Fees $186.00 |
using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Data.Consolidators; using QuantConnect.Indicators; using QuantConnect.Data.Market; using Accord.MachineLearning.VectorMachines; using Accord.MachineLearning.VectorMachines.Learning; using Accord.Statistics.Kernels; using AForge; namespace QuantConnect.Algorithm { /* * QuantConnect University: FOREX - Using Currency Data * * QuantConnect allows you to use currency data for your backtest with a * simple line of code. See the SecurityType.Forex below. */ public class ExampleMachineLearning : QCAlgorithm { static int trainSize = 1000; static int historyBarCount = 2; static decimal targetPct = 2m; static decimal stopPct = 2m; static decimal targetOverShoot = 0.1m; static int mainPeriod = 21; static int slowEmaPeriod = 55; static int middleEmaPeriod = 21; static int fastEmaPeriod = 8; static int waitPeriod = 20; string symbol = "SPY"; static decimal tradePct = 1m; bool useComplexInputs = true; bool useComplexTargets = true; bool useLinearSVM = false; bool useWickStop = false; bool expandTakeProfit = true; bool refreshBiquarterly = trainSize > 800; bool refreshQuarterly = trainSize <= 800 && trainSize > 400; bool refreshMonthly = trainSize <= 400 && trainSize > 120; DateTime enterDate; int windowSize = historyBarCount + trainSize + 2 + waitPeriod - 1; bool printedSupportVectors = true; bool printedInputs = true; bool printedDayWindow = true; bool printedError = true; decimal stopPrice; decimal targetPrice; RollingWindow<TradeBar> dayWindow; TradeBarIndicatorRollingWindow atrWindow; RollingWindow<decimal> wickStopWindow; DataPointIndicatorRollingWindow fastEmaWindow; DataPointIndicatorRollingWindow middleEmaWindow; DataPointIndicatorRollingWindow slowEmaWindow; IndicatorFeatureCollection featureCollection; IndicatorBase<IndicatorDataPoint> wickStop; IndicatorBase<TradeBar> wickStopPoint; IndicatorBase<TradeBar> closingPrice; DataPointIndicatorRollingWindow redLine; Queue<double[]> samples = new Queue<double[]>(trainSize); SupportVectorMachine svm; Queue<TimeSpan> trainingTimes = new Queue<TimeSpan>(); Queue<TimeSpan> computeTimes = new Queue<TimeSpan>(); List<string> logStrings = new List<string>(); double error = 0; int targetPositives = 0; int targetNegatives = 0; /// <summary> /// Initialize QuantConnect Strategy. /// </summary> private void LogLater(string message) { logStrings.Add(message); } public override void OnEndOfAlgorithm() { base.OnEndOfAlgorithm(); var totalTrainingTime = trainingTimes.Aggregate(TimeSpan.FromMilliseconds(0), (acc, next) => acc + next); var averageTrainingTime = totalTrainingTime.TotalSeconds / trainingTimes.Count; var totalComputeTime = computeTimes.Aggregate(TimeSpan.FromMilliseconds(0), (acc, next) => acc + next); var averageComputeTime = totalComputeTime.TotalSeconds / computeTimes.Count; Log(string.Format("SVM Training (seconds) Total: {0:0.##} Average: {1:0.####} Count: {2}", totalTrainingTime.TotalSeconds, averageTrainingTime, trainingTimes.Count)); Log(string.Format("SVM Compute (seconds) Total: {0:0.##} Average: {1:0.####} Count: {2}", totalComputeTime.TotalSeconds, averageComputeTime, computeTimes.Count)); foreach (var message in logStrings) { Log(message); } } public override void Initialize() { dayWindow = new RollingWindow<TradeBar>(windowSize); atrWindow = new TradeBarIndicatorRollingWindow(windowSize, new AverageTrueRange(mainPeriod), false); fastEmaWindow = new DataPointIndicatorRollingWindow(windowSize, new ExponentialMovingAverage(fastEmaPeriod), null, false); slowEmaWindow = new DataPointIndicatorRollingWindow(windowSize, new ExponentialMovingAverage(slowEmaPeriod), null, false); middleEmaWindow = new DataPointIndicatorRollingWindow(windowSize, new ExponentialMovingAverage(middleEmaPeriod), null, false); redLine = new DataPointIndicatorRollingWindow(windowSize, new SimpleMovingAverage(200), null, false); featureCollection = new IndicatorFeatureCollection( atrWindow, fastEmaWindow, middleEmaWindow, slowEmaWindow, redLine ); wickStopWindow = new RollingWindow<decimal>(windowSize); wickStopPoint = new FunctionalIndicator<TradeBar>("WickStop", t => t.Close > t.Open ? t.Open - t.Low : t.Close < t.Open ? t.High - t.Open : Math.Max(t.High - t.Open, t.Open - t.Low), i => i.Samples > 0); closingPrice = new FunctionalIndicator<TradeBar>("Close", t => t.Close, i => i.Samples > 0); var wickStopDev = new StandardDeviation(mainPeriod).Of(wickStopPoint); var wickStopMA = new SimpleMovingAverage(mainPeriod).Of(wickStopPoint); wickStop = new CompositeIndicator<IndicatorDataPoint>(wickStopDev, wickStopMA, (left, right) => 2 * left + right); var endDate = new DateTime(2015, 1, 1); var startDate = endDate.AddYears(-10).AddDays((-trainSize - historyBarCount - 9) * 9 / 5); SetStartDate(startDate); SetEndDate(endDate); Log(String.Format("{0} - {1}", startDate, endDate)); SetCash(10000); AddSecurity(SecurityType.Equity, symbol, Resolution.Minute); //_atr = ATR(_symbol, 20, MovingAverageType.Simple, Resolution.Daily); // Construct Day Consolidator var dayConsolidator = new TradeBarConsolidator(TimeSpan.FromDays(1)); dayConsolidator.DataConsolidated += OnDataDay; SubscriptionManager.AddConsolidator(symbol, dayConsolidator); } /// <summary> /// OnData Callback (called every minute) - Prediction is made once per day, 1m after market opens /// StopLoss is handled on a minutely basis. /// </summary> public void OnData(TradeBars data) { // Wait for data to become ready if (!dayWindow.IsReady || !featureCollection.IsReady) return; Func<int, double[]> inputGenerator; //inputGenerator = (lookback) => // Enumerable.Range(lookback, inputSize) // .Select(i=> (double) ((_daywindow[i].Close - _daywindow[i+1].Close)/_daywindow[i+1].Close > 0 ? 1 : -1)) // .ToArray(); //Func<int, decimal> getSL = (lookback) => wickStopWindow[lookback]; var stopWindow = useWickStop ? wickStopWindow : atrWindow; Func<int, decimal> getSL = (lookback) => stopPct * stopWindow[lookback]; Func<int, decimal> getTP = (lookback) => atrWindow[lookback] * targetPct; Func<int, decimal> getTrainTP = (lookback) => stopWindow[lookback] * (targetPct + targetPct * targetOverShoot); Func<int, double[]> simpleInputGenerator = (lookback) => Enumerable.Range(lookback, historyBarCount) .Select(i => new double[] { (double) ((dayWindow[i].Close - dayWindow[i+1].Close)/dayWindow[i+1].Close) }).SelectMany<double[], double>(i => i) .ToArray(); Func<int, double[]> complexInputGenerator = (lookback) => Enumerable.Range(lookback, historyBarCount) .Select(i => new double[] { (double) ((dayWindow[i].Open - dayWindow[i+1].Close)/dayWindow[i+1].Close), (double) ((dayWindow[i].Close - dayWindow[i+1].Close)/dayWindow[i+1].Close), (double) ((dayWindow[i].High - dayWindow[i].Open)/dayWindow[i].Open), (double) ((dayWindow[i].Open - dayWindow[i].Low)/dayWindow[i].Open), (double) ((dayWindow[i].Close - dayWindow[i].Open)/dayWindow[i].Open), }).SelectMany<double[], double>(i => i) .Concat(featureCollection[lookback]) .Concat(new double[] { (double) ((atrWindow[lookback] - dayWindow[lookback].Close) / dayWindow[lookback].Close), (double) ((dayWindow[lookback].Close - slowEmaWindow[lookback])/slowEmaWindow[lookback]), (double) ((fastEmaWindow[lookback]-slowEmaWindow[lookback])/slowEmaWindow[lookback]), (double) ((fastEmaWindow[lookback]-fastEmaWindow[lookback+1])/fastEmaWindow[lookback+1]), (double) ((middleEmaWindow[lookback]-slowEmaWindow[lookback])/slowEmaWindow[lookback]), (double) ((middleEmaWindow[lookback]-middleEmaWindow[lookback+1])/middleEmaWindow[lookback+1]), (double) ((slowEmaWindow[lookback]-slowEmaWindow[lookback+1])/slowEmaWindow[lookback+1]) }) .ToArray(); inputGenerator = useComplexInputs ? complexInputGenerator : simpleInputGenerator; Func<int, IEnumerable<TradeBar>> waitWindow = (lookback) => dayWindow.Skip(lookback - 1).Take(waitPeriod).OrderBy(t => t.Time); Func<int, int[]> complexTargetGenerator = (lookback) => Enumerable.Range(lookback, trainSize) .Select(i => new { Window = waitWindow(i), SL = getSL(i + waitPeriod - 1), TP = getTrainTP(i + waitPeriod - 1) }) .Select(w => new { SLPrice = w.Window.First().Open - w.SL, TPPrice = w.Window.First().Open + w.TP, Window = w.Window }) .Select(v => v.Window.Select(b => b.High >= v.TPPrice && (b.Low > v.SLPrice || b.Close < b.Open) ? 1 : b.Low <= v.SLPrice ? -1 : 0).FirstOrDefault(r => r != 0) > 0 ? 1 : -1) .ToArray(); Func<int, int[]> simpleTargetGenerator = (lookback) => Enumerable.Range(lookback, trainSize) .Select(i => new { Window = waitWindow(i) }) .Select(v => v.Window.Last().Close > v.Window.First().Open ? 1 : -1) .ToArray(); var targetGenerator = useComplexTargets ? complexTargetGenerator : simpleTargetGenerator; var urProfit = Math.Round(Securities[symbol].Holdings.UnrealizedProfit, 2); // At Market Open use historical close data to make a prediction if ((data.Time.Hour == 9) && (data.Time.Minute == 31)) { if (svm == null || (refreshMonthly == false && refreshQuarterly == false && refreshBiquarterly == false) || data.Time.Month != dayWindow[0].Time.Month && (refreshMonthly || refreshQuarterly && (dayWindow[0].Time.Month - 1) % 3 == 0 || refreshBiquarterly && (dayWindow[0].Time.Month - 1) % 6 == 0)) { var beforeTrainingSVM = DateTime.Now; var targets = targetGenerator(1); targetPositives = targets.Count(t => t > 0); targetNegatives = targets.Count(t => t <= 0); samples.Clear(); for (int i = 0; i < trainSize; i++) { double[] returns = inputGenerator(i + waitPeriod); samples.Enqueue(returns); } if (!printedDayWindow) { printedDayWindow = true; Log("Day Window: " + String.Join(", ", dayWindow.Select(t => t.Close.ToString()).ToArray())); } double[][] inputs = samples.ToArray(); var actualInputSize = inputs[0].Length; if (inputs.Length != targets.Length) Log(String.Format("Inputs: {0} Targets: {1}", inputs.Length, targets.Length)); if (inputs != null && printedInputs == false) { printedInputs = true; Log(String.Format("Inputs: {0}", inputs.Length)); foreach (var input in inputs.Take(6)) { foreach (var value in input) { Log("" + value); } Log("--------------"); } return; } ISupportVectorMachineLearning teacher; if (useLinearSVM == false) { //var kernel = Gaussian.Estimate(inputs); var kernel = Laplacian.Estimate(inputs); var sigma = kernel.Sigma; svm = new KernelSupportVectorMachine(kernel, actualInputSize); teacher = new SequentialMinimalOptimization(svm, inputs, targets); ((SequentialMinimalOptimization)teacher).UseComplexityHeuristic = true; } else { svm = new SupportVectorMachine(inputs: actualInputSize); teacher = new LinearCoordinateDescent(svm, inputs, targets); ((LinearCoordinateDescent)teacher).UseComplexityHeuristic = true; } error = teacher.Run(true); trainingTimes.Enqueue(DateTime.Now - beforeTrainingSVM); if (printedError == false) { printedError = true; Log(string.Format("Error: {0} Positives: {1} Negatives: {2}", error, targetPositives, targetNegatives)); } } var beforeCompute = DateTime.Now; double[] currentInputs = inputGenerator(0); double output = svm.Compute(currentInputs); computeTimes.Enqueue(DateTime.Now - beforeCompute); var supportVectors = svm.SupportVectors; if (supportVectors != null && printedSupportVectors == false) { printedSupportVectors = true; Log(String.Format("Support Vectors: {0}", supportVectors.Length)); foreach (var value in supportVectors[0]) { Log("" + value); } Log("--------------"); return; } if (output > 0) { var newStopPrice = data[symbol].Close - getSL(0); var newTargetPrice = data[symbol].Close + getTP(0); if (Securities[symbol].Holdings.Quantity != 0) { stopPrice = newStopPrice > stopPrice ? newStopPrice : stopPrice; targetPrice = expandTakeProfit && newTargetPrice > targetPrice ? newTargetPrice : targetPrice; } else { stopPrice = newStopPrice; targetPrice = newTargetPrice; } LogLater(String.Format("O: {0:0.##} E: {1:0.##} P: {2} N: {3} SL: {4:0.##} TP: {5:0.##}", Math.Round(output, 2), Math.Round(error, 2), targetPositives, targetNegatives, stopPrice, targetPrice)); // update stoploss enterDate = DateTime.Now; // Go Long if (Securities[symbol].Holdings.Quantity == 0) { Order(symbol, Portfolio.TotalPortfolioValue / data[symbol].Close * tradePct); LogLater("Buy Shares: " + Securities[symbol].Holdings.Quantity); } else if (Securities[symbol].Holdings.Quantity < 0) { // if we are in a short position: calculate how many shares needed to go long Order(symbol, ((decimal)-Securities[symbol].Holdings.Quantity) + (Portfolio.TotalPortfolioValue / data[symbol].Close * tradePct)); LogLater("Buy Shares: " + Securities[symbol].Holdings.Quantity); } } else if ((output < 0 && Math.Round((DateTime.Now - enterDate).TotalDays / waitPeriod) >= 1) && Securities[symbol].Holdings.Quantity != 0) { // Exit Market Liquidate(symbol); LogLater("Exiting Market:" + urProfit); } } // Handle Stop Loss if (Securities[symbol].Holdings.Quantity != 0) { if (Securities[symbol].Holdings.IsLong) { if (data[symbol].Low <= stopPrice) { Liquidate(symbol); LogLater("StopLoss: " + urProfit); } if (data[symbol].High >= targetPrice) { Liquidate(symbol); LogLater("TakeProfit: " + urProfit); } } if (Securities[symbol].Holdings.IsShort) { if (data[symbol].High >= stopPrice) { Liquidate(symbol); Log("Hit StopLoss: " + data[symbol].High); } } } } /// <summary> /// OnDataDay Callback (called every day) - Used to build the daily history (rolling window) of close prices /// </summary> private void OnDataDay(object sender, TradeBar consolidated) { //Inject data into the rolling window. if (consolidated.Close > consolidated.Open) wickStopPoint.Update(consolidated); closingPrice.Update(consolidated); featureCollection.Update(consolidated); dayWindow.Add(consolidated); if (IndicatorsReady()) { wickStopWindow.Add(wickStop); } } private bool IndicatorsReady() { return wickStop.IsReady; } } public abstract class RollingWindowIndicator<T> : RollingWindow<decimal> { #region Constructors public RollingWindowIndicator(int windowSize, bool includeInFeatureCollection = true) : base(windowSize) { this.IncludeInFeatureCollection = includeInFeatureCollection; } #endregion #region Properties public bool IncludeInFeatureCollection { get; set; } #endregion #region Methods public abstract void Update(T data); #endregion } public class IndicatorFeatureCollection { #region Fields private List<RollingWindowIndicator<TradeBar>> indicatorList = new List<RollingWindowIndicator<TradeBar>>(); #endregion #region Constructors public IndicatorFeatureCollection(params RollingWindowIndicator<TradeBar>[] initialList) { indicatorList.AddRange(initialList); } #endregion #region Properties public bool IsReady { get { return indicatorList.All(i => i.IsReady); } } public double[] this[int index] { get { return indicatorList.Where(i => i.IncludeInFeatureCollection).Select(i => (double)i[index]).ToArray(); } } #endregion #region Methods public void Add(RollingWindowIndicator<TradeBar> indicator) { indicatorList.Add(indicator); } public void Update(TradeBar data) { foreach (var indicator in indicatorList) { indicator.Update(data); } } #endregion } public class TradeBarIndicatorRollingWindow : RollingWindowIndicator<TradeBar> { IndicatorBase<TradeBar> indicator; public TradeBarIndicatorRollingWindow( int windowSize, IndicatorBase<TradeBar> indicator, bool includeInFeatureCollection = true) : base(windowSize, includeInFeatureCollection) { this.indicator = indicator; } public override void Update(TradeBar data) { indicator.Update(data); if (indicator.IsReady) Add(indicator); } } public class DataPointIndicatorRollingWindow : RollingWindowIndicator<TradeBar> { IndicatorBase<IndicatorDataPoint> indicator; Func<TradeBar, decimal> projection; public DataPointIndicatorRollingWindow( int windowSize, IndicatorBase<IndicatorDataPoint> indicator, Func<TradeBar, decimal> projection = null, bool includeInFeatureCollection = true) : base(windowSize, includeInFeatureCollection) { this.indicator = indicator; this.projection = projection ?? (t => t.Value); } public override void Update(TradeBar data) { indicator.Update(data.Time, projection(data)); if (indicator.IsReady) Add(indicator); } } }