| Overall Statistics |
|
Total Trades 404 Average Win 40.70% Average Loss -2.47% Compounding Annual Return 29.572% Drawdown 75.600% Expectancy 9.234 Net Profit 10630.579% Sharpe Ratio 0.826 Probabilistic Sharpe Ratio 7.846% Loss Rate 41% Win Rate 59% Profit-Loss Ratio 16.49 Alpha 0.233 Beta 0.351 Annual Standard Deviation 0.303 Annual Variance 0.092 Information Ratio 0.635 Tracking Error 0.315 Treynor Ratio 0.714 Total Fees $2032.52 Estimated Strategy Capacity $3200000.00 Lowest Capacity Asset GLD T3SKPOF94JFP |
#######################################################################################################
# Name: Dynamic Turtle [Plain Vanilla]
# Strategy: Dynamic weighted rebalancing based on momentum with tax harvesting
# Version: 1.0
# Status: Dev + QA
#
# TODOS:
# Quarterly vs annual tax harvesting
# Dynamic securities selection
# Dynamic growth vs safety concentration
# Quarterly securities rotation
# Incorporate VXX more safely
# Tighten tax harvesting
# Hedging component if VXX not sufficient
# Explore if balancing can be more tax efficient
# Test with other indicators
#
# BUGS/ISSUES:
# Tax sweep not working as expected
# Metrics not plotting properly
# Reduce drawdown, leverage
# Verify with QC community how SetHoldings adjusts by percent vs Liquidation
# Check with QC team why backtesting summary returns are skewed with Portfolio.SetCash
#######################################################################################################
#region imports
from AlgorithmImports import *
from clr import AddReference
AddReference("System.Core")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
from System import *
from QuantConnect import *
from QuantConnect.Algorithm import QCAlgorithm
from QuantConnect.Data.UniverseSelection import *
import decimal as d
from datetime import datetime, timedelta
from decimal import Decimal
import numpy as np
#endregion
class DynamicWeightedRebalancingTaxHarvesterAlgo(QCAlgorithm):
def Initialize(self):
# set up params
self.cash = 1000.0 # Starting cash
self.deposit = 100.0 # Periodic cash addons
self.symbols = ['SPY', 'GOOG', 'GLD',] # Securities of interest (market, growth, reserve)
self.mom = [] # Momemtum indicator vector
self.weights = [0.4, 0.4, 0.2] # Fixed weight allocation
self.dynamic = True # Dynamic weight toggle
self.addons = True # Periodic cash deposits
self.investment = self.cash # Starting investment
self.gains = 0.0 # Last annual gains
self.taxrate = 0.3 # Short term capital gains tax rate (TODO: optimize)
self.taxsweep = 0.0 # Additional profits to generate to pay off taxes
period = 15 # Better returns, sharpe than 30 days but drawdown, winrate needs improvement
start = datetime(2000, 1, 1)
self.SetStartDate(start - timedelta(period)) # Step back from start to warm up
self.SetEndDate(2018,1,1) # Plus one day to calc taxes for 2017
self.SetCash(self.cash) # Set starting cash
# set up symbols and indicators
for symbol in self.symbols:
data = self.AddEquity(symbol, Resolution.Minute) # Add securities to portfolio
data.SetLeverage(10)
self.mom.append(self.MOM(symbol, period, Resolution.Daily)) # Add momentum indicators
# schedule rebalancing
self.Schedule.On(self.DateRules.MonthStart(), \
self.TimeRules.At(9, 45), \
Action(self.Rebalance))
# set up chart to track metrics
metrics = Chart('Metrics')
def Rebalance(self):
# check if indicators warmed up
if not self.mom[0].IsReady:
return
# keep Uncle Sam in mind when year closes
today = datetime.now()
if( today.month == 1 and self.gains == 0):
# calculate annual taxes to sweep
if(self.Portfolio.TotalProfit > 0):
self.gains = float(self.Portfolio.TotalPortfolioValue) - self.investment
if( self.gains > 0 ):
self.taxsweep = self.gains * self.taxrate
self.Log("Total Profit = ${:.2f}, Annual Gains = ${:.2f}, Taxes @ {}% = {:.2f}".format( \
float(self.Portfolio.TotalProfit), self.gains, self.taxrate * 100, self.taxsweep))
self.Log("Tax Sweep Harvesting Initiated!")
self.Plot('Metrics', 'Annual Gains', self.gains)
self.Plot('Metrics', 'Annaul Taxes', self.taxsweep)
else:
if( self.gains < 0 ):
self.Log("Report Losses for Taxes = ${:.2f}".format(self.gains))
self.Plot('Metrics', 'Annual Losses', self.gains)
self.gains = 0.0
else:
self.Log("No Taxes To Pay!")
# Based on April calendar for personal taxes, will have to be quarterly if based on corporate tax rate
if( today.month <= 4 and self.gains > 0 and self.taxsweep > 0 ):
cash = float(self.Portfolio.Cash)
if( cash <= 0 and self.Portfolio[self.symbols[0]].Quantity > 0):
# sell a portion of portfolio to pay off taxes
order = self.MarketOrder(self.symbols[0], -1, False, 'Tax Sweep')
cash = float(order.QuantityFilled * order.AverageFillPrice)
self.Plot('Metrics', 'Liquidation', cash)
cashOut = self.taxsweep if cash > self.taxsweep else cash
self.Log("{}-{}-{}: Cash = ${:.2f}, Tax Sweep = ${:.2f}, Balance Taxes = ${:.2f}".format( \
today.month, today.day, today.year, cash, cashOut, self.taxsweep))
self.Portfolio.SetCash( cash - cashOut )
self.taxsweep -= cashOut
if(self.taxsweep <= 0):
self.Log("Tax Sweep Completed!")
# add periodic cash deposit to portfolio if enabled
if self.addons:
self.Portfolio.SetCash( float(self.Portfolio.Cash) + self.deposit )
self.investment += self.deposit
self.Plot('Metrics', 'Total Invested', self.investment)
# calculate momemtum based weights
weights = self.weights
if self.dynamic:
s = 0.0
for i in range(0, len(self.mom)):
s += float(self.mom[i].Current.Value)
weights = [float(self.mom[i].Current.Value)/s for i in range(0, len(self.mom))]
# adjust holdings based on new weights
for i in range(0, len(self.symbols)):
if self.Securities.ContainsKey(self.symbols[i]) and self.Securities[self.symbols[i]].Price != 0:
self.SetHoldings( self.symbols[i], weights[i] )
def OnData(self, data):
pass