Overall Statistics |
Total Orders 2646 Average Win 0.07% Average Loss -0.07% Compounding Annual Return 1.617% Drawdown 18.300% Expectancy 0.249 Start Equity 100000000 End Equity 126910221.94 Net Profit 26.910% Sharpe Ratio -0.095 Sortino Ratio -0.049 Probabilistic Sharpe Ratio 0.040% Loss Rate 36% Win Rate 64% Profit-Loss Ratio 0.94 Alpha -0.021 Beta 0.181 Annual Standard Deviation 0.047 Annual Variance 0.002 Information Ratio -0.759 Tracking Error 0.123 Treynor Ratio -0.025 Total Fees $384670.43 Estimated Strategy Capacity $690000000.00 Lowest Capacity Asset QQQ RIWIV7K5Z9LX Portfolio Turnover 1.97% |
# region imports from AlgorithmImports import * # endregion class SwimmingLightBrownGalago(QCAlgorithm): def initialize(self): self.set_start_date(2010, 1, 1) self.set_cash(100_000_000) self.settings.daily_precise_end_time = False self.settings.minimum_order_margin_portfolio_percentage = 0 lookback_years = 12 # 12 years includes 3 non-leap years & 3 election cycles self._lookback = lookback_years * 252 self._period = 21 # Trading days self.universe_settings.resolution = Resolution.DAILY self._universe = self.add_universe(self._select_assets) spy = Symbol.create('SPY', SecurityType.EQUITY, Market.USA) self.schedule.on( self.date_rules.every_day(spy), self.time_rules.midnight, self._rebalance ) self.set_portfolio_construction(AccumulativeInsightPortfolioConstructionModel(Resolution.DAILY, PortfolioBias.LONG, 1/self._period)) self._all_history = pd.DataFrame() def _select_assets(self, fundamentals): return [ Symbol.create(ticker, SecurityType.EQUITY, Market.USA) for ticker in ['SPY', 'QQQ'] ] def on_securities_changed(self, changes): for security in changes.removed_securities: self._all_history.drop(security.symbol, inplace=True) history = self.history([security.symbol for security in changes.added_securities], self._lookback, Resolution.DAILY) if not history.empty: self._all_history = self._all_history.join(history.open.unstack(0), how='outer') def _rebalance(self): # Append new rows. self._all_history = pd.concat([ self._all_history, self.history(list(self._universe.selected), 1, Resolution.DAILY).open.unstack(0) ]) # Drop rows with duplicate indices, then trim to lookback window size. self._all_history = self._all_history.loc[~self._all_history.index.duplicated(keep='last')].iloc[-self._lookback:] # Calculate expected return of entering at next market open. period_returns = self._all_history.dropna(axis=1).pct_change(self._period).shift(-self._period).dropna() expected_return_by_day = period_returns.groupby(period_returns.index.strftime('%m-%d')).mean() expected_return = expected_return_by_day.loc[(self.time + timedelta(1)).strftime('%m-%d')] # Calculate signal for this trade. std = expected_return_by_day.std() mean = expected_return_by_day.mean() long_threshold = mean + std short_threshold = mean - std signals = ( (expected_return >= long_threshold).astype(int) # Long signals - (expected_return <= short_threshold).astype(int) # Short signals ) # Rebalance. #self.set_holdings([PortfolioTarget(symbol, signal/len(signals)) for symbol, signal in signals.items()], True) self.emit_insights([Insight.price(symbol, timedelta(30), signal) for symbol, signal in signals.items()])