| Overall Statistics |
|
Total Trades 4282 Average Win 0.09% Average Loss -0.07% Compounding Annual Return 65.909% Drawdown 6.300% Expectancy 0.231 Net Profit 44.597% Sharpe Ratio 4.313 Sortino Ratio 6.063 Probabilistic Sharpe Ratio 99.961% Loss Rate 44% Win Rate 56% Profit-Loss Ratio 1.18 Alpha 0 Beta 0 Annual Standard Deviation 0.09 Annual Variance 0.008 Information Ratio 4.915 Tracking Error 0.09 Treynor Ratio 0 Total Fees $5840.69 Estimated Strategy Capacity $3900000.00 Lowest Capacity Asset HLT VMCPV8L4GJTX Portfolio Turnover 108.55% |
# import python libraries
from AlgorithmImports import *
import random
################################################################################
class CustomFillModel(ImmediateFillModel):
def __init__(self, algorithm):
super().__init__()
self.algorithm = algorithm
self.absoluteRemainingByOrderId = {}
self.random = Random(387510346)
# def MarketFill(self, asset: Security, order: MarketOrder) -> OrderEvent:
# absoluteRemaining = order.AbsoluteQuantity
# if order.Id in self.absoluteRemainingByOrderId.keys():
# absoluteRemaining = self.absoluteRemainingByOrderId[order.Id]
# fill = super().MarketFill(asset, order)
# absoluteFillQuantity = int(
# min(absoluteRemaining,
# self.random.Next(0, 2*int(order.AbsoluteQuantity)))
# )
# fill.FillQuantity = np.sign(order.Quantity) * absoluteFillQuantity
# if absoluteRemaining == absoluteFillQuantity:
# fill.Status = OrderStatus.Filled
# if self.absoluteRemainingByOrderId.get(order.Id):
# self.absoluteRemainingByOrderId.pop(order.Id)
# else:
# absoluteRemaining = absoluteRemaining - absoluteFillQuantity
# self.absoluteRemainingByOrderId[order.Id] = absoluteRemaining
# fill.Status = OrderStatus.PartiallyFilled
# self.algorithm.MyLog(f"CustomFillModel: {fill}")
# return fill
def LimitFill(self, asset: Security, order: LimitOrder) -> OrderEvent:
fill = super().LimitFill(asset, order)
# Set the fill price to the be limit order price
limit_price = order.LimitPrice
fill.FillPrice = limit_price
return super().LimitFill(asset, order)
def MarketOnOpenFill(self, asset: Security, order: MarketOnOpenOrder) -> OrderEvent:
return super().MarketOnOpenFill(asset, order)
def MarketOnCloseFill(self, asset: Security, order: MarketOnCloseOrder) -> OrderEvent:
return super().MarketOnCloseFill(asset, order)
"""
Alternative Data Strategy
Version 1.0.3
Platform: QuantConnect
By: Aaron Eller
www.excelintrading.com
aaron@excelintrading.com
Revision Notes:
1.0.0 (04/18/2023) - Initial rewrite from 'Alpha V3'.
- Added scheduled function to get daily signal prior
to the premarket open up until the regular session
market open.
- Added logic to verify orders were not invalid.
- Added option for initial orders to be market on open
- Added slippage and fill models.
- Added max intraday leverage tracking and plot.
1.0.1 (04/20/2023) - Changed log in self.GetSignal() when signal not found
to be more clear.
- Added debugging logs in self.GetSignal() to see what
the contents of the Google Sheet are for each check.
1.0.2 (04/21/2023) - Explicitly put to allow orders outside of regular trading
hours.
- Changed self.file to be filename and not download.
1.0.3 (04/25/2023) - Added self.OnOrderEvent built-in function.
- Added self.AfterMarketClose scheduled event.
- Added tag to HLT end of day exit order.
- Added tag to all other end of day exit orders.
- Cancel any open entry orders still open at liquidation times.
- Updated TaggedTrailingStop to skip after daily liquidations.
- Added entry fill stat variables and logs at end of backtest.
- Added logs to TaggedTrailingStop when triggered.
1.0.4 (04/28/2023) - Kash - Updated code to track premarket and rth fills hourly
- Added daily profit and loss plot
Consider ignoring TaggedTrailingStop during premarket?
Make sure TaggedTrailingStop doesn't trigger multiple times in premarket
References:
-QC (Lean) Class List
https://lean-api-docs.netlify.app/annotated.html
"""
# import python libraries
from AlgorithmImports import *
from io import StringIO
import pandas as pd
import datetime as dt
# Import from files
from reality_models import CustomFillModel
from TaggedTrailingStop import TaggedTrailingStop
################################################################################
# User inputs / global variables
PRINT_MESSAGES = True
EXTENDED_MARKET_HOURS = True
# Turn on/off using market on open orders #
# If set to True, it instead enters the initial positions via market on open orders rather than limit orders in the premarket session.
# -> test impact of premarket order fills that are not as realistic
USE_MARKET_ON_OPEN = False
# Set the limit order offset amount
LIMIT_ORDER_OFFSET_AMOUNT = 0.05
# Turn on/off using a custom fill model
# Will fill limit orders at the limit price
USE_CUSTOM_FILL = False
# Constant percentage slippage model
USE_CONSTANT_SLIPPAGE = False
CONSTANT_SLIPPAGE_PERCENT = 0.0003
# default = 0.0001 or 0.01%, which is pretty low, e.g. $0.01 for $100 price
# Volume share slippage model
USE_VOLUME_SHARE_SLIPPAGE = False
VS_VOLUME_LIMIT = 0.05 # default #0.025
VS_PRICE_IMPACT = 0.2 # default 0.1
################################################################################
class AlternativeDataAlgorithm(QCAlgorithm):
def Initialize(self):
# Set backtest details
self.SetBacktestDetails()
# Add strategy variables required for the algo
self.AddStrategyVariables()
# Add instrument data to the algo
self.AddInstrumentData()
# Schedule functions
self.ScheduleFunctions()
# Add risk management model
#self.AddRiskManagement(TaggedTrailingStop(self, 0.05))
# Set benchmark
# self.SetBenchmark("SPY")
# self.SetWarmUp(timedelta(60))
#Add entry check directory
self.entry_orders_filled_premarket = {}
self.entry_orders_filled_rth = {}
#------------------------------------------------------------------------------
def SetBacktestDetails(self):
"""Set the backtest details."""
self.SetStartDate(2023, 4, 26)
self.SetCash(250000)
self.SetTimeZone("US/Eastern")
# Setup trading framework
# Transaction and submit/execution rules will use IB models
self.SetBrokerageModel(
BrokerageName.InteractiveBrokersBrokerage,
AccountType.Margin
)
# Make sure that orders are allowed to fill in the premarket hours
self.DefaultOrderProperties = InteractiveBrokersOrderProperties()
self.DefaultOrderProperties.OutsideRegularTradingHours = True
# Set the percentage of the portfolio to free up for position sizing
# calculations to avoid buying power issues
self.Settings.FreePortfolioValuePercentage = 0.05
# Configure all universe securities
# This sets the data normalization mode
# You can also set custom fee, slippage, fill, and buying power models
self.SetSecurityInitializer(
CustomSecurityInitializer(
self.BrokerageModel,
FuncSecuritySeeder(self.GetLastKnownPrices),
self
)
)
#------------------------------------------------------------------------------
def AddStrategyVariables(self):
"""Create required strategy variables."""
# Save link to the Alternative Data Stream link
self.file = 'https://docs.google.com/spreadsheets/d/19yCql9Ts95qIF' + \
'i5kDT-F7_h_ZIqqHN3WQ0y9RVgRzuA/export?format=csv' #ALPHA 2
# self.file = 'https://docs.google.com/spreadsheets/d/1zSmq9U8IoqXojgXVIOwUJ' + \
# 'BLdwosTKNfKGSWvPQx8fxY/export?format=csv' #ALPHA 4
# self.file = 'https://docs.google.com/spreadsheets/d/1PWnU6QCP7hQN1ONSpbLBE6bSWu8' + \
# 'XjvdArUEd8HbJ7KQ/export?format=csv' #ALPHA 1
# Keep track of the daily signal
self.signal = None
# Set the hedge percentage amount
self.hedgeAmount = 0.10 # 20% hedge
# Define the list of tickers and hedges to trade
# self.tickers=[
# "HLT","H","MAR","CHH","WH","IHG","SHO","MGM", #HOSPITALITY
# "EXR",'SELF',"NSA","PSA","CUBE","LSI" #STORAGE
# ]
self.tickers=[
"HLT","NSA","PSA","CUBE","MLM","H","MAR",'WH','CHH','EXR','LSI'
]
self.hedges = ["AMED","SAM","SMG"]
# Calculate the traded ticker's weight
self.weight = (1-self.hedgeAmount) / len(self.tickers)
# Calculate the desired weight per hedge instrument
self.hedge_weight = (1.0 / len(self.hedges)) * self.hedgeAmount
# Create dictionary for pointer to the SymbolData instances
self.symbolData = {}
# Keep track of max leverage for the day
self.max_leverage = 0
# Keep track when HLT is liquidated each day
self.hlt_liquidated = False
# Keep track when the remaining symbols are liquidated each day
self.liquidated = False
# Track entry orders filled premarket vs rth
self.entry_orders_filled_premarket = 0
self.entry_orders_filled_rth = 0
self.entry_orders_canceled = 0
#For profit and loss
self.yesterday_total_profit = 0
self.yesterday_total_fees = 0
#------------------------------------------------------------------------------
def AddInstrumentData(self):
"""Add instrument data to the algo."""
# Save the data resolution that we want to use
if self.LiveMode:
self.data_resolution = Resolution.Second
else:
self.data_resolution = Resolution.Minute
# Loop through the list of tickers
self.symbols = {}
for ticker in self.tickers:
# Add data and save the QC Symbol object
symbol = self.AddEquity(
ticker, self.data_resolution, Market.USA, leverage=0.00,
extendedMarketHours=EXTENDED_MARKET_HOURS
).Symbol
# Save link to symbols
self.symbols[ticker] = symbol
# Create and save link to the SymbolData instance for the ticker
self.symbolData[symbol] = SymbolData(self, symbol, hedge=False)
# Loop through the list of hedges
for ticker in self.hedges:
# Add data and save the QC Symbol object
symbol = self.AddEquity(
ticker, self.data_resolution, Market.USA, leverage=0.00,
extendedMarketHours=EXTENDED_MARKET_HOURS
).Symbol
# Save link to symbols
self.symbols[ticker] = symbol
# Create and save link to the SymbolData instance for the ticker
self.symbolData[symbol] = SymbolData(self, symbol, hedge=True)
#------------------------------------------------------------------------------
def ScheduleFunctions(self):
"""Schedule the functions required by the algo."""
# Get the daily signal
# Premarket session is 4am-930am ET
t = dt.datetime(2023,1,1,3,55)
times_to_check = []
while True:
# Schedule event to get the signal
self.Schedule.On(
self.DateRules.EveryDay("HLT"),
self.TimeRules.At(t.hour, t.minute),
self.GetSignal
)
# Immediately break for backtesting
if not self.LiveMode:
# Download and save the file as a df
f = self.Download(self.file) # Alpha Signal 2
df = pd.read_csv(StringIO(f))
df['Date'] = pd.to_datetime(df['Date']).dt.date
self.df = df
break
times_to_check.append(t.time())
# Increment t by 1 minute
t += dt.timedelta(minutes=1)
# Break at 930am
if t.time() >= dt.time(9,30):
break
# Check for Trade Entry
self.Schedule.On(
self.DateRules.EveryDay("HLT"),
self.TimeRules.Every(TimeSpan.FromMinutes(1)),
self.TradeEntryCheck
)
# Liquidate HLT 180 minutes before the close
self.Schedule.On(
self.DateRules.EveryDay("HLT"),
self.TimeRules.BeforeMarketClose(self.tickers[0], 180),
self.HLTLiquidation
)
# Liquidate the positions 30 minutes prior to the close
self.Schedule.On(
self.DateRules.EveryDay("HLT"),
self.TimeRules.BeforeMarketClose(self.tickers[0], 30),
self.DailyLiquidation
)
# Reset variables after the close
self.Schedule.On(
self.DateRules.EveryDay("HLT"),
self.TimeRules.BeforeMarketClose(self.tickers[0], -5),
self.AfterMarketClose
)
#------------------------------------------------------------------------------
def MyLog(self, message):
"""Add algo time to log if live trading. Otherwise just log message."""
if self.LiveMode:
self.Log(f'{self.Time}: {message}')
elif PRINT_MESSAGES:
self.Log(message)
#------------------------------------------------------------------------------
def GetSignal(self):
"""Read the Google doc's alternative data to get today's signal."""
# Skip if we already have the signal for the day
if self.signal != None:
return
# Read as a df
if self.LiveMode:
try:
f = self.Download(self.file) # Alpha Signal 2
df = pd.read_csv(StringIO(f))
df['Date'] = pd.to_datetime(df['Date']).dt.date
# For debugging
self.MyLog(f"Google Sheet last row: {df.tail(1)}")
except:
self.MyLog("Error reading Google doc!")
self.signal = None
return
else:
# Backtesting: we've already downloaded the contents of the file
df = self.df
# Get and save the alternative data signal for today
alternative_data = df[df['Date'] == self.Time.date()]
# Set signal to None if empty dataframe
if alternative_data.empty:
self.signal = None
self.MyLog(f"Do not have today's ({self.Time.date()}) signal")
else:
self.signal = alternative_data['Signal'].iloc[0]
self.MyLog(f"Today's signal: {self.signal}")
#------------------------------------------------------------------------------
def TradeEntryCheck(self):
# Skip if warming up the algo
if self.IsWarmingUp:
return
# Get a list of the open orders
openOrders = self.Transactions.GetOpenOrders()
# Get a list of tickers we are invested in
invested = [x.Symbol.Value for x in self.Portfolio.Values if x.Invested]
# Skip if we have open orders or positions for each ticker
if (len(openOrders)+len(invested))==(len(self.tickers)+len(self.hedges)):
return
# Skip if <= 4am or after 10am
if self.Time.time() <= dt.time(4, 0) or self.Time.time() > dt.time(10, 0):
return
# Enter trades based on today's signal
self.ExecuteTrade()
#------------------------------------------------------------------------------
def ExecuteTrade(self):
"""Execute today's trades based on the signal and desired weights."""
# Return if signal not 1 or 2
if self.signal != 1 and self.signal != 2:
return
# Otherwise loop through the traded tickers
for ticker in self.tickers:
# Get the SymbolData instance for the ticker and it's symbol
symbol = self.symbols[ticker]
sd = self.symbolData[symbol]
symbol = sd.symbol
# Check if not traded for today
if not sd.traded_flag:
# Get the desired weight and qty to order
if self.signal == 1:
weight = self.weight
else:
weight = -self.weight
quantity = self.CalculateOrderQuantity(symbol, weight)
# Skip if quantity is 0
if quantity == 0:
continue
# Log message
price = self.Securities[symbol].Price
bid = self.Securities[symbol].BidPrice
ask = self.Securities[symbol].AskPrice
self.MyLog(
f"{ticker} desired weight={weight}, price={price}, "
f"and desired qty={quantity} / bid={bid}, ask={ask}"
)
# Use a market on open order when desired
if USE_MARKET_ON_OPEN:
order = self.MarketOnOpenOrder(
symbol, quantity, tag="MOO entry order"
)
# Set traded_flag if order is accepted
if order.Status != OrderStatus.Invalid:
sd.traded_flag = True
sd.entry_order = order
# Use a limit order if before the market open
elif self.Time.time() < dt.time(9, 30):
# Get the limit price
limit_price = None
if price is not None and bid is not None and ask is not None:
if price != 0 and bid != 0 and ask != 0:
# Add buffer to buy price
if weight > 0:
limit_price = \
round(price+LIMIT_ORDER_OFFSET_AMOUNT,2)
# Or subtract buffer from sell price
else:
limit_price = \
round(price-LIMIT_ORDER_OFFSET_AMOUNT,2)
# Create limit order if we have a valid price
if limit_price:
order = self.LimitOrder(
symbol, quantity, limit_price, tag='entry limit order'
)
# Set traded_flag if order is accepted
if order.Status != OrderStatus.Invalid:
sd.traded_flag = True
sd.entry_order = order
# Otherwise use a market order
else:
order = self.MarketOrder(
symbol, quantity, tag='entry market order'
)
# Set traded_flag if order is accepted
if order.Status != OrderStatus.Invalid:
sd.traded_flag = True
sd.entry_order = order
# Always rebalance the hedges
self.HedgeRebalance()
#------------------------------------------------------------------------------
def HedgeRebalance(self):
"""Execute today's hedge trades."""
# Loop through the list of hedges
for ticker in self.hedges:
# Get the SymbolData instance for the ticker and it's symbol
symbol = self.symbols[ticker]
sd = self.symbolData[symbol]
symbol = sd.symbol
# Check if not traded for today
if not sd.traded_flag:
# Get the desired weight and qty to order
if self.signal == 1:
weight = -self.hedge_weight
else:
weight = self.hedge_weight
quantity = self.CalculateOrderQuantity(symbol, weight)
# Skip if quantity is 0
if quantity == 0:
continue
# Log message
price = self.Securities[symbol].Price
bid = self.Securities[symbol].BidPrice
ask = self.Securities[symbol].AskPrice
self.MyLog(
f"{ticker} desired weight={weight}, price={price}, "
f"and desired qty={quantity} / bid={bid}, ask={ask}"
)
# Use a limit order if before the market open
if self.Time.time() < dt.time(9, 30):
# Get the limit price
limit_price = None
if price is not None and bid is not None and ask is not None:
if price != 0 and bid != 0 and ask != 0:
# Is this correct or backwards?
# Subtract buffer from buy price
if weight > 0:
limit_price = \
round(price-LIMIT_ORDER_OFFSET_AMOUNT,2)
# Or add buffer to sell price
else:
limit_price = \
round(price+LIMIT_ORDER_OFFSET_AMOUNT,2)
# Create limit order if we have a valid price
if limit_price:
order = self.LimitOrder(
symbol, quantity, limit_price, tag='entry limit order'
)
# Set traded_flag if order is accepted
if order.Status != OrderStatus.Invalid:
sd.traded_flag = True
sd.entry_order = order
# Otherwise use a market order
else:
order = self.MarketOrder(
symbol, quantity, tag='entry market order'
)
# Set traded_flag if order is accepted
if order.Status != OrderStatus.Invalid:
sd.traded_flag = True
sd.entry_order = order
#------------------------------------------------------------------------------
def HLTLiquidation(self):
"""Liquidate open HLT position at the end of the day."""
self.MyLog("Time for HLTLiquidation")
# Keep track when HLT is liquidated each day
self.hlt_liquidated = True
# Reset the traded_flag variable
symbol = self.symbols["HLT"]
sd = self.symbolData[symbol]
sd.traded_flag = False
# Cancel any open entry orders
if sd.entry_order:
response = sd.entry_order.Cancel()
if response.IsSuccess:
sd.entry_order = None
self.entry_orders_canceled += 1
# Exit any open position immediately with a market order
quantity = self.Portfolio["HLT"].Quantity
if quantity != 0:
self.MarketOrder("HLT", -quantity, tag="HLT EOD exit")
self.Log(f"HLT end of day exit for {quantity} shares")
#------------------------------------------------------------------------------
def DailyLiquidation(self):
"""Liquidate any open positions at the end of the day."""
self.MyLog("Time for DailyLiquidation")
# Keep track when the remaining symbols are liquidated each day
self.liquidated = True
# Loop through SymbolData instances
for symbol, sd in self.symbolData.items():
# Reset the traded_flag variable
sd.traded_flag = False
# Cancel any open entry orders
if sd.entry_order:
response = sd.entry_order.Cancel()
if response.IsSuccess:
sd.entry_order = None
self.entry_orders_canceled += 1
# Exit any open position with a market on close order
quantity = self.Portfolio[symbol].Quantity
if quantity != 0:
self.MarketOnCloseOrder(symbol, -quantity, tag="EOD MOC exit")
self.Log(f"End of day exit for {quantity} {symbol} shares")
# Set the signal back to None
# Will get reset the next market day
self.signal = None
# Plot the account leverage
self.Plot('Leverage', 'Leverge', self.max_leverage)
# Keep track of max leverage for the day
self.max_leverage = 0
#------------------------------------------------------------------------------
# def AfterMarketClose(self):
# """Function called after the end of the day."""
# # Reset liquidated variables
# self.hlt_liquidated = False
# self.liquidated = False
# # Track leverage end of day
# leverage = self.Portfolio.TotalHoldingsValue \
# / self.Portfolio.TotalPortfolioValue
# for symbol, sd in self.symbolData.items():
# qty = self.Portfolio[symbol].Quantity
# if qty != 0:
# self.Log(f"Open position at end of day! {qty} of {symbol}")
# if not self.LiveMode:
# raise
# self.Plot('End of Day Leverage', 'End of Day Leverge', leverage)
def AfterMarketClose(self):
"""Function called after the end of the day."""
# Reset liquidated variables
self.hlt_liquidated = False
self.liquidated = False
# Track leverage end of day
leverage = self.Portfolio.TotalHoldingsValue \
/ self.Portfolio.TotalPortfolioValue
for symbol, sd in self.symbolData.items():
qty = self.Portfolio[symbol].Quantity
if qty != 0:
self.Log(f"Open position at end of day! {qty} of {symbol}")
if not self.LiveMode:
self.Debug(f"Open position at end of day! {qty} of {symbol}")
else:
# Handle the exception in live mode
self.Log("Open position at end of day! Exception handled.")
self.Plot('End of Day Leverage', 'End of Day Leverage', leverage)
#-------------------------------------------------------------------------------
def OnData(self, data):
"""Built-in event handler for new data."""
# Track max intraday leverage
leverage = self.Portfolio.TotalHoldingsValue \
/ self.Portfolio.TotalPortfolioValue
if leverage > self.max_leverage:
self.max_leverage = leverage
# Added Profit and Loss Visual
# self.Plot("Daily Realized Pnl", "Value", self.get_daily_realized_pnl())
def OnEndOfDay(self):
self.yesterday_total_profit = self.Portfolio.TotalProfit
self.yesterday_total_fees = self.Portfolio.TotalFees
# def get_daily_realized_pnl(self):
# daily_gross_profit = self.Portfolio.TotalProfit - self.yesterday_total_profit
# daily_fees = self.Portfolio.TotalFees - self.yesterday_total_fees
# return daily_gross_profit - daily_fees
#-------------------------------------------------------------------------------
def OnEndOfAlgorithm(self):
"""Built-in event handler for end of backtest."""
# Log entry orders filled premarket by hour
for hour, count in self.entry_orders_filled_premarket.items():
self.Debug(f"{count} entry orders filled premarket in hour {hour}."
)
# Log entry orders filled in regular trading session by hour
for hour, count in self.entry_orders_filled_rth.items():
self.Debug(f"{count} entry orders filled in regular trading session for hour {hour}."
)
self.Debug(f"{self.entry_orders_canceled} entry orders not filled.")
#-------------------------------------------------------------------------------
def OnOrderEvent(self, orderEvent):
"""Built-in event handler for orders."""
# Skip if not filled
if orderEvent.Status != OrderStatus.Filled:
if self.LiveMode:
self.Log(f"New order event: {orderEvent}")
return
# Get the order details
order = self.Transactions.GetOrderById(orderEvent.OrderId)
order_qty = int(order.Quantity)
avg_fill = orderEvent.FillPrice
tag = order.Tag
symbol = order.Symbol
# Log message for order
self.Log(f"{symbol} {tag} order filled: {order_qty} @ {avg_fill}")
# Get the SymbolData instance for the symbol
if self.symbolData.get(symbol) is None:
# Catch for backtesting to handle before live trading
if not self.LiveMode:
raise ValueError(
f"SymbolData instance not found for {symbol}"
)
self.Log(f"SymbolData instance not found for {symbol}")
return
else:
sd = self.symbolData.get(symbol)
# Check for type of order filled
if 'entry' in tag:
sd.entry_order = None
# Update counter
if self.Time.time() < dt.time(9, 30):
hour = self.Time.hour
if hour in self.entry_orders_filled_premarket:
self.entry_orders_filled_premarket[hour] += 1
else:
self.entry_orders_filled_premarket[hour] = 1
else:
hour = self.Time.hour
if hour in self.entry_orders_filled_rth:
self.entry_orders_filled_rth[hour] += 1
else:
self.entry_orders_filled_rth[hour] = 1
################################################################################
class SymbolData(object):
"""Class to store data for a specific symbol."""
def __init__(self, algo, symbol_object, hedge=False):
"""Initialize SymbolData object."""
# Save a reference to the QCAlgorithm class
self.algo = algo
# Save the string symbol as ticker and the .Symbol object as symbol
self.ticker = symbol_object.Value
self.symbol = symbol_object
# Save if the symbol is a hedge
self.hedge = hedge
# Keep track if the symbol has been traded today
self.traded_flag = None
# Keep track of the entry order
self.entry_order = None
###############################################################################
class CustomSecurityInitializer(BrokerageModelSecurityInitializer):
def __init__(
self,
brokerage_model: IBrokerageModel,
security_seeder: ISecuritySeeder,
algorithm
) -> None:
self.algorithm = algorithm
super().__init__(brokerage_model, security_seeder)
def Initialize(self, security: Security) -> None:
"""
Define models to be used for securities as they are added to the
algorithm's universe.
"""
# First, call the superclass definition
# This method sets the reality models of each security using the
# default reality models of the brokerage model
super().Initialize(security)
# Define the data normalization mode
security.SetDataNormalizationMode(DataNormalizationMode.Raw)
# Define the fee model to use for the security
# security.SetFeeModel()
# Define the slippage model to use for the security
if USE_VOLUME_SHARE_SLIPPAGE:
security.SetSlippageModel(
VolumeShareSlippageModel(VS_VOLUME_LIMIT, VS_PRICE_IMPACT)
)
if USE_CONSTANT_SLIPPAGE:
security.SetSlippageModel(
ConstantSlippageModel(CONSTANT_SLIPPAGE_PERCENT)
)
# security.SetSlippageModel(AlphaStreamsSlippageModel())
# Define the fill model to use for the security
if USE_CUSTOM_FILL:
security.SetFillModel(CustomFillModel(self.algorithm))
# Define the buying power model to use for the security
# security.SetBuyingPowerModel()#region imports
from AlgorithmImports import *
#endregion
# Your New Python File
from AlgorithmImports import *
import datetime as dt
class TaggedTrailingStop(RiskManagementModel):
def __init__(self, algo, trailingStopPercent=0.05, trailingStopActivationPercent=0.03, partialLiquidationPercent=0.00):
self.algo = algo
self.trailingStopPercent = abs(trailingStopPercent)
self.trailingStopActivationPercent = abs(trailingStopActivationPercent)
self.partialLiquidationPercent = abs(partialLiquidationPercent)
self.trailingAbsoluteHoldingsState = dict()
self.highestProfit = dict()
self.marketVolatility = None # Placeholder for market volatility
def ManageRisk(self, algorithm, targets):
# Update market volatility (e.g., using VIX or a custom metric)
self.marketVolatility = self.calculateMarketVolatility()
riskAdjustedTargets = list()
if self.algo.Time.time() < dt.time(9, 31):
return riskAdjustedTargets
for kvp in algorithm.Securities:
symbol = kvp.Key
security = kvp.Value
# Initialize the symbol in dictionaries if not present
if symbol not in self.trailingAbsoluteHoldingsState:
self.trailingAbsoluteHoldingsState[symbol] = HoldingsState(None, 0)
if symbol not in self.highestProfit:
self.highestProfit[symbol] = 0
if not security.HasData or not security.Invested:
self.resetState(symbol)
continue
self.updateHoldingsState(symbol, security)
trailingAbsoluteHoldingsState = self.trailingAbsoluteHoldingsState[symbol]
unrealizedProfitPercent = self.calculateUnrealizedProfitPercent(symbol, security)
# Incorporate fundamental data in trailing stop logic
fundamentalMultiplier = self.getFundamentalMultiplier(symbol, security)
# Use dict.get() method to safely access the value with a default if key doesn't exist
highestProfitForSymbol = self.highestProfit.get(symbol, 0) # Default to 0 if not present
# Dynamic adjustment of trailing stop level
trailingStopLevel = max(self.trailingStopPercent * fundamentalMultiplier * (1 - self.highestProfit[symbol]),
self.trailingStopActivationPercent * self.marketVolatility)
trailingStopPrice = trailingAbsoluteHoldingsState.absoluteHoldingsValue * (1 - trailingStopLevel)
if self.shouldTriggerStop(unrealizedProfitPercent, security, trailingStopPrice):
self.executeTrailingStop(symbol, security, trailingStopPrice, riskAdjustedTargets)
return riskAdjustedTargets
# Additional methods to be implemented
def calculateMarketVolatility(self):
# Example: Use a proxy like VIX to represent market volatility
# This requires VIX data to be added to the algorithm's universe
vixSecurity = self.algo.Securities['VIX']
if vixSecurity.Price > 0:
return vixSecurity.Price / 100 # Normalize the VIX value
else:
return 1 # Default value if VIX data is not available
def getFundamentalMultiplier(self, symbol, security):
# Example: Use P/E ratio as a fundamental factor
# This requires fundamental data for the securities
if security.Fundamentals.ValuationRatios.PERatio is not None:
peRatio = security.Fundamentals.ValuationRatios.PERatio
# Example logic: Lower P/E ratio might imply a stronger fundamental position
return 1 / max(peRatio, 1) # Ensuring the multiplier is never zero
else:
return 1 # Default multiplier if P/E ratio is not available
def calculateUnrealizedProfitPercent(self, symbol, security):
holdingsState = self.trailingAbsoluteHoldingsState.get(symbol, None)
if holdingsState is not None:
return (security.Holdings.UnrealizedProfit / holdingsState.absoluteHoldingsValue)
return 0
def shouldTriggerStop(self, unrealizedProfitPercent, security, trailingStopPrice):
# Trigger stop if unrealized profit is beyond a certain threshold and price is below trailing stop price
return unrealizedProfitPercent >= self.trailingStopActivationPercent and security.Price <= trailingStopPrice
def executeTrailingStop(self, symbol, security, trailingStopPrice, riskAdjustedTargets):
price = security.Price
partialLiquidationValue = self.partialLiquidationPercent * security.Holdings.AbsoluteHoldingsValue
sharesToSell = partialLiquidationValue / price
self.algo.Log(f"Trailing stop triggered for {symbol}. Selling {sharesToSell} shares.")
riskAdjustedTargets.append(PortfolioTarget(symbol, security.Holdings.Quantity - sharesToSell))
def resetState(self, symbol):
self.trailingAbsoluteHoldingsState.pop(symbol, None)
self.highestProfit.pop(symbol, None)
def updateHoldingsState(self, symbol, security):
position = PositionSide.Long if security.Holdings.IsLong else PositionSide.Short
absoluteHoldingsValue = security.Holdings.AbsoluteHoldingsValue
if symbol not in self.trailingAbsoluteHoldingsState:
# If the symbol is not already in the state dictionary, add it
self.trailingAbsoluteHoldingsState[symbol] = HoldingsState(position, absoluteHoldingsValue)
else:
# If the symbol is already in the state dictionary, update it
trailingState = self.trailingAbsoluteHoldingsState[symbol]
if trailingState.position != position:
# If the position type (long/short) has changed, update the holdings state
self.trailingAbsoluteHoldingsState[symbol] = HoldingsState(position, absoluteHoldingsValue)
else:
# If the position type is the same, update the holdings value if it's higher than the recorded value
trailingState.absoluteHoldingsValue = max(trailingState.absoluteHoldingsValue, absoluteHoldingsValue)
class HoldingsState:
def __init__(self, position, absoluteHoldingsValue):
self.position = position
self.absoluteHoldingsValue = absoluteHoldingsValue
#region imports
from AlgorithmImports import *
#endregion
# Your New Python File
from AlgorithmImports import *
import datetime as dt
class TaggedTrailingStop(RiskManagementModel):
def __init__(self, algo, trailingStopPercent=0.05, trailingStopActivationPercent=0.03, partialLiquidationPercent=0.00):
self.algo = algo
self.trailingStopPercent = abs(trailingStopPercent)
self.trailingStopActivationPercent = abs(trailingStopActivationPercent)
self.partialLiquidationPercent = abs(partialLiquidationPercent)
self.trailingAbsoluteHoldingsState = dict()
self.highestProfit = dict()
self.marketVolatility = None # Placeholder for market volatility
def ManageRisk(self, algorithm, targets):
# Update market volatility (e.g., using VIX or a custom metric)
self.marketVolatility = self.calculateMarketVolatility()
riskAdjustedTargets = list()
if self.algo.Time.time() < dt.time(9, 31):
return riskAdjustedTargets
for kvp in algorithm.Securities:
symbol = kvp.Key
security = kvp.Value
# Initialize the symbol in dictionaries if not present
if symbol not in self.trailingAbsoluteHoldingsState:
self.trailingAbsoluteHoldingsState[symbol] = HoldingsState(None, 0)
if symbol not in self.highestProfit:
self.highestProfit[symbol] = 0
if not security.HasData or not security.Invested:
self.resetState(symbol)
continue
self.updateHoldingsState(symbol, security)
trailingAbsoluteHoldingsState = self.trailingAbsoluteHoldingsState[symbol]
unrealizedProfitPercent = self.calculateUnrealizedProfitPercent(symbol, security)
# Incorporate fundamental data in trailing stop logic
fundamentalMultiplier = self.getFundamentalMultiplier(symbol, security)
# Use dict.get() method to safely access the value with a default if key doesn't exist
highestProfitForSymbol = self.highestProfit.get(symbol, 0) # Default to 0 if not present
# Dynamic adjustment of trailing stop level
trailingStopLevel = max(self.trailingStopPercent * fundamentalMultiplier * (1 - self.highestProfit[symbol]),
self.trailingStopActivationPercent * self.marketVolatility)
trailingStopPrice = trailingAbsoluteHoldingsState.absoluteHoldingsValue * (1 - trailingStopLevel)
if self.shouldTriggerStop(unrealizedProfitPercent, security, trailingStopPrice):
self.executeTrailingStop(symbol, security, trailingStopPrice, riskAdjustedTargets)
return riskAdjustedTargets
# Additional methods to be implemented
def calculateMarketVolatility(self):
# Example: Use a proxy like VIX to represent market volatility
# This requires VIX data to be added to the algorithm's universe
vixSecurity = self.algo.Securities['VIX']
if vixSecurity.Price > 0:
return vixSecurity.Price / 100 # Normalize the VIX value
else:
return 1 # Default value if VIX data is not available
def getFundamentalMultiplier(self, symbol, security):
# Example: Use P/E ratio as a fundamental factor
# This requires fundamental data for the securities
if security.Fundamentals.ValuationRatios.PERatio is not None:
peRatio = security.Fundamentals.ValuationRatios.PERatio
# Example logic: Lower P/E ratio might imply a stronger fundamental position
return 1 / max(peRatio, 1) # Ensuring the multiplier is never zero
else:
return 1 # Default multiplier if P/E ratio is not available
def calculateUnrealizedProfitPercent(self, symbol, security):
holdingsState = self.trailingAbsoluteHoldingsState.get(symbol, None)
if holdingsState is not None:
return (security.Holdings.UnrealizedProfit / holdingsState.absoluteHoldingsValue)
return 0
def shouldTriggerStop(self, unrealizedProfitPercent, security, trailingStopPrice):
# Trigger stop if unrealized profit is beyond a certain threshold and price is below trailing stop price
return unrealizedProfitPercent >= self.trailingStopActivationPercent and security.Price <= trailingStopPrice
def executeTrailingStop(self, symbol, security, trailingStopPrice, riskAdjustedTargets):
price = security.Price
partialLiquidationValue = self.partialLiquidationPercent * security.Holdings.AbsoluteHoldingsValue
sharesToSell = partialLiquidationValue / price
self.algo.Log(f"Trailing stop triggered for {symbol}. Selling {sharesToSell} shares.")
riskAdjustedTargets.append(PortfolioTarget(symbol, security.Holdings.Quantity - sharesToSell))
def resetState(self, symbol):
self.trailingAbsoluteHoldingsState.pop(symbol, None)
self.highestProfit.pop(symbol, None)
def updateHoldingsState(self, symbol, security):
position = PositionSide.Long if security.Holdings.IsLong else PositionSide.Short
absoluteHoldingsValue = security.Holdings.AbsoluteHoldingsValue
if symbol not in self.trailingAbsoluteHoldingsState:
# If the symbol is not already in the state dictionary, add it
self.trailingAbsoluteHoldingsState[symbol] = HoldingsState(position, absoluteHoldingsValue)
else:
# If the symbol is already in the state dictionary, update it
trailingState = self.trailingAbsoluteHoldingsState[symbol]
if trailingState.position != position:
# If the position type (long/short) has changed, update the holdings state
self.trailingAbsoluteHoldingsState[symbol] = HoldingsState(position, absoluteHoldingsValue)
else:
# If the position type is the same, update the holdings value if it's higher than the recorded value
trailingState.absoluteHoldingsValue = max(trailingState.absoluteHoldingsValue, absoluteHoldingsValue)
class HoldingsState:
def __init__(self, position, absoluteHoldingsValue):
self.position = position
self.absoluteHoldingsValue = absoluteHoldingsValue