Overall Statistics |
Total Trades 278 Average Win 0.55% Average Loss -0.23% Compounding Annual Return 56.572% Drawdown 8.900% Expectancy 0.913 Net Profit 37.281% Sharpe Ratio 2.337 Loss Rate 43% Win Rate 57% Profit-Loss Ratio 2.35 Alpha 0.386 Beta -0.053 Annual Standard Deviation 0.162 Annual Variance 0.026 Information Ratio 1.426 Tracking Error 0.176 Treynor Ratio -7.151 Total Fees $394.37 |
using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Data; using QuantConnect.Orders; namespace QuantConnect.Algorithm.CSharp { class AaEtfOpti : QCAlgorithm { // ##### ALGO PARAM ############ private int allocationFrequency = 3; // ##### END ALGO PARAM ############ private Queue<Symbol> stocks; private bool allocationTime = false; private int allocate = 0; private double[] weights; public override void Initialize() { SetStartDate(2017, 1, 1); SetEndDate(2017, 9, 15); SetCash(25000); stocks = new Queue<Symbol>(); var ref_etf = AddEquity("UPRO", Resolution.Minute).Symbol; stocks.Enqueue(ref_etf); stocks.Enqueue(AddEquity("UGLD", Resolution.Minute).Symbol); stocks.Enqueue(AddEquity("TMF", Resolution.Minute).Symbol); //stocks.Enqueue(AddEquity("XIV", Resolution.Minute).Symbol); int c = stocks.Count; weights = new double[c]; Schedule.On(DateRules.EveryDay(ref_etf), TimeRules.AfterMarketOpen(ref_etf, 60), () => { allocationTime = true; }); } public override void OnData(Slice data) { if (allocate % allocationFrequency == 0 && allocationTime) { Allocate(data); allocate = 0; allocationTime = false; } } private void Allocate(Slice data) { // I tend to convert everythign to double because a lot of the matrix existing manip/functions, // only work on doubles and precision should have very little impact on that var priceData = new List<double[]>(); int lookbackPeriod = allocationFrequency * 3; var resolution = Resolution.Daily; int dataCount = lookbackPeriod; //will hold returns.mean/return.std for all stocks var retNorm = new double[stocks.Count]; //constructing price matrix stocks.count x lookbackPeriod var allHistoryBars = new double[stocks.Count(), lookbackPeriod+1]; int ii = 0; foreach (var security in stocks) { var history = History(security, lookbackPeriod, resolution); //the tmp array dupe the price for the current security because in matrix // I have no way that I know of of accessign columns var tmp = new double[history.Count()+1]; //fill the price matrix int jj = 0; foreach(var h in history) { tmp[jj] = (double)h.Close; jj++; } //Add current bar to history var p = (double)history.Last().Value; if (data.Bars.Keys.Contains(security))//just in case, it happened { p = (double)data.Bars[security].Close; } allHistoryBars[ii, jj] = p; tmp[jj] = p; var pctChanges = tmp.PctChange(); for (int j = 0; j < pctChanges.Length; j++) allHistoryBars[ii, j] = pctChanges[j]; Log("RetNorm " + string.Join(", ", tmp)); //StdDev is a custom extension on decimal and double var secRetNorm = tmp.Average() /tmp.StdDev(); retNorm[ii] = secRetNorm; priceData.Add(tmp); ii++; } PrintArray(allHistoryBars); weights = AaOptimizer.OptimiseWeights(allHistoryBars, retNorm); Trade(data); } private void Trade(Slice data) { Log("Trade"); if (Transactions.GetOpenOrders().Count > 0) { return; } Log("Trade weights: " + string.Join(",", weights)); for (int i = 0; i < stocks.Count; i++) { SetHoldings(stocks.ElementAt(i), weights[i]); } } public override void OnEndOfDay() { allocate++; } private void PrintArray(double[,] arr) { Log("########### print matrix to optimize"); int rowLength = arr.GetLength(0); int colLength = arr.GetLength(1); Log(string.Join(",", stocks)); for (int j = 0; j < colLength; j++) { string msg = ""; for (int i = 0; i < rowLength; i++) { msg += " " + string.Format("{0} ", arr[i, j].ToString("0.000000")); } Log(msg); } Log("#########################################"); } } }
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Accord.Math; using Accord.Math.Optimization; using Accord.Statistics; namespace QuantConnect.Algorithm.CSharp { public static class AaOptimizer { public static double[] MergeVector(double [] a, double [] b) { return a.Concatenate(b); } public static double[] OptimiseWeights(double[,] allHistoryBars, double [] retNorm) { var covMatrix = allHistoryBars.Transpose().Covariance(); var maxRet = 0.9 * retNorm.Max(); Func<double[], double> retNormFunc = (x) => x.Dot(retNorm) - maxRet; // The scoring function var f = new NonlinearObjectiveFunction(covMatrix.GetLength(0), x => x.DotAndDot(covMatrix, x)); // Under the following constraints var constraints = new[] { //the sum of all weights is equal to 1 (ish) //using Enumerable sum because of Accord.Math extension redefinition //https://stackoverflow.com/questions/32380592/why-am-i-required-to-reference-system-numerics-with-this-simple-linq-expression new NonlinearConstraint(covMatrix.GetLength(0), x => -Math.Pow(Enumerable.Sum(x) - 1, 2) >= -1e-6), new NonlinearConstraint(covMatrix.GetLength(0), x => retNormFunc(x) >= 0), //the values are bounded between 0 and 1 new NonlinearConstraint(covMatrix.GetLength(0), x => x.Min() >= 0), new NonlinearConstraint(covMatrix.GetLength(0), x => -x.Max() >= -1), }; var algo = new Cobyla(f, constraints); // Optimize it //TODO handle !success bool success = algo.Minimize(); double minimum = algo.Value; double[] weights = algo.Solution; return weights; } } }
using System; using System.Collections.Generic; using System.Linq; namespace QuantConnect.Algorithm.CSharp { public static class AaComputationExtensions { public static decimal[] RollingMax(this decimal[] array, int period) { if (period == 0) return (decimal[])array.Clone(); var answer = new decimal[array.Length]; var rollingData = new Queue<decimal>(); int count = 0; foreach (var v in array) { count++; rollingData.Enqueue(v); if (count > period) { rollingData.Dequeue(); } answer[count - 1] = rollingData.Max(); } return answer; } public static double[] RollingMax(this double[] array, int period) { var answer = new double[array.Length]; var rollingData = new Queue<double>(); //for (int i = 0; i < array.Length; i++) int count = 0; foreach (var v in array) { count++; rollingData.Enqueue(v); if (count > period) { rollingData.Dequeue(); } answer[count - 1] = rollingData.Max(); } return answer; } public static double StdDev(this IEnumerable<double> values) { double ret = 0; if (values.Any()) { //Compute the Average double avg = values.Average(); //Perform the Sum of (value-avg)_2_2 double sum = values.Sum(d => Math.Pow(d - avg, 2)); //Put it all together ret = Math.Sqrt((sum) / (values.Count() - 1)); } return ret; } public static decimal StdDev(this IEnumerable<decimal> values) { double ret = 0; if (values.Any()) { //Compute the Average decimal avg = values.Average(); //Perform the Sum of (value-avg)_2_2 decimal sum2 = values.Sum(d => (decimal)Math.Pow((double)(d - avg), 2)); decimal sum = values.Sum(d => (d - avg) * (d - avg)); //Put it all together ret = Math.Sqrt((double)(sum) / (values.Count() - 1)); } return (decimal)ret; } public static decimal[] RollingStd(this IEnumerable<decimal> array, int period) { return array.ToArray().RollingStd(period); } public static decimal[] LastN(this decimal[] array, int count) { if (count > array.Length) return null; var lastN = new decimal[count]; Array.Copy(array, array.Length - count, lastN, 0, count); return lastN; } public static double[] LastN(this double[] array, int count) { if (count > array.Length) return null; var lastN = new double[count]; Array.Copy(array, array.Length - count, lastN, 0, count); return lastN; } public static decimal[] RollingStd(this decimal[] array, int period) { var answer = new decimal[array.Length]; var rollingData = new Queue<decimal>(); //for (int i = 0; i < array.Length; i++) int count = 0; foreach (var v in array) { count++; rollingData.Enqueue(v); if (count >= period) { rollingData.Dequeue(); answer[count - 1] = rollingData.StdDev(); } } return answer; } public static double[] PctChange(this double[] array, int period = 1) { var result = new double[array.Length]; var prevs = new Queue<double>(); int count = 0; foreach (var v in array) { if (count >= period) { result[count] = v / prevs.ElementAt(0) - 1; prevs.Dequeue(); } else { result[count] = 0; } prevs.Enqueue(v); count++; } return result; } } }