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