| Overall Statistics |
|
Total Orders 74 Average Win 1.19% Average Loss -0.82% Compounding Annual Return 3.663% Drawdown 5.000% Expectancy 0.728 Start Equity 400000 End Equity 495374.89 Net Profit 23.844% Sharpe Ratio -0.118 Sortino Ratio -0.042 Probabilistic Sharpe Ratio 4.365% Loss Rate 30% Win Rate 70% Profit-Loss Ratio 1.46 Alpha -0.012 Beta 0.078 Annual Standard Deviation 0.044 Annual Variance 0.002 Information Ratio -0.563 Tracking Error 0.165 Treynor Ratio -0.066 Total Fees $414.90 Estimated Strategy Capacity $5900000.00 Lowest Capacity Asset VOO UPSZVZA9EQUD Portfolio Turnover 2.88% Drawdown Recovery 819 |
from AlgorithmImports import *
from pandas.tseries.offsets import BDay
from object_store_helper import ObjectStoreHelper
from typing import Any, List, Dict
from traded_strategy import TradedStrategy
# Algorithm notes:
# - Buy VOO 15 min before close on Monday if current price is at 10 day minumim at that time.
# - Liquidate position on Tuesday 15 min before close.
class MetatronTurnaroundTuesday(QCAlgorithm):
_traded_strategy: TradedStrategy = TradedStrategy.SHORT_TERM_MIN
_notional_value: float = 400_000
_trade_exec_minute_offset: int = 15
_history_period: int = 10
_consecutive_days_period: int = 2
_order_liquidation_date: Optional[datetime] = None
# True -> Looking for trading everyday.
# False -> Trading only at Monday.
_every_day_trading_flag: bool = False
_intraday_performance_filter_flag: bool = False
if _traded_strategy == TradedStrategy.TRUNAROUND_TUESDAY:
_every_day_trading_flag = False
def initialize(self) -> None:
self.set_start_date(2020, 1, 1)
self.set_cash(self._notional_value)
self._traded_asset: Symbol = self.add_equity('VOO', Resolution.MINUTE).symbol
self.log(f'Strategy {self._traded_strategy} is selected')
self._trade_flag: bool = False
if self._every_day_trading_flag:
self.schedule.on(
self.date_rules.every_day(),
self.time_rules.before_market_close(self._traded_asset, self._trade_exec_minute_offset),
self._selection
)
else:
self.schedule.on(
self.date_rules.every(DayOfWeek.MONDAY),
self.time_rules.before_market_close(self._traded_asset, self._trade_exec_minute_offset),
self._selection
)
self.settings.daily_precise_end_time = False
def on_data(self, slice: Slice) -> None:
if self._order_liquidation_date:
if self.portfolio.invested and self.time >= self._order_liquidation_date:
self.log(f'Liquidation date: {self._order_liquidation_date}; Current date: {self.time}')
self.liquidate()
self._order_liquidation_date = None
if not self._trade_flag:
return
self._trade_flag = False
if slice.contains_key(self._traded_asset) and slice[self._traded_asset]:
self.log(f'{self._traded_asset} present in algorithm')
history: DataFrame = self.history(
TradeBar, self._traded_asset, start=self.time - BDay(self._history_period), end=self.time
).unstack(level=0)
if not history.empty:
closes: DataFrame = history.resample('B').last().close[-self._history_period:]
if len(closes) < self._history_period:
self.log(f'{self._traded_asset} history has not enough data points with lenght of {len(closes)} while {self._history_period} is required')
return
trade_flag: bool = False
self.log(f'Intraday performance filter set: {self._intraday_performance_filter_flag}')
# Trade if there were last two consecutive down days.
if self._traded_strategy == TradedStrategy.TRUNAROUND_TUESDAY:
return_series = closes.diff()[-self._consecutive_days_period:]
self.log(f'Recent daily return series: {return_series}')
if (return_series < 0).all().bool():
trade_flag = True
elif self._traded_strategy == TradedStrategy.SHORT_TERM_MIN:
# Trade close at 10 day minimum.
close: float = closes.iloc[-1][0]
close_min: float = closes.min()[0]
if close == close_min:
self.log(f'Trade close is at {self._history_period} day minimum; Close: {close}; Min: {close_min}')
if self._intraday_performance_filter_flag:
intraday_history = history.loc[history.index.date == self.time.date()]
recent_close: float = intraday_history.close.iloc[-1][0]
previous_close: float = intraday_history.close.iloc[0][0]
if recent_close < previous_close:
self.log(f'Recent close is bellow previous close; recent close: {recent_close}; previous close: {previous_close}')
trade_flag = True
else:
self.log(f'Recent close is NOT bellow previous close; recent close: {recent_close}; previous close: {previous_close}')
else:
trade_flag = True
else:
self.log(f'Trade close is NOT at {self._history_period} day minimum; Close: {close}; Min: {close_min}')
self.log(f'Trade flag set: {trade_flag}')
if trade_flag:
self.log(f'Submitting market order')
self.market_order(
self._traded_asset,
self._notional_value // slice[self._traded_asset].price,
tag='MarketOrder'
)
else:
self.log(f'{self._traded_asset} history is empty')
else:
self.log(f'{self._traded_asset} NOT present in algorithm')
def on_order_event(self, orderEvent: OrderEvent) -> None:
order_ticket: OrderTicker = self.transactions.get_order_ticket(orderEvent.order_id)
symbol: Symbol = order_ticket.symbol
if orderEvent.status == OrderStatus.FILLED:
if 'MarketOrder' in order_ticket.tag:
# Set date for liquidation.
self._order_liquidation_date = self.securities[symbol].exchange.hours.get_next_trading_day(self.time)
self.log(f'Liquidation date set: {self._order_liquidation_date}')
def _selection(self) -> None:
self.log(f'New selection/rebalance signal on {self.time.date()}')
self._trade_flag = True# region imports
from AlgorithmImports import *
import json
from traded_strategy import TradedStrategy
# endregion
class ObjectStoreHelper:
def __init__(
self,
algorithm: QCAlgorithm,
path: str
) -> None:
"""
Initializes ObjectStoreHelper with reference to the algorithm instance.
"""
self._algorithm: QCAlgorithm = algorithm
self._path: str = path
def save_state(self, state: Dict) -> None:
"""
Saves a dictionary `state` to the Object Store as JSON.
"""
if not self._algorithm.live_mode:
return
json_data = json.dumps(state)
self._algorithm.object_store.save(self._path, json_data)
self._algorithm.log(f"Saved state to Object Store: {json_data}")
def load_state(self) -> Dict:
"""
Loads a JSON string from the Object Store and returns it as a dictionary.
"""
if self._algorithm.object_store.contains_key(self._path) and self._algorithm.live_mode:
json_data = self._algorithm.object_store.read(self._path)
if json_data:
self._algorithm.log(f"Loaded state from Object Store: {json_data}")
result: Dict = json.loads(json_data)
result['trade_signal'] = {TradedStrategy._member_map_[key]: value for key, value in result['trade_signal'].items() if key in TradedStrategy._member_map_}
return result
else:
return {
'trade_signal': {
TradedStrategy.CALENDAR: False,
TradedStrategy.REVERSAL_MODEL: False
},
'reversal_model_days_held': 0
}
return {}# region imports
from AlgorithmImports import *
from enum import Enum
# endregion
class TradedStrategy(Enum):
TRUNAROUND_TUESDAY = 1
SHORT_TERM_MIN = 2