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