Overall Statistics |
Total Orders 1620 Average Win 1.09% Average Loss -0.90% Compounding Annual Return 7.413% Drawdown 21.500% Expectancy 0.181 Start Equity 160000 End Equity 538139.73 Net Profit 236.337% Sharpe Ratio 0.454 Sortino Ratio 0.508 Probabilistic Sharpe Ratio 3.719% Loss Rate 47% Win Rate 53% Profit-Loss Ratio 1.21 Alpha 0 Beta 0 Annual Standard Deviation 0.083 Annual Variance 0.007 Information Ratio 0.657 Tracking Error 0.083 Treynor Ratio 0 Total Fees $28899.02 Estimated Strategy Capacity $1000.00 Lowest Capacity Asset DBP TP2MIF0KNIAT Portfolio Turnover 5.37% |
# region imports from AlgorithmImports import * # endregion class TradedStrategy(Enum): MOMENTUM = 1 FRONT_MONTH = 2 COMBO = 3
# region imports from AlgorithmImports import * import pandas as pd from pandas.core.frame import DataFrame from typing import List from traded_strategy import TradedStrategy # endregion class MetatronCommoditySeasonality(QCAlgorithm): _notional_value: int = 160_000 _long_period: int = 250 _short_period: int = 20 _asset_count: int = 2 _offset_months: int = 10 _trade_exec_minute_offset: int = 15 _traded_strategy: TradedStrategy = TradedStrategy.COMBO def initialize(self) -> None: self.set_start_date(2008, 1, 1) self.set_cash(self._notional_value) leverage: int = 3 self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN) tickers: List[str] = ['DBA', 'DBB', 'DBE', 'DBP'] self._traded_assets: List[Symbol] = [ self.add_equity(ticker, Resolution.MINUTE, leverage=leverage).symbol for ticker in tickers ] self._trade_flag: bool = False self.settings.minimum_order_margin_portfolio_percentage = 0. self.schedule.on( self.date_rules.month_end(self._traded_assets[0]), self.time_rules.before_market_close(self._traded_assets[0], self._trade_exec_minute_offset), self.selection ) def on_data(self, slice: Slice) -> None: # monthly rebalance if not self._trade_flag: return self._trade_flag = False long: List[str] = [] short: List[str] = [] # correlation signal history: DataFrame = self.history( TradeBar, self._traded_assets, self._long_period, resolution=Resolution.DAILY ) prices: DataFrame = history.close.unstack(level=0) monthly_returns: DataFrame = prices.groupby(pd.Grouper(freq='M')).last().pct_change().dropna() if len(prices) == self._long_period and len(prices.columns) == len(self._traded_assets): observed_performance: DataFrame = monthly_returns[monthly_returns.index.month == (self.time - pd.DateOffset(months=self._offset_months)).month].iloc[0] returns: DataFrame = prices.pct_change().dropna() long_corr: DataFrame = returns.iloc[-self._long_period:].corr() short_corr: DataFrame = returns.iloc[-self._short_period:].corr() long_corr_mean: float = long_corr.values[np.triu_indices_from(long_corr.values, 1)].mean() short_corr_mean: float = short_corr.values[np.triu_indices_from(short_corr.values, 1)].mean() trade_flag: bool = False if short_corr_mean > long_corr_mean else True sorted_assets: List[str] = list((prices.iloc[-1] / prices.iloc[0] - 1).sort_values(ascending=trade_flag if self._traded_strategy == TradedStrategy.MOMENTUM else False).index) if self._traded_strategy == TradedStrategy.MOMENTUM: long = sorted_assets[:self._asset_count] short = sorted_assets[-self._asset_count:] else: if trade_flag: if self._traded_strategy in [TradedStrategy.FRONT_MONTH, TradedStrategy.COMBO]: above_median_bool: Series = observed_performance > monthly_returns.median() for symbol, is_above in zip(observed_performance.index, above_median_bool): long.append(symbol) if is_above else short.append(symbol) else: if self._traded_strategy == TradedStrategy.COMBO: long = sorted_assets[:self._asset_count] short = sorted_assets[-self._asset_count:] else: self.log('Not enough data for correlation signal.') # order execution for i, portfolio in enumerate([long, short]): for symbol in portfolio: if slice.contains_key(str(symbol)) and slice[str(symbol)]: q: int = self._notional_value / len(portfolio) // slice[symbol].price self.market_order( symbol, ((-1) ** i) * q, ) def selection(self) -> None: self._trade_flag = True self.liquidate()