Overall Statistics |
Total Trades 100 Average Win 1.88% Average Loss -0.92% Compounding Annual Return 36.856% Drawdown 3.700% Expectancy 0.701 Net Profit 36.738% Sharpe Ratio 3.369 Probabilistic Sharpe Ratio 98.219% Loss Rate 44% Win Rate 56% Profit-Loss Ratio 2.04 Alpha 0 Beta 0 Annual Standard Deviation 0.112 Annual Variance 0.012 Information Ratio 3.369 Tracking Error 0.112 Treynor Ratio 0 Total Fees $3600.32 Estimated Strategy Capacity $4400000.00 |
# https://github.com/QuantConnect/Lean/blob/master/Algorithm.Framework/Risk/MaximumDrawdownPercentPortfolio.py # QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. # Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from clr import AddReference AddReference("System") AddReference("QuantConnect.Common") AddReference("QuantConnect.Algorithm") AddReference("QuantConnect.Algorithm.Framework") from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework import * from QuantConnect.Algorithm.Framework.Portfolio import PortfolioTarget from QuantConnect.Algorithm.Framework.Risk import RiskManagementModel class MaximumDrawdownPercentPortfolio(RiskManagementModel): '''Provides an implementation of IRiskManagementModel that limits the drawdown of the portfolio to the specified percentage.''' def __init__(self, maximumDrawdownPercent = 0.2, isTrailing = False): '''Initializes a new instance of the MaximumDrawdownPercentPortfolio class Args: maximumDrawdownPercent: The maximum percentage drawdown allowed for algorithm portfolio compared with starting value, defaults to 5% drawdown</param> isTrailing: If "false", the drawdown will be relative to the starting value of the portfolio. If "true", the drawdown will be relative the last maximum portfolio value''' self.maximumDrawdownPercent = -abs(maximumDrawdownPercent) self.isTrailing = isTrailing self.initialised = False self.portfolioHigh = 0; def ManageRisk(self, algorithm, targets): '''Manages the algorithm's risk at each time step Args: algorithm: The algorithm instance targets: The current portfolio targets to be assessed for risk''' currentValue = algorithm.Portfolio.TotalPortfolioValue if not self.initialised: self.portfolioHigh = currentValue # Set initial portfolio value self.initialised = True # Update trailing high value if in trailing mode if self.isTrailing and self.portfolioHigh < currentValue: self.portfolioHigh = currentValue return [] # return if new high reached pnl = self.GetTotalDrawdownPercent(currentValue) if pnl < self.maximumDrawdownPercent and len(targets) != 0: self.initialised = False # reset the trailing high value for restart investing on next rebalcing period return [ PortfolioTarget(target.Symbol, 0) for target in targets ] return [] def GetTotalDrawdownPercent(self, currentValue): return (float(currentValue) / float(self.portfolioHigh)) - 1.0
from clr import AddReference AddReference("System") AddReference("QuantConnect.Algorithm") AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Orders import * from QuantConnect.Orders.Fees import * from QuantConnect.Securities import * from QuantConnect.Orders.Fills import * from QuantConnect.Brokerages import * # from QuantConnect.Algorithm.Framework.Risk import * from QuantConnect.Data.Custom.USTreasury import * from sklearn.linear_model import LinearRegression import numpy as np import pandas as pd import math import statsmodels.api as sm from statsmodels.tsa.stattools import coint, adfuller from datetime import datetime # from VolatilityModel import CustomVolatilityModel from RiskManagement import MaximumDrawdownPercentPortfolio # Compute the Value-at-Risk and Tail VaR for Pairs-Trading Portfolio class RiskManagementForPairTrading(QCAlgorithm): def Initialize(self): # Backtest between 7/1/2017 and 7/1/2018 self.SetStartDate(2017, 7, 1) self.SetEndDate(2018,7,1) self.cash = 1000000 self.SetCash(self.cash) self.enter = 1 self.exit = 0 self.betarange = 0.25 self.lookback = 30 self.resolution = Resolution.Daily # Pairs ---------------------------------------------------------------- self.pairs =['MS','GS'] # 0.25 order matters since beta -> 1 is much more profitable self.symbols =[] self.securities = [] for ticker in self.pairs: security = self.AddEquity(ticker, Resolution.Hour) self.symbols.append(security.Symbol) self.securities.append(security) # Cash Buffer ------------------------------------------------------------ # Adjust the cash buffer from the default 2.5% to 5% self.Settings.FreePortfolioValuePercentage = 45 self.buffer = 0.4 # Brokerage ------------------------------------------------------------ self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) # Risk Management: Maximum Drawdown ------------------------------------ # self.AddRiskManagement( MaximumDrawdownPercentPortfolio() ) # stop-loss self.stop = 4 # daily-pnl-volatility self.port_daily_pnl = [] # sharpe ratio self.rate_of_return = [] self.daily_performance = [] # valu-at-risk self.var_limit = 5000 self.ci = 0.95 # Execution ------------------------------------------------------------ self.SetExecution(ImmediateExecutionModel()) # Risk Free Rate ------------------------------------------------------------ self.yield_curve = self.AddData(USTreasuryYieldCurveRate, "USTYCR").Symbol self.rates = [] # Warm Up -------------------------------------------------------------- self.SetWarmUp(timedelta(self.lookback)) # ------------------------------------------------------------ self.yesterday_total_profit = 0 self.yesterday_total_fees = 0 self.yesterday_total_unrealized_profit = 0 self.yesterday_total_portfolio_value = self.cash def risk_management_sharpe_ratio(self,rates, c): if (self.sharpe_ratio_calc(rates) <= c): self.Liquidate() # Calculate annualized sharpe ratio def sharpe_ratio_calc(self, rates): sharpe_ratio =( self.annualized_rate_of_return_calc() - rates) /self.annualized_port_std_calc() self.Log(f"SR: {sharpe_ratio}") return sharpe_ratio # Calculate annualized rate of return def annualized_rate_of_return_calc(self): annualized_rate_of_return = Math.Pow(np.mean(self.daily_performance) + 1, 252) - 1 # annualized_rate_of_return = Math.Pow(np.mean(self.rate_of_return[-self.lookback:]) + 1, 252) - 1 self.Log(f"annualized ror: {annualized_rate_of_return}") return annualized_rate_of_return # Calculate annualted portfolio standard deviation def annualized_port_std_calc(self): annualized_port_std = np.std(self.daily_performance) * math.sqrt(252) self.Log(f"std: {annualized_port_std}") return annualized_port_std def OnData(self,data): if self.IsWarmingUp: return if data.ContainsKey(self.yield_curve): rate = data[self.yield_curve] if rate.OneYear: # self.Log(f"one year {rate.OneYear}") self.rates.append(rate.OneYear/100.0) self.risk_free_rate = np.mean(self.rates[-self.lookback:]) # self.Log(f" one year {self.risk_free_rate}") if self.Time.hour == 10: if not data.ContainsKey(self.pairs[0]): return if not data.ContainsKey(self.pairs[1]): return # self.rate_of_return.append(self.Portfolio.TotalPortfolioValue/self.cash - 1 ) [zscore,self.beta,self.adf] = self.port_check(self.symbols[0], self.symbols[1]) ticker1_holdings = self.Portfolio[self.pairs[0]] self.equity = self.Portfolio.TotalPortfolioValue if ticker1_holdings.Invested: if (ticker1_holdings.IsShort and zscore >= self.exit) or \ (ticker1_holdings.IsLong and zscore <= self.exit): self.Liquidate(self.pairs[0]) self.Liquidate(self.pairs[1]) # Risk Management: Sharpe Ratio -------------------------------- # Sharpe Ratio > 0.5 self.risk_management_sharpe_ratio(self.risk_free_rate, 0.5) elif [self.beta >= 1 - self.betarange and self.beta <= 1+self.betarange]: # If portfolio is not invested, then equity = cash = margin = buying power when leverage is 2 wt1 = self.beta/(1+self.beta) wt2 = 1/(1+self.beta) C = self.equity *2 *(1-self.buffer) # self.Debug(str(data[self.symbols[0]].Price) + ", "+ str(self.Portfolio[self.pairs[0]].Price)) price1 = data[self.symbols[0]].Price price2 = data[self.symbols[1]].Price pos1 = round(wt1 * C / price1) pos2 = round(wt2 * C / price2) # Buy ticker1, Sell ticker2 if zscore > self.enter: self.MarketOrder(self.symbols[0], pos1) self.MarketOrder(self.symbols[1], -pos2) # self.Log(f"adf {self.adf}") # Sell ticker1, Buy ticker2 if zscore < - self.enter: self.MarketOrder(self.symbols[0], -pos1) self.MarketOrder(self.symbols[1], pos2) # self.Log(f"adf {self.adf}") else: pass def OnEndOfDay(self, symbol): self.daily_performance.append((self.Portfolio.TotalPortfolioValue - self.yesterday_total_portfolio_value)/self.yesterday_total_portfolio_value ) self.yesterday_total_portfolio_value = self.Portfolio.TotalPortfolioValue def port_check(self, symbol1, symbol2): x = np.log(self.History(symbol1, self.lookback, self.resolution).open.values) y = np.log(self.History(symbol2, self.lookback, self.resolution).open.values) regr = LinearRegression() x_constant = np.column_stack([np.ones(len(x)), x]) regr.fit(x_constant, y) beta = regr.coef_[1] # weight of ticker1: ticker2 = beta : 1 alpha = regr.intercept_ res = y - x*beta - alpha mean = np.mean(res) std = np.std(res) zscore = (res[-1] - mean)/std adf = adfuller(res) return [zscore, beta, adf]