| Overall Statistics |
|
Total Orders 286 Average Win 0.35% Average Loss -0.29% Compounding Annual Return 2.347% Drawdown 3.700% Expectancy 0.331 Start Equity 1000000 End Equity 1147770.44 Net Profit 14.777% Sharpe Ratio -0.684 Sortino Ratio -0.338 Probabilistic Sharpe Ratio 21.312% Loss Rate 39% Win Rate 61% Profit-Loss Ratio 1.19 Alpha 0 Beta 0 Annual Standard Deviation 0.022 Annual Variance 0 Information Ratio 0.751 Tracking Error 0.022 Treynor Ratio 0 Total Fees $1068.60 Estimated Strategy Capacity $21000000.00 Lowest Capacity Asset SPY R735QTJ8XC9X Portfolio Turnover 4.00% Drawdown Recovery 353 |
# region imports
from AlgorithmImports import *
from pandas.tseries.offsets import BDay
from dataclasses import dataclass
from datetime import date
# endregion
@dataclass
class HoldingItem():
quantity: int
holding_period: int = 0
FED_DAYS_flag: bool = False
class FedDays(PythonData):
algo = None
@staticmethod
def set_algo(algo) -> None:
FedDays.algo = algo
def GetSource(self, config: SubscriptionDataConfig, date: datetime, isLiveMode: bool) -> SubscriptionDataSource:
# if isLiveMode:
# # FedDays.algo.Log(f"Edited GetSource date {FedDays.algo.Time}")
# return SubscriptionDataSource("https://data.quantpedia.com/backtesting_data/economic/fed_days.json", SubscriptionTransportMedium.RemoteFile, FileFormat.UnfoldingCollection)
return SubscriptionDataSource("https://data.quantpedia.com/backtesting_data/economic/fed_days.csv", SubscriptionTransportMedium.RemoteFile)
def Reader(self, config: SubscriptionDataConfig, line: str, date: datetime, isLiveMode: bool) -> BaseData:
# if isLiveMode:
# try:
# # FedDays.algo.Log(f"Reader")
# objects = []
# data = json.loads(line)
# end_time = None
# for index, sample in enumerate(data):
# custom_data = FedDays()
# custom_data.Symbol = config.Symbol
# custom_data.Time = (datetime.strptime(str(sample["fed_date"]), "%Y-%m-%d") - BDay(1)).replace(hour=9, minute=31)
# # FedDays.algo.Log(f"{custom_data.Time}")
# end_time = custom_data.Time
# objects.append(custom_data)
# return BaseDataCollection(end_time, config.Symbol, objects)
# except ValueError:
# # FedDays.algo.Log(f"Reader Error")
# return None
# else:
# csv parsing
if not (line.strip() and line[0].isdigit()):
return None
custom = FedDays()
custom.Symbol = config.Symbol
custom.Time = (datetime.strptime(line, "%Y-%m-%d") - BDay(1)).replace(hour=9, minute=31)
custom.Value = 0.
custom["fed_date_str"] = line
return custom
##################
# ECB Strategy #
##################
@dataclass
class TradePosition:
buy_date: date
sell_date: date
ticket: OrderTicket | None = None
class ECBMeetingsData(PythonData):
_ecb_holding_period: int
@staticmethod
def set_holding_period(period: int) -> None:
ECBMeetingsData._ecb_holding_period = period
def get_source(self, config: SubscriptionDataConfig, date: datetime | date, isLiveMode: bool) -> SubscriptionDataSource:
url: str = 'https://data.quantpedia.com/backtesting_data/calendar/ECB_MEETINGS.csv'
return SubscriptionDataSource(url, SubscriptionTransportMedium.REMOTE_FILE)
def reader(self, config: SubscriptionDataConfig, line: str, date: datetime | date, isLiveMode: bool) -> BaseData:
data = ECBMeetingsData()
data.symbol = config.symbol
if not line[0].isdigit(): return None
split = line.split(';')
data.time = datetime.strptime(split[0], "%Y-%m-%d") - BDay(self._ecb_holding_period + 1)
return data
class CustomFeeModel(FeeModel):
def get_order_fee(self, parameters: OrderFeeParameters) -> OrderFee:
fee = parameters.security.price * parameters.order.absolute_quantity * 0.00001
return OrderFee(CashAmount(fee, "USD"))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
from data_tools import FedDays, HoldingItem, TradePosition, CustomFeeModel, ECBMeetingsData
class MetatronFEDDayPlusTOM(QCAlgorithm):
_notional_value: float = 1_000_000
_trade_exec_minute_offset: int = 15
_observed_period: int = 20
_traded_weight: float = .33
_holding_manager: List[HoldingItem] = []
# True -> Use indicator logic
_indicator_flag: bool = True
_traded_asset: str = 'SPY'
# FED days Strategy
_close_hour: int = 12
# ToM Strategy
_holding_period: int = 2
# ECB Strategy
_ecb_holding_period: int = 1 # Day
def initialize(self) -> None:
self.set_start_date(2020, 1, 1)
self.set_cash(self._notional_value)
self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)
self.settings.minimum_order_margin_portfolio_percentage = 0
self.settings.daily_precise_end_time = True
security: Security = self.add_equity(self._traded_asset, Resolution.MINUTE)
security.mom: Momentum = self.mom(self._traded_asset, self._observed_period, Resolution.DAILY)
self._traded_asset: Symbol = security.symbol
self._fed_days_symbol: Symbol = self.add_data(FedDays, 'fed_days', Resolution.DAILY, TimeZones.NEW_YORK).symbol
FedDays.set_algo(self)
self.set_warm_up(self._observed_period, Resolution.DAILY)
# ECB init
self._ecb_positions: list[TradePosition] = []
self._ewg_security: Security = self.add_equity("EWG", Resolution.MINUTE)
self._ewg_security.set_fee_model(CustomFeeModel())
self._ewg: Symbol = self._ewg_security.symbol
self._ecb_meetings: Symbol = self.add_data(ECBMeetingsData, 'ecb_meetings', Resolution.DAILY).symbol
ECBMeetingsData.set_holding_period(self._ecb_holding_period)
# schedule functions
self._month_close_flag: bool = False
self._day_close_flag: bool = False
self.schedule.on(
self.date_rules.every_day(self._traded_asset),
self.time_rules.before_market_close(self._traded_asset, self._trade_exec_minute_offset),
self._before_eod
)
self.schedule.on(
self.date_rules.month_end(self._traded_asset),
self.time_rules.before_market_close(self._traded_asset, self._trade_exec_minute_offset),
self._month_close
)
self.schedule.on(self.date_rules.every_day(self._ewg),
self.time_rules.before_market_close(self._ewg, self._trade_exec_minute_offset),
self._trade_ecb)
def on_data(self, slice: Slice) -> None:
if self.is_warming_up: return
if slice.contains_key(self._ecb_meetings) and slice[self._ecb_meetings]:
ecb_date: datetime = slice[self._ecb_meetings].time
self._ecb_positions.append(TradePosition(ecb_date.date(), (ecb_date + BDay(self._ecb_holding_period)).date()))
self.log(f"New ECB buying date arrived: {ecb_date}. Active ECB dates: {len(self._ecb_positions)} (should == 1)")
if not slice.contains_key(self._traded_asset):
return
items_to_delete: List[HoldingItem] = []
# Liquidate.
if self.portfolio[self._traded_asset].invested:
for holding_item in self._holding_manager:
if holding_item.FED_DAYS_flag:
if self.time.hour == self._close_hour:
self.log(f"FOMC meeting day; submitting an market order to close opened position...")
self.market_order(self._traded_asset, -holding_item.quantity)
items_to_delete.append(holding_item)
else:
if self._day_close_flag:
holding_item.holding_period -= 1
if holding_item.holding_period == 0:
self.log(f'Liquidating opened position after holding {self._holding_period} days.')
self.market_order(self._traded_asset, -holding_item.quantity)
items_to_delete.append(holding_item)
for item in items_to_delete:
self._holding_manager.remove(item)
# if self.is_warming_up: return
if self._day_close_flag:
self._day_close_flag = False
if self.securities.contains_key(self._fed_days_symbol) and self.securities[self._fed_days_symbol].get_last_data():
if self.time.date() == self.securities[self._fed_days_symbol].get_last_data().time.date():
# New fed day data arrived.
if self.securities[self._traded_asset].get_last_data():
self.log(f"New FOMC meeting data arrived: {self.time}; submitting an market order...")
quantity: int = self._notional_value * self._traded_weight // slice[self._traded_asset].price
self.market_order(self._traded_asset, quantity)
self._holding_manager.append(HoldingItem(quantity, FED_DAYS_flag=True))
if not self._month_close_flag:
return
self._month_close_flag = False
trade_flag: bool = False
asset_security: Security = self.securities[self._traded_asset]
if self._indicator_flag:
if asset_security.mom.is_ready:
trade_flag = True if asset_security.mom.current.value > 0 else False
self.log(f'{asset_security.symbol} momentum value: {asset_security.mom.current.value}; signal: {asset_security.mom.current.value > 0}')
else:
self.log(f'Momentum indicator is not ready for {asset_security.symbol}')
else:
trade_flag = True
if trade_flag:
# Turn of the month.
self.log(f'Turn of the month; submiting market order...')
quantity: int = self._notional_value * self._traded_weight // slice[self._traded_asset].price
self.market_order(self._traded_asset, quantity)
self._holding_manager.append(HoldingItem(quantity, holding_period=self._holding_period))
def _before_eod(self) -> None:
self._day_close_flag = True
def _month_close(self) -> None:
self._month_close_flag = True
def _trade_ecb(self) -> None:
# Open position
for position in self._ecb_positions:
if position.buy_date == self.time.date():
quantity_ewg: float = self._notional_value * self._traded_weight // self.securities[self._ewg].price
buy_ticket = self.market_order(self._ewg, quantity_ewg)
position.ticket = buy_ticket
self.log(f"Open ECB order | Quantity filled: {buy_ticket.quantity_filled}")
# Close position
positions_to_remove = []
for position in self._ecb_positions:
if self.time.date() >= position.sell_date:
if position.ticket and self.portfolio[self._ewg].invested:
sell_ticket = self.market_order(self._ewg, -self.portfolio[self._ewg].quantity)
self.log(f"Close ECB order | Quantity filled: {sell_ticket.quantity_filled}")
positions_to_remove.append(position)
for position in positions_to_remove:
self._ecb_positions.remove(position)
# 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):
FED_DAYS = 1
TOM = 2