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