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