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()