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