| Overall Statistics |
|
Total Orders 811 Average Win 4.19% Average Loss -5.73% Compounding Annual Return 7.368% Drawdown 61.400% Expectancy 0.112 Start Equity 10000 End Equity 53846.09 Net Profit 438.461% Sharpe Ratio 0.25 Sortino Ratio 0.148 Probabilistic Sharpe Ratio 0.004% Loss Rate 36% Win Rate 64% Profit-Loss Ratio 0.73 Alpha 0.03 Beta 0.76 Annual Standard Deviation 0.263 Annual Variance 0.069 Information Ratio 0.079 Tracking Error 0.237 Treynor Ratio 0.087 Total Fees $10728.49 Estimated Strategy Capacity $11000000.00 Lowest Capacity Asset TQQQ UK280CGTCB51 Portfolio Turnover 9.36% |
# region imports
from AlgorithmImports import *
# endregion
# Your New Python File
class PortfolioManager():
def __init__(self, algo):
self.algo = algo
def HasHoldings(self, symbol):
if self.algo.Securities[symbol].Type == SecurityType.Crypto:
## TODO: Explore a better way to do this. Not clear how base_currency.amount should be used
min_lot_size = self.algo.securities[symbol].symbol_properties.lot_size
asset = self.algo.securities[symbol]
base_currency = asset.base_currency
# quantity = min(asset.holdings.quantity, base_currency.amount)
quantity = abs(asset.holdings.quantity)
# abs(self.securities[self.symbol].symbol_properties.lot_size - self.securities[self.symbol].holdings.quantity) > 0.000000001
return abs(quantity) >= self.algo.securities[symbol].symbol_properties.minimum_order_size
# return abs(quantity - min_lot_size) > min_lot_size
else:
return self.algo.Portfolio[symbol].Invested
# region imports
from AlgorithmImports import *
# endregion
# Your New Python File
class TradeUtils():
def __init__(self, algo):
self.algo = algo
# Convenience method to liquidate with a message
def LiquidateWithMsg(self, symbol, exitReason):
pnl = round(100 * self.algo.Portfolio[symbol].UnrealizedProfitPercent,2)
biasText = 'Long' if (self.algo.Portfolio[symbol].IsLong) else 'Short'
winlossText = 'win' if pnl > 0 else 'loss'
orderNote = f"[{pnl}% {winlossText}] {exitReason} | Exiting {biasText} position"
# If Crypto, call LiquidateMarketOrder
if self.algo.Securities[symbol].Type == SecurityType.Crypto:
self.LiquidateMarketOrder(symbol=symbol, tag=orderNote)
else:
self.algo.liquidate(symbol, tag=orderNote)
## Liquidate via market order. Necessary for crypto
def LiquidateMarketOrder(self, symbol, tag):
crypto = self.algo.securities[symbol]
base_currency = crypto.base_currency
# Avoid negative amount after liquidate
quantity = min(crypto.holdings.quantity, base_currency.amount)
# Round down to observe the lot size
lot_size = crypto.symbol_properties.lot_size;
quantity = (round(quantity / lot_size) - 1) * lot_size
if self.is_valid_order_size(crypto, quantity):
# self.algo.debug(f"------------ [START] Market Order: liquidation start")
self.algo.debug(f" Liquidating: {quantity} units of {symbol.Value}")
self.algo.market_order(symbol, -quantity, tag=tag)
self.algo.debug(f"Market Order liquidation was Successful")
self.algo.debug(f" Leftover: {crypto.holdings.quantity} units of {symbol.Value}")
self.algo.debug(f"------------ [END] Market Order liquidation")
if( abs(crypto.holdings.quantity) > lot_size):
self.LiquidateMarketOrder(symbol, tag="reomving trailing coins")
else:
self.algo.debug(f"ERROR ERRROR ---- ")
self.algo.debug(f"ERROR ERRROR Invalid order size: {quantity}")
# Brokerages have different order size rules
# Binance considers the minimum volume (price x quantity):
def is_valid_order_size(self, crypto, quantity):
return abs(crypto.price * quantity) > crypto.symbol_properties.minimum_order_size
def HasHoldings(self, symbol):
if self.algo.Securities[symbol].Type == SecurityType.Crypto:
## TODO: Explore a better way to do this. Not clear how base_currency.amount should be used
min_lot_size = self.algo.securities[symbol].symbol_properties.lot_size
asset = self.algo.securities[symbol]
base_currency = asset.base_currency
# quantity = min(asset.holdings.quantity, base_currency.amount)
quantity = abs(asset.holdings.quantity)
# abs(self.securities[self.symbol].symbol_properties.lot_size - self.securities[self.symbol].holdings.quantity) > 0.000000001
return abs(quantity) >= self.algo.securities[symbol].symbol_properties.minimum_order_size
# return abs(quantity - min_lot_size) > min_lot_size
else:
return self.algo.Portfolio[symbol].Investedfrom AlgorithmImports import *
from PortfolioManager import *
from TradeUtils import *
class IBSMeanReversionOnIndex(QCAlgorithm):
"""
Inspired by Quantitativo:
https://www.quantitativo.com/p/robustness-of-the-211-sharpe-mean
Additional reference:
https://tradingstrategy.medium.com/the-internal-bar-strength-ibs-indicator-trading-strategies-rules-video-e638f135edb6
Entry rules:
-----------------------
Compute the rolling mean of High minus Low over the last 25 days;
Compute the IBS indicator: (Close - Low) / (High - Low);
Compute a lower band as the rolling High over the last 10 days minus 2.5 x the rolling mean of High mins Low (first bullet);
Go long whenever SPY closes under the lower band (3rd bullet) and IBS is lower than 0.3;
Exit rules
-----------------------
- Close the trade whenever the SPY close is higher than yesterday's high;
- Close the trade whenever the price is lower than the 300-SMA.
"""
## Initialize the algo
## ------------------------
def Initialize(self):
self.FolioMgr = PortfolioManager(self)
self.TradeMgr = TradeUtils(self)
# Init backtest params, etc
self.ticker = "TQQQ" # Ticker symbol to trade
self.SetBenchmark("SPY") # Benchmark for reporting (buy and hold)
self.SetStartDate(2000, 12, 1) # Backtest start date
# self.SetEndDate(2023, 7, 11) # Backtest end date
self.SetCash(10000) # Starting portfolio balance
# Subscrbe to a minute data feed (minute bars)
self.symbol = self.AddEquity(self.ticker, Resolution.Minute).symbol
# Set up a rollingwindow to store consolidated daily bars
self.dailyBars = RollingWindow[TradeBar](2)
# Set up the daily bar consolidator
self.dailyConsolidator = TradeBarConsolidator(timedelta(days=1))
self.dailyConsolidator.DataConsolidated += self.OnDailyBarFormed
self.SubscriptionManager.AddConsolidator(self.symbol, self.dailyConsolidator)
# 300 SMA
self.sma_300 = self.sma(self.symbol, 300, Resolution.Daily)
# Schedule a daily chron job to check for signals at the open
self.Schedule.On(self.DateRules.EveryDay(), \
self.TimeRules.AfterMarketOpen(self.ticker, 5),
self.CheckForSignals)
def OnDailyBarFormed(self, val, dailyBar):
self.dailyBars.add(dailyBar)
def CheckForSignals(self):
if not self.Portfolio.Invested:
if self.EntrySignalFired():
self.SetHoldings(self.ticker, 1)
else:
if self.ExitSignalFired():
self.LiquidateWithMsg(self.symbol,f"IBS Signal Exit")
# self.Liquidate(tag=f"IBS Signal Exit")
## Go long when:
## - SPy closes under lower band
## - IBS < 0.3
## ------------------------------
def EntrySignalFired(self):
if self.dailyBars.IsReady:
# if SPY closes under the lower band (3rd bullet)
# If IBS is lower than 0.3;
# IBS= (Close-Low)/(High-Low)
lastBar = self.dailyBars[0]
if(lastBar.high != lastBar.low):
ibsValue = (lastBar.close - lastBar.low) / (lastBar.high - lastBar.low)
return (ibsValue < 0.2 )
return False
## Exit the trade when:
# - SPY close is higher than yesterday's high;
# - price is lower than the 300-SMA.
## -------------------------------------------------------------------------------
def ExitSignalFired(self):
if self.dailyBars.IsReady:
# if SPY closes under the lower band (3rd bullet)
# If IBS is lower than 0.3;
# IBS= (Close-Low)/(High-Low)
lastBar = self.dailyBars[0]
if(lastBar.high != lastBar.low):
ibsValue = (lastBar.close - lastBar.low) / (lastBar.high - lastBar.low)
return (ibsValue > 0.8 )
return False
# if self.Portfolio.Invested:
# if (self.dailyBars[0].close > self.dailyBars[1].high):
# self.Liquidate(tag=f"Exit @ last close > prev high: {self.dailyBars[0].close} > {self.dailyBars[1].high}")
# Convenience method to liquidate with PnL message
def LiquidateWithMsg(self, symbol, exitReason):
pnl = round(100 * self.Portfolio[symbol].UnrealizedProfitPercent,2)
biasText = 'Long' if (self.Portfolio[symbol].IsLong) else 'Short'
winlossText = 'win' if pnl > 0 else 'loss'
orderNote = f"[{pnl}% {winlossText}] {exitReason} | Exiting {biasText} position"
self.liquidate(symbol, tag=orderNote)