| Overall Statistics |
|
Total Orders 10403 Average Win 0.70% Average Loss -0.73% Compounding Annual Return -20.728% Drawdown 91.600% Expectancy -0.055 Start Equity 10000000 End Equity 902861.02 Net Profit -90.971% Sharpe Ratio -0.767 Sortino Ratio -0.854 Probabilistic Sharpe Ratio 0.000% Loss Rate 52% Win Rate 48% Profit-Loss Ratio 0.96 Alpha -0.172 Beta 0.26 Annual Standard Deviation 0.201 Annual Variance 0.04 Information Ratio -0.994 Tracking Error 0.226 Treynor Ratio -0.591 Total Fees $962687.53 Estimated Strategy Capacity $370000000.00 Lowest Capacity Asset CL YUBLBNKSVGSH Portfolio Turnover 267.01% |
# region imports
from AlgorithmImports import *
from futures_roll_model import FuturesRollModel
# endregion
class DefaultRollFuturesModel(FuturesRollModel):
def _find_near_contract(
self,
futures_symbol: Symbol,
min_expiry: int,
contract_offset: int,
) -> Symbol|None:
contract_symbols: List[Symbol] = self._algo.future_chain_provider.get_future_contract_list(
futures_symbol, self._algo.time + timedelta(days=min_expiry)
)
if len(contract_symbols) < contract_offset + 1:
self._algo.log(f'Not enough contracts found for {futures_symbol}')
return None
return sorted(contract_symbols, key=lambda symbol: symbol.id.date)[contract_offset]
def _execute(self) -> None:
if self._algo.portfolio.invested:
self._algo.liquidate()
near_contract: None|Symbol = self._find_near_contract(
self._futures_symbol,
self._min_expiry,
self._contract_offset
)
if near_contract is not None:
self._open_position(near_contract)
self._algo.log(f'Selected contract for {self._futures_symbol}: {near_contract.value} (expiry: {near_contract.id.date})')
else:
self._algo.log(f'Could not find contract for {self._futures_symbol}')
def _open_position(self, contract_symbol: Symbol) -> None:
self._algo.add_future_contract(contract_symbol, Resolution.MINUTE)
if self._algo.securities[contract_symbol].get_last_data():
notional_value: float = self._algo.securities[contract_symbol].get_last_data().price * self._algo.securities[contract_symbol].symbol_properties.contract_multiplier
quantity: int = self._algo.portfolio.total_portfolio_value // notional_value
self._algo.market_order(contract_symbol, self._trade_direction * quantity)
def _close_position(self, contract_symbol: Symbol) -> None:
self._algo.remove_security(contract_symbol)# region imports
from AlgorithmImports import *
from abc import abstractmethod, ABC
# endregion
class FuturesRollModel(ABC):
def __init__(
self,
algo,
futures_symbol: Symbol,
min_expiry: int,
contract_offset: int,
trade_direction: int) -> None:
self._algo = algo
self._min_expiry = min_expiry
self._contract_offset: int = contract_offset
self._trade_direction: int = trade_direction
self._futures_symbol: Future = futures_symbol
@abstractmethod
def _find_near_contract(
self,
futures_symbol: Symbol,
min_expiry: int,
contract_offset: int) -> Symbol|None:
pass
@abstractmethod
def _execute(self) -> None:
pass# https://quantpedia.com/strategies/intraday-drift-in-crude-oil-price/
#
# The investment universe for this strategy includes Brent Crude Oil futures contracts, specifically the F1 (front-month) and F3 (third-month) contracts.
# The approach also considers reinvestment opportunities in the S&P 500 Index (we selected that) and 3-month Treasury Bills.
# (Raw data consisted of tick-by-tick, transaction-level observations of all trades carried out on the Exchange (not just oil), post-processed by authors.)
# Short Summary: The strategy uses historical data analysis to identify intra-day seasonal patterns and price anomalies in the Brent Crude Oil futures market.
# (The methodology involves entering long or short positions based on deviations from expected seasonal patterns.)
# Strategy Execution (strategy version shorting futures, not spread CL1 vs. CL3):
# Short (sell) CL 3-month out contract at 11:00 a.m.
# Cover (buy) CL 3-month out contract at 4:00 p.m.
# Weighting & Rebalancing: Traded intra-daily, profits re-invested in asset tracking S&P 500 Index (for example, ETFs SPY or VOO, or CFD possibly).
#
# Implementation changes:
# - Position cover is done at 5:00 p.m.
# - CL3 contract is used as a trading asset.
# region imports
from AlgorithmImports import *
from default_futures_roll_model import DefaultRollFuturesModel
from typing import Dict, Tuple
# endregion
class FinecoFuturesRolling(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2015, 1, 1)
self.set_cash(10_000_000)
cover_hour: int = 17
execute_hour: int = 11
contract_offset: int = 2
min_expiry: int = 2
futures: Dict[str, Tuple] = {
# Tuple (market, contract offset, trade direction)
Futures.Energy.CRUDE_OIL_WTI: (Market.NYMEX, contract_offset, -1),
Futures.Indices.SP_500_E_MINI: (Market.CME, 0, 1)
}
self._futures_models: Dict[str, DefaultRollFuturesModel] = {
f : DefaultRollFuturesModel(
self,
Symbol.create(f, SecurityType.FUTURE, market),
min_expiry,
contract_offset,
trade_direction
) for f, (market, contract_offset, trade_direction) in futures.items()
}
seeder = FuncSecuritySeeder(self.get_last_known_prices)
self.set_security_initializer(BrokerageModelSecurityInitializer(self.brokerage_model, seeder))
market: Symbol = self.add_equity('SPY', Resolution.MINUTE).symbol
self.schedule.on(
self.date_rules.every_day(market),
self.time_rules.at(execute_hour, 0),
lambda: self._futures_models[Futures.Energy.CRUDE_OIL_WTI]._execute()
)
self.schedule.on(
self.date_rules.every_day(market),
self.time_rules.at(cover_hour, 0),
lambda: self._futures_models[Futures.Indices.SP_500_E_MINI]._execute()
)
def on_data(self, slice: Slice) -> None:
pass