| Overall Statistics |
|
Total Trades 398 Average Win 6.59% Average Loss -4.22% Compounding Annual Return 30.185% Drawdown 38.300% Expectancy 0.600 Net Profit 11588.020% Sharpe Ratio 1.147 Loss Rate 38% Win Rate 62% Profit-Loss Ratio 1.56 Alpha 0.239 Beta -0.13 Annual Standard Deviation 0.207 Annual Variance 0.043 Information Ratio 1.068 Tracking Error 0.207 Treynor Ratio -1.83 Total Fees $461.87 |
#######################################################################################################
# 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
#######################################################################################################
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
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:
self.AddEquity(symbol, Resolution.Daily) # Add securities to portfolio
self.mom.append(self.MOM(symbol, period)) # 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)):
self.SetHoldings( self.symbols[i], weights[i] )
def OnData(self, data):
pass