| Overall Statistics |
|
Total Orders 1144 Average Win 0.91% Average Loss -0.61% Compounding Annual Return 0.879% Drawdown 33.600% Expectancy 0.048 Start Equity 100000 End Equity 124491.15 Net Profit 24.491% Sharpe Ratio -0.167 Sortino Ratio -0.197 Probabilistic Sharpe Ratio 0.000% Loss Rate 58% Win Rate 42% Profit-Loss Ratio 1.51 Alpha -0.014 Beta 0.008 Annual Standard Deviation 0.079 Annual Variance 0.006 Information Ratio -0.321 Tracking Error 0.176 Treynor Ratio -1.584 Total Fees $3189.42 Estimated Strategy Capacity $2000000000.00 Lowest Capacity Asset SPY R735QTJ8XC9X Portfolio Turnover 7.05% |
# region imports
from AlgorithmImports import *
from scipy.optimize import minimize
import numpy as np
# endregion
class TI_Research(QCAlgorithm):
def initialize(self):
self.set_start_date(2000, 1, 1)
self.set_cash(100000)
ticker = "SPY"
self.eq = self.add_equity(ticker, Resolution.DAILY).Symbol
self.lookback = 252
self.data_window = RollingWindow[TradeBar](self.lookback)
self.initial_params = [0.01, 0.01, 0.1]
self.ti_window = RollingWindow[float](3)
def on_data(self, data: Slice):
# Data preperation
if not (data.contains_key(self.eq) and data[self.eq] is not None):
return
if not self.data_window.is_ready:
trade_bars = self.history[TradeBar](self.eq, self.lookback+1, Resolution.DAILY)
for tb in trade_bars:
self.data_window.add(tb)
tb = data.bars.get(self.eq)
self.data_window.add(tb)
dw = list(self.data_window)
volume = [tb.volume for tb in dw]
lgr = [np.log((dw[i-1].close / dw[i].close)) for i in range(1, len(dw))] # Log returns for later reusability
result = minimize(self.transient_impact, self.initial_params, args=(volume, lgr), method='CG')
# alpha, beta, lambda_ = result.x
# Logging output is so output is easier to read
ti = np.log10(self.transient_impact(result.x, volume, lgr) + 0.00000000001) # small value to avoid log(0)
self.ti_window.add(ti)
self.plot("Transient impact", "Value", ti)
if not self.ti_window.is_ready: return
ti_min = self.ti_window[2] > self.ti_window[1] and self.ti_window[0] > self.ti_window[1] # Simple local minimum in transient impact
# To make this more sophisticated, could use a longer ti window and check if the 0 index is a saddle point or not
long = ti_min and self.data_window[1].close > self.data_window[0].close # Long off recent dips
short = ti_min and self.data_window[1].close < self.data_window[0].close # Short off recent tops
if long: # Enter long
self.set_holdings(self.eq, 0.50, True)
elif short: # Enter short
self.set_holdings(self.eq, -0.50, True)
def transient_impact(self, params, volume, lgr):
# Get input how we need it
alpha, beta, lambda_ = params
volume = np.array(volume)
# Get the current level of impact
instantaneous = alpha * volume[:-1]
# Get the impact we expect to see with exponential decay
transient = np.array([beta * np.sum(volume[:t] * np.exp(-lambda_ * (t - np.arange(t)))) for t in range(1, len(volume))])
# Evaluate the transient impact model value
predicted_change = instantaneous + transient
return np.sum((lgr - predicted_change) ** 2)