Untitled Diff

Created Diff never expires
42 removals
335 lines
117 additions
417 lines
##########################################################################
##########################################################################
# Inspired by @nitay-rabinovich at QuqntConnect
# Inspired by @nitay-rabinovich at QuqntConnect
# https://www.quantconnect.com/forum/discussion/12768/share-kalman-filter-crossovers-for-crypto-and-smart-rollingwindows/p1/comment-38144
# https://www.quantconnect.com/forum/discussion/12768/share-kalman-filter-crossovers-for-crypto-and-smart-rollingwindows/p1/comment-38144
##########################################################################
##########################################################################
#
#
# EMA Crossover In a Crypto Universe
# EMA Crossover In a Crypto Universe
# ---------------------------------------------
# ---------------------------------------------
# FOR EDUCATIONAL PURPOSES ONLY. DO NOT DEPLOY.
# FOR EDUCATIONAL PURPOSES ONLY. DO NOT DEPLOY.
#
#
#
#
# Entry:
# Entry:
# -------
# -------
# Minimum volume threshold traded
# Minimum volume threshold traded
# and
# and
# Price > Fast Daily EMA
# Price > Fast Daily EMA
# and
# and
# Fast Daily EMA > Slow Daily EMA
# Fast Daily EMA > Slow Daily EMA
#
#
# Exit:
# Exit:
# ------
# ------
# Price < Slow Daily EMA
# Price < Slow Daily EMA
# or
# or
# Slow Daily EMA < Fast Daily EMA
# Slow Daily EMA < Fast Daily EMA
#
#
# Additional Consideration:
# Additional Consideration:
# --------------------------
# --------------------------
# Max exposure pct: Total % of available capital to trade with at any time
# Max exposure pct: Total % of available capital to trade with at any time
# Max holdings: Total # of positions that can be held simultaneously
# Max holdings: Total # of positions that can be held simultaneously
# Rebalance Weekly: If false, only rebalance when we add/remove positions
# Rebalance Weekly: If false, only rebalance when we add/remove positions
# UseMomWeight: If true, rebalance w/momentum-based weights (top gainers=more weight)
# UseMomWeight: If true, rebalance w/momentum-based weights (top gainers=more weight)
#
#
#########################################################################
#########################################################################


from SmartRollingWindow import *
from SmartRollingWindow import *
class EMACrossoverUniverse(QCAlgorithm):
class EMACrossoverUniverse(QCAlgorithm):
##
##
def Initialize(self):
def Initialize(self):
self.InitAlgoParams()
self.InitAlgoParams()
self.InitAssets()
self.InitAssets()
self.InitUniverse()
self.InitUniverse()
self.InitBacktestParams()
self.InitBacktestParams()
self.ScheduleRoutines()
self.ScheduleRoutines()


## Set backtest params: dates, cash, etc. Called from Initialize().
## Set backtest params: dates, cash, etc. Called from Initialize().
## ----------------------------------------------------------------
## ----------------------------------------------------------------
def InitBacktestParams(self):
def InitBacktestParams(self):
self.SetStartDate(2020, 1, 1)
self.SetStartDate(2020, 1, 1)
# self.SetEndDate(2019, 2, 1)
# self.SetEndDate(2019, 2, 1)
self.SetCash(100000)
self.SetCash(100000)
self.SetBenchmark(Symbol.Create("BTCUSDT", SecurityType.Crypto, Market.Binance))
self.SetBenchmark(Symbol.Create("BTCUSDT", SecurityType.Crypto, Market.Binance))


def InitUniverse(self):
def InitUniverse(self):
self.UniverseSettings.Resolution = Resolution.Daily
self.UniverseSettings.Resolution = Resolution.Daily
self.symDataDict = { }
self.symDataDict = { }


self.UniverseTickers = ["SOLUSDT", "ETHUSDT", "BNBUSDT", "ADAUSDT", "BTCUSDT"]
self.UniverseTickers = ["SOLUSDT", "ETHUSDT", "BNBUSDT", "ADAUSDT", "BTCUSDT"]

## More test tickers
##
# self.UniverseTickers = ["ANTUSDT","BATUSDT","BNBUSDT","BNTUSDT",
# "BTCUSDT", "BTGUSDT",
# "DAIUSDT","DASHUSDT","DGBUSDT",
# "EOSUSDT","ETCUSDT",
# "ETHUSDT","FUNUSDT",
# "IOTAUSDT","KNCUSDT","LRCUSDT",
# "LTCUSDT","MKRUSDT",
# "NEOUSDT","OMGUSDT",
# "PNTUSDT","QTUMUSDT","REQUSDT",
# "STORJUSDT","TRXUSDT","UTKUSDT","VETUSDT",
# "XLMUSDT","XMRUSDT",
# "XRPUSDT","XTZUSDT","XVGUSDT","ZECUSDT",
# "ZILUSDT","ZRXUSDT"]
universeSymbols = []
universeSymbols = []
for symbol in self.UniverseTickers:
for symbol in self.UniverseTickers:


universeSymbols.append(Symbol.Create(symbol, SecurityType.Crypto, Market.Binance))
universeSymbols.append(Symbol.Create(symbol, SecurityType.Crypto, Market.Binance))
self.SetUniverseSelection(ManualUniverseSelectionModel(universeSymbols))
self.SetUniverseSelection(ManualUniverseSelectionModel(universeSymbols))




# --------------------
# --------------------
def InitAlgoParams(self):
def InitAlgoParams(self):
self.emaSlowPeriod = int(self.GetParameter('emaSlowPeriod'))
self.emaSlowPeriod = int(self.GetParameter('emaSlowPeriod'))
self.emaFastPeriod = int(self.GetParameter('emaFastPeriod'))
self.emaFastPeriod = int(self.GetParameter('emaFastPeriod'))
self.mompPeriod = int(self.GetParameter('mompPeriod')) # used for momentum based weight
self.mompPeriod = int(self.GetParameter('mompPeriod')) # used for momentum based weight
self.minimumVolPeriod = int(self.GetParameter('minimumVolPeriod')) # used for volume threshold
self.minimumVolPeriod = int(self.GetParameter('minimumVolPeriod')) # used for volume threshold
self.warmupPeriod = max(self.emaSlowPeriod, self.mompPeriod, self.minimumVolPeriod)
self.warmupPeriod = max(self.emaSlowPeriod, self.mompPeriod, self.minimumVolPeriod)
self.useMomWeight = (int(self.GetParameter("useMomWeight")) == 1)
self.useMomWeight = (int(self.GetParameter("useMomWeight")) == 1)
self.maxExposurePct = float(self.GetParameter("maxExposurePct"))/100
self.maxExposurePct = float(self.GetParameter("maxExposurePct"))/100
self.rebalanceWeekly = (int(self.GetParameter("rebalanceWeekly")) == 1)
self.rebalanceWeekly = (int(self.GetParameter("rebalanceWeekly")) == 1)


self.minimumVolume = int(self.GetParameter("minimumVolume"))
self.minimumVolume = int(self.GetParameter("minimumVolume"))
self.maxHoldings = int(self.GetParameter("maxHoldings"))
self.maxHoldings = int(self.GetParameter("maxHoldings"))
## Experimental:
## Experimental:
## self.maxSecurityDrawDown = float(self.GetParameter("maxSecurityDrawDown"))
## self.maxSecurityDrawDown = float(self.GetParameter("maxSecurityDrawDown"))
# --------------------
# --------------------
def InitAssets(self):
def InitAssets(self):
self.symbol = "BTCUSDT"
self.symbol = "BTCUSDT"
self.SetBrokerageModel(BrokerageName.Binance, AccountType.Cash)
self.SetBrokerageModel(BrokerageName.Binance, AccountType.Cash)
self.SetAccountCurrency("USDT")
self.SetAccountCurrency("USDT")
self.AddCrypto(self.symbol, Resolution.Daily)
self.AddCrypto(self.symbol, Resolution.Daily)


self.EnableAutomaticIndicatorWarmUp = True
self.EnableAutomaticIndicatorWarmUp = True
self.SetWarmUp(timedelta(self.warmupPeriod))
self.SetWarmUp(timedelta(self.warmupPeriod))
self.SelectedSymbolsAndWeights = {}
self.SelectedSymbolsAndWeights = {}
## Experimental:
## Experimental:
## self.AddRiskManagement(MaximumUnrealizedProfitPercentPerSecurity(self.maxSecurityDrawDown))
## self.AddRiskManagement(MaximumUnrealizedProfitPercentPerSecurity(self.maxSecurityDrawDown))
# ------------------------
## Schedule routines
## ------------------------
def ScheduleRoutines(self):
def ScheduleRoutines(self):
## TODO:
## Check if rebalancing has happened in the last 7 days,
## If it has, do not rebalance again
if(self.rebalanceWeekly):
if(self.rebalanceWeekly):
self.Schedule.On( self.DateRules.WeekStart(self.symbol),
self.Schedule.On( self.DateRules.WeekStart(self.symbol),
self.TimeRules.AfterMarketOpen(self.symbol, 31),
self.TimeRules.AfterMarketOpen(self.symbol, 31),
self.RebalanceHoldings )
self.RebalanceHoldings )


##
## Check if we are already holding the max # of open positions.
## Check if we are already holding the max # of open positions.
## TODO:
## When we start using limit orders, include pending holdings
## ------------------------------------------------------------
## ------------------------------------------------------------
@property
@property
def PortfolioAtCapacity(self):
def PortfolioAtCapacity(self):
numHoldings = len([x.Key for x in self.Portfolio if x.Value.Invested])
numHoldings = len([x.Key for x in self.Portfolio if x.Value.Invested])
return numHoldings >= self.maxHoldings
return numHoldings >= self.maxHoldings
## In the OnData Event handler, check for signals
## TODO:
## Test logic below for pending holdings
# pendingOrders = len( [x for x in self.Transactions.GetOpenOrders()
# if x.Direction == OrderDirection.Buy
# and x.Type == OrderType.Limit ] )
## Check for signals
## ------------------------------------------------
## ------------------------------------------------
def OnData(self, dataSlice):
def OnData(self, dataSlice):


## loop through the symbols in the slice
## loop through the symbols in the slice
for symbol in dataSlice.Keys:
for symbol in dataSlice.Keys:


## if we have this symbol in our data dictioary
## if we have this symbol in our data dictioary
if symbol in self.symDataDict:
if symbol in self.symDataDict:


symbolData = self.symDataDict[symbol]
symbolData = self.symDataDict[symbol]


## Update the symbol with the data slice data
## Update the symbol with the data slice data
symbolData.OnSymbolData(self.Securities[symbol].Price, dataSlice[symbol])
symbolData.OnSymbolData(self.Securities[symbol].Price, dataSlice[symbol])
## If we're invested in this symbol, manage any open positions
## If we're invested in this symbol, manage any open positions
if(self.Portfolio[symbolData.symbol.Value].Invested):
if(self.Portfolio[symbolData.symbol.Value].Invested):
symbolData.ManageOpenPositions()
symbolData.ManageOpenPositions()
## otherwise, if we're not invested, check for entry signal
## otherwise, if we're not invested, check for entry signal
else:
else:


## First check if we are at capacity for new positions.
## First check if we are at capacity for new positions.
##
##
## TODO:
## TODO:
## For Go-Live, note that the portfolio capacity may not be accurate while
## For Go-Live, note that the portfolio capacity may not be accurate while
## checking it inside this for-loop. It will be accurate after the positions
## checking it inside this for-loop. It will be accurate after the positions
## have been open. IE: When the orders are actually filled.
## have been open. IE: When the orders are actually filled.
if(not self.PortfolioAtCapacity):
if(not self.PortfolioAtCapacity):
if( symbolData.EntrySignalFired() ):
if( symbolData.EntrySignalFired() ):
self.OpenNewPosition(symbolData.symbol)
self.OpenNewPosition(symbolData.symbol)
## TODO:
## TODO:
## For Go-Live, call OnNewPositionOpened only after
## For Go-Live, call OnNewPositionOpened only after
## the order is actually filled
## the order is actually filled
symbolData.OnNewPositionOpened()
symbolData.OnNewPositionOpened()
## Logic to rebalance our portfolio of holdings.
## Logic to rebalance our portfolio of holdings.
## We will either rebalance with equal weighting,
## We will either rebalance with equal weighting,
## or assign weights based on momentum.
## or assign weights based on momentum.
## ----------------------------------------------
##
def RebalanceHoldings(self):
## TODO:
try:
## Check if rebalancing has happened in the last 7 days,
## If it has, do not rebalance again
## -----------------------------------------------------
def RebalanceHoldings(self, rebalanceCurrHoldings=False):
# try:
if self.useMomWeight:
momentumSum = sum(self.symDataDict[symbol].momp.Current.Value for symbol in self.SelectedSymbolsAndWeights)
if (momentumSum == 0):
self.useMomWeight = False
for symbol in self.SelectedSymbolsAndWeights:
if self.useMomWeight:
if self.useMomWeight:
momentumSum = sum(self.symDataDict[symbol].momp.Current.Value for symbol in self.SelectedSymbolsAndWeights)
symbolWeight = round((self.symDataDict[symbol].momp.Current.Value / momentumSum),4)
else:
symbolWeight = round(1/len(self.SelectedSymbolsAndWeights),4)
self.SetWeightedHolding(symbol,symbolWeight)

return


## Allocate the specified weight (pct) of the portfolio value to
## the specified symbol. This weight will first be adjusted to consider
## cost basis, whether the position is already open and has profit.
## We are doing this to solve the problem where re-balancing causes winners
## to reduce in position size.
## --------–--------–--------–--------–--------–--------–--------–--------–
def SetWeightedHolding(self,symbol,symbolWeight):

## Calculate the basis (the denominator) for rebalancing weights
## This is the sum of costs basis, plus uninvested cash

if( self.Portfolio.Invested ):
# numHoldings = len([x.Key for x in self.Portfolio if x.Value.Invested])
totalCostBasis = sum( [x.Value.HoldingsCost for x in self.Portfolio if x.Value.Invested] )
else:
totalCostBasis = 0.0

## it's okay if this includes cash reserved for pending orders
## because we have alread considered those orders in the symbolsAndWeights list
cashAvailable = self.Portfolio.CashBook["USDT"].Amount
weightingBasis = totalCostBasis + cashAvailable
amtToInvest = weightingBasis * symbolWeight

## if already invested, our adjusted weight needs to account for
## the profits gained, so we adjust the 'amt to invest' based on
## unrealized profit pct of the position.

if(self.Portfolio[symbol].Invested):
profitPct = self.Portfolio[symbol].UnrealizedProfitPercent
adjustedAmtToInvest = amtToInvest * (1 + profitPct)
adjustedWeight = adjustedAmtToInvest / self.Portfolio.TotalPortfolioValue
else:
adjustedWeight = amtToInvest / self.Portfolio.TotalPortfolioValue
for symbol in self.SelectedSymbolsAndWeights:
symbolWeight = self.GetTruncatedValue(symbolWeight,3)
adjustedWeight = self.GetTruncatedValue(adjustedWeight,3)


if self.useMomWeight:
self.SelectedSymbolsAndWeights[symbol] = adjustedWeight
symbolWeight = round((self.symDataDict[symbol].momp.Current.Value / momentumSum),4)
else:
symbolWeight = round(1/len(self.SelectedSymbolsAndWeights),4)
## Truncate symbolweight decimal places
truncFactor = 10.0 ** 2
symbolWeight = math.trunc(symbolWeight * truncFactor) / truncFactor
self.SelectedSymbolsAndWeights[symbol] = symbolWeight


## TODO: Calculate order qty instead of using % setholdings
## TODO: Calculate order qty instead of using % setholdings
## https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/BasicTemplateCryptoAlgorithm.py
## https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/BasicTemplateCryptoAlgorithm.py
orderMsg = f"{symbol} | {round(symbolWeight*100,2)}% alloc. | price: {round(self.Securities[symbol].Close,2)}"
orderMsg = f"{symbol} | {round(symbolWeight*100,2)}% alloc. ({round(adjustedWeight*100,2)}% adjusted) "
if(self.Portfolio[symbol].Invested):
if(self.Portfolio[symbol].Invested):
orderMsg = f"[Re-Balancing] {orderMsg}"
orderMsg = f"[Re-Balancing] {orderMsg}"
else:
else:
orderMsg = f"[NEW Addition] {orderMsg}"
orderMsg = f"[NEW Addition] {orderMsg}"
self.SetHoldings(symbol, symbolWeight * self.maxExposurePct, tag=orderMsg)
self.SetHoldings(symbol, adjustedWeight * self.maxExposurePct, tag=orderMsg)


except:
self.Debug(f"Failed to rebalance")




## Adding the symbol to our dictionary will ensure
## Adding the symbol to our dictionary will ensure
## that it gets processed in the rebalancing routine
## that it gets processed in the rebalancing routine
## -------------------------------------------------
## -------------------------------------------------
def OpenNewPosition(self, symbol):
def OpenNewPosition(self, symbol):
self.SelectedSymbolsAndWeights[symbol] = 0
self.SelectedSymbolsAndWeights[symbol] = 0
self.RebalanceHoldings()
self.RebalanceHoldings()
## Removing the symbol from our dictionary will ensure
## Removing the symbol from our dictionary will ensure
## that it wont get processed in the rebalancing routine
## that it wont get processed in the rebalancing routine
## -----------------------------------------------------
## -----------------------------------------------------
def ExitPosition(self, symbol, exitMsg=""):
def ExitPosition(self, symbol, exitMsg=""):
profitPct = round(self.Securities[symbol].Holdings.UnrealizedProfitPercent,2)
profitPct = round(self.Securities[symbol].Holdings.UnrealizedProfitPercent,2)
self.Liquidate(symbol, tag=f"SELL {symbol.Value} ({profitPct}% profit) [{exitMsg}]")
self.Liquidate(symbol, tag=f"SELL {symbol.Value} ({profitPct}% profit) [{exitMsg}]")
self.SelectedSymbolsAndWeights.pop(symbol)
self.SelectedSymbolsAndWeights.pop(symbol)
## TODO:
## Before go-live, wait until liquidation has happened before rebalancing
## Perhaps Call RebalanceHoldings after an order event has occured.
self.RebalanceHoldings()
self.RebalanceHoldings()
return
return


## Create new symboldata object and add to our dictionary
## Create new symboldata object and add to our dictionary
## ------------------------------------------------------
## ------------------------------------------------------
def OnSecuritiesChanged(self, changes):
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
for security in changes.AddedSecurities:
symbol = security.Symbol
symbol = security.Symbol
if( symbol in self.UniverseTickers and \
if( symbol in self.UniverseTickers and \
symbol not in self.symDataDict.keys()):
symbol not in self.symDataDict.keys()):
self.symDataDict[symbol] = SymbolData(symbol, self)
self.symDataDict[symbol] = SymbolData(symbol, self)


def GetTruncatedValue(self, value, decPlaces):
truncFactor = 10.0 ** decPlaces
return math.trunc(value * truncFactor) / truncFactor
##################################
##################################
# SymbolData Class
# SymbolData Class
##################################
##################################
class SymbolData():
class SymbolData():
def __init__(self, theSymbol, algo):
def __init__(self, theSymbol, algo):


## Algo / Symbol / Price reference
## Algo / Symbol / Price reference
self.algo = algo
self.algo = algo
self.symbol = theSymbol
self.symbol = theSymbol
self.lastPrice = 0
self.lastPrice = 0
self.price = 0
self.price = 0


## Initialize indicators
## Initialize indicators
self.InitIndicators()
self.InitIndicators()
## ----------------------------------------
## ----------------------------------------
def InitIndicators(self):
def InitIndicators(self):


self.indicators = { 'EMA_FAST' : self.algo.EMA(self.symbol,self.algo.emaFastPeriod,Resolution.Daily),
self.indicators = { 'EMA_FAST' : self.algo.EMA(self.symbol,self.algo.emaFastPeriod,Resolution.Daily),
'EMA_SLOW' : self.algo.EMA(self.symbol,self.algo.emaSlowPeriod,Resolution.Daily),
'EMA_SLOW' : self.algo.EMA(self.symbol,self.algo.emaSlowPeriod,Resolution.Daily),
'30DAY_VOL' : IndicatorExtensions.Times( self.algo.SMA(self.symbol,self.algo.minimumVolPeriod, Resolution.Daily, Field.Volume),
'30DAY_VOL' : IndicatorExtensions.Times( self.algo.SMA(self.symbol,self.algo.minimumVolPeriod, Resolution.Daily, Field.Volume),
self.algo.SMA(self.symbol,self.algo.minimumVolPeriod, Resolution.Daily, Field.Close)),
self.algo.SMA(self.symbol,self.algo.minimumVolPeriod, Resolution.Daily, Field.Close)),
'MOMP' : self.algo.MOMP(self.symbol,self.algo.mompPeriod,Resolution.Daily)}
'MOMP' : self.algo.MOMP(self.symbol,self.algo.mompPeriod,Resolution.Daily)}
## for easy reference from main algo
## for easy reference from main algo
self.momp = self.indicators['MOMP']
self.momp = self.indicators['MOMP']
for key, indicator in self.indicators.items():
for key, indicator in self.indicators.items():
self.algo.WarmUpIndicator(self.symbol, indicator, Resolution.Minute)
self.algo.WarmUpIndicator(self.symbol, indicator, Resolution.Minute)


self.emaFastWindow = SmartRollingWindow("float", 2)
self.emaFastWindow = SmartRollingWindow("float", 2)
self.emaSlowWindow = SmartRollingWindow("float", 2)
self.emaSlowWindow = SmartRollingWindow("float", 2)
self.lastPriceWindow = SmartRollingWindow("float", 2)
self.lastPriceWindow = SmartRollingWindow("float", 2)
## ----------------------------------------
## ----------------------------------------
def OnSymbolData(self, lastKnownPrice, tradeBar):
def OnSymbolData(self, lastKnownPrice, tradeBar):
self.lastPrice = lastKnownPrice
self.lastPrice = lastKnownPrice
self.UpdateRollingWindows()
self.UpdateRollingWindows()
self.PlotCharts()
self.PlotCharts()
## ----------------------------------------
## ----------------------------------------
def UpdateRollingWindows(self):
def UpdateRollingWindows(self):
self.emaFastWindow.Add(self.indicators['EMA_FAST'].Current.Value)
self.emaFastWindow.Add(self.indicators['EMA_FAST'].Current.Value)
self.emaSlowWindow.Add(self.indicators['EMA_SLOW'].Current.Value)
self.emaSlowWindow.Add(self.indicators['EMA_SLOW'].Current.Value)
self.lastPriceWindow.Add(self.lastPrice)
self.lastPriceWindow.Add(self.lastPrice)
## ----------------------------------------
## ----------------------------------------
def IsReady(self):
def IsReady(self):
return (self.indicators['EMA_FAST'].IsReady and self.indicators['EMA_SLOW'].IsReady \
return (self.indicators['EMA_FAST'].IsReady and self.indicators['EMA_SLOW'].IsReady \
and self.indicators['30DAY_VOL'].IsReady)
and self.indicators['30DAY_VOL'].IsReady)


## ----------------------------------------
## ----------------------------------------
def MinimumVolTraded(self):
def MinimumVolTraded(self):
if( self.indicators['30DAY_VOL'].IsReady ):
if( self.indicators['30DAY_VOL'].IsReady ):
dollarVolume = self.indicators['30DAY_VOL'].Current.Value
dollarVolume = self.indicators['30DAY_VOL'].Current.Value
if( dollarVolume >= self.algo.minimumVolume ):
if( dollarVolume >= self.algo.minimumVolume ):
return True
return True
return False
return False
## ----------------------------------------
## ----------------------------------------
def EntrySignalFired(self):
def EntrySignalFired(self):
if( self.IsReady() ):
if( self.IsReady() ):
if( self.MinimumVolTraded() ):
if( self.MinimumVolTraded() ):
if( self.emaFastWindow.isAbove(self.emaSlowWindow) and \
if( self.emaFastWindow.isAbove(self.emaSlowWindow) and \
self.lastPriceWindow.isAbove(self.emaFastWindow) ):
self.lastPriceWindow.isAbove(self.emaFastWindow) ):
return True
return True
return False
return False


## ----------------------------------------
## ----------------------------------------
def ExitSignalFired(self):
def ExitSignalFired(self):
if( self.IsReady() ):
if( self.IsReady() ):
if ( self.lastPriceWindow.isBelow(self.emaSlowWindow) or \
if ( self.lastPriceWindow.isBelow(self.emaSlowWindow) or \
self.emaSlowWindow.isAbove(self.emaFastWindow) ):
self.emaSlowWindow.isAbove(self.emaFastWindow) ):
return True
return True
return False
return False


## Logic to run immediately after a new position is opened.
## Logic to run immediately after a new position is opened.
## ---------------------------------------------------------
## ---------------------------------------------------------
def OnNewPositionOpened(self):
def OnNewPositionOpened(self):
# self.algo.Log(f"[BOUGHT {self.symbol.Value}] @ ${self.lastPrice:.2f}")
# self.algo.Log(f"[BOUGHT {self.symbol.Value}] @ ${self.lastPrice:.2f}")
return
return
## Manage open positions if any. ie: close them, update stops, add to them, etc
## Manage open positions if any. ie: close them, update stops, add to them, etc
## Called periodically, eg: from a scheduled routine
## Called periodically, eg: from a scheduled routine
##
##
## TODO:
## TODO:
## Consilder also liquidating if volume or liquidity thresholds arent met
## Consilder also liquidating if volume or liquidity thresholds arent met
## -----------------------------------------------------------------------------
## -----------------------------------------------------------------------------
def ManageOpenPositions(self):
def ManageOpenPositions(self):


## if( not self.MinimumVolTraded() ):
## if( not self.MinimumVolTraded() ):
## self.ExitPosition(exitMsg="No longer liquid")
## self.ExitPosition(exitMsg="Trading volume below threshold")
if(self.ExitSignalFired()):
if(self.ExitSignalFired()):
self.ExitPosition(exitMsg="Exit Signal Fired")
self.ExitPosition(exitMsg="Exit Signal Fired")
# ----------------------------------------
# ----------------------------------------
def ExitPosition(self, exitMsg):
def ExitPosition(self, exitMsg):
# self.algo.Log(f"[SELL {self.symbol.Value}] @ ${self.lastPrice:.2f}")
# self.algo.Log(f"[SELL {self.symbol.Value}] @ ${self.lastPrice:.2f}")
self.algo.ExitPosition(self.symbol, exitMsg)
self.algo.ExitPosition(self.symbol, exitMsg)



# ----------------------------------------
# ----------------------------------------
def PlotCharts(self):
def PlotCharts(self):
## To Plot charts, comment out the below
## To Plot charts, comment out the below
# self.algo.Plot(f"{self.symbol}-charts", "Price", self.lastPriceWindow[0])
# self.algo.Plot(f"{self.symbol}-charts", "Price", self.lastPriceWindow[0])
# self.algo.Plot(f"{self.symbol}-charts", "EMA Fast", self.indicators['EMA_FAST'].Current.Value)
# self.algo.Plot(f"{self.symbol}-charts", "EMA Fast", self.indicators['EMA_FAST'].Current.Value)
# self.algo.Plot(f"{self.symbol}-charts", "EMA Slow", self.indicators['EMA_SLOW'].Current.Value)
# self.algo.Plot(f"{self.symbol}-charts", "EMA Slow", self.indicators['EMA_SLOW'].Current.Value)
return
return