| Overall Statistics |
|
Total Trades 935 Average Win 0.23% Average Loss -0.13% Compounding Annual Return 2.702% Drawdown 7.500% Expectancy 0.784 Net Profit 66.665% Sharpe Ratio 0.631 Probabilistic Sharpe Ratio 2.491% Loss Rate 36% Win Rate 64% Profit-Loss Ratio 1.80 Alpha 0.009 Beta 0.404 Annual Standard Deviation 0.03 Annual Variance 0.001 Information Ratio -0.146 Tracking Error 0.035 Treynor Ratio 0.048 Total Fees $2339.62 Estimated Strategy Capacity $1400000.00 Lowest Capacity Asset STPZ UFAW82TG117P |
"""
- One of the original Agile Strategies. Momentum is not working correctly with the migration. Returns are much less and drawdown is 1.5x what we showed previously.
Look at different selection criteria in QC
- Agile Propritary
"""
import math
import sys
import numpy as np
import statistics
from time import sleep
'''
Institutional Bond
'''
class DeterminedLightBrownAlpaca(QCAlgorithm):
#List<SymbolData> SymbolData = new List<SymbolData>();
def Initialize(self):
# LIVE TRADING
if self.LiveMode:
self.Debug("Trading Live!")
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
# Group Trading
# Use a default FA Account Group with an Allocation Method
self.DefaultOrderProperties = InteractiveBrokersOrderProperties()
# account group created manually in IB/TWS
self.DefaultOrderProperties.FaGroup = "Institutional Bond"
# supported allocation methods are: EqualQuantity, NetLiq, AvailableEquity, PctChange
self.DefaultOrderProperties.FaMethod = "AvailableEquity"
# set a default FA Allocation Profile
# Alex: I commented the following line out, since it would "reset" the previous settings
#self.DefaultOrderProperties = InteractiveBrokersOrderProperties()
# allocation profile created manually in IB/TWS
# self.DefaultOrderProperties.FaProfile = "TestProfileP"
#Algo Start
self.SetCash(100000)
self.SetStartDate(2003, 1, 1)
#self.SetEndDate(2007, 1, 1)
self.SetBenchmark("AGG")
self.SymbolData = []
self.Keeps = int(self.GetParameter("Keeps"))
self.CashReservePct = float(self.GetParameter("CashReservePct"))
self.WeightRa = float(self.GetParameter("WeightRa"))
self.WeightRb = float(self.GetParameter("WeightRb"))
self.WeightV = float(self.GetParameter("WeightV"))
self.FilterSMA = int(self.GetParameter("FirstSMA"))
self.LookbackRa = self.GetParameter("LookbackRa")
self.LookbackRb = self.GetParameter("LookbackRb")
self.LookbackV = self.GetParameter("LookbackV")
self.Url = ""
self.RebalancePeriod = self.GetParameter("RebalancePeriod")
self.GrowthSymbols = []
self.SafetySymbol = self.AddEquity("SHY", Resolution.Daily).Symbol
# List<string> GrowthSymbols = new List<String>{
# // safety symbol
# "SHY"
# };
self.first = True
self.LastRotationTime = self.Time
#self.LastRotationTime = DateTime.Now
self.SetGrowthSymbols()
self.Debug("WeightRa: " + str(self.WeightRa))
self.Debug("WeightRb: " + str(self.WeightRb))
self.Debug("WeightV: " + str(self.WeightV))
self.Debug("SymbolUrl: " + str(self.Url))
self.Debug("RebalancePeriod: " + str(self.RebalancePeriod))
periodADays = self.DaysFromLookback(self.LookbackRa)
periodBDays = self.DaysFromLookback(self.LookbackRb)
periodVolDays = self.DaysFromLookback(self.LookbackV)
#self.Debug("LookbackRa: {0} ({1} days)", str(self.LookbackRa), str(periodADays))
#self.Debug("LookbackRb: {0} ({1} days)", str(self.LookbackRb), str(periodBDays))
#self.Debug("LookbackV: {0} ({1} days)", str(self.LookbackV), str(periodVolDays))
for symbol in self.GrowthSymbols:
self.Debug("adding symbol to universe: " + symbol);
symbolObject = self.AddSecurity(SecurityType.Equity, symbol, Resolution.Daily).Symbol
# TODO - add parameters for lookback periods
periodAPerf = self.MOMP(symbol, periodADays, Resolution.Daily)
periodBPerf = self.MOMP(symbol, periodBDays, Resolution.Daily) #// (252/12) * x months
''' FIXME '''
#// FIXME -The moving average is calculated using the closing price from the last day of each month(e.g. a 10 month moving average has 10 datapoints)
#filterSMA = self.SMA(symbol, 21*self.FilterSMA, Resolution.Daily)
''' Update this indicator every month '''
#filterSMA = SimpleMovingAverage(21*self.FilterSMA)
filterSMA = SimpleMovingAverage(self.FilterSMA)
consolidator = TradeBarConsolidator(CalendarType.Monthly)
self.RegisterIndicator(symbolObject, filterSMA, consolidator)
#// we use log returns instead of definition in replay
std = StandardDeviation(periodVolDays)
annualizationFactor = float(math.sqrt(252))
logReturns = self.LOGR(symbol, periodVolDays, Resolution.Daily)
#CompositeIndicator<IndicatorDataPoint> volatility = std.Of(logReturns).Times(annualizationFactor);
volatility_ = IndicatorExtensions.Of(std, logReturns)
volatility = IndicatorExtensions.Times(volatility_, annualizationFactor)
#sleep(1)
sData = SymbolData(symbol, periodAPerf, periodBPerf, volatility, filterSMA)
self.SymbolData.append(sData)
# SymbolData.Add(new SymbolData
# {
# Symbol = symbol,
# ReturnA = periodAPerf,
# ReturnB = periodBPerf,
# Volatility = volatility,
# FilterSMA = filterSMA
# });
#// assumption we don't look back more than a year
self.SetWarmup(timedelta(days=365))
self.lastDay = -1
self.newWeek = True
self.newMonth = True
self.counter = 2
self.newBiWeek = True
self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
self.Schedule.On(self.DateRules.MonthStart("SPY"),
self.TimeRules.At(0, 0),
self.TrackMonth)
self.Schedule.On(self.DateRules.WeekStart("SPY"),
self.TimeRules.At(0, 0),
self.TrackWeek)
self.Schedule.On(self.DateRules.WeekStart("SPY"),
self.TimeRules.At(0, 0),
self.TrackBiWeekly)
def TrackMonth(self):
self.newMonth = True
def TrackWeek(self):
self.newWeek = True
def TrackBiWeekly(self):
if self.counter == 2:
self.newBiWeek = True
self.counter = 1
else:
self.counter += 1
def DaysFromLookback(self, period):
strAmount = period[0:len(period)-1]
#strAmount = period.substr(0, len(period)-1)
amount = np.int32(strAmount)
#amount = int32.Parse(strAmount)
multiplier = period.endswith("M")
if multiplier == False:
multiplier = 1
else:
multiplier = 21
return multiplier * amount
def SetGrowthSymbols(self):
self.GrowthSymbols.append("BIL")
self.GrowthSymbols.append("FLOT")
self.GrowthSymbols.append("FLTR")
self.GrowthSymbols.append("GBF")
self.GrowthSymbols.append("GOVT")
self.GrowthSymbols.append("GSY")
self.GrowthSymbols.append("GVI")
self.GrowthSymbols.append("IEI")
self.GrowthSymbols.append("LQD")
self.GrowthSymbols.append("MBB")
self.GrowthSymbols.append("MINT")
self.GrowthSymbols.append("SHM")
self.GrowthSymbols.append("SHY")
self.GrowthSymbols.append("SPIP")
self.GrowthSymbols.append("SPTI")
self.GrowthSymbols.append("STIP")
self.GrowthSymbols.append("STPZ")
self.GrowthSymbols.append("USIG")
self.GrowthSymbols.append("VCSH")
#self.GrowthSymbols.append("TBF")
#self.GrowthSymbols.append("TBX")
self.GrowthSymbols.append("TLT")
def GetEtfReplayRankings(self):
#orderedReturnA = self.SymbolData.OrderByDescending(x.ReturnA)
orderedReturnA = sorted(self.SymbolData, key = lambda x: x.ReturnA.Current.Value, reverse = True)
#orderedReturnB = self.SymbolData.OrderByDescending(x.ReturnB)
orderedReturnB = sorted(self.SymbolData, key = lambda x: x.ReturnB.Current.Value, reverse = True)
#orderedVol = self.SymbolData.OrderBy(x.Volatility)
orderedVol = sorted(self.SymbolData, key = lambda x: x.Volatility.Current.Value, reverse = False)
rankings = {}
#// add all the symbols
for sd in self.SymbolData:
rankings[sd.Symbol] = 0.0
for i in range(0, len(orderedReturnA), 1):
current = orderedReturnA[i]
rankings[current.Symbol] += i * self.WeightRa
for i in range(0, len(orderedReturnB), 1):
current = orderedReturnB[i]
rankings[current.Symbol] += i * self.WeightRb
for i in range(0, len(orderedVol), 1):
current = orderedVol[i]
rankings[current.Symbol] += i * self.WeightV
return rankings
def OnData(self, data):
if self.IsWarmingUp: return
self.Log("OnData slice: date=" + str(self.Time))# + ", cnt=" + data.Count);
try:
rebalance = False
if self.first:
rebalance = True
self.first = False
else:
#// var delta = Time.Subtract(LastRotationTime);
#// rebalance = delta > RotationInterval;
rebalance = self.LastRotationTime.month != self.Time.month
if (self.RebalancePeriod == "Quarterly"):
#// January - 1
#// April - 4
#// July - 7
#// Oct - 10
rebalance = (rebalance and
((self.Time.month == 1) or
(self.Time.month == 4) or
(self.Time.month == 7) or
(self.Time.month == 10)))
if rebalance == True:
self.Debug("Rebalancing Quarterly on " + str(self.Time))
elif (self.RebalancePeriod == "Daily"):
if self.Time.day != self.lastDay:
rebalance = True
self.lastDay = self.Time.day
self.Debug("Rebalancing Daily on " + str(self.Time))
else:
rebalance = False
elif (self.RebalancePeriod == "Weekly"):
if self.newWeek == True:
rebalance = True
self.newWeek = False
self.Debug("Rebalancing Weekly on " + str(self.Time))
else:
rebalance = False
elif (self.RebalancePeriod == "Bi-Monthly"):
if self.newBiWeek == True:
rebalance = True
self.newBiWeek = False
self.Debug("Rebalancing Bi Weekly on " + str(self.Time))
else:
rebalance = False
elif(self.RebalancePeriod == "Monthly"):
if self.newMonth == True:
rebalance = True
self.newMonth = False
self.Debug("Rebalancing Monthly on " + str(self.Time))
else:
rebalance = False
if rebalance:
self.LastRotationTime = self.Time
#// pick which one is best from growth and safety symbols
#//var orderedObjScores = SymbolData.OrderByDescending(x => x.ObjectiveScore).ToList();
rankings = self.GetEtfReplayRankings()
#orderedObjScores = self.SymbolData.OrderBy(rankings[x.Symbol]).ToList()
orderedObjScores = sorted(self.SymbolData, key = lambda x: rankings[x.Symbol], reverse = False)
nextHoldings = []
self.Debug("OrderedObjScores Count: " + str(len(orderedObjScores)))
for i in range(0, self.Keeps, 1):
currentSymbolData = orderedObjScores[i]
lastClose = self.Securities[currentSymbolData.Symbol].Close
maFilter = lastClose > currentSymbolData.FilterSMA.Current.Value
if maFilter:
#// this meets our investment criteria
if self.Portfolio[self.SafetySymbol].Invested:
self.Liquidate(self.SafetySymbol)
nextHoldings.append(currentSymbolData.Symbol)
else:
if self.SafetySymbol not in nextHoldings:
nextHoldings.append(self.SafetySymbol)
#// FIXME - allocate to "Safety" symbol, right now saftey symbol is part of the growth symbols so it should bubble up but that doesn't match replay
#else:
# if not self.Portfolio[self.SafetySymbol].Invested:
# self.Liquidate()
# self.SetHoldings(self.SafetySymbol, 1)
# break
# if self.Portfolio[self.SafetySymbol].Invested:
# break
if self.Portfolio[self.SafetySymbol].Invested:
return
self.Log(">>NextPositions<<")
for position in nextHoldings:
self.Log("\t>>" + str(position))
if len(nextHoldings) == 0:
#// goto 100% cash
self.Log("LIQUIDATE>>CASH")
self.Liquidate()
else:
for kvp in self.Portfolio.Securities.Values:
#// liquidate anything we are currently invested in but not part of our next portfolio state
if kvp.Invested and not (kvp.Symbol in nextHoldings):
self.Log("LIQUIDATE>>" + str(kvp.Symbol))
self.Liquidate(kvp.Symbol)
allocationPercentage = (1.0 - self.CashReservePct) / self.Keeps
for symbol in nextHoldings:
self.Log("BUY>>" + str(symbol))
self.SetHoldings([PortfolioTarget(symbol, allocationPercentage)])
except:
print("Unexpected error:", sys.exc_info()[0])
raise
#Error("OnData: " + ex.Message + "\r\n\r\n" + ex.StackTrace);
class SymbolData:
def __init__(self, symbol, ReturnA, ReturnB, Volatility, FilterSMA):
self.Symbol = symbol
self.ReturnA = ReturnA
self.ReturnB = ReturnB
self.Volatility = Volatility
self.FilterSMA = FilterSMA