| 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);
}
}
}