| 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))