Overall Statistics |
Total Trades 3180 Average Win 0.16% Average Loss -0.14% Compounding Annual Return 446.467% Drawdown 4.300% Expectancy 0.774 Net Profit 446.467% Sharpe Ratio 9.527 Loss Rate 19% Win Rate 81% Profit-Loss Ratio 1.19 Alpha 1.399 Beta 0.025 Annual Standard Deviation 0.147 Annual Variance 0.022 Information Ratio 6.575 Tracking Error 0.207 Treynor Ratio 56.006 Total Fees $3180.00 |
//Copyright HardingSoftware.com 2019, granted to the public domain. //Use at your own risk. Do not remove this copyright notice. namespace QuantConnect.Algorithm.CSharp { public class Pair : QCAlgorithm { Symbol symbol = QuantConnect.Symbol.Create("MLAB", SecurityType.Equity, Market.USA); decimal limitRatio = 0m; int priceDecimals = 2; int period = 60 * 60; decimal valueWeightExponent = 1m; decimal timeWeightExponent = 1m; Resolution resolution = Resolution.Second; TimeSpan orderExpiryTime = new TimeSpan(0,0,0,59); List<TradeBar> history = new List<TradeBar>(); decimal startCash = 5000; public override void Initialize() { SetStartDate(2018, 9, 6); //Set Start Date SetEndDate(2019, 9, 6); SetCash(startCash); //Set Strategy Cash AddEquity(symbol, resolution); } /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. /// Slice object keyed by symbol containing the stock data public override void OnData(Slice data) { CancelExpiredOrders(); if (data.Bars.ContainsKey(symbol) && data.Bars[symbol] != null) { history.Add(data.Bars[symbol]); if (history.Count > period) { history.RemoveAt(0); } else { return; } List<decimal> lows = history.Select(x => x.Low).ToList(); decimal buyPrice = Reversal.CalculateBuyPrice(lows, timeWeightExponent, valueWeightExponent); List<decimal> highs = history.Select(x => x.High).ToList(); decimal sellPrice = Reversal.CalculateSellPrice(highs, timeWeightExponent, valueWeightExponent); decimal range = sellPrice - buyPrice; if (Portfolio[symbol].Quantity == 0) { decimal price = buyPrice - range * limitRatio; price = Math.Round(price, priceDecimals); if (price > 0) { //decimal quantity = tradeQuantity; //decimal quantity = Math.Floor((Portfolio.Cash - 2) / price); decimal quantity = startCash / price; if (OrderIsPlaced(symbol, quantity) == false) { Transactions.CancelOpenOrders(); LimitOrder(symbol, quantity, price); } } } else if (Portfolio[symbol].Quantity > 0) { decimal price = sellPrice + range * limitRatio; price = Math.Round(price, priceDecimals); if (price > 0) { decimal quantity = -Portfolio[symbol].Quantity; if (OrderIsPlaced(symbol, quantity) == false) { Transactions.CancelOpenOrders(); LimitOrder(symbol, quantity, price); } } } } } public bool OrderIsPlaced(Symbol symbol, decimal quantity) { List<Order> orders = Transactions.GetOpenOrders(symbol); foreach (Order order in orders) { if (order.Symbol == symbol) { if (Math.Sign(quantity) == Math.Sign(order.Quantity)) { return true; } } } return false; } public void CancelExpiredOrders() { List<Order> orders = Transactions.GetOpenOrders(); foreach (Order order in orders) { if (Time > order.Time + orderExpiryTime) { Transactions.CancelOrder(order.Id); } } } public class Reversal { //Copyright HardingSoftware.com 2019, granted to the public domain. //Use at your own risk. Do not remove this copyright notice. public int Index; public decimal Value; public static decimal CalculateBuyPrice(List<decimal> lows, decimal timeWeightExponent, decimal valueWeightExponent) { List<Reversal> minima = Reversal.Minima(lows).OrderByDescending(x => x.Value).ToList(); decimal[] timeWeights = minima.Select(x => (decimal)Math.Pow((double)x.Index, (double)timeWeightExponent)).ToArray(); decimal[] valueWeights = ExponentialWeights(minima.Count, valueWeightExponent); decimal[] totalWeights = timeWeights.Zip(valueWeights, (t, v) => t * v).ToArray(); decimal[] values = minima.Select(x => x.Value).ToArray(); return WeightedAverage(values, totalWeights); } public static decimal CalculateSellPrice(List<decimal> highs, decimal timeWeightExponent, decimal valueWeightExponent) { List<Reversal> maxima = Reversal.Maxima(highs).OrderBy(x => x.Value).ToList(); decimal[] timeWeights = maxima.Select(x => (decimal)Math.Pow((double)x.Index, (double)timeWeightExponent)).ToArray(); decimal[] valueWeights = ExponentialWeights(maxima.Count, 2); decimal[] totalWeights = timeWeights.Zip(valueWeights, (t, v) => t * v).ToArray(); decimal[] values = maxima.Select(x => x.Value).ToArray(); return WeightedAverage(values, totalWeights); } public static List<Reversal> Maxima(List<decimal> values) { List<Reversal> reversals = new List<Reversal>(); for (int i = 1; i < values.Count - 1; i++) { if (values[i -1] < values[i]) { if (values[i + 1] < values[i]) { Reversal reversal = new Reversal(); reversal.Index = i; reversal.Value = values[i]; reversals.Add(reversal); } } } if (values[0] > values[1]) { Reversal reversal = new Reversal(); reversal.Index = 0; reversal.Value = values[0]; reversals.Add(reversal); } if (values.Last() > values[values.Count - 2]) { Reversal reversal = new Reversal(); reversal.Index = values.Count; reversal.Value = values.Last(); reversals.Add(reversal); } return reversals; } public static List<Reversal> Minima(List<decimal> values) { List<Reversal> reversals = new List<Reversal>(); for (int i = 1; i < values.Count - 1; i++) { if (values[i - 1] > values[i]) { if (values[i + 1] > values[i]) { Reversal reversal = new Reversal(); reversal.Index = i; reversal.Value = values[i]; reversals.Add(reversal); } } } if (values[0] < values[1]) { Reversal reversal = new Reversal(); reversal.Index = 0; reversal.Value = values[0]; reversals.Add(reversal); } if (values.Last() < values[values.Count - 2]) { Reversal reversal = new Reversal(); reversal.Index = values.Count; reversal.Value = values.Last(); reversals.Add(reversal); } return reversals; } public static decimal WeightedAverage(decimal[] values, decimal[] weights) { decimal d = weights.Sum(); if (d != 0) { return values.Zip(weights, (x, y) => x * y).Sum() / d; } else { return 0; } } public static decimal[] TriangularWeightsDecimal(int length) { int[] intWeights = Enumerable.Range(1, length).ToArray(); return intWeights.Select(x => Convert.ToDecimal(x)).ToArray(); } public static decimal TriangularMovingAverage(decimal[] values) { return WeightedAverage(values, TriangularWeightsDecimal(values.Length)); } public static decimal[] ExponentialWeights(int length, decimal exponent) { List<decimal> weights = new List<decimal>(); double exponentDouble = (double)exponent; for (int i=0;i<length;i++) { double w = (double)(i + 1); weights.Add((decimal)Math.Pow(w, exponentDouble)); } return weights.ToArray(); } } } }