Overall Statistics |
Total Trades 18 Average Win 15.11% Average Loss 0% Compounding Annual Return 130.090% Drawdown 41.100% Expectancy 0 Net Profit 249.914% Sharpe Ratio 1.882 Probabilistic Sharpe Ratio 64.976% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha -0.15 Beta 0.706 Annual Standard Deviation 0.601 Annual Variance 0.361 Information Ratio -1.75 Tracking Error 0.39 Treynor Ratio 1.602 Total Fees $3998.92 Estimated Strategy Capacity $33000000.00 Lowest Capacity Asset SOXL UKTSIYPJHFMT |
# Purpose: SIMPLIFIED ALGORITH FOR SUPPORT PURPOSES class BuyLowSellHighDrawdowns(QCAlgorithm): # SOXL splits: 05/20/2015 4 for 1 | 03/02/2021 15 for 1 def Initialize(self): # PREPARATORY ITEMS BEFORE SIMULATION self.SetStartDate(2020, 7, 1) # Ignored in live (will cover split date) self.SetEndDate(2022, 1, 1) # Ignored in live #self.DURATION = self.SetEndDate(2021, 12, 7) - self.SetStartDate(2020, 12, 7) # Doesn't work yet self.DURATION = "LIVE" self.INVESTMENT_AMOUNT = 1000000 self.SetCash(self.INVESTMENT_AMOUNT) # IB uses default of $1,000,000 for paper trading. self.debug = False # This is the manual method to do safe logging only when live to avoid huge logs. #STOCKS TO TRADE self.STOCK_ONE = "SOXL" # BUY THRESHOLDS self.BUY_THRESHOLD_ONE = -10 # Percent below HH (Drawdown) to buy at initially # BUY QUANTITY THRESHOLDS self.BUY_QUANTITY_PERC_ONE = 1.0 # Percent of cash to use in first buy event # SELL THRESHOLDS self.SELL_THRESHOLD_ONE = -1 # Percent below HH (Drawdown) to sell at, currently only supports one # OTHER VARIABLES self.CYCLE_LIMIT = 1 # Track number of sequential buys and limit it (includes zero) self.SPLIT_DIFF = 1.0 # Track the split differential for us in calculating/adjusting drawdown (52-wk-high/HH) self.Settings.FreePortfolioValuePercentage = 0.03 # Amount of portfolio to keep in cash for operating expenses self.BUY_CYCLE_CNT = 0 # Track the number of buy cycles self.BUY_SELL_CYCLE_CNT = 0 # Track the number of full buy-sell cycles (closed investments) self.SPENT = 0 # Local variable to track how much is spent during each buy self.EARNED = 0 # Local variable to track how much is earned (gross) in each sell self.STOCK_PRICE = 0 # Adjusted stock price (used by default) self.ORIG_STOCK_PRICE = 0 # Original (raw) stock price (used for bank transactions) self.MANUAL_HH = 0 # Since QC doesn't support slit-adjusted HH, track this and adjust after splits self.SPLIT_OCCURRED = 0 # Track if a split happened self.CURRENT_DRAWDOWN = 0 # GET PORTFOLIO DETAILS self.Portfolio.Invested # Hold at least one stock self.Portfolio.Cash # Sum of all currencies in account (only settled cash) self.Portfolio.UnsettledCash # Sum of all currencies in account (only unsettled cash) self.Portfolio.TotalFees # Fees incurred since backtest start self.Portfolio.TotalHoldingsValue # Absolute sum portfolio items self.Portfolio.MarginRemaining # Remaining margin on the account self.Portfolio.TotalMarginUsed # Sum of margin used across all securities self.Portfolio.TotalPortfolioValue # Portfolio equity self.Portfolio.TotalProfit # Sum of all gross profit self.Portfolio.TotalUnrealizedProfit # Holdings profit/loss # PREP THE STOCK self.CURRENT_STOCK = self.AddEquity(self.STOCK_ONE, Resolution.Daily) # Day (good for debugging) self.CURRENT_STOCK.SetDataNormalizationMode(DataNormalizationMode.Adjusted) # Use adjusted data self.HISTORICAL_HIGH = self.MAX(self.STOCK_ONE, 253, Resolution.Daily, Field.High) # Day: Get 52-wk-high (adjusted, raw) self.HH_DAY = self.MAX(self.STOCK_ONE, 1, Resolution.Daily, Field.High) # Day: Trying to get the high price from the last day self.SetBenchmark(self.STOCK_ONE) self.SetWarmUp(timedelta(days=253)) # DAILY LOG REPORT - hour after market open self.Schedule.On(self.DateRules.EveryDay(self.STOCK_ONE), self.TimeRules.AfterMarketOpen(self.STOCK_ONE, 60), self.EveryDayAfterMarketOpen) def OnData(self, data): # PROCESSES REPEATEDLY THROUGHOUT SIMULATION # Don't place trades until our indicators are warmed up (note this delays the alg from starting trading for 4 months due to warmup time.) if not self.HISTORICAL_HIGH.IsReady: self.Log("I failed to run today because HH is not ready yet.") return #if self.IsWarmingUp: # return # Local variables for the sim run self.STOCK_PRICE = self.Securities[self.STOCK_ONE].Price # Get current price (should auto-adjust to splits) self.STOCK_PRICE_HIGH = self.HH_DAY.Current.Value # Get HH for today self.MANUAL_HH = self.HISTORICAL_HIGH.Current.Value # Update the historical high in case a split occurred today #INITIAL VARS self.CASH_REMAINING = self.Portfolio.Cash # Get cash remaining self.STOCK_POSITION = self.Portfolio[self.STOCK_ONE].Quantity # Get number of shares currently held for this stock self.Time # Current simulation time # Determine if there was a split """if data.Splits.ContainsKey(self.STOCK_ONE): ## Log split information stockSplit = data.Splits[self.STOCK_ONE] if stockSplit.Type == 0: self.Log('Stock will split next trading day') if stockSplit.Type == 1: self.SPLIT_DIFF = stockSplit.SplitFactor # Save the new split differential if one occurred self.SPLIT_OCCURRED += 1 # Record that a split has occurred # Adjust the previous HH by the split diff and use that as the new HH self.MANUAL_HH = self.MANUAL_HH * self.SPLIT_DIFF # Adjust the HH based on the new split (this is manual way to handle it) self.Log("Split type: {0}, Split factor: {1}, Reference price: {2}".format(stockSplit.Type, stockSplit.SplitFactor, stockSplit.ReferencePrice)) """ # Get current drawdown amount in the form of a negative full percent if (self.MANUAL_HH == 0): # During the beginning of the simulation there is no value set for this yet self.CURRENT_DRAWDOWN = 0 else: self.CURRENT_DRAWDOWN = -100 * (self.MANUAL_HH - self.STOCK_PRICE)/self.MANUAL_HH # TRADING BEHAVIOR #--BUY #1: If stock price dips below Drawdown Buy Threshold 1, and more than X shares being purchased, then buy if (self.CURRENT_DRAWDOWN < self.BUY_THRESHOLD_ONE) and (self.BUY_CYCLE_CNT == 0): # DO THE BUY self.SetHoldings(self.STOCK_ONE, self.BUY_QUANTITY_PERC_ONE) # Buy the stock, according to the assigned percentage #self.SPENT = self.NUM_SHARES_CAN_PURCHASE * self.STOCK_PRICE self.Log(f"BUY #1: Cycle: {self.BUY_SELL_CYCLE_CNT} | {self.CURRENT_STOCK} at: ${self.STOCK_PRICE:.2f} use {self.BUY_QUANTITY_PERC_ONE}% of ${self.CASH_REMAINING} | DD: {self.CURRENT_DRAWDOWN:.1f}% < {self.BUY_THRESHOLD_ONE:.1f}%") self.BUY_CYCLE_CNT += 1 #Start or encrement buy cycle # --PRINT BUY DIAGNOSTICS self.STOCK_POSITION = self.Portfolio[self.STOCK_ONE].Quantity #--SELL: If stock price goes above Drawdown Sell Threshold, and position > 0, liquidate holdings elif (self.CURRENT_DRAWDOWN > self.SELL_THRESHOLD_ONE) and (self.STOCK_POSITION > 0): # DO THE SELL self.Liquidate(self.STOCK_ONE) # Sell all holdings of this ticker #self.EARNED = self.STOCK_POSITION * self.STOCK_PRICE self.Log(f"SELL: Cycle: {self.BUY_SELL_CYCLE_CNT} | {self.STOCK_POSITION} {self.CURRENT_STOCK} at: ${self.STOCK_PRICE:.2f} | DD: C:{self.CURRENT_DRAWDOWN:.1f}% < {self.SELL_THRESHOLD_ONE:.1f}%") self.BUY_CYCLE_CNT = 0 # Reset buy cycle self.BUY_SELL_CYCLE_CNT += 1 # Count number of buy-sell cycles (investments) # --PRINT SELL DIAGNOSTICS self.STOCK_POSITION = self.Portfolio[self.STOCK_ONE].Quantity # DEBUG: Can use this to only produce error messages when doing paper/live trading def SafeLog(self, message): if self.LiveMode or self.debug: self.Log(message) def EveryDayAfterMarketOpen(self): #if self.HISTORICAL_HIGH.IsReady: self.Log(f"-----{self.STOCK_ONE} D: {self.DURATION} | ADJ_PRICE: ${self.STOCK_PRICE:.2f} | HH: ${self.HISTORICAL_HIGH.Current.Value:.2f} | DD: {self.CURRENT_DRAWDOWN:.1f}% | CASH: ${self.Portfolio.Cash:.2f} | PROFIT: ${self.Portfolio.TotalProfit:.2f} ** B1: {self.BUY_THRESHOLD_ONE}% (Using: {self.BUY_QUANTITY_PERC_ONE*100}%) | SELL: {self.SELL_THRESHOLD_ONE}%") def OnOrderEvent(self, orderEvent): self.Log("{} {}".format(self.Time, orderEvent.ToString())) def OnEndOfAlgorithm(self): self.PROFIT_PERC = (self.Portfolio.TotalProfit / self.INVESTMENT_AMOUNT) * 100 self.Log(f"__END SUMMARY_______________________________________________________") self.Log(f"PROFIT: ${self.Portfolio.TotalProfit:.2f} | PROFIT: {self.PROFIT_PERC:.1f}% | COMPLETED CYCLES: {self.BUY_SELL_CYCLE_CNT} | CASH: ${self.Portfolio.Cash:.2f}") self.Log(f"B1: {self.BUY_THRESHOLD_ONE}% (Using: {self.BUY_QUANTITY_PERC_ONE*100}%) | SELL: {self.SELL_THRESHOLD_ONE}%") self.Log("{} - TotalPortfolioValue: {}".format(self.Time, self.Portfolio.TotalPortfolioValue)) self.Log("{} - CashBook: {}".format(self.Time, self.Portfolio.CashBook))