| Overall Statistics |
|
Total Trades 1364 Average Win 3.94% Average Loss -2.51% Compounding Annual Return 179.200% Drawdown 59.400% Expectancy 0.352 Net Profit 13182.872% Sharpe Ratio 2.275 Probabilistic Sharpe Ratio 85.461% Loss Rate 47% Win Rate 53% Profit-Loss Ratio 1.57 Alpha 0.717 Beta 0.644 Annual Standard Deviation 0.673 Annual Variance 0.452 Information Ratio 0.481 Tracking Error 0.557 Treynor Ratio 2.376 Total Fees $161206.01 Estimated Strategy Capacity $37000.00 Lowest Capacity Asset ADAUSD 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):
#Backtest dates
self.SetStartDate(2017, 1, 2) # Set Start Date
#self.SetEndDate(2020,10,1)
#Algorithm cash
self.SetCash(4000)
#Download returns from each strategy
csv = self.Download('https://raw.githubusercontent.com/sergiosierram/SharpSignal/main/data/market_cap.csv')
self.market_data = pd.read_csv(StringIO(csv), index_col=0)
self.aux_func = lambda x: x+"USD"
self.symbols = []
self.last_symbols = []
self.resolution = Resolution.Hour
#Parameters
self.window = int(self.GetParameter("window"))
self.rebalance = int(self.GetParameter("rebalance"))
self.topx = int(self.GetParameter("TOPX"))
self.t_count = -1
self.week_count = 0
self.symbols = list(self.market_data.iloc[self.week_count,0:self.topx])
self.symbols = list(map(self.aux_func, self.symbols))
#Additional variables
#List to store previous weights
self.last_w = [0 for i in range(len(self.symbols))]
self.use_last = True
self.initializeSymbols()
self.initRollingWindow()
self.Rf=0 # April 2019 average risk free rate of return in USA approx 3%
annRiskFreeRate = self.Rf/100
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 initializeSymbols(self):
self.symbols_objects = []
for symbol in self.symbols:
try:
data = self.AddCrypto(symbol, self.resolution, Market.Bitfinex)
data.SetFeeModel(CustomFeeModel(self))
self.symbols_objects.append(data.Symbol)
except:
self.symbols.remove(symbol)
#self.Debug("Symbol not available: "+str(symbol))
return
def initRollingWindow(self):
c = 0
unavailable = 0
self.rolling = [RollingWindow[float](self.window) for symbol in self.symbols]
for symbol in self.symbols:
df = pd.DataFrame()
while df.empty:
try:
df = self.History(self.Symbol(symbol), self.window)
d = df['close'].to_list()
for x in d:
self.rolling[c].Add(x)
except:
unavailable += 1
#self.Debug("Unavailable data for: "+symbol)
break
c += 1
self.rolling[0:-unavailable]
return
def OnData(self, data):
self.t_count += 1
if self.t_count % self.rebalance == 0:
self.SpecificTime()
day, month, year = list(map(int, self.market_data.index[self.week_count].split('/')))
prevd = datetime(year+2000, month, day)
day, month, year = list(map(int, self.market_data.index[self.week_count+1].split('/')))
nextd = datetime(year+2000, month, day)
currentd = self.Time
if currentd > prevd and currentd <= nextd:
pass
else:
self.week_count += 1
self.last_symbols = list(self.symbols)
self.symbols = list(self.market_data.iloc[self.week_count,0:self.topx])
self.symbols = list(map(self.aux_func, self.symbols))
self.initializeSymbols()
self.initRollingWindow()
self.portfolioSize = len(self.symbols)
c = 0
for symbol in self.symbols:
if data.ContainsKey(symbol):
self.rolling[c].Add(data[symbol].Close)
c+=1
return
def SpecificTime(self):
#Check the len of the rolling windows
flag = True
for roll in self.rolling:
l = [i for i in roll][::-1]
self.Log(len(l))
if len(l) < self.window:
flag = False
if not flag:
return
#try:
Ri = []
c = 0
for symbol in self.symbols:
Ri.append([i for i in self.rolling[c]][::-1])
c+=1
Ri = np.array(Ri).transpose()
self.Log(Ri)
Ri = StockReturnsComputing(Ri, self.window, self.portfolioSize)
Ei = np.mean(Ri, axis = 0)
#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)
if self.week_count > 1:
self.LiquidateOldSymbols()
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, 0.75*w[i]))
if self.use_last:
self.SetHoldings(targets)
return
def LiquidateOldSymbols(self):
for symbol in self.last_symbols:
if symbol not in self.symbols:
self.Debug("###### not in last: "+symbol)
self.Liquidate(symbol)
return
def OnEndOfAlgorithm(self):
self.Liquidate()
return
# 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