| Overall Statistics |
|
Total Trades 1210 Average Win 0.48% Average Loss -0.37% Compounding Annual Return 318.681% Drawdown 66.500% Expectancy 0.287 Net Profit 82.255% Sharpe Ratio 3.559 Probabilistic Sharpe Ratio 64.006% Loss Rate 44% Win Rate 56% Profit-Loss Ratio 1.29 Alpha 3.837 Beta 0.883 Annual Standard Deviation 1.062 Annual Variance 1.129 Information Ratio 4.194 Tracking Error 0.917 Treynor Ratio 4.283 Total Fees $513.06 Estimated Strategy Capacity $23000000.00 Lowest Capacity Asset LUNAUSD E3 |
from io import StringIO
from datetime import datetime
import pandas as pd
import numpy as np
from scipy import optimize
class CasualFluorescentYellowCaterpillar(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2021, 4, 19) # Set Start Date
self.SetEndDate(2021, 9, 18)
self.SetCash(4000)
self.symbols = ['BTCUSD','ETHUSD', 'XRPUSD', 'SOLUSD', 'LUNAUSD']
self.warm_up = len(self.symbols)
self.warm_up_count = 1
self.window = 24*14
self.rebalance = 8
self.last_w = [0 for i in range(len(self.symbols))]
self.use_last = True
#Download returns from each strategy
#Ris_csv = self.Download('https://raw.githubusercontent.com/sergiosierram/SharpSignal/main/data/CryptoDataReturns.csv')
#self.Ris = pd.read_csv(StringIO(Ris_csv), index_col=0)
#Initialize symbols
self.initializeSymbols()
#self.Ris = self.Ris.to_numpy() #.transpose()
#set risk free asset rate of return
self.Rf=0 # April 2019 average risk free rate of return in USA approx 3%
annRiskFreeRate = self.Rf/100
#compute daily risk free rate in percentage
self.r0 = (np.power((1 + annRiskFreeRate), (1.0 / 360.0)) - 1.0) * 100
self.portfolioSize = len(self.symbols)
self.SetBrokerageModel(BrokerageName.Bitfinex)
self.SetBenchmark("BTCUSD")
return
def OnData(self, data):
if self.warm_up_count < self.warm_up:
self.warm_up_count += 1
return
self.warm_up_count += 1
if self.warm_up_count % self.rebalance == 0:
try:
#Ri = self.Ris[0:self.warm_up_count, :]
#Ei = np.mean(Ri, axis = 0)
if self.window == 0:
self.window = self.warm_up_count
Rix = self.History(self.symbols_objects, self.window)
Ri = []
for symbol in self.symbols:
d = Rix.loc[symbol]['close'].to_list()
Ri.append(d)
Ri = np.array(Ri).transpose()
Ri = StockReturnsComputing(Ri, self.window, self.portfolioSize)
Ei = np.mean(Ri, axis = 0)
#self.Debug(Ei)
except:
self.Log(str("Error during data extraction"))
return
cov = np.cov(Ri, rowvar=False)
#initialization
xOptimal =[]
minRiskPoint = []
expPortfolioReturnPoint =[]
maxSharpeRatio = 0
#compute maximal Sharpe Ratio and optimal weights
result = MaximizeSharpeRatioOptmzn(Ei, cov, self.r0, self.portfolioSize)
xOptimal.append(result.x)
w = list(xOptimal[0])
w = [ 0 if wx < 0.0000001 else wx for wx in w ]
Rix2 = self.History(self.symbols_objects, self.window)
adjust = []
for symbol in self.symbols:
d1 = Rix.loc[symbol]['close'].to_list()
d1 = d1[-1]
d2 = Rix2.loc[symbol]['close'].to_list()
d2 = d2[-1]
adjust.append(0.9-((d2-d1)/d1))
if not self.use_last:
self.Liquidate()
targets = []
for i in range(len(w)):
currency = self.symbols[i]
if not self.use_last:
self.SetHoldings(currency, w[i])
else:
targets.append(PortfolioTarget(currency, adjust[i]*w[i]))
if self.use_last:
self.SetHoldings(targets)
return
def initializeSymbols(self):
#self.symbols = [ name+"USD" for name in self.Ris.columns.tolist()]
#self.Debug(str(self.symbols))
self.symbols_objects = []
for symbol in self.symbols:
data = self.AddCrypto(symbol, Resolution.Hour, Market.Bitfinex)
data.SetFeeModel(CustomFeeModel(self))
self.symbols_objects.append(data.Symbol)
return
def OnEndOfAlgorithm(self):
self.Liquidate()
try:
#Ri = self.Ris[0:self.warm_up_count, :]
#Ei = np.mean(Ri, axis = 0)
if self.window == 0:
self.window = self.warm_up_count
Rix = self.History(self.symbols_objects, self.window)
Ri = []
for symbol in self.symbols:
d = Rix.loc[symbol]['close'].to_list()
Ri.append(d)
Ri = np.array(Ri).transpose()
Ri = StockReturnsComputing(Ri, self.window, self.portfolioSize)
Ei = np.mean(Ri, axis = 0)
#self.Debug(Ei)
except:
self.Log(str("Error during data extraction"))
return
cov = np.cov(Ri, rowvar=False)
#initialization
xOptimal =[]
minRiskPoint = []
expPortfolioReturnPoint =[]
maxSharpeRatio = 0
#compute maximal Sharpe Ratio and optimal weights
result = MaximizeSharpeRatioOptmzn(Ei, cov, self.r0, self.portfolioSize)
xOptimal.append(result.x)
w = list(xOptimal[0])
w = [ 0 if wx < 0.0000001 else wx for wx in w ]
self.Debug(w)
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.002
return OrderFee(CashAmount(fee, "USD"))
def MaximizeSharpeRatioOptmzn(MeanReturns, CovarReturns, RiskFreeRate, PortfolioSize):
# define maximization of Sharpe Ratio using principle of duality
def f(x, MeanReturns, CovarReturns, RiskFreeRate, PortfolioSize):
funcDenomr = np.sqrt(np.matmul(np.matmul(x, CovarReturns), x.T) )
funcNumer = np.matmul(np.array(MeanReturns),x.T)-RiskFreeRate
func = -(funcNumer / funcDenomr)
return func
#define equality constraint representing fully invested portfolio
def constraintEq(x):
A=np.ones(x.shape)
b=1
constraintVal = np.matmul(A,x.T)-b
return constraintVal
#define bounds and other parameters
xinit=np.repeat(0.33, PortfolioSize)
cons = ({'type': 'eq', 'fun':constraintEq})
lb = 0
ub = 1
bnds = tuple([(lb,ub) for x in xinit])
#invoke minimize solver
opt = optimize.minimize (f, x0 = xinit, args = (MeanReturns, CovarReturns,\
RiskFreeRate, PortfolioSize), method = 'SLSQP', \
bounds = bnds, constraints = cons, tol = 10**-3)
return opt
def StockReturnsComputing(StockPrice, Rows, Columns):
StockReturn = np.zeros([Rows-1, Columns])
for j in range(Columns): # j: Assets
for i in range(Rows-1): # i: Daily Prices
StockReturn[i,j]=((StockPrice[i+1, j]-StockPrice[i,j])/StockPrice[i,j])*100
return StockReturn