| Overall Statistics |
|
Total Trades 148 Average Win 17.81% Average Loss -1.83% Compounding Annual Return 453.180% Drawdown 33.800% Expectancy 3.207 Net Profit 3082.024% Sharpe Ratio 4.959 Probabilistic Sharpe Ratio 99.447% Loss Rate 61% Win Rate 39% Profit-Loss Ratio 9.73 Alpha 2.301 Beta 0.407 Annual Standard Deviation 0.567 Annual Variance 0.321 Information Ratio 2.477 Tracking Error 0.629 Treynor Ratio 6.91 Total Fees $45830.80 Estimated Strategy Capacity $500000.00 Lowest Capacity Asset BNBUSDT 18N |
## https://www.quantconnect.com/forum/discussion/12768/share-kalman-filter-crossovers-for-crypto-and-smart-rollingwindows/p1/comment-38206
from datetime import timedelta
from AlgorithmImports import *
import math
# from Shared.IntradayMomentumIndex importIn
from SmartRollingWindow import *
def truncate(number, decimals=0):
"""
Returns a value truncated to a specific number of decimal places.
"""
if not isinstance(decimals, int):
raise TypeError("decimal places must be an integer.")
elif decimals < 0:
raise ValueError("decimal places has to be 0 or more.")
elif decimals == 0:
return math.trunc(number)
factor = 10.0 ** decimals
return math.trunc(number * factor) / factor
class Asset():
def __init__(self, algorithm, symbol):
self.symbol = symbol
self.algorithm = algorithm
def InitIndicators(self, slowPeriod, fastPeriod, resolution):
self.fast = self.algorithm.EMA(self.symbol, fastPeriod, Resolution.Daily)
self.slow = self.algorithm.EMA(self.symbol, slowPeriod, Resolution.Daily)
self.slowWindow = SmartRollingWindow('float', 2)
self.fastWindow = SmartRollingWindow('float', 2)
self.priceWindow = SmartRollingWindow('float', 2)
def UpdateAssetWindows(self):
self.slowWindow.Add(self.slow.Current.Value)
self.fastWindow.Add(self.fast.Current.Value)
self.priceWindow.Add(self.algorithm.Securities[self.symbol].Price)
def PrefixWithSymbol(self, str):
return "{}: {}".format(self.symbol.Value, str)
def Plot(self, chartName):
self.algorithm.Plot(chartName, self.PrefixWithSymbol("Price"), self.algorithm.Securities[self.symbol].Price)
self.algorithm.Plot(chartName, self.PrefixWithSymbol("Slow"), self.slow.Current.Value)
self.algorithm.Plot(chartName, self.PrefixWithSymbol("Fast"), self.fast.Current.Value)
class FocusedYellowGreenJellyfish(QCAlgorithm):
def Initialize(self):
self.InitAlgoParams()
self.InitBacktestParams()
self.InitAssets()
self.InitIndicators()
def InitAlgoParams(self):
self.benchmarkTicker = 'BTCUSDT';
self.tickers = ["SOLUSDT", "ETHUSDT", "BNBUSDT"]
self.assets = {}
self.ticker = "SOLUSDT"
self.resolution = Resolution.Minute
self.slowPeriod = int(self.GetParameter('slowPeriod'))
self.fastPeriod = int(self.GetParameter('fastPeriod'))
def InitBacktestParams(self):
self.SetAccountCurrency("USDT")
self.SetCash(100000)
self.SetStartDate(2020, 1, 1)
def InitAssets(self):
self.SetBrokerageModel(BrokerageName.Binance, AccountType.Cash)
self.benchmarkSymbol = self.AddCrypto(self.benchmarkTicker, self.resolution).Symbol
for ticker in self.tickers:
symbol = self.AddCrypto(ticker, self.resolution).Symbol
self.assets[ticker] = Asset(self, symbol)
self.SetBenchmark(self.benchmarkTicker)
def InitIndicators(self):
self.SetWarmUp(self.slowPeriod * 2, self.resolution)
for asset in self.assets.values():
asset.InitIndicators(self.slowPeriod, self.fastPeriod, self.resolution)
self.Consolidate(self.benchmarkSymbol, Resolution.Daily, self.OnConsolidatedBarClose)
def UpdateRollingWindows(self):
for asset in self.assets.values():
asset.UpdateAssetWindows()
def ShouldExit(self, asset):
return asset.priceWindow.isBelow(asset.slowWindow) or asset.slowWindow.isAbove(asset.fastWindow)
def ShouldEnter(self, asset):
return asset.fastWindow.isAbove(asset.slowWindow) and asset.priceWindow.isAbove(asset.fastWindow)
def OnEndOfDay(self):
self.PlotCharts()
def OnConsolidatedBarClose(self, bar):
self.UpdateRollingWindows()
for asset in self.assets.values():
if not asset.slowWindow.IsReady():
return
if self.Portfolio[asset.symbol].Invested and self.ShouldExit(asset):
self.Liquidate(asset.symbol)
elif not self.Portfolio[asset.symbol].Invested and self.ShouldEnter(asset):
percent = truncate(1 / len(self.assets.values()), 1)
cost = self.Portfolio.TotalPortfolioValue * percent
cash = self.Portfolio.TotalPortfolioValue - self.Portfolio.TotalHoldingsValue
self.Log("Cost: {}, Cash: {}".format(cost, cash))
if (cost > cash):
percent = truncate(cash / self.Portfolio.TotalPortfolioValue, 1)
self.SetHoldings(asset.symbol, percent);
def PlotCharts(self):
chartName = "Charts"
# self.Plot('Benchmark', "Benchmark", self.Securities[self.benchmarkSymbol].Price)
for asset in self.assets.values():
asset.Plot(chartName)
###################################################
#
# Smart Rolling window
# ========================
# Convenience object to build on RollingWindow functionality
#
# Methods:
# -------------------------
# mySmartWindow.IsRising()
# mySmartWindow.IsFalling()
# mySmartWindow.crossedAboveValue(value)
# mySmartWindow.crossedBelowValue(value)
# mySmartWindow.crossedAbove(otherWindow)
# mySmartWindow.crossedBelow(otherWindow)
# mySmartWindow.IsFlat(decimalPrecision)
# mySmartWindow.hasAtLeastThisMany(value)
#
#
# Author:ekz
###################################################
class SmartRollingWindow():
def __init__(self, windowType, windowLength):
self.window = None
self.winLength = windowLength
if (windowType is "int"):self.window = RollingWindow[int](windowLength)
elif (windowType is "bool"):self.window = RollingWindow[bool](windowLength)
elif (windowType is "float"):self.window = RollingWindow[float](windowLength)
elif (windowType is "TradeBar"):self.window = RollingWindow[TradeBar](windowLength)
def crossedAboveValue(self, value):return (self.window[1] <= value < self.window[0])
def crossedBelowValue(self, value): return (self.window[1] >= value > self.window[0])
def crossedAbove(self, series): return (self.window[1] <= series[1] and self.window[0] > series[0])
def crossedBelow(self, series): return (self.window[1] >= series[1] and self.window[0] < series[0])
def isAbove(self, series): return (self.window[0] > series[0])
def isBelow(self, series): return (self.window[0] < series[0])
def isFlat(self): return (self.window[1] == self.window[0])
def isFalling(self): return (self.window[1] > self.window[0])
def isRising(self): return (self.window[1] < self.window[0])
def Add(self,value):
self.window.Add(value)
def IsReady(self):
return (self.window is not None) and \
(self.window.Count >= self.winLength) ## TODO: just use rw.IsReady?
def __getitem__(self, index):
return self.window[index]