Diff
checker
テキスト
テキスト
画像
ドキュメント
Excel
フォルダ
Legal
Enterprise
デスクトップ
料金
ログイン
Diffchecker デスクトップのダウンロード
テキスト比較
2 つのテキスト ファイルの違いを見つける
ツール
履歴
ライブエディター
空白の変更を非表示
未変更行を折りたたむ
折り返しなし
レイアウト
分割
統合
比較精度
スマート
単語
文字
テキストスタイル
外観を変更
シンタックスハイライト
構文を選択
無視
テキスト変換
最初の差分へ移動
入力を編集
Diffchecker Desktop
Diffcheckerを実行する最も安全な方法。Diffchecker Desktopアプリを入手:あなたの差分はコンピューターから出ることはありません!
Desktopを入手
Untitled Diff
作成日
5 年前
差分は期限切れになりません
クリア
エクスポート
共有
説明
34 削除
行
合計
削除
文字
合計
削除
この機能を引き続き使用するには、アップグレードしてください
Diff
checker
Pro
価格を見る
336 行
すべてコピー
121 追加
行
合計
追加
文字
合計
追加
この機能を引き続き使用するには、アップグレードしてください
Diff
checker
Pro
価格を見る
417 行
すべてコピー
##########################################################################
##########################################################################
# 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, c
heck 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 ] )
##
C
heck 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:
symbol
Weight = round(1/len(
self.SelectedSymbolsAndWeights)
,4)
コピー
コピー済み
コピー
コピー済み
for symbol in
self.
SelectedSymbolsAndWeights:
self.
SetWeightedHolding(symbol,symbolWeight)
コピー
コピー済み
コピー
コピー済み
if self.useMomWeight:
return
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
## 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)}"
if(self.Portfolio[symbol].Invested):
orderMsg = f"[Re-Balancing] {orderMsg}"
else:
orderMsg = f"[NEW Addition] {orderMsg}"
self.SetHoldings(symbol, symbolWeight * self.maxExposurePct, tag=orderMsg)
コピー
コピー済み
コピー
コピー済み
except:
## Allocate the specified weight (pct) of the portfolio value to
self.Debug(f"Failed to rebalance")
## 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
symbolWeight = self.GetTruncatedValue(symbolWeight,3)
adjustedWeight = self.GetTruncatedValue(adjustedWeight,3)
self.SelectedSymbolsAndWeights[symbol] = adjustedWeight
## TODO: Calculate order qty instead of using % setholdings
## https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/BasicTemplateCryptoAlgorithm.py
orderMsg = f"{symbol} | {round(symbolWeight*100,2)}% alloc. ({round(adjustedWeight*100,2)}% adjusted) "
if(self.Portfolio[symbol].Invested):
orderMsg = f"[Re-Balancing] {orderMsg}"
else:
orderMsg = f"[NEW Addition] {orderMsg}"
self.SetHoldings(symbol, adjustedWeight * self.maxExposurePct, tag=orderMsg)
## 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
保存された差分
原文
ファイルを開く
########################################################################## # 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 ########################################################################## # # EMA Crossover In a Crypto Universe # --------------------------------------------- # FOR EDUCATIONAL PURPOSES ONLY. DO NOT DEPLOY. # # # Entry: # ------- # Minimum volume threshold traded # and # Price > Fast Daily EMA # and # Fast Daily EMA > Slow Daily EMA # # Exit: # ------ # Price < Slow Daily EMA # or # Slow Daily EMA < Fast Daily EMA # # Additional Consideration: # -------------------------- # Max exposure pct: Total % of available capital to trade with at any time # Max holdings: Total # of positions that can be held simultaneously # Rebalance Weekly: If false, only rebalance when we add/remove positions # UseMomWeight: If true, rebalance w/momentum-based weights (top gainers=more weight) # ######################################################################### from SmartRollingWindow import * class EMACrossoverUniverse(QCAlgorithm): ## def Initialize(self): self.InitAlgoParams() self.InitAssets() self.InitUniverse() self.InitBacktestParams() self.ScheduleRoutines() ## Set backtest params: dates, cash, etc. Called from Initialize(). ## ---------------------------------------------------------------- def InitBacktestParams(self): self.SetStartDate(2020, 1, 1) # self.SetEndDate(2019, 2, 1) self.SetCash(100000) self.SetBenchmark(Symbol.Create("BTCUSDT", SecurityType.Crypto, Market.Binance)) def InitUniverse(self): self.UniverseSettings.Resolution = Resolution.Daily self.symDataDict = { } self.UniverseTickers = ["SOLUSDT", "ETHUSDT", "BNBUSDT", "ADAUSDT", "BTCUSDT"] universeSymbols = [] for symbol in self.UniverseTickers: universeSymbols.append(Symbol.Create(symbol, SecurityType.Crypto, Market.Binance)) self.SetUniverseSelection(ManualUniverseSelectionModel(universeSymbols)) # -------------------- def InitAlgoParams(self): self.emaSlowPeriod = int(self.GetParameter('emaSlowPeriod')) self.emaFastPeriod = int(self.GetParameter('emaFastPeriod')) self.mompPeriod = int(self.GetParameter('mompPeriod')) # used for momentum based weight self.minimumVolPeriod = int(self.GetParameter('minimumVolPeriod')) # used for volume threshold self.warmupPeriod = max(self.emaSlowPeriod, self.mompPeriod, self.minimumVolPeriod) self.useMomWeight = (int(self.GetParameter("useMomWeight")) == 1) self.maxExposurePct = float(self.GetParameter("maxExposurePct"))/100 self.rebalanceWeekly = (int(self.GetParameter("rebalanceWeekly")) == 1) self.minimumVolume = int(self.GetParameter("minimumVolume")) self.maxHoldings = int(self.GetParameter("maxHoldings")) ## Experimental: ## self.maxSecurityDrawDown = float(self.GetParameter("maxSecurityDrawDown")) # -------------------- def InitAssets(self): self.symbol = "BTCUSDT" self.SetBrokerageModel(BrokerageName.Binance, AccountType.Cash) self.SetAccountCurrency("USDT") self.AddCrypto(self.symbol, Resolution.Daily) self.EnableAutomaticIndicatorWarmUp = True self.SetWarmUp(timedelta(self.warmupPeriod)) self.SelectedSymbolsAndWeights = {} ## Experimental: ## self.AddRiskManagement(MaximumUnrealizedProfitPercentPerSecurity(self.maxSecurityDrawDown)) # ------------------------ def ScheduleRoutines(self): if(self.rebalanceWeekly): self.Schedule.On( self.DateRules.WeekStart(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol, 31), self.RebalanceHoldings ) ## Check if we are already holding the max # of open positions. ## ------------------------------------------------------------ @property def PortfolioAtCapacity(self): numHoldings = len([x.Key for x in self.Portfolio if x.Value.Invested]) return numHoldings >= self.maxHoldings ## In the OnData Event handler, check for signals ## ------------------------------------------------ def OnData(self, dataSlice): ## loop through the symbols in the slice for symbol in dataSlice.Keys: ## if we have this symbol in our data dictioary if symbol in self.symDataDict: symbolData = self.symDataDict[symbol] ## Update the symbol with the data slice data symbolData.OnSymbolData(self.Securities[symbol].Price, dataSlice[symbol]) ## If we're invested in this symbol, manage any open positions if(self.Portfolio[symbolData.symbol.Value].Invested): symbolData.ManageOpenPositions() ## otherwise, if we're not invested, check for entry signal else: ## First check if we are at capacity for new positions. ## ## TODO: ## 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 ## have been open. IE: When the orders are actually filled. if(not self.PortfolioAtCapacity): if( symbolData.EntrySignalFired() ): self.OpenNewPosition(symbolData.symbol) ## TODO: ## For Go-Live, call OnNewPositionOpened only after ## the order is actually filled symbolData.OnNewPositionOpened() ## Logic to rebalance our portfolio of holdings. ## We will either rebalance with equal weighting, ## or assign weights based on momentum. ## ---------------------------------------------- def RebalanceHoldings(self): try: if self.useMomWeight: momentumSum = sum(self.symDataDict[symbol].momp.Current.Value for symbol in self.SelectedSymbolsAndWeights) for symbol in self.SelectedSymbolsAndWeights: if self.useMomWeight: 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 ## 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)}" if(self.Portfolio[symbol].Invested): orderMsg = f"[Re-Balancing] {orderMsg}" else: orderMsg = f"[NEW Addition] {orderMsg}" self.SetHoldings(symbol, symbolWeight * self.maxExposurePct, tag=orderMsg) except: self.Debug(f"Failed to rebalance") ## Adding the symbol to our dictionary will ensure ## that it gets processed in the rebalancing routine ## ------------------------------------------------- def OpenNewPosition(self, symbol): self.SelectedSymbolsAndWeights[symbol] = 0 self.RebalanceHoldings() ## Removing the symbol from our dictionary will ensure ## that it wont get processed in the rebalancing routine ## ----------------------------------------------------- def ExitPosition(self, symbol, exitMsg=""): profitPct = round(self.Securities[symbol].Holdings.UnrealizedProfitPercent,2) self.Liquidate(symbol, tag=f"SELL {symbol.Value} ({profitPct}% profit) [{exitMsg}]") self.SelectedSymbolsAndWeights.pop(symbol) self.RebalanceHoldings() return ## Create new symboldata object and add to our dictionary ## ------------------------------------------------------ def OnSecuritiesChanged(self, changes): for security in changes.AddedSecurities: symbol = security.Symbol if( symbol in self.UniverseTickers and \ symbol not in self.symDataDict.keys()): self.symDataDict[symbol] = SymbolData(symbol, self) ################################## # SymbolData Class ################################## class SymbolData(): def __init__(self, theSymbol, algo): ## Algo / Symbol / Price reference self.algo = algo self.symbol = theSymbol self.lastPrice = 0 self.price = 0 ## Initialize indicators self.InitIndicators() ## ---------------------------------------- def InitIndicators(self): 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), '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)), 'MOMP' : self.algo.MOMP(self.symbol,self.algo.mompPeriod,Resolution.Daily)} ## for easy reference from main algo self.momp = self.indicators['MOMP'] for key, indicator in self.indicators.items(): self.algo.WarmUpIndicator(self.symbol, indicator, Resolution.Minute) self.emaFastWindow = SmartRollingWindow("float", 2) self.emaSlowWindow = SmartRollingWindow("float", 2) self.lastPriceWindow = SmartRollingWindow("float", 2) ## ---------------------------------------- def OnSymbolData(self, lastKnownPrice, tradeBar): self.lastPrice = lastKnownPrice self.UpdateRollingWindows() self.PlotCharts() ## ---------------------------------------- def UpdateRollingWindows(self): self.emaFastWindow.Add(self.indicators['EMA_FAST'].Current.Value) self.emaSlowWindow.Add(self.indicators['EMA_SLOW'].Current.Value) self.lastPriceWindow.Add(self.lastPrice) ## ---------------------------------------- def IsReady(self): return (self.indicators['EMA_FAST'].IsReady and self.indicators['EMA_SLOW'].IsReady \ and self.indicators['30DAY_VOL'].IsReady) ## ---------------------------------------- def MinimumVolTraded(self): if( self.indicators['30DAY_VOL'].IsReady ): dollarVolume = self.indicators['30DAY_VOL'].Current.Value if( dollarVolume >= self.algo.minimumVolume ): return True return False ## ---------------------------------------- def EntrySignalFired(self): if( self.IsReady() ): if( self.MinimumVolTraded() ): if( self.emaFastWindow.isAbove(self.emaSlowWindow) and \ self.lastPriceWindow.isAbove(self.emaFastWindow) ): return True return False ## ---------------------------------------- def ExitSignalFired(self): if( self.IsReady() ): if ( self.lastPriceWindow.isBelow(self.emaSlowWindow) or \ self.emaSlowWindow.isAbove(self.emaFastWindow) ): return True return False ## Logic to run immediately after a new position is opened. ## --------------------------------------------------------- def OnNewPositionOpened(self): # self.algo.Log(f"[BOUGHT {self.symbol.Value}] @ ${self.lastPrice:.2f}") return ## Manage open positions if any. ie: close them, update stops, add to them, etc ## Called periodically, eg: from a scheduled routine ## ## TODO: ## Consilder also liquidating if volume or liquidity thresholds arent met ## ----------------------------------------------------------------------------- def ManageOpenPositions(self): ## if( not self.MinimumVolTraded() ): ## self.ExitPosition(exitMsg="No longer liquid") if(self.ExitSignalFired()): self.ExitPosition(exitMsg="Exit Signal Fired") # ---------------------------------------- def ExitPosition(self, exitMsg): # self.algo.Log(f"[SELL {self.symbol.Value}] @ ${self.lastPrice:.2f}") self.algo.ExitPosition(self.symbol, exitMsg) # ---------------------------------------- def PlotCharts(self): ## 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", "EMA Fast", self.indicators['EMA_FAST'].Current.Value) # self.algo.Plot(f"{self.symbol}-charts", "EMA Slow", self.indicators['EMA_SLOW'].Current.Value) return
変更されたテキスト
ファイルを開く
########################################################################## # 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 ########################################################################## # # EMA Crossover In a Crypto Universe # --------------------------------------------- # FOR EDUCATIONAL PURPOSES ONLY. DO NOT DEPLOY. # # # Entry: # ------- # Minimum volume threshold traded # and # Price > Fast Daily EMA # and # Fast Daily EMA > Slow Daily EMA # # Exit: # ------ # Price < Slow Daily EMA # or # Slow Daily EMA < Fast Daily EMA # # Additional Consideration: # -------------------------- # Max exposure pct: Total % of available capital to trade with at any time # Max holdings: Total # of positions that can be held simultaneously # Rebalance Weekly: If false, only rebalance when we add/remove positions # UseMomWeight: If true, rebalance w/momentum-based weights (top gainers=more weight) # ######################################################################### from SmartRollingWindow import * class EMACrossoverUniverse(QCAlgorithm): ## def Initialize(self): self.InitAlgoParams() self.InitAssets() self.InitUniverse() self.InitBacktestParams() self.ScheduleRoutines() ## Set backtest params: dates, cash, etc. Called from Initialize(). ## ---------------------------------------------------------------- def InitBacktestParams(self): self.SetStartDate(2020, 1, 1) # self.SetEndDate(2019, 2, 1) self.SetCash(100000) self.SetBenchmark(Symbol.Create("BTCUSDT", SecurityType.Crypto, Market.Binance)) def InitUniverse(self): self.UniverseSettings.Resolution = Resolution.Daily self.symDataDict = { } 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 = [] for symbol in self.UniverseTickers: universeSymbols.append(Symbol.Create(symbol, SecurityType.Crypto, Market.Binance)) self.SetUniverseSelection(ManualUniverseSelectionModel(universeSymbols)) # -------------------- def InitAlgoParams(self): self.emaSlowPeriod = int(self.GetParameter('emaSlowPeriod')) self.emaFastPeriod = int(self.GetParameter('emaFastPeriod')) self.mompPeriod = int(self.GetParameter('mompPeriod')) # used for momentum based weight self.minimumVolPeriod = int(self.GetParameter('minimumVolPeriod')) # used for volume threshold self.warmupPeriod = max(self.emaSlowPeriod, self.mompPeriod, self.minimumVolPeriod) self.useMomWeight = (int(self.GetParameter("useMomWeight")) == 1) self.maxExposurePct = float(self.GetParameter("maxExposurePct"))/100 self.rebalanceWeekly = (int(self.GetParameter("rebalanceWeekly")) == 1) self.minimumVolume = int(self.GetParameter("minimumVolume")) self.maxHoldings = int(self.GetParameter("maxHoldings")) ## Experimental: ## self.maxSecurityDrawDown = float(self.GetParameter("maxSecurityDrawDown")) # -------------------- def InitAssets(self): self.symbol = "BTCUSDT" self.SetBrokerageModel(BrokerageName.Binance, AccountType.Cash) self.SetAccountCurrency("USDT") self.AddCrypto(self.symbol, Resolution.Daily) self.EnableAutomaticIndicatorWarmUp = True self.SetWarmUp(timedelta(self.warmupPeriod)) self.SelectedSymbolsAndWeights = {} ## Experimental: ## self.AddRiskManagement(MaximumUnrealizedProfitPercentPerSecurity(self.maxSecurityDrawDown)) ## Schedule routines ## ------------------------ def ScheduleRoutines(self): ## TODO: ## Check if rebalancing has happened in the last 7 days, ## If it has, do not rebalance again if(self.rebalanceWeekly): self.Schedule.On( self.DateRules.WeekStart(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol, 31), self.RebalanceHoldings ) ## ## Check if we are already holding the max # of open positions. ## TODO: ## When we start using limit orders, include pending holdings ## ------------------------------------------------------------ @property def PortfolioAtCapacity(self): numHoldings = len([x.Key for x in self.Portfolio if x.Value.Invested]) return numHoldings >= self.maxHoldings ## 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): ## loop through the symbols in the slice for symbol in dataSlice.Keys: ## if we have this symbol in our data dictioary if symbol in self.symDataDict: symbolData = self.symDataDict[symbol] ## Update the symbol with the data slice data symbolData.OnSymbolData(self.Securities[symbol].Price, dataSlice[symbol]) ## If we're invested in this symbol, manage any open positions if(self.Portfolio[symbolData.symbol.Value].Invested): symbolData.ManageOpenPositions() ## otherwise, if we're not invested, check for entry signal else: ## First check if we are at capacity for new positions. ## ## TODO: ## 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 ## have been open. IE: When the orders are actually filled. if(not self.PortfolioAtCapacity): if( symbolData.EntrySignalFired() ): self.OpenNewPosition(symbolData.symbol) ## TODO: ## For Go-Live, call OnNewPositionOpened only after ## the order is actually filled symbolData.OnNewPositionOpened() ## Logic to rebalance our portfolio of holdings. ## We will either rebalance with equal weighting, ## or assign weights based on momentum. ## ## TODO: ## 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: 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 symbolWeight = self.GetTruncatedValue(symbolWeight,3) adjustedWeight = self.GetTruncatedValue(adjustedWeight,3) self.SelectedSymbolsAndWeights[symbol] = adjustedWeight ## TODO: Calculate order qty instead of using % setholdings ## https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/BasicTemplateCryptoAlgorithm.py orderMsg = f"{symbol} | {round(symbolWeight*100,2)}% alloc. ({round(adjustedWeight*100,2)}% adjusted) " if(self.Portfolio[symbol].Invested): orderMsg = f"[Re-Balancing] {orderMsg}" else: orderMsg = f"[NEW Addition] {orderMsg}" self.SetHoldings(symbol, adjustedWeight * self.maxExposurePct, tag=orderMsg) ## Adding the symbol to our dictionary will ensure ## that it gets processed in the rebalancing routine ## ------------------------------------------------- def OpenNewPosition(self, symbol): self.SelectedSymbolsAndWeights[symbol] = 0 self.RebalanceHoldings() ## Removing the symbol from our dictionary will ensure ## that it wont get processed in the rebalancing routine ## ----------------------------------------------------- def ExitPosition(self, symbol, exitMsg=""): profitPct = round(self.Securities[symbol].Holdings.UnrealizedProfitPercent,2) self.Liquidate(symbol, tag=f"SELL {symbol.Value} ({profitPct}% profit) [{exitMsg}]") 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() return ## Create new symboldata object and add to our dictionary ## ------------------------------------------------------ def OnSecuritiesChanged(self, changes): for security in changes.AddedSecurities: symbol = security.Symbol if( symbol in self.UniverseTickers and \ symbol not in self.symDataDict.keys()): self.symDataDict[symbol] = SymbolData(symbol, self) def GetTruncatedValue(self, value, decPlaces): truncFactor = 10.0 ** decPlaces return math.trunc(value * truncFactor) / truncFactor ################################## # SymbolData Class ################################## class SymbolData(): def __init__(self, theSymbol, algo): ## Algo / Symbol / Price reference self.algo = algo self.symbol = theSymbol self.lastPrice = 0 self.price = 0 ## Initialize indicators self.InitIndicators() ## ---------------------------------------- def InitIndicators(self): 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), '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)), 'MOMP' : self.algo.MOMP(self.symbol,self.algo.mompPeriod,Resolution.Daily)} ## for easy reference from main algo self.momp = self.indicators['MOMP'] for key, indicator in self.indicators.items(): self.algo.WarmUpIndicator(self.symbol, indicator, Resolution.Minute) self.emaFastWindow = SmartRollingWindow("float", 2) self.emaSlowWindow = SmartRollingWindow("float", 2) self.lastPriceWindow = SmartRollingWindow("float", 2) ## ---------------------------------------- def OnSymbolData(self, lastKnownPrice, tradeBar): self.lastPrice = lastKnownPrice self.UpdateRollingWindows() self.PlotCharts() ## ---------------------------------------- def UpdateRollingWindows(self): self.emaFastWindow.Add(self.indicators['EMA_FAST'].Current.Value) self.emaSlowWindow.Add(self.indicators['EMA_SLOW'].Current.Value) self.lastPriceWindow.Add(self.lastPrice) ## ---------------------------------------- def IsReady(self): return (self.indicators['EMA_FAST'].IsReady and self.indicators['EMA_SLOW'].IsReady \ and self.indicators['30DAY_VOL'].IsReady) ## ---------------------------------------- def MinimumVolTraded(self): if( self.indicators['30DAY_VOL'].IsReady ): dollarVolume = self.indicators['30DAY_VOL'].Current.Value if( dollarVolume >= self.algo.minimumVolume ): return True return False ## ---------------------------------------- def EntrySignalFired(self): if( self.IsReady() ): if( self.MinimumVolTraded() ): if( self.emaFastWindow.isAbove(self.emaSlowWindow) and \ self.lastPriceWindow.isAbove(self.emaFastWindow) ): return True return False ## ---------------------------------------- def ExitSignalFired(self): if( self.IsReady() ): if ( self.lastPriceWindow.isBelow(self.emaSlowWindow) or \ self.emaSlowWindow.isAbove(self.emaFastWindow) ): return True return False ## Logic to run immediately after a new position is opened. ## --------------------------------------------------------- def OnNewPositionOpened(self): # self.algo.Log(f"[BOUGHT {self.symbol.Value}] @ ${self.lastPrice:.2f}") return ## Manage open positions if any. ie: close them, update stops, add to them, etc ## Called periodically, eg: from a scheduled routine ## ## TODO: ## Consilder also liquidating if volume or liquidity thresholds arent met ## ----------------------------------------------------------------------------- def ManageOpenPositions(self): ## if( not self.MinimumVolTraded() ): ## self.ExitPosition(exitMsg="Trading volume below threshold") if(self.ExitSignalFired()): self.ExitPosition(exitMsg="Exit Signal Fired") # ---------------------------------------- def ExitPosition(self, exitMsg): # self.algo.Log(f"[SELL {self.symbol.Value}] @ ${self.lastPrice:.2f}") self.algo.ExitPosition(self.symbol, exitMsg) # ---------------------------------------- def PlotCharts(self): ## 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", "EMA Fast", self.indicators['EMA_FAST'].Current.Value) # self.algo.Plot(f"{self.symbol}-charts", "EMA Slow", self.indicators['EMA_SLOW'].Current.Value) return
違いを見つける