| Overall Statistics |
|
Total Trades 789 Average Win 1.24% Average Loss -1.73% Compounding Annual Return 111.432% Drawdown 11.000% Expectancy 0.214 Net Profit 309.293% Sharpe Ratio 3.166 Probabilistic Sharpe Ratio 98.687% Loss Rate 29% Win Rate 71% Profit-Loss Ratio 0.72 Alpha 0.721 Beta 0.02 Annual Standard Deviation 0.228 Annual Variance 0.052 Information Ratio 2.44 Tracking Error 0.278 Treynor Ratio 36.164 Total Fees $2079.69 Estimated Strategy Capacity $1600000.00 Lowest Capacity Asset BLVD VPIXQ30PC2G5 |
#region imports
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Drawing;
using QuantConnect;
using QuantConnect.Algorithm.Framework;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Risk;
using QuantConnect.Parameters;
using QuantConnect.Benchmarks;
using QuantConnect.Brokerages;
using QuantConnect.Util;
using QuantConnect.Interfaces;
using QuantConnect.Algorithm;
using QuantConnect.Indicators;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Custom;
using QuantConnect.DataSource;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Notifications;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using QuantConnect.Securities.Equity;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.Option;
using QuantConnect.Securities.Forex;
using QuantConnect.Securities.Crypto;
using QuantConnect.Securities.Interfaces;
using QuantConnect.Storage;
using QuantConnect.Data.Custom.AlphaStreams;
using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion
namespace QuantConnect.Algorithm.CSharp
{
/// Types of Default Covers
public enum COVER_TYPE
{
COVER_AT_NEXT_OPEN,
COVER_AT_CLOSE
}
/// Used For Grouping Indicator/Price Exit Types For Cleaner Adjustment/Configuration
/// Doing this to avoid commenting code out becoming overwhelming
public enum EXIT_TYPE
{
HOURLY_EMA_EXIT,
VWAP_PERCENTAGE_EXTENSIONS,
VWAP_ATR_EXTENSIONS,
PERCENT_EXTENSION_FROM_OPEN,
RELATIVE_ATR_EXTENSION_FROM_OPEN,
}
public enum ENTRY_TYPE
{
IMMEDIATE_ENTRY,
SPECIALIZED_OPEN_ENTRY,
}
/// Ways in Which We Dynamically Adjust Our Stop
/// Doing this to avoid commenting code out becoming overwhelming
public enum DYNAMIC_TIMING_ADJUSTMENT
{
PRICE_GREATER_THAN_OPEN, /// Only Adjust at Some Time Point When Price Above Open(BEST INITIAL RESULTS WHEN TESTING SINGLE PARAMETER)
ALWAYS_ADJUST, /// Always Adjust at Some Time Point
NEVER /// Never Adjust
}
public class GapUpShort : QCAlgorithm
{
/** REFERENCE LINKS **/
// https://www.quantconnect.com/forum/discussion/4010/pre-market-scanning/p1
// https://www.quantconnect.com/docs/v2/writing-algorithms/reality-modeling/trade-fills/key-concepts
// https://www.quantconnect.com/docs/v2/writing-algorithms/reality-modeling/trade-fills/supported-models
// https://www.quantconnect.com/docs/v2/writing-algorithms/reality-modeling/slippage/key-concepts
// https://www.lean.io/docs/v2/lean-engine/class-reference/FillModel_8cs_source.html
// https://github.com/QuantConnect/Lean/blob/master/Algorithm.CSharp/CustomModelsAlgorithm.cs
// https://www.quantconnect.com/forum/discussion/4797/stopmarketorder-fill-price/p1
/** CONSTANTS **/
private const string BENCHMARK = "SPY";
public const string BUY_TAG = "BUY";
public const string SELL_TAG = "SELL";
private const decimal MINIMUM_GAP_UP_PERCENTAGE = 29;
private const decimal MAXIMUM_MARKET_CAP = 1_000_000_000;
private const decimal MINIMUM_PREMARKET_VOLUME = 500_000;
private const decimal UNIVERSE_ENTRY_CAP = MAXIMUM_MARKET_CAP * 2;
private const decimal MINIMUM_OPEN_PRICE = 2m;
private const decimal PERCENT_EXPOSURE_PER_POSITION = 6;
private const decimal STOP_LOSS_PERCENT = 50;
private const decimal MINIMUM_PREMARKET_FADE_PERCENT = 15;
const bool ENABLE_TESTING = true;
/** DATA TRACKERS **/
/// Indicator Data Tracking
List<Symbol> _symbolTracker = new List<Symbol>();
Dictionary<Symbol, SymbolData> _symbolDict = new Dictionary<Symbol, SymbolData>();
Dictionary<Symbol, TradeStruct> _tradeInfo = new Dictionary<Symbol, TradeStruct>();
/** PRIVATE VARS **/
string[] _TestShortsList = {"NCTY", "OCGN", "BNGO", "ANTE", "CHEK", "PECK", "TATT", "NCTY", "CBAT", "CNET", "VIH", "CMRX", "WORX", "GRNQ", "GOVX", "LMFA", "ACTC", "LJPC", "QLI", "LIVE", "ACOR", "DTSS", "DPW", "CNET", "CHRA", "OBSV", "AT", "DBVT", "TENX", "DBVT", "ACRS", "NERV", "GEVO", "TRXC", "CHNR", "GRTS", "DARE", "APM", "CLBS", "STCN", "NDRA", "OBLN", "ADXN", "BBIG", "CLSN", "QTT", "CLPS", "ADMP", "AMTX", "ATOS", "EXPR", "VYNE", "RCON", "OEG", "VTNR", "EDSA", "KOSS", "EXPR", "KOSS", "JAN", "XSPA", "NAKD", "ACY", "MNPR", "RHE", "LMFA", "SIEB", "PSAC", "NAKD", "EXPR", "CATB", "THCB", "ALYA", "IMTE", "LODE", "ASM", "AREC", "MBRX", "HOL", "DRRX", "SAVA", "TYME", "SLS", "SINO", "AACG", "ATOS", "AVXL", "STG", "TTOO", "JG", "OTLK", "AAME", "METX", "BSQR", "LAIX", "AREC", "RYB", "OCGN", "NMRD", "BSQR", "CRHM", "ITRM", "HTBX", "KALV", "AUVI", "CHCI", "ZSAN", "CMLF", "CGIX", "BPTH", "GMDA", "NEPT", "OGI", "GNFT", "OLB", "BHTG", "MNPR", "CLSN", "ACIU", "RCMT", "ARTL", "ALJJ", "TRCH", "LKCO", "SCKT", "MYT", "PETZ", "DOGZ", "LODE", "CLPS", "CNET", "ONCY", "CEI", "QUIK", "CAPA", "GTEC", "AVGR", "WATT", "VCNX", "RETO", "CLSN", "XELB", "CTIB", "SYPR", "NVOS", "RRD", "KOSS", "DYNT", "LIXT", "VACQ", "ECOR", "NNVC", "JCS", "OVID", "SLGG", "KMPH", "UWMC", "FPRX", "EYES", "ANCN", "EYES", "EARS", "XELA", "EYES", "ANPC", "INVO", "CETX", "XELA", "KOSS", "MNOV", "ENVB", "OBLN", "ENTX", "NVFY", "OGI", "LGVN", "SIEB", "NLSP", "ENTX", "ANIX", "CHEK", "SGLB", "NTEC", "ACER", "SSY", "ENZ", "IMTX", "ZCMD", "HUGE", "SINO", "TKAT", "SPCB", "SNES", "SPRT", "DYAI", "DLPN", "BNTC", "HOTH", "APTO", "ZKIN", "DLPN", "ALYA", "HWCC", "MX", "WAFU", "ATNF", "CYAN", "HGEN", "CSCW", "VTSI", "RKDA", "CEMI", "BPTH", "IGIC", "BTX", "SJ", "ASTC", "VERY", "ORBC", "AFMD", "CELC", "WHLM", "LGVN", "MFNC", "GALT", "MTSL", "KNL", "AMBO", "MYMD", "GRTX", "ATNX", "CHMA", "AMS", "LHDX", "ULBI", "ANVS", "JZXN", "TLC", "PRVB", "PIRS", "NBRV", "VTNR", "VTNR", "CTRM", "OEG", "MITO", "BCTX", "CNST", "GTT", "JZXN", "SENS", "RMED", "LMNL", "AEMD", "CKPT", "AEHL", "MDLY", "MEDS", "NOVN", "PTIX", "MDIA", "RAPT", "ENOB", "CLSD", "TRCH", "APRE", "KIN", "AUVI", "MTP", "ISUN", "MNOV", "TRCH", "ENTX", "MRIN", "OSMT", "FFHL", "MRIN", "CNSP", "BSQR", "XELA", "BLIN", "VTNR", "OCUP", "CUEN", "BLIN", "BGI", "MEDS", "TATT", "OSG", "STAF", "WAVE", "MRIN", "MRIN", "OSAT", "BLIN", "DTST", "DARE", "ARPO", "DBGI", "CARV", "CARV", "GALT", "SGOC", "MTSL", "CETX", "CLNN", "BLIN", "WORX", "OSAT", "OXBR", "DTSS", "DTSS", "AEHR", "NRXP", "NRBO", "CEMI", "NURO", "PRFX", "NURO", "ALZN", "NWHM", "ACOR", "SCKT", "XBIO", "IMV", "EYEG", "INFI", "GRVI", "NAOV", "ERYP", "EVK", "WHLM", "OTLK", "BYSI", "ENSC", "HUSN", "ANY", "WINT", "GNOG", "ZEV", "FULC", "IMV", "IEC", "TVTX", "VRPX", "VRPX", "PMCB", "SNOA", "GOVX", "TRIL", "VVOS", "BPTH", "ADTX", "RGC", "LWAC", "GSMG", "BLUW", "NNA", "SPRT", "NURO", "BBIG", "SPRT", "ATER", "ACIU", "ABVC", "GBS", "IPHA", "OMQS", "RNXT", "IRNT", "KDMN", "ISEE", "LIFE", "GSKY", "IRNT", "CRVS", "HLBZ", "IPHA", "ZIVO", "EDSA", "HLBZ", "NNVC", "MRIN", "AMHC", "ZIVO", "UFAB", "RCAT", "SNOA", "DBGI", "PALT", "BMRA", "XENE", "VYGR", "CEI", "FLXN", "PTGX", "ADMS", "NTRB", "GRVI", "LMFA", "JSPR", "IO", "RDUS", "CRTD", "BENE", "SONM", "BKKT", "GRNQ", "MARK", "PAE", "CRTD", "WBX", "RDBX", "EQOS", "DBGI", "IFRX", "HCWB", "ABVC", "SBEA", "OLB", "RRD", "NPTN", "EVAX", "PETZ", "AUTL", "PPSI", "ATER", "EVGO", "LAZR", "RNXT", "EYPT", "CSPR", "CREX", "GOVX", "KZR", "LGVN", "GTEC", "ISPC", "ASTR", "APVO", "ISPC", "KTTA", "PTPI", "OCUP", "BFRI", "AHPI", "BFRI", "ISPC", "NRXP", "PTPI", "KRYS", "IMGN", "FWBI", "NLSP", "PPSI", "CPIX", "ABUS", "OP", "LGVN", "CYAD", "ACET", "TACO", "ISIG", "SYTA", "EFOI", "ENSC", "IINN", "FHTX", "BLU", "GRTX", "GMTX", "QNRX", "CALT", "BLPH", "GNFT", "CANF", "BRG", "SOPA", "ARDS", "BFRI", "RELI", "QNRX", "PPSI", "SEAC", "BVXV", "KTTA", "MBOT", "RELI", "ISIG", "ISIG", "NTRB", "CELZ", "IMMX", "PSTV", "LIXT", "EAR", "FRLN", "ABSI", "TSRI", "IMMX", "IMRN", "ZGNX", "EXTN", "BTBD", "APM", "SRRA", "IINN", "OLB", "MDJH", "TLMD", "AREB", "ECOL", "RHE", "MODD", "BDSI", "RESN", "HOOK", "QNGY", "TEN", "INDO", "UUU", "CTIC", "INDO", "NINE", "HUSA", "ENSV", "HUSA", "NINE", "USWS", "PLM", "USEG", "BRN", "SBFM", "INDO", "ENSV", "MGLD", "VOLT", "HYMC", "UTME", "HUSN", "DPRO", "CELZ", "LBPS", "BVXV", "IGMS", "LGVN", "CLVS", "HUSN", "SBFM", "MOBQ", "TUFN", "VERU", "IVDA", "LIXT", "SRRA", "ATRS", "CASA", "STSS", "CMPI", "CYN", "NKTX", "AVDL", "VIVK", "COSM", "IDAI", "GTYH", "ZYME", "FNCH", "APRN", "SNOA", "RVSN", "SOPA", "EVAX", "DTST", "NURO", "IMMX", "HMLP", "STON", "HUSA", "JAN", "TXMD", "HSDT", "TNXP", "IMMX", "YMTX", "JAN", "EFOI", "AUVI", "COGT", "RDBX", "EVOK", "ADN", "REV", "CNVY", "BKSY", "REV", "FSTX", "TBLT", "AXSM", "NRSN", "KZR", "NRSN", "ASPN", "VRME", "RBCN", "IINN", "RFP", "SRG", "PLRX", "TBLT", "LJPC", "HSTO", "GOEV", "INDO", "XRTX", "ADN", "TKLF", "FRGT", "PTPI", "AYLA", "MGAM", "GOVX", "APDN", "HSDT", "PSTX", "MDIA", "NVIV", "NNVC", "SOPA", "KZIA", "IONM", "PEGY", "MOBQ", "BNSO", "BBBY", "CLWT", "VCSA", "ARHS", "VRDN", "ARTL", "FRZA", "HIL", "VLCN", "AERI", "SSY", "OLB", "MGAM", "NOGN", "HPCO", "FMTX", "PXMD", "CMCM", "ECOM", "PIXY", "AKRO", "ETNB", "ADTX", "NRBO", "PRPL", "SNTI", "EVAX", "SPRO", "ATXI", "MOTS", "FXLV", "LITM", "AIMD", "ATXI", "LVWR", "LOGC", "PEGY", "KITT", "PEGY", "BEAT", "LUCY", "IMUX", "LASE", "FORG", "HPCO", "LASE", "ATXI", "IMRA", "INPX", "AKUS", "RMED", "AVEO", "TSHA", "USER", "AGFS", "EFSH", "BNFT", "FRZA", "SNTG", "OYST", "MACK", "SNAL", "PXMD", "IMGO", };
//string[] _TestShortsList = {"NTRB", "CELZ", "IMMX", "PSTV", "HOTH", "IMMX", "LIXT", "EAR", "FRLN", "ABSI", "TSRI", "IMRN", "IMMX", "RMTI", "ZGNX", "APM", "EXTN", "BTBD", "SRRA", "APM", "IINN", "IO", "MDJH", "OLB", "TLMD", "AREB", "VLDR", "ECOL", "RHE", "MODD", "MOTS", "BDSI", "RESN", "HOOK", "QNGY", "TEN", "INDO", "CEI", "UUU", "MULN", "GBS", "CTIC", "ATXI", "INDO", "HUSA", "NINE", "BRN", "HYMC", "CEI", "TMC", "USEG", "PLM", "HUSA", "NINE", "USWS", "ENSV", "SBFM", "ASTS", "TLSA", "PT", "ENSV", "INDO", "CLSD", "MGLD", "EXN", "MULN", "VOLT", "HYMC", "HUIZ", "NILE", "UTME", "HUSN", "DPRO", "PALI", "SMFL", "LBPS", "CREX", "CELZ", "BVXV", "AMPE", "GNLN", "HYMC", "KXIN", "IGMS", "LGVN", "CLVS", "HUSN", "PLX", "SPI", "SBFM", "MOBQ", "TUFN", "IMAC", "MDVL", "NDRA", "MDVL", "IVDA", "GNCA", "VERU", "HOTH", "LIXT", "ARTL", "SRRA", "ATRS", "CRXT", "CASA", "STSS", "CMPI", "IO", "WINT", "CYN", "ERYP", "ARDX", "NKTX", "JZXN", "EVOK", "AVDL", "VIVK", "COSM", "AFIB", "IDAI", "GTYH", "ZYME", "FNCH", "APRN", "SNOA", "RVSN", "HYMC", "BBIG", "SREV", "SOPA", "LOGC", "VRM", "EVAX", "DTST", "IDRA", "GNUS", "NURO", "IMMX", "GOVX", "CBIO", "BIMI", "CTEK", "STON", "HMLP", "TCBP", "SPRC", "AVDL", "JAN", "HUSA", "TXMD", "VTVT", "TNXP", "MTP", "HSDT", "IMMX", "YMTX", "KRBP", "JAN", "CYRN", "EFOI", "AUVI", "COGT", "RGS", "BIMI", "RDBX", "SONM", "REV", "QD", "BOXD", "EVOK", "ACOR", "ADN", "MREO", "REV", "BKSY", "CNVY", "QTNT", "BHAT", "REV", "USWS", "BHAT", "FSTX", "PEV", "SMFL", "RDHL", "TBLT", "EVFM", "AGRX", "AXSM", "NRSN", "EPZM", "AFIB", "AGRX", "TOUR", "KZR", "EVFM", "BRDS", "NRSN", "ASPN", "VRME", "RBCN", "RFP", "IINN", "WORX", "AREB", "SRG", "LJPC", "PLRX", "TBLT", "AKAN", "HSTO", "GOEV", "INDO", "XRTX", "NEXI", "ADN", "QD", "ADXN", "TKLF", "GOVX", "AYLA", "FRGT", "DAVE", "PTPI", "TBLT", "AYLA", "GOVX", "AEMD", "MGAM", "CZOO", "APDN", "PSTX", "EAR", "HSDT", "MDIA", "APDN", "TOUR", "CEMI", "NVIV", "NNVC", "SOPA", "MOBQ", "KZIA", "MRKR", "PEGY", "IONM", "ATNX", "HLBZ", "BBBY", "BNSO", "RETO", "TUEM", "CLWT", "LOTZ", "VCSA", "GMBL", "ARHS", "UBX", "NEPT", "TISI", "VRDN", "RGS", "PSTV", "ARTL", "FRZA", "HIL", "BBIG", "CRIS", "PSTV", "VLCN", "SMMT", "AGLE", "SMMT", "AERI", "WINT", "SPRC", "SSY", "BXRX", "OLB", "MGAM", "NOGN", "NUWE", "BGXX", "HPCO", "FMTX", "PXMD", "CMCM", "HYRE", "ECOM", "PIXY", "DMS", "JZXN", "AKRO", "ETNB", "SPRC", "ADTX", "NRBO", "BEAT", "PRPL", "SNTI", "EVAX", "SPRO", "ATXI", "SOBR", "MOTS", "FXLV", "AIMD", "LITM", "ATXI", "LVWR", "LOGC", "ATXI", "CNXA", "KITT", "PEGY", "PEGY", "GGE", "BEAT", "LUCY", "IMUX", "FORG", "LASE", "HPCO", "FNHC", "LASE", "ATXI", "IMRA", "CTM", "FNHC", "INPX", "ACOR", "RMED", "AKUS", "FNHC", "AVEO", "RDHL", "HOOK", "EFOI", "TSHA", "RNAZ", "USER", "AGFS", "QNGY", "EFSH", "FRZA", "BNFT", "SNTG", "SPRC", "OYST", "MACK", "SNAL", "PEGY", "ELVT", "PXMD", "IMGO", };
//string[] _TestShortsList = {"LIXT", "MYNZ", "EAR", "ABSI", "TSRI", "IMMX", "SBEV", "BSFC", "APM", "MDJH", "OLB", "VLDR", "RHE", "IPW", "HOOK", "NRGV", "TEN", "INDO", "HUSA", "USEG", "TMC", "USWS", "MARPS", "NINE", "ENSV", "SBFM", "DRCT", "HYMC", "HYMC", "PIK", "CELZ", "IGMS", "ADGI", "CLVS", "LGVN", "MOBQ", "MNTS", "UUU", "BDSX", "STSS", "CYN", "VLON", "VIVK", "COSM", "IDAI", "FNCH", "ZYME", "SNOA", "RVSN", "SIDU", "BBIG", "NURO", "STON", "SPRC", "AMLX", "JAN", "CYRN", "EFOI", "AUVI", "COGT", "KAVL", "EVOK", "ADN", "FSTX", "FSTX", "TBLT", "KZR", "ASPN", "NRSN", "RPID", "IINN", "RFP", "SRG", "HSTO", "GOEV", "HSTO", "GOEV", "USEA", "HUSN", "BWV", "BTTX", "PRPB", "AYLA", "ILAG", "MDIA", "PSTX", "APDN", "XCUR", "GRNA", "NNVC", "SOPA", "NVIV", "IONM", "CLWT", "VCSA", "ARHS", "VEEE", "FRZA", "ARTL", "HIL", "BRSH", "VLCN", "AERI", "SSY", "MGAM", "BRPM", "PXMD", "BIAF", "PIXY", "ETNB", "AKRO", "VRAX", "ATXI", "ABOS", "MOTS", "FXLV", "AIMD", "LITM", "CLAQ", "BEAT", "LUCY", "LASE", "DICE", "HPCO", "IMRA", "RMED", "AVEO", "AKUS", "TSHA", "AGFS", "USER", "NUVL", "BNFT", "SNTG", "IGNY", "MACK", "SNAL", };
/// NYS = NYSE
/// NAS = NASDAQ
/// ASE = AMEX
/// See: https://www.quantconnect.com/forum/discussion/12234/exchange-id-mapping/p1
/// See: https://www.quantconnect.com/forum/discussion/11121/how-to-backtest-and-live-trade-on-chinese-stocks/p1
string[] _ValidExchanges = {"NYS", "NAS", "ASE"};
/// *** CONFIGURATION VARIABLES *** ///
private DateTime _startDate = new DateTime(2021, 1, 1);
private DateTime _endDate = new DateTime(2022, 11, 20);
private int _accountStartSize = 20_000;
/// Counts Number of Trades That Are Covered using hourly ema exit, when using hourly ema
private int _hourlyEmaCoverCounter = 0;
/// Defaults to How Algorithm Will Exit Short Position
private COVER_TYPE _coverType = COVER_TYPE.COVER_AT_NEXT_OPEN;
private ENTRY_TYPE _entryType = ENTRY_TYPE.IMMEDIATE_ENTRY;
/// Used So That It's easy to configure which exit(s) to test against
/// To-Do For Possible Optimizations would be to include a mapping to value, so for instance
// define both HourlyEma as the exit type, and 5 for 5 Hour Ema.
EXIT_TYPE[] _AvailableExits = { };
private DYNAMIC_TIMING_ADJUSTMENT _timingAdjustmentMode = DYNAMIC_TIMING_ADJUSTMENT.PRICE_GREATER_THAN_OPEN;
/// Used For Consolidating Open/Close Price For Custom Time Ranges
private const int CUSTOM_TIME_PERIOD_IN_MINUTES = 5;
/// --
/// Initialize and Prepare Algo
/// Warm-up Data and Indicators
public override void Initialize()
{
/// Date Setup
SetStartDate(_startDate);
SetEndDate(_endDate);
/// Account Setup
SetCash(_accountStartSize);
/// Benchmark Setup
var lBenchmarkSecurity = AddEquity(BENCHMARK, Resolution.Minute);
SetBenchmark(BENCHMARK);
/// Universe Setup
UniverseSettings.Resolution = Resolution.Minute;
UniverseSettings.DataNormalizationMode = DataNormalizationMode.SplitAdjusted;
UniverseSettings.ExtendedMarketHours = true;
EnableAutomaticIndicatorWarmUp = true;
/// SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage);
AddUniverseSelection(new FineFundamentalUniverseSelectionModel(SelectCoarse, SelectFine));
/// Market Defaults
var lMarketOpenHour = 9;
var lMarketOpenMinute = 30;
/// If Taking a Specialized Entry, We Check for Entry One Minute after Market Open instead of immediate open entry
/// Additionally, Pending Orders are cancelled at 11am
if(_entryType == ENTRY_TYPE.SPECIALIZED_OPEN_ENTRY)
{
lMarketOpenMinute += CUSTOM_TIME_PERIOD_IN_MINUTES;
Schedule.On(Schedule.DateRules.EveryDay(),
Schedule.TimeRules.At(new TimeSpan(11, 0, 0)), CancelPendingOrders);
}
Schedule.On(Schedule.DateRules.EveryDay(),
Schedule.TimeRules.At(new TimeSpan(lMarketOpenHour, lMarketOpenMinute, 0)), OnEachMarketOpen);
Schedule.On(Schedule.DateRules.EveryDay(),
Schedule.TimeRules.BeforeMarketClose(BENCHMARK, 1), OnEachMarketClose);
/// Warmup One Week Worth Of Data
SetWarmUp(new TimeSpan(2, 0, 0, 0));
}
public void OnData(TradeBars aTradeBars)
{
/// QuantConnect Seemingly Has a Bug where using StopMarketOrders with
/// a ticker that has the same Open High Low Close, causes erroneous fills
/// so instead I use a direct Liquidation once the high exceeds the stop
foreach(var lKeyValuePair in Portfolio)
{
Symbol lSymbol = lKeyValuePair.Key;
if (aTradeBars.ContainsKey(lSymbol))
{
var lTradeBar = aTradeBars[lSymbol];
if(lKeyValuePair.Value.Invested)
{
if(_tradeInfo.ContainsKey(lSymbol))
{
/// Update Highs
_tradeInfo[lSymbol].HighPrice = Math.Max(lTradeBar.High, _tradeInfo[lSymbol].HighPrice);
AdjustForDynamicTiming(lTradeBar);
if(lTradeBar.High > _tradeInfo[lSymbol].StopLoss
|| HaveExitConditionsBeenMet(lTradeBar)
)
{
Liquidate(lSymbol, tag: BUY_TAG);
Log("Closing Order On: " + lSymbol);
/// Log("AskPrice: " + ActiveSecurities[lSymbol].AskPrice);
/// Log("BidPrice: " + ActiveSecurities[lSymbol].BidPrice);
_tradeInfo.Remove(lSymbol);
}
}
}
}
}
}
/// Called To Cancel Any Pending Sell Stops
/// Used For Dynamic Time Adjusting Logic
public void CancelPendingOrders()
{
if(IsWarmingUp) return;
Transactions.CancelOpenOrders();
}
public void OnEachMarketClose()
{
/// Cover All Positions At Open
/// Using This Logic In Place of Liquidation to Avoid Cancellation Issues
foreach(var position in _tradeInfo)
{
if(ActiveSecurities[position.Key].Invested)
{
switch(_coverType)
{
case COVER_TYPE.COVER_AT_NEXT_OPEN:
MarketOnOpenOrder(position.Key, position.Value.ShareQuantity, tag: BUY_TAG);
break;
case COVER_TYPE.COVER_AT_CLOSE:
MarketOrder(position.Key, position.Value.ShareQuantity, tag: BUY_TAG);
break;
default:
MarketOrder(position.Key, position.Value.ShareQuantity, tag: BUY_TAG);
break;
}
Log("Closing Order On: " + position.Key);
}
}
_tradeInfo.Clear();
}
/// Called On Each Open To Execute Gap Up Short Criteria
public void OnEachMarketOpen()
{
/// Ignore Warmup Periods
if(IsWarmingUp) return;
/// Ignore Mondays
if(Time.Day == (int)DayOfWeek.Monday) return;
decimal lGapPercentMultiplier = (100 + MINIMUM_GAP_UP_PERCENTAGE) / 100;
/// Get All Potential Setups
List<Symbol> lPotentialShortSetups = (from pair in ActiveSecurities
where _symbolDict.ContainsKey(pair.Key)
where pair.Value.Fundamentals != null
where pair.Value.Fundamentals.MarketCap <= MAXIMUM_MARKET_CAP
/// where pair.Value.Close > MINIMUM_OPEN_PRICE
select pair.Key).ToList();
/// Final Selection Process = Check Premarket Volume, We
/// do this last as we have to do a full history request and that is more taxing performance wise
var lPreMarketStartHour = 4;
DateTime lStartTime = new DateTime(Time.Year, Time.Month, Time.Day, lPreMarketStartHour, 0, 0);
List<Symbol> lShortSetups = new List<Symbol>();
foreach(var lSymbol in lPotentialShortSetups)
{
CustomConsolidatedBar lBar = GetCustomTradeBar(lSymbol);
decimal lPriceAtOpen = _entryType == ENTRY_TYPE.IMMEDIATE_ENTRY ? lBar.Open : lBar.Close;
bool lMinimumPriceMet;
if(_entryType == ENTRY_TYPE.IMMEDIATE_ENTRY)
{
lMinimumPriceMet = (lBar.Close > MINIMUM_OPEN_PRICE);
}
else
{
lMinimumPriceMet = (lBar.Open > MINIMUM_OPEN_PRICE);
}
if(!lMinimumPriceMet)
{
continue;
}
/// Get Historical Data For Checking Prior Highs + Gaps
bool lProperPricing = false;
var lPriorDayBarHistory = History<TradeBar>(lSymbol, new TimeSpan(1,0,0,0), Resolution.Daily);
if(!lPriorDayBarHistory.IsNullOrEmpty())
{
TradeBar lPriorDayBar = lPriorDayBarHistory.First();
/// decimal lPriceAtOpen = _entryType == ENTRY_TYPE.IMMEDIATE_ENTRY ? ActiveSecurities[lSymbol].Close : ActiveSecurities[lSymbol].Open;
if(lPriceAtOpen > lPriorDayBar.High &&
lPriceAtOpen > lPriorDayBar.Close * lGapPercentMultiplier)
{
lProperPricing = true;
}
}
if(!lProperPricing)
{
continue;
}
/// Get All Premarket Data For Volume Purposes
bool lPremarketFadeValid = false;
var lTradeBars = History<TradeBar>(lSymbol, lStartTime, Time).ToList();
decimal lTotalVolume = 0;
foreach(var lTradebar in lTradeBars)
{
lTotalVolume += lTradebar.Volume;
}
if(lTradeBars.Count > 0)
{
decimal lHighPrice = lTradeBars.Max(bar => bar.High);
lPremarketFadeValid = lPriceAtOpen <= (lHighPrice * ConvertPercentToNegativeMultiplier(MINIMUM_PREMARKET_FADE_PERCENT));
}
if(lTotalVolume > MINIMUM_PREMARKET_VOLUME) /// && lPremarketFadeValid)
{
lShortSetups.Add(lSymbol);
}
}
decimal lCurrentPortfolioValue = Portfolio.TotalPortfolioValue;
foreach(var lSymbolToShort in lShortSetups)
{
/// No Duplicate Trades
if(!ActiveSecurities[lSymbolToShort].Invested)
{
HandlePotentialShortTrade(lSymbolToShort, lCurrentPortfolioValue);
/*
decimal lPriceAtOpen = _entryType == ENTRY_TYPE.IMMEDIATE_ENTRY ? ActiveSecurities[lSymbolToShort].Close : ActiveSecurities[lSymbolToShort].Open;
/// Calculate Stop
decimal lStopLoss = lPriceAtOpen * ConvertPercentToPositiveMultiplier(STOP_LOSS_PERCENT);
/// Calculate Exposure
var lNumberOfSharesToShort = GetNumberOfSharesToShort(lCurrentPortfolioValue, lSymbolToShort);
/// Track Stop & Number Of Shares to Short
_tradeInfo[lSymbolToShort] = new TradeStruct(lStopLoss, lNumberOfSharesToShort, lPriceAtOpen);
/// Place Order Immediately At Open
MarketOrder(lSymbolToShort, -lNumberOfSharesToShort, false, SELL_TAG);
Log("Opening Trade on - " + lSymbolToShort);
if(!_symbolTracker.Contains(lSymbolToShort))
{
_symbolTracker.Add(lSymbolToShort);
}
*/
}
}
}
public CustomConsolidatedBar GetCustomTradeBar(Symbol aSymbol)
{
var lOpeningFiveMinutes = History<TradeBar>(aSymbol, CUSTOM_TIME_PERIOD_IN_MINUTES, Resolution.Minute).ToList();
if(lOpeningFiveMinutes.Count <= 1)
{
return new CustomConsolidatedBar(ActiveSecurities[aSymbol].Open, ActiveSecurities[aSymbol].Close);
}
CustomConsolidatedBar lBar;
if(_entryType == ENTRY_TYPE.IMMEDIATE_ENTRY)
{
lBar = new CustomConsolidatedBar(ActiveSecurities[aSymbol].Open, ActiveSecurities[aSymbol].Close);
}
else
{
lBar = new CustomConsolidatedBar(lOpeningFiveMinutes[0].Open, lOpeningFiveMinutes[lOpeningFiveMinutes.Count - 1].Close);
}
return lBar;
}
public void HandlePotentialShortTrade(Symbol aSymbol, decimal aTotalPortfolioValue)
{
CustomConsolidatedBar lBar = GetCustomTradeBar(aSymbol);
/*decimal lEntryPrice = ActiveSecurities[aSymbol].Close;
decimal lPriceAtOpen = _entryType == ENTRY_TYPE.IMMEDIATE_ENTRY ? ActiveSecurities[aSymbol].Close : ActiveSecurities[aSymbol].Open;*/
decimal lEntryPrice = lBar.Close;
decimal lPriceAtOpen = _entryType == ENTRY_TYPE.IMMEDIATE_ENTRY ? lBar.Close : lBar.Open;
bool lDownClose = lBar.Close < lBar.Open;
switch(_entryType)
{
case ENTRY_TYPE.SPECIALIZED_OPEN_ENTRY:
/// Down Bar in First Minute = Immediate Short
/// if(ActiveSecurities[aSymbol].Close < ActiveSecurities[aSymbol].Open)
if(lDownClose)
{
/// lEntryPrice = ActiveSecurities[aSymbol].Close;
}
/// Up Bar In First Minute = Add a 'Stretch Condition' Lasting Until 11AM
else
{
/// Apply a Percentage Stretch
lEntryPrice = ActiveSecurities[aSymbol].Close * 1.35m;
/// lEntryPrice = ActiveSecurities[aSymbol].Close + (_symbolDict[aSymbol].ATR * 5m);
}
break;
case ENTRY_TYPE.IMMEDIATE_ENTRY:
/// For Immediate Entry At Open. Take 9:30am Pricing
/// lEntryPrice = ActiveSecurities[aSymbol].Close
default:
break;
}
/// Calculate Stop
decimal lStopLoss = lEntryPrice * ConvertPercentToPositiveMultiplier(STOP_LOSS_PERCENT);
/// Calculate Exposure
var lNumberOfSharesToShort = GetNumberOfSharesToShort(aTotalPortfolioValue, aSymbol, lEntryPrice);
/// Track Stop & Number Of Shares to Short
_tradeInfo[aSymbol] = new TradeStruct(lStopLoss, lNumberOfSharesToShort, lEntryPrice);
if(_entryType == ENTRY_TYPE.IMMEDIATE_ENTRY ||
/// ( (_entryType == ENTRY_TYPE.SPECIALIZED_OPEN_ENTRY) && ActiveSecurities[aSymbol].Close < ActiveSecurities[aSymbol].Open ) )
( (_entryType == ENTRY_TYPE.SPECIALIZED_OPEN_ENTRY) && lDownClose ) )
{
/// Place Order Immediately
MarketOrder(aSymbol, -lNumberOfSharesToShort, false, SELL_TAG);
}
else
{
/// Put in Sell Stop Order
StopMarketOrder(aSymbol, -lNumberOfSharesToShort, lEntryPrice, SELL_TAG);
}
/// Tracking Symbols Meeting Criteria
if(!_symbolTracker.Contains(aSymbol))
{
_symbolTracker.Add(aSymbol);
}
}
/// Called At Exit of Algorithm
public override void OnEndOfAlgorithm()
{
string lSymbolsString = "string[] _TestShortsList = {";
foreach(var symbol in _symbolTracker)
{
lSymbolsString += "\"" + symbol.Value + "\", ";
}
lSymbolsString += " };";
Log(lSymbolsString);
/// Log("Number Of Hourly Ema Covers = " + _hourlyEmaCoverCounter);
base.OnEndOfAlgorithm();
}
/// Called Based On Changes To Current Stock Universe
public override void OnSecuritiesChanged(SecurityChanges changes)
{
// If we have no changes, do nothing
if (changes == SecurityChanges.None) return;
foreach (var security in changes.AddedSecurities)
{
/// Set Leverage to 1 For Added Securities
security.SetLeverage(1);
/// Override QC Default Fill Model
/*security.SetSlippageModel(new ConstantSlippageModel(0));
security.SetFillModel(new LatestPriceFillModel());
security.SetFeeModel(new ConstantFeeModel(0));*/
//security.SetSlippageModel(new CustomSlippageModel(this));
///security.SetFeeModel(new CustomFeeModel(this));
security.SetFillModel(new CustomFillModel(this));
_symbolDict[security.Symbol] = new SymbolData(this, security.Symbol);
}
// If you have a dynamic universe, track removed securities
foreach (var security in changes.RemovedSecurities)
{
if (_symbolDict.TryGetValue(security.Symbol, out var lSymbolData))
{
lSymbolData.Dispose();
_symbolDict.Remove(security.Symbol);
}
}
}
/// Coarse Universe Selector
IEnumerable<Symbol> SelectCoarse(IEnumerable<CoarseFundamental> coarse)
{
var lStocks = (from security in coarse
where security.Volume > 0 && security.Price > 0
where DoesTickerExist(security.Symbol.Value)
select security.Symbol).ToList();
return lStocks;
}
/// Fine Universe Selector
IEnumerable<Symbol> SelectFine(IEnumerable<FineFundamental> fine)
{
var filteredFine =
(from security in fine
where _ValidExchanges.Contains(security.CompanyReference.PrimaryExchangeID)
where security.MarketCap < MAXIMUM_MARKET_CAP
orderby security.MarketCap descending
select security.Symbol).ToList();
return filteredFine;
}
public bool HourlyEmaExitConditionsMet(TradeBar aBar)
{
bool lConditionsMet = false;
/// Ema Breach = Conditions Met
if(aBar.Low < _symbolDict[aBar.Symbol].HourlyEma)
{
lConditionsMet = true;
/// DEBUG
if(aBar.Low < _symbolDict[aBar.Symbol].HourlyEma)
{
Log("Hourly TenEma = " + _symbolDict[aBar.Symbol].HourlyEma);
_hourlyEmaCoverCounter++;
}
}
return lConditionsMet;
}
public bool PercentExtensionFromOpenExitConditionsMet(TradeBar aBar)
{
bool lConditionsMet = false;
if(aBar.Low < _tradeInfo[aBar.Symbol].OpenPrice * 0.8m)
{
lConditionsMet = true;
}
return lConditionsMet;
}
/// If Testing Is Enabled. We Only Run The Code With Our Test List
public bool DoesTickerExist(string ticker)
{
if(!ENABLE_TESTING)
{
return true;
}
bool lTickerExists = false;
foreach(var symbol in _TestShortsList)
{
if(ticker == symbol)
{
lTickerExists = true;
break;
}
}
return lTickerExists;
}
public decimal ConvertPercentToMultiplier(decimal aPercentage)
{
return aPercentage / 100;
}
public decimal ConvertPercentToNegativeMultiplier(decimal aPercentage)
{
return 1 - (aPercentage / 100);
}
public decimal ConvertPercentToPositiveMultiplier(decimal aPercentage)
{
return 1 + (aPercentage / 100);
}
public int GetNumberOfSharesToShort(decimal aCurrentPortfolioValueAtOpen, Symbol aSymbol, decimal aEntryPrice)
{
decimal lTotalDollarExposure = (aCurrentPortfolioValueAtOpen) * ConvertPercentToMultiplier(PERCENT_EXPOSURE_PER_POSITION);
int lNumberOfSharesToShort = (int) Math.Round(lTotalDollarExposure / aEntryPrice);
return lNumberOfSharesToShort;
}
/// Based On Configured Dynamic Time Stop Adjustment, Apply Changes
public void AdjustForDynamicTiming(TradeBar aBar)
{
bool lPriceAboveOpen = aBar.High > _tradeInfo[aBar.Symbol].OpenPrice;
bool lIsCorrectTimeOfDay = aBar.EndTime.Hour == 11 && aBar.EndTime.Minute == 31;
switch(_timingAdjustmentMode)
{
case DYNAMIC_TIMING_ADJUSTMENT.ALWAYS_ADJUST:
if(lIsCorrectTimeOfDay)
{
_tradeInfo[aBar.Symbol].StopLoss = _tradeInfo[aBar.Symbol].HighPrice;
Log("Auto Adjusting Stop to High of Day");
}
break;
case DYNAMIC_TIMING_ADJUSTMENT.PRICE_GREATER_THAN_OPEN:
/// Check For High > Open @ 11AM, Update Stop Loss To High If This Occurs
if(lPriceAboveOpen && lIsCorrectTimeOfDay)
{
_tradeInfo[aBar.Symbol].StopLoss = _tradeInfo[aBar.Symbol].HighPrice;
Log("High Above Open For " + aBar.Symbol.Value + ", Adjusting Stop Loss To High of Day");
}
break;
case DYNAMIC_TIMING_ADJUSTMENT.NEVER:
default:
break;
}
}
/// On a Given Time Step, Given a Bar and the type of Exit to execute
/// Determine if Exit Conditions Have Been Hit
public bool HaveExitConditionsBeenMet(TradeBar aBar)
{
bool lConditionsMet = false;
foreach(EXIT_TYPE lExitType in _AvailableExits)
{
switch(lExitType)
{
case EXIT_TYPE.HOURLY_EMA_EXIT:
lConditionsMet = HourlyEmaExitConditionsMet(aBar);
break;
case EXIT_TYPE.PERCENT_EXTENSION_FROM_OPEN:
lConditionsMet = PercentExtensionFromOpenExitConditionsMet(aBar);
break;
case EXIT_TYPE.VWAP_PERCENTAGE_EXTENSIONS:
break;
case EXIT_TYPE.VWAP_ATR_EXTENSIONS:
break;
default:
break;
}
/// If Any Conditions = True, Move On
if(lConditionsMet)
{
break;
}
}
return lConditionsMet;
}
}
/// Custom Bar Data Tracker, Used to Get the Open and Close Range of a given time period
public class CustomConsolidatedBar
{
private decimal _openPrice;
private decimal _closePrice;
public CustomConsolidatedBar(decimal aOpenPrice, decimal aClosePrice)
{
_openPrice = aOpenPrice;
_closePrice = aClosePrice;
}
public decimal Open
{
get
{
return _openPrice;
}
}
public decimal Close
{
get
{
return _closePrice;
}
}
}
public class TradeStruct
{
private decimal _stopLoss;
private decimal _shareQuantity;
private decimal _openPrice;
private decimal _highPrice;
public TradeStruct(decimal aStopLoss, decimal aShareQuantity, decimal aOpenPrice)
{
_stopLoss = aStopLoss;
_shareQuantity = aShareQuantity;
_openPrice = aOpenPrice;
_highPrice = aOpenPrice;
}
public decimal StopLoss
{
get
{
return _stopLoss;
}
set
{
_stopLoss = value;
}
}
public decimal OpenPrice
{
get
{
return _openPrice;
}
}
public decimal ShareQuantity
{
get
{
return _shareQuantity;
}
}
public decimal HighPrice
{
get
{
return _highPrice;
}
set
{
_highPrice = value;
}
}
}
public class CustomFillModel : ImmediateFillModel
{
private readonly QCAlgorithm _algorithm;
private readonly Random _random = new Random(387510346); // seed it for reproducibility
private readonly Dictionary<long, decimal> _absoluteRemainingByOrderId = new Dictionary<long, decimal>();
public CustomFillModel(QCAlgorithm algorithm)
{
_algorithm = algorithm;
}
public override OrderEvent MarketFill(Security asset, MarketOrder order)
{
// this model randomly fills market orders
decimal absoluteRemaining;
if (!_absoluteRemainingByOrderId.TryGetValue(order.Id, out absoluteRemaining))
{
absoluteRemaining = order.AbsoluteQuantity;
_absoluteRemainingByOrderId.Add(order.Id, order.AbsoluteQuantity);
}
var fill = base.MarketFill(asset, order);
/// Rather Than Any Weird QuantConnect Fills. Market Orders
/// will be filled at bid or ask, or close and with minor touches
switch (order.Direction)
{
case OrderDirection.Buy:
/// fill.FillPrice = _algorithm.ActiveSecurities[order.Symbol].AskPrice *
fill.FillPrice = _algorithm.ActiveSecurities[order.Symbol].Close * 1.0035m;
break;
case OrderDirection.Sell:
/// fill.FillPrice = _algorithm.ActiveSecurities[order.Symbol].BidPrice *
fill.FillPrice = _algorithm.ActiveSecurities[order.Symbol].Close * 0.9965m;
break;
}
var absoluteFillQuantity = (int) (Math.Min(absoluteRemaining, _random.Next(0, 2*(int)order.AbsoluteQuantity)));
fill.FillQuantity = Math.Sign(order.Quantity) * absoluteFillQuantity;
if (absoluteRemaining == absoluteFillQuantity)
{
fill.Status = OrderStatus.Filled;
_absoluteRemainingByOrderId.Remove(order.Id);
}
else
{
absoluteRemaining = absoluteRemaining - absoluteFillQuantity;
_absoluteRemainingByOrderId[order.Id] = absoluteRemaining;
fill.Status = OrderStatus.PartiallyFilled;
}
// _algorithm.Log($"CustomFillModel: {fill}");
return fill;
}
}
/// Handles Tracking Indicators and Warming Up the Data For Indicators as well as
/// attaching to the Algorithm
public class SymbolData
{
private QCAlgorithm _algorithm;
private Symbol _symbol;
private TradeBarConsolidator _consolidator;
private TradeBarConsolidator _hourlyConsolidator;
/// Daily Indicators
public AverageTrueRange ATR;
/// Hourly Indicators
public ExponentialMovingAverage HourlyEma;
public SymbolData(QCAlgorithm algorithm, Symbol symbol)
{
_algorithm = algorithm;
_symbol = symbol;
// Create Indicators
ATR = new AverageTrueRange(10);
HourlyEma = new ExponentialMovingAverage(6);
// Create a consolidator to update the indicator
_consolidator = new TradeBarConsolidator(TimeSpan.FromDays(1));
_consolidator.DataConsolidated += DailyDataUpdate;
_hourlyConsolidator = new TradeBarConsolidator(TimeSpan.FromHours(1));
_hourlyConsolidator.DataConsolidated += HourlyDataUpdate;
// Register the consolidator to update the indicator
algorithm.SubscriptionManager.AddConsolidator(symbol, _consolidator);
algorithm.SubscriptionManager.AddConsolidator(symbol, _hourlyConsolidator);
// Warm Up Indicators
algorithm.WarmUpIndicator(symbol, ATR, Resolution.Daily);
algorithm.WarmUpIndicator(symbol, HourlyEma, Resolution.Hour);
}
/// Updates Indicators that run on specifically at a daily resolution
/// Updates Once a Day
private void DailyDataUpdate(object sender, TradeBar consolidatedBar)
{
ATR.Update(consolidatedBar);
}
private void HourlyDataUpdate(object sender, TradeBar consolidatedBar)
{
/// 10 Ema Data Should Only Be Updated Based On The Past 10
/// Hours of Market Time Data, No Premarket or After Hour Session Data
/// This is a workaround for the data pipeline being piped in
if(consolidatedBar.EndTime.Hour < 10 ||
consolidatedBar.EndTime.Hour > 16 ||
(consolidatedBar.EndTime.Hour == 10 && consolidatedBar.EndTime.Minute < 30) ||
(consolidatedBar.EndTime.Hour == 16 && consolidatedBar.EndTime.Minute != 0))
{
return;
}
HourlyEma.Update(consolidatedBar.EndTime, consolidatedBar.Close);
}
// If you have a dynamic universe, remove consolidators for the securities removed from the universe
public void Dispose()
{
_algorithm.SubscriptionManager.RemoveConsolidator(_symbol, _consolidator);
}
}
}