Overall Statistics |
Total Trades 38 Average Win 0.79% Average Loss -0.28% Compounding Annual Return 5.652% Drawdown 13.400% Expectancy 2.353 Net Profit 0.888% Sharpe Ratio 0.344 Probabilistic Sharpe Ratio 39.361% Loss Rate 12% Win Rate 88% Profit-Loss Ratio 2.80 Alpha 0.558 Beta 1.13 Annual Standard Deviation 0.323 Annual Variance 0.105 Information Ratio 2.117 Tracking Error 0.239 Treynor Ratio 0.098 Total Fees $38.00 Estimated Strategy Capacity $1800000.00 Lowest Capacity Asset FTNT UHQJ0EDGU6HX |
/* This program was developed by Quantify and is property of Bilal Sharif Usage and marketing of this program is permitted. Quantify Developer(s): Conor Flynn Date Created: 06/11/2021 Client: Bilal Sharif Client ID: 532920135 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 b_sharif1 : QCAlgorithm { // BACKTESTING PARAMETERS // ================================================================================================================= // general settings: // set starting cash private int starting_cash = 1000; // backtesting start date time: // date setting variables private int start_year = 2020; 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 = true; // date setting variables private int end_year = 2020; private int end_month = 3; private int end_day = 1; // enable logging // shows logs of what is going on in the program // RECOMMENDED: comment out line 174 when using second resolution private readonly bool enable_logging = false; // 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.Second; // stock list // list of stocks you want in the universe // used in manual selection of universe // set selection_type = false for activation private readonly String[] manual_universe = new String[]{"TTD","PAYC","EPAM","NOW","FTNT"}; // position settings: // percent of portfolio to enter a position with // note this value is 1 / totalNumberOfStocks private decimal portfolio_alloc_position = 1.0m; // take profit percentage // value to take profit at // note this is in decimal form: // i.e. 0.02m => 2% private decimal take_profit_position = 0.02m; // indicator settings: // period to observe ask size over // note it is in the resolution of the universe (seconds) // i.e. for 30 minutes it will be 30 * 60 = 1800 private readonly int ask_size_period = 1800; // ask size conditional value // value for the ask size to go over to count towards the sum over the period // i.e. Security.AskSize > ask_size_conditional ? 1 : 0 private readonly int ask_size_conditional = 500; // ask size count value // number of times the ask size goes over the conditional value over the period private readonly int ask_size_count = 800; // period to observe ask price ROC over // note it is in the resolution of the universe (seconds) // i.e. for 30 minutes it will be 30 * 60 = 1800 private readonly int ask_price_period = 1800; // ================================================================================================================= // best stock values: // best ask size // default to make conditional value private int best_ask_size; // best ask price roc // default to decimal min value private decimal best_ask_price; // PDT Variables: // PDT limit private readonly int pdt_limit = 3; // open position counter private int open_counter = 0; // PDT counter private int pdt_counter = 0; // creates new universe variable setting private List<StockData> universe = new List<StockData>(); public Dictionary<Symbol, StockData> universeDict = new Dictionary<Symbol, StockData>(); // security changes variable private SecurityChanges securityChanges = SecurityChanges.None; // Number of business days until pdt_counter is reset private int business_days = 5; // True when pdt has been hit, false when pdt has not been hit public bool pdt_hit = false; public bool count_down = false; public DateTime pdt_time_hit = default(DateTime); public int pdt_c = 0; public int LastDay = -1; public DateTime pdt_start_date; public int day_count = 0; 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); foreach(string s in manual_universe) AddEquity(s, resolution); // init best values best_ask_size = ask_size_count; best_ask_price = Decimal.MinValue; pdt_start_date = new DateTime(start_year, start_month, start_day); // disable margins Portfolio.MarginCallModel = MarginCallModel.Null; } public void TrackTime() { if (pdt_counter == pdt_limit && pdt_hit == false) { pdt_hit = true; count_down = true; pdt_time_hit = Time; } // var delta = Time - pdt_start_date; // Debug(day_count); if (day_count >= 5) { day_count = 0; pdt_counter = 0; pdt_hit = false; pdt_start_date = Time; } // if (count_down == true) { // var delta = Time - pdt_time_hit; // if (delta.Days == business_days) { // pdt_counter = 0; // pdt_hit = false; // count_down = false; // pdt_time_hit = default(DateTime); // return; // } // } } public void TrackPrevious(Symbol symbol) { if (!Portfolio[symbol].Invested) { universeDict[symbol].previous_position = "None"; } else if (Portfolio[symbol].IsLong) { universeDict[symbol].previous_position = "Long"; } else if (Portfolio[symbol].IsShort) { universeDict[symbol].previous_position = "Short"; } } public bool OppositeOrder(Symbol symbol) { var current_position = ""; if(universeDict[symbol].previous_position == "Long" && (!Portfolio[symbol].Invested || Portfolio[symbol].IsShort)) { current_position = universeDict[symbol].previous_position; TrackPrevious(symbol); Debug("Symbol:" + symbol.Value.ToString() + " at " + Time.ToString() + ". Was: " + current_position + ". Is: " + universeDict[symbol].previous_position); return true; } if(universeDict[symbol].previous_position == "Short" && (!Portfolio[symbol].Invested || Portfolio[symbol].IsLong)) { current_position = universeDict[symbol].previous_position; TrackPrevious(symbol); Debug("Symbol:" + symbol.Value.ToString() + " at " + Time.ToString() + ". Was: " + current_position + ". Is: " + universeDict[symbol].previous_position); return true; } TrackPrevious(symbol); return false; } public void LogOrder(Symbol symbol) { if(universeDict[symbol].last_trade_date.ToString("yyyy-MM-dd") == Time.ToString("yyyy-MM-dd") && OppositeOrder(symbol)) { pdt_counter += 1; } universeDict[symbol].last_trade_date = Time; } public void EnterPosition() { // PDT limiter // if(open_counter + pdt_counter >= pdt_limit) // return; if(pdt_hit == true) { return; } // loop through universe and find stocks that match high conditional List<StockData> high_conditional = new List<StockData>(); foreach(StockData sd in universe) { /* if(sd.IsHigh(Securities[sd.ticker].Price)) { high_conditional.Add(sd); }*/ high_conditional.Add(sd); } // sort tickers by ROC IEnumerable<StockData> stocks = (from sd in high_conditional where Securities[sd.ticker].Price < Portfolio.Cash orderby sd.price descending select sd); // if list is empty return if(stocks.Count() == 0) { return; } // get best stock StockData first = stocks.First(); // if data is not ready return if(!first.size.IsReady || !first.price.IsReady) return; // check AskSize conditional and log that conditional is not met if(first.size > ask_size_count) { if(enable_logging) { //Log("POSITION::AskSize conditional not met for " + first.ticker + "(" + first.size + " > " + ask_size_count + ") at " + Time); } //return; } // check to make sure that values are better than current first /*if(best_ask_size < first.size || best_ask_price > first.price) { if(enable_logging) { if(best_ask_size < first.size) { Log("======== Conditional Not Met ========"); Log("Reason: AskSize not better than current best"); Log("Ticker: " + first.ticker); Log("Current Best Ask Size: " + best_ask_size); Log("Selected Ask Size: " + first.size); Log("Current Best Ask Price: " + best_ask_price); Log("Selected Ask Price: " + first.price); Log("====================================="); } else { Log("======== Conditional Not Met ========"); Log("Reason: AskPrice not better than current best"); Log("Ticker: " + first.ticker); Log("Current Best Ask Size: " + best_ask_size); Log("Selected Ask Size: " + first.size); Log("Current Best Ask Price: " + best_ask_price); Log("Selected Ask Price: " + first.price); Log("====================================="); } } return; }*/ // check that ask price is greater than the stock price if(Securities[first.ticker].AskPrice <= Securities[first.ticker].Price) return; // determine take profit decimal profit_price = Securities[first.ticker].Price * (1 + take_profit_position); // determine number of contracts int quantity = (int)(Portfolio.Cash / Securities[first.ticker].Price); if(quantity < 1) return; // save portfolio cash quantity for second position calculation decimal cash = Portfolio.Cash; // place orders // entry order first.main_ot = MarketOrder(first.ticker, quantity); // take profit first.tp_ot = LimitOrder(first.ticker, quantity * -1, profit_price); // enter position if(enable_logging) { Log("========= Entering Position ========="); Log("Ticker: " + first.ticker); Log("Contracts Purchased: "); Log("Take Profit Value: "); Log("Time: " + Time); Log("=====================================" + System.Environment.NewLine); } // update best values best_ask_size = (int)first.size; best_ask_price = first.price; // ========================================================== // Second position // if first position is not open, then do not open a secondary position stocks = (from sd in stocks where Securities[sd.ticker].Price < Portfolio.Cash where !Equals(sd.ticker, first.ticker) select sd); // find secondary stock to enter if(stocks.Count() == 0) { Log("WARNING::No secondary position to enter"); return; } StockData second = stocks.ElementAt(0); // if data is not ready return if(!second.size.IsReady || !second.price.IsReady) return; // check AskSize conditional and log that conditional is not met if(second.size > ask_size_count) { if(enable_logging) { //Log("POSITION::AskSize conditional not met for " + first.ticker + "(" + first.size + " > " + ask_size_count + ") at " + Time); } return; } // check that ask price is greater than the stock price if(Securities[second.ticker].AskPrice <= Securities[second.ticker].Price) return; // PDT limiter if(open_counter + pdt_counter >= pdt_limit) return; // determine take profit profit_price = Securities[second.ticker].Price * (1 + take_profit_position); // determine number of contracts quantity = (int)((Portfolio.Cash - (quantity * Securities[first.ticker].Price)) / Securities[second.ticker].Price); if(quantity < 1) return; // place orders // entry order second.main_ot = MarketOrder(second.ticker, quantity); // take profit second.tp_ot = LimitOrder(second.ticker, quantity * -1, profit_price); // enter position if(enable_logging) { Log("========= Entering Position ========="); Log("Ticker: " + second.ticker); Log("Contracts Purchased: "); Log("Take Profit Value: "); Log("Time: " + Time); Log("=====================================" + System.Environment.NewLine); } } // 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) { if (LastDay != Time.Day) { Debug(pdt_counter); Debug(pdt_hit.ToString()); LastDay = Time.Day; day_count+=1; } // Tracks PDT Time TrackTime(); // loops through each stock in universe foreach(StockData sd in universe) { // should a position fire immediately after and not properly trigger the update function if(open_counter > 0 && !Portfolio.Invested){ open_counter = 0; } // update data sd.size.Update(Time, Securities[sd.ticker].AskSize > ask_size_conditional ? 1 : 0); sd.price.Update(Time, Securities[sd.ticker].AskPrice); // make sure data is properly loaded into variables before using if(!sd.size.IsReady || !sd.price.IsReady) continue; } // if not invested, find the best ticker to invest in and allocate entire account to it EnterPosition(); } public override void OnOrderEvent(OrderEvent oe) { // get order var order = Transactions.GetOrderById(oe.OrderId); // get symbol of order var symbol = order.Symbol; // get sd associated with order LogOrder(symbol); var list = (from s in universe where s.ticker == symbol select s).Take(1); if(list.Count() < 1) return; StockData sd = list.First(); update(sd); } public void update(StockData sd) { if(sd.tp_ot != null && sd.main_ot != null) { // if filled // if(sd.tp_ot.Status == OrderStatus.Filled) { // open_counter--; // // check if same day // if(sd.time.Date == Time.Date) { // Debug("PDT"); // pdt_counter++; // // set time to current time so that PDT can be removed for stock after 5 days // sd.time = Time.Date; // sd.pdt_active = true; // } // } // set positions to null sd.tp_ot = null; sd.main_ot = null; } // if more than 5 days have passed since the PDT position, remove it from the counter // if(sd.pdt_active && (Time - sd.time).TotalDays >= 5) { // pdt_counter--; // sd.pdt_active = false; // } // if(sd.tp_ot == null && sd.main_ot == null) { // } } // OnSecuritiesChanged runs when the universe updates current securities public override void OnSecuritiesChanged(SecurityChanges changes) { securityChanges = changes; // remove stocks from list that get removed from universe foreach (var security in securityChanges.RemovedSecurities) { List<StockData> stockDatas = universe.Where(x=>x.ticker == security.Symbol).ToList(); if (stockDatas.Count >= 1) { // check to see if position is open and if so close position if(Portfolio[stockDatas.First().ticker].Invested) { // closes position Liquidate(stockDatas.First().ticker); } // removes stock from list if it is removed from the universe if(enable_logging) Log("UNIVERSE::Removed ticker from universe: " + stockDatas.First().ticker + " at " + Time); universe.Remove(stockDatas.First()); universeDict.Remove(stockDatas.First().ticker); } } // add new securities to universe list foreach(var security in securityChanges.AddedSecurities) { // create StockData variable for security StockData sd = new StockData(); // initalize all indicators sd.algorithm = this; sd.ticker = security.Symbol; sd.size = new Sum(sd.ticker, ask_size_period); sd.price = new RateOfChange(sd.ticker, ask_price_period); sd.ath = sd.GetHigh(); // add stockdata to universe universeDict[security.Symbol] = sd; universe.Add(sd); if(enable_logging) Log("UNIVERSE::Added ticker to universe: " + sd.ticker + " at " + Time); } } // default class containing all ticker information public class StockData { // QCAlgorithm variable public QCAlgorithm algorithm; // stock ticker public string ticker = ""; // sum of times ticker has had ask size go over n over a period of n public Sum size; // rate of change of the ask price over a period of n public RateOfChange price; // all time high value public decimal ath = 0.0m; // entry ticket public OrderTicket main_ot; // take profit ticket public OrderTicket tp_ot; // entry time public DateTime time = new DateTime(2000, 01, 01); // if pdt counter is active public bool pdt_active = false; // Last trade date public DateTime last_trade_date = default(DateTime); // Tracks previous position public String previous_position = "None"; public bool IsHigh(decimal price) { if(price > ath) { ath = price; return true; } return false; } // returns the all time high for a ticker over given period public decimal GetHigh() { // int years int years = 7; // int days int days = 253; var history = algorithm.History(ticker, years * days, Resolution.Daily); if(history.Count() == 0) { algorithm.Log("WARNING::No bars detected in historical data retrieval"); return Decimal.MinValue; } var high = history.First(); foreach(var bar in history) { if(bar.High > high.High) high = bar; } algorithm.Log($"High detected for {ticker} at price {high.High} on date {high.Time}"); return high.High; } } } }