| Overall Statistics |
|
Total Trades 1435 Average Win 0.29% Average Loss -0.27% Compounding Annual Return 5.968% Drawdown 35.000% Expectancy 0.060 Net Profit 58.596% Sharpe Ratio 0.416 Loss Rate 49% Win Rate 51% Profit-Loss Ratio 1.07 Alpha 0.047 Beta 0.132 Annual Standard Deviation 0.142 Annual Variance 0.02 Information Ratio -0.135 Tracking Error 0.221 Treynor Ratio 0.447 Total Fees $1489.20 |
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
namespace QuantConnect.Algorithm.CSharp
{
public class YEARMOMENTUMMODEL : QCAlgorithm
{
//Set this number anywhere betweet 0.001 and 0.01 to decide how strict the rebalancing difference has to be.
double rebalancingStrictness = 0.005;
//This is how far a position can go from it default allocation so 0.5 would mean that if the defauly is 2%, the position would be allocated anywhere between 1% and 3%..
double allocationSmoothingStrictness = 0.7;
//Can leverage your risk
double leverage = 1.5;
//List<string> _symbols = new List<string>() { "BAL", "BNO", "CORN", "COW", "DIA", "EWG", "EWH", "EWJ", "EWT", "FXA", "FXB", "FXC", "FXE", "FXF", "FXI", "FXY", "GLD", "IWD", "JJC", "JJG", "JO", "PALL", "PPLT", "QQQQ", "SGG", "SLV", "SOYB", "SPY", "UGA", "UHN", "UNG", "USO", "UUP", "VGK", "VXX", "WEAT" };
//FXE causes QC to break down.
//List of all tickers
List<string> _symbols = new List<string>() { "BAL", "BNO", "CORN", "COW", "DIA", "EWG", "EWH", "EWJ", "EWT", "FXA", "FXB", "FXC", "FXF", "FXI", "FXY", "GLD", "IWD", "JJC", "JJG", "JO", "PALL", "PPLT", "QQQQ", "SLV", "SOYB", "SPY", "UGA", "UHN", "UNG", "USO", "UUP", "VGK", "VXX", "WEAT" };
//Dictionary of stocks and their stored values.
Dictionary<string, StoredStock> stockData = new Dictionary<string, StoredStock>() { };
//Used to save the current date to ensure algorithm does not run twice/day.
DateTime now;
public bool firstRun = true;
//Ensures algorithm only begins after this data. It should be the same as the date in SetStartDate
DateTime start = new DateTime(2010, 1, 1);
//Initialize the data and resolution you require for your strategy:
public override void Initialize()
{
//Initialize
//Set start and end date (YYYY,MM,DD)
SetStartDate(2010, 1, 1);
SetEndDate(2016, 7, 5);
//Set the amount of cash to start with
SetCash(25000);
//Warm up period should be rather large to store old stock prices.
SetWarmup(365);
//Loads data list on first run
InitializeStockData();
//Updates average true range
UpdateATR();
}
/// <summary>
/// Main function. Executed every day/minute and runs the trading algorith.
/// </summary>
/// <param name="data"></param>
public void OnData(TradeBars data)
{
//Print header for CSV file in Log
FirstRunHeaderLogger();
//Only run code once/day by checking if this day already executed
if (!stockData["SPY"].ATR.IsReady || now.Date == Time.Date) return;
//Update prices and save them
UpdateValues(data);
//Make sure we are in test period
if (Time.Date > start && Time.DayOfWeek == DayOfWeek.Friday)
{
//Sell all stocks who currently trade at a price lower than 250 days ago
//SellTradingFunction(data);
//Buy all stocks whos current price is above 250 days ago (Based on average true range allocation)
SellBuyTradingFuction(data);
}
now = Time.Date;
}
/// <summary>
/// Prints header to log file
/// </summary>
private void FirstRunHeaderLogger()
{
if (firstRun == true)
{
var header = string.Format
("{0},{1},{2},{3},{4},{5}",
"Date",
"Stock",
"New Allocation",
"ATR",
"Old Price",
"Current Price"
);
Log(header);
firstRun = false;
}
}
/// <summary>
/// Buys all stocks that currently trade at a higher price than 250 days back. Uses an atr based allocation.
/// Higher ATR leads to a lower allcoation.
/// Average allocation = 1/35
/// Highest and lowest is the half or 50% more than the average allocation.
/// IMPORTANT: Need to add trade logic to prevent small transaction size rebalancing.
/// </summary>
/// <param name="data"></param>
private void SellBuyTradingFuction(TradeBars data)
{
var universeToTrade = stockData.Where(f => f.Value.OldValues.Count > 10);
List<decimal> ATRSCORES = universeToTrade.Select(x => x.Value.ATR.Current.Value).ToList();
if (universeToTrade.Count() != 0)
{
var allocation = ((decimal)1 / _symbols.Count())*(decimal)leverage;
foreach (var stock in universeToTrade)
{
var smoothedAdjuster = 0.0m;
try
{
var max = allocation * (decimal)allocationSmoothingStrictness;
smoothedAdjuster = (max-(-max)) / ((ATRSCORES.Max() - ATRSCORES.Min())) * (stock.Value.ATR.Current.Value - ATRSCORES.Max()) + (max);
}
catch (Exception err99)
{
smoothedAdjuster = allocation / 2;
}
var percentage = allocation - smoothedAdjuster;
if (stock.Value.CompareValue.price > data[stock.Key].Close)
{
percentage = percentage * -1;
}
if (stock.Value.lastallocation == 0 || Math.Abs(stock.Value.lastallocation - (double)percentage) > rebalancingStrictness)
{
stock.Value.lastallocation = (double)percentage;
SetHoldings(stock.Key, percentage);
LineLogger(data, stock, percentage);
}
}
}
}
/// <summary>
/// Prints a one line to the LOG everytime a trade is executed.
/// </summary>
/// <param name="data"></param>
/// <param name="stock"></param>
/// <param name="smoothedAdjuster"></param>
private void LineLogger(TradeBars data, KeyValuePair<string, StoredStock> stock, decimal smoothedAdjuster)
{
var line = string.Format
("{0},{1},{2},{3},{4},{5}",
Time.Date,
stock.Key,
smoothedAdjuster * 100 + "%",
stock.Value.ATR.Current.Value,
stock.Value.CompareValue.price,
data[stock.Key].Close
);
Log(line);
}
/// <summary>
/// Updates the 14 day exponential average true range
/// </summary>
private void UpdateATR()
{
foreach (var stock in stockData)
{
var todayATR = ATR(stock.Key, 14, MovingAverageType.Exponential, Resolution.Daily);
stock.Value.ATR = todayATR;
}
}
/// <summary>
/// Initializes all the data in the stockData dictionary
/// </summary>
private void InitializeStockData()
{
if (stockData.Count() == 0)
{
foreach (var stock in _symbols)
{
AddSecurity(SecurityType.Equity, stock, Resolution.Daily,true,(decimal)leverage + 1,false);
var tobeadded = new StoredStock();
var ticker = stock;
stockData.Add(ticker, tobeadded);
}
}
}
/// <summary>
/// Updates the prices into the dictionary
/// </summary>
/// <param name="dataset"></param>
public void UpdateValues(TradeBars dataset)
{
var date = Time.Date;
foreach (var stock in stockData.ToList())
{
if (dataset.ContainsKey(stock.Key))
{
try
{
OldValue data = new OldValue();
data.date = date;
data.price = Securities[stock.Key].Price;
stock.Value.OldValues.Add(data);
stock.Value.CompareValue.price = stock.Value.OldValues.OrderBy(x => x.date).FirstOrDefault().price;
stock.Value.CompareValue.date = stock.Value.OldValues.OrderBy(x => x.date).FirstOrDefault().date;
if (stock.Value.OldValues.Count() > 250)
{
stock.Value.OldValues.Remove(stock.Value.OldValues.OrderBy(x => x.date).FirstOrDefault());
}
}
catch (Exception err)
{
//What should happen here?
};
}
}
}
/// <summary>
/// Class saving a sample of an old price
/// </summary>
class OldValue
{
public DateTime date;
public decimal price;
}
/// <summary>
/// The value of the stockData dictionary for each stock
/// </summary>
class StoredStock
{
public double lastallocation;
public AverageTrueRange ATR;
public OldValue CompareValue = new OldValue() { };
public List<OldValue> OldValues = new List<OldValue> { };
}
}
}