| Overall Statistics |
|
Total Orders 260 Average Win 0.49% Average Loss -0.40% Compounding Annual Return 2.375% Drawdown 6.000% Expectancy 0.172 Start Equity 10000000 End Equity 11245912.79 Net Profit 12.459% Sharpe Ratio -0.39 Sortino Ratio -0.479 Probabilistic Sharpe Ratio 8.905% Loss Rate 47% Win Rate 53% Profit-Loss Ratio 1.23 Alpha 0 Beta 0 Annual Standard Deviation 0.033 Annual Variance 0.001 Information Ratio 0.498 Tracking Error 0.033 Treynor Ratio 0 Total Fees $10680.28 Estimated Strategy Capacity $0 Lowest Capacity Asset CL YRTZN5AFWT6P Portfolio Turnover 1.89% |
#region imports
from AlgorithmImports import *
#endregion
# Algorithm framework model that produces insights
class OilFuturesAlphaModel(AlphaModel):
_roc = RateOfChange(1)
_rebalance = True
def __init__(self, algorithm):
# Subscribe to the oil production dataset
self._dataset_symbol = algorithm.add_data(USEnergy, USEnergy.Petroleum.UnitedStates.WEEKLY_FIELD_PRODUCTION_OF_CRUDE_OIL).symbol
def update(self, algorithm: QCAlgorithm, data: Slice) -> List[Insight]:
# Get the latest oil production data
if data.contains_key(self._dataset_symbol):
data_point = data[self._dataset_symbol]
self._roc.update(data_point.end_time, data_point.value)
if self._roc.is_ready:
self._rebalance = True
# Rebalance when we aren't invested (this occurs when the current contract we're holding expires)
if not algorithm.portfolio.invested and self._roc.is_ready:
self._rebalance = True
# Rebalance when there is new oil production data and there is Futures data in the current slice
if not self._rebalance or data.future_chains.count == 0:
return []
self._rebalance = False
# Law of supply and demand: increasing supply => decreasing price; decreasing supply => increasing price
# If supply is increasing, sell futures. Otherwise, buy futures.
direction = InsightDirection.DOWN if self._roc.current.value > 0 else InsightDirection.UP
for continuous_contract_symbol, chain in data.futures_chains.items():
contract = sorted(chain, key=lambda contract: contract.open_interest, reverse=True)[0]
insight = Insight.price(contract.symbol, timedelta(30), direction, weight=0.01)
return [insight]
# region imports
from AlgorithmImports import *
from universe import FrontMonthFutureUniverseSelectionModel
from alpha import OilFuturesAlphaModel
# endregion
class CreativeOrangeBull(QCAlgorithm):
_undesired_symbols_from_previous_deployment = []
_checked_symbols_from_previous_deployment = False
def initialize(self):
self.set_end_date(datetime.now())
self.set_start_date(self.end_date - timedelta(5*365))
self.set_cash(1_000_0000)
self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)
self.settings.minimum_order_margin_portfolio_percentage = 0
self.set_security_initializer(BrokerageModelSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)))
self.universe_settings.extended_market_hours = True
self.add_universe_selection(FrontMonthFutureUniverseSelectionModel())
self.add_alpha(OilFuturesAlphaModel(self))
self.settings.rebalance_portfolio_on_security_changes = False
self.set_portfolio_construction(InsightWeightingPortfolioConstructionModel(lambda time: None))
self.add_risk_management(NullRiskManagementModel())
self.set_execution(ImmediateExecutionModel())
self.set_warm_up(timedelta(31))
def on_data(self, data):
# Exit positions that aren't backed by existing insights.
# If you don't want this behavior, delete this method definition.
if not self.is_warming_up and not self._checked_symbols_from_previous_deployment:
for security_holding in self.portfolio.values():
if not security_holding.invested:
continue
symbol = security_holding.symbol
if not self.insights.has_active_insights(symbol, self.utc_time):
self._undesired_symbols_from_previous_deployment.append(symbol)
self._checked_symbols_from_previous_deployment = True
for symbol in self._undesired_symbols_from_previous_deployment[:]:
if self.is_market_open(symbol):
self.liquidate(symbol, tag="Holding from previous deployment that's no longer desired")
self._undesired_symbols_from_previous_deployment.remove(symbol)
#region imports from AlgorithmImports import * #endregion # 05/19/2023: -Added a warm-up period to restore the algorithm state between deployments. # -Added OnWarmupFinished to liquidate existing holdings that aren't backed by active insights. # https://www.quantconnect.com/terminal/processCache?request=embedded_backtest_4f0fa464125a3306e9237254176c5ab4.html # # 07/13/2023: -Fixed warm-up logic to liquidate undesired portfolio holdings on re-deployment # -Set the MinimumOrderMarginPortfolioPercentage to 0 # https://www.quantconnect.com/terminal/processCache?request=embedded_backtest_7f923538e767ce02d42c960dbea0f539.html # # 04/15/2024: -Updated to PEP8 style # https://www.quantconnect.com/terminal/processCache?request=embedded_backtest_a2aeb172d58ba770560f82537b63b3d8.html
#region imports
from AlgorithmImports import *
from Selection.FutureUniverseSelectionModel import FutureUniverseSelectionModel
#endregion
class FrontMonthFutureUniverseSelectionModel(FutureUniverseSelectionModel):
def __init__(self,) -> None:
super().__init__(timedelta(1), self._select_future_chain_symbols)
def _select_future_chain_symbols(self, _: datetime) -> List[Symbol]:
return [Symbol.create(Futures.Energies.CRUDE_OIL_WTI, SecurityType.FUTURE, Market.NYMEX)]
def filter(self, filter: FutureFilterUniverse) -> FutureFilterUniverse:
return filter.front_month().only_apply_filter_at_market_open()