| Overall Statistics |
|
Total Trades 4777 Average Win 0.06% Average Loss -0.05% Compounding Annual Return 13.905% Drawdown 2.300% Expectancy 0.091 Net Profit 7.189% Sharpe Ratio 2.404 Probabilistic Sharpe Ratio 81.322% Loss Rate 54% Win Rate 46% Profit-Loss Ratio 1.35 Alpha -0.008 Beta 0.419 Annual Standard Deviation 0.059 Annual Variance 0.004 Information Ratio -2.719 Tracking Error 0.08 Treynor Ratio 0.34 Total Fees $0.00 Estimated Strategy Capacity $0 Lowest Capacity Asset SPY R735QTJ8XC9X |
/*
This program was developed by Quantify and is property of Mario Sarli
Usage and marketing of this program is permitted.
Quantify Developer(s): Conor Flynn
Date Created: 07/13/2021
Client: Mario Sarli
Client ID: 341994651
If you find a bug or an inconsistantcy please contact your assigned developer.
Contact: cflynn@quantify-co.com
To request a new copy of your program please contact support:
Contact: support@quantify-co.com
Note: Client ID is required for client verification upon requesting a new copy
*/
namespace QuantConnect.Algorithm.CSharp
{
public class Main : QCAlgorithm
{
// BACKTESTING PARAMETERS
// =================================================================================================================
// general settings:
// set starting cash
private int starting_cash = 100000;
// backtesting start date time:
// date setting variables
private int start_year = 2021;
private int start_month = 1;
private int start_day = 1;
// backtesting end date time:
// determines whether there is a specified end date
// if false it will go to the current date (if 'true' it will go to the specified date)
private bool enable_end_date = false;
// date setting variables
private int end_year = 2021;
private int end_month = 5;
private int end_day = 15;
// universe settings:
// data update resolution
// changes how often the data updates and algorithm looks for entry
// determines how often the function OnData runs
// list of resolutions:
// Resolution.Tick; Resolution.Second; Resolution.Minute; Resolution.Hour; Resolution.Daily
private readonly Resolution resolution = Resolution.Tick;
// consolidated resolution count
// refers the number of datapoints for the resolution to consolidate
// example: if resolution = Resolution.Second and resolution_consolidation = 5
// the program will run on 5 second resolution
private readonly int resolution_consolidation = 20;
// stock list
// list of stocks you want in the universe
// used in manual selection of universe
// format:
// new StockBase(TICKER, PORTFOLIO_ALLOC)
// TICKER: ticker identifier
// PORTFOLIO_ALLOC: percentage of portfolio to allocate (note in decimal form: 0.50m -> 50%)
private readonly StockBase[] manual_universe = new StockBase[]{
new StockBase("SPY", 0.50m),
//new StockBase("AAPL", 0.50m)
};
// drawdown limitation
// determines the max drawdown per ticker per day
// once reached the ticker will be liquidated and cannot be traded for the remainder of the day
// set as -1 to disable
// (note in decimal form: 0.03m -> 03%)
private static readonly decimal drawdown_universe = 0.03m;
// position settings:
// percentage of order to be set as a market order
// in array form, so each element is a different position
// (note in decimal form: 0.40m -> 40%)
private readonly decimal[] mo_percent_position = new decimal[] {
0.40m
};
// percentage of order to be set as limit orders
// in array form, so each element is a different position
// (note in decimal form: 0.20m -> 20%)
private readonly decimal[] lo_percent_position = new decimal[] {
0.60m
};
// indicator settings:
// length of the SMA for the base line (line 2, init line 4, 5)
private readonly int length_base = 50;
// base calculation type: (line 4, 5)
// 1: OHLC4
// 2: (H+L) / 2
private static readonly int calculation_base = 1;
// length of the SMA for the space line: (line 2, init line 21)
private readonly int length_space = 50;
// space calculation type: (line 21)
// 1: (H-L)
// 2: abs(C-O)
private static readonly int calculation_space = 1;
// space factor: (line 24)
// determines the factor at which the calculated space is multipled by
private static readonly decimal factor_space = 1.0m;
// number of spacers from the base line (default 10)
private readonly int count_space = 10;
// =================================================================================================================
// creates new universe variable setting
private List<StockData> universe = new List<StockData>();
// security changes variable
private SecurityChanges securityChanges = SecurityChanges.None;
public override void Initialize()
{
// set start date
SetStartDate(start_year, start_month, start_day);
// set end date
if(enable_end_date)
SetEndDate(end_year, end_month, end_day);
// set starting cash
SetCash(starting_cash);
// add all equities into the universe
foreach(StockBase sb in manual_universe) {
AddEquity(sb.ticker, resolution);
// create StockData variable for security
StockData sd = new StockData();
sd.algorithm = this;
sd.ticker = sb.ticker;
sd.alloc = sb.alloc;
sd.sma_base = new SimpleMovingAverage(length_base);
sd.sma_space = new SimpleMovingAverage(length_space);
sd.pos_space = new decimal[count_space];
sd.neg_space = new decimal[count_space];
// add stockdata to universe
universe.Add(sd);
// set fee model to 0 since no fees
Securities[sd.ticker].FeeModel = new ConstantFeeModel(0);
// define consolidator
// tick resolution
if(resolution == Resolution.Tick) {
var consolidator = new TickConsolidator(TimeSpan.FromTicks(resolution_consolidation));
consolidator.DataConsolidated += OnDataConsolidated;
SubscriptionManager.AddConsolidator(sd.ticker, consolidator);
}
// second resolution
else if(resolution == Resolution.Second) {
var consolidator = new TradeBarConsolidator(TimeSpan.FromSeconds(resolution_consolidation));
consolidator.DataConsolidated += OnDataConsolidated;
SubscriptionManager.AddConsolidator(sd.ticker, consolidator);
}
// minute resolution
else if(resolution == Resolution.Minute) {
var consolidator = new TradeBarConsolidator(TimeSpan.FromMinutes(resolution_consolidation));
consolidator.DataConsolidated += OnDataConsolidated;
SubscriptionManager.AddConsolidator(sd.ticker, consolidator);
}
// hour resolution
else if(resolution == Resolution.Hour) {
var consolidator = new TradeBarConsolidator(TimeSpan.FromHours(resolution_consolidation));
consolidator.DataConsolidated += OnDataConsolidated;
SubscriptionManager.AddConsolidator(sd.ticker, consolidator);
}
// daily resolution
else if(resolution == Resolution.Daily) {
var consolidator = new TradeBarConsolidator(TimeSpan.FromDays(resolution_consolidation));
consolidator.DataConsolidated += OnDataConsolidated;
SubscriptionManager.AddConsolidator(sd.ticker, consolidator);
} else {
Error("INVALID RESOLUTION TYPE");
}
}
}
// method for entering positions
// direction: true = long, false = short
public void EnterPosition(StockData sd, bool direction) {
// if currently in position verify direction with current position
// if long and open long or short and open short, return
if(sd.direction == 1 && direction || sd.direction == -1 && !direction)
return;
// if position is open, liquidate ticker
if(Portfolio[sd.ticker].Invested)
Liquidate(sd.ticker);
// determine entry contracts based on position sizing
// enter market orders
for(int i = 0; i < mo_percent_position.Count(); i++) {
// determine contract count
int contracts = (int)(Portfolio.MarginRemaining * sd.alloc * mo_percent_position[i] / Securities[sd.ticker].Price);
// invert if short
if(!direction)
contracts *= -1;
// place market order
MarketOrder(sd.ticker, contracts);
}
// enter limit orders
for(int i = 0; i < lo_percent_position.Count(); i++) {
// determine contract count
int contracts = (int)(Portfolio.MarginRemaining * sd.alloc * lo_percent_position[i] / Securities[sd.ticker].Price);
// invert if short
if(!direction)
contracts *= -1;
// place limit order
LimitOrder(sd.ticker, contracts, Securities[sd.ticker].Price);
}
// update stock data direction
sd.direction = direction ? 1 : -1;
// log entry
//if(direction)
// Debug($"LONG: {sd.prev_index} -> {sd.index} <{Time}>");
//else
// Debug($"SHORT: {sd.prev_index} -> {sd.index} <{Time}>");
}
// 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) {
// loops through each stock in universe
foreach(StockData sd in universe) {
// if data is not ready then skip
if(!sd.IsReady)
continue;
//Debug(sd.IsLong() + " " + sd.IsShort() + $" {sd.direction} {sd.prev_index} -> {sd.index} <{Time}>");
// update the index position
sd.UpdateIndex(Securities[sd.ticker]);
// if short check for long
if(sd.direction == -1 && sd.IsLong()) {
EnterPosition(sd, true);
}
// if long check for short
else if(sd.direction == 1 && sd.IsShort()) {
EnterPosition(sd, false);
}
// if not in position wait for either
else if(sd.direction == 0) {
if(sd.IsLong()) {
EnterPosition(sd, true);
} else if(sd.IsShort()) {
EnterPosition(sd, false);
}
}
}
}
// OnDataConsolidated event runs when the data is properly consolidated at the bar level
// will execute based on user parameters
public void OnDataConsolidated(object sender, TradeBar bar) {
// loops through each stock in universe
foreach(StockData sd in universe) {
// update values
sd.update();
// if data is not ready then skip
if(!sd.IsReady)
continue;
//Debug(sd.IsLong() + " " + sd.IsShort() + $" {sd.direction} {sd.prev_index} -> {sd.index} <{Time}>");
// update the index position
//sd.UpdateIndex(Securities[sd.ticker]);
/*
// if short check for long
if(sd.direction == -1 && sd.IsLong()) {
EnterPosition(sd, true);
}
// if long check for short
else if(sd.direction == 1 && sd.IsShort()) {
EnterPosition(sd, false);
}
// if not in position wait for either
else if(sd.direction == 0) {
if(sd.IsLong()) {
EnterPosition(sd, true);
} else if(sd.IsShort()) {
EnterPosition(sd, false);
}
}*/
}
}
// contains default information of ticker
public class StockBase {
// ticker name
public string ticker;
// portfolio cash allocation
public decimal alloc;
// constructor
public StockBase(string ticker, decimal alloc) {
this.ticker = ticker;
this.alloc = alloc;
}
}
// default class containing all ticker information
public class StockData {
// QCAlgorithm variable
public QCAlgorithm algorithm;
// contains base information of ticker
public StockBase sb;
// stock ticker
public string ticker = "";
// position allocation
public decimal alloc;
// simple moving average baseline (line 7)
public SimpleMovingAverage sma_base;
// spacing period (line 21)
public SimpleMovingAverage sma_space;
// stores all line space calculations
public decimal[] pos_space;
public decimal[] neg_space;
// stores the prior and current index
public int index = -10000;
public int prev_index = -10000;
// variable determining long, short, or null (1, -1, 0):
public int direction = 0;
// determines if data is ready
public bool IsReady => sma_base.IsReady && sma_space.IsReady && index != -10000 && prev_index != -10000;
// updates spacing calculations
public void update() {
// define security object
Security sym = algorithm.Securities[ticker];
// Update SMAs
UpdateSMA(sym);
// Update Spacing
UpdateSpacing(sym);
// Update Index
UpdateIndex(sym);
}
// updates the SMAs used for calculation
private void UpdateSMA(Security sym) {
// update the base sma
decimal price;
if(Main.calculation_base == 1) {
// OHLC4
price = (sym.Open + sym.High + sym.Low + sym.Close) / 4;
} else if(Main.calculation_base == 2) {
// (H+L) / 2
price = (sym.High + sym.Low) / 2;
} else {
// invalid value
algorithm.Error("INVALID <calculation_base> VALUE");
price = -1.0m;
}
// push to object
sma_base.Update(algorithm.Time, price);
// update the space sma
if(Main.calculation_space == 1) {
// (H-L)
price = (sym.High - sym.Low);
} else if(Main.calculation_space == 2) {
// abs(C-O)
price = Math.Abs(sym.Close - sym.Open);
} else {
// invalid value
algorithm.Error("INVALID <calculation_space> VALUE");
price = -1.0m;
}
// push to object
sma_space.Update(algorithm.Time, price);
}
// updates the indicies of the spacing arrays
private void UpdateSpacing(Security sym) {
// determine if above or below base
if(sym.Price > sma_base) {
// set 0 as base
pos_space[0] = sma_space;
// update spacing and determine where the price is at
for(int i = 1; i < pos_space.Count(); i++)
pos_space[i] = sma_base + (sma_space * i * Main.factor_space);
} else if(sym.Price < sma_base) {
// set 0 as base
neg_space[0] = sma_space;
// update spacing and determine where the price is at
for(int i = 1; i < neg_space.Count(); i++)
neg_space[i] = sma_base - (sma_space * i * Main.factor_space);
} else {
// price == sma_base
prev_index = index;
index = 0;
}
}
public void UpdateIndex(Security sym) {
// determine if above or below base
if(sym.Price > sma_base) {
// update spacing and determine where the price is at
for(int i = 1; i < pos_space.Count(); i++) {
// if between prior index and current then set accoring
if(sym.Price > pos_space[i - 1] && sym.Price < pos_space[i]) {
prev_index = index;
index = i;
}
}
} else if(sym.Price < sma_base) {
// update spacing and determine where the price is at
for(int i = 1; i < neg_space.Count(); i++) {
// if between prior index and current then set accoring
if(sym.Price < neg_space[i - 1] && sym.Price > neg_space[i]) {
prev_index = index;
index = i * -1;
}
}
} else {
// price == sma_base
prev_index = index;
index = 0;
}
// make sure data is ready
//if(IsReady)
//algorithm.Debug($"{prev_index} -> {index} <{algorithm.Time}>");
}
// determines if the algorithm crossed long
public bool IsLong() {
return IsReady && index > prev_index;
}
// determine if the algorithm crossed short
public bool IsShort() {
return IsReady && index < prev_index;
}
}
}
}