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