Overall Statistics
Total Orders
151
Average Win
3.39%
Average Loss
-2.20%
Compounding Annual Return
5.116%
Drawdown
24.800%
Expectancy
0.425
Start Equity
100000
End Equity
227959.26
Net Profit
127.959%
Sharpe Ratio
0.19
Sortino Ratio
0.137
Probabilistic Sharpe Ratio
0.047%
Loss Rate
44%
Win Rate
56%
Profit-Loss Ratio
1.55
Alpha
0.023
Beta
0.031
Annual Standard Deviation
0.135
Annual Variance
0.018
Information Ratio
-0.343
Tracking Error
0.194
Treynor Ratio
0.825
Total Fees
$348.27
Estimated Strategy Capacity
$950000000.00
Lowest Capacity Asset
GC YYP4UM3T47ML
Portfolio Turnover
2.92%
Drawdown Recovery
3107
from AlgorithmImports import *
from datetime import timedelta, datetime
import pandas as pd
import pandas_ta as ta

class MGC_Strategy:

    def __init__(self, algorithm):
        self.algorithm = algorithm

        # ─── NQ Future pro obchodování ───────────────────────────────────── Futures.Metals.MICRO_GOLD
        self._future = self.algorithm.add_future(Futures.Metals.GOLD, Resolution.MINUTE, extended_market_hours=True)
        self._future.set_filter(90, 500)
        self.future_canonical = self._future.symbol
        
        # ─── QQQ pro signály (daily data) ───────────────────────────────────
        self.gld = self.algorithm.add_equity("GLD", Resolution.DAILY)
        self.gld_symbol = self.gld.symbol

        self.tlt = self.algorithm.add_equity("TLT", Resolution.DAILY)
        self.tlt_symbol = self.tlt.symbol
        
        # ─── QC Indikátory pro QQQ ──────────────────────────────────────────
        
        # Management proměnné
        self._max_days = 10
        self.diagnostical_loging = True
        self.entry_time = None
        self.active_future_contract = None
        self.regime = None
        self.trading = 0
        self._atr_limit_order = 0.10
        self._exposure = 1
        self.strategy = None
        self.trend_following = None
        self.breakout = None
        self._gld_breakout_qty = None
        
        self.algorithm.debug("MGC strategy inicializována s QQQ signály")
        
        # ─── Schedule po uzavření QQQ trhu ──────────────────────────────────
        self.algorithm.schedule.on(
            self.algorithm.date_rules.every_day("QQQ"),
            self.algorithm.time_rules.after_market_close("QQQ", 0),
            self._execute_trading_logic
        )

        self.algorithm.schedule.on(
            self.algorithm.date_rules.every_day(),
            self.algorithm.time_rules.before_market_open("QQQ", 60),
            self._cancel_orders
        )
        
    def on_warm_up_finished(self):
        self.algorithm.debug("MNQ MODULE - warm up finished")
        self._recover_after_restart()

    def _cancel_orders(self):
        if not self.algorithm.is_warming_up:
            self.algorithm.transactions.cancel_open_orders()
            

    def _recover_after_restart(self):
        for holding in self.algorithm.portfolio.values():
            symbol = holding.symbol
            if holding.invested and "GC" in symbol.value:
                self.active_future_contract = symbol
                holding_info = self.algorithm.portfolio[symbol]
                self.regime = "long" if holding_info.quantity > 0 else "short"
                
                key = f"entry_time_{symbol.value}"
                try:
                    saved_time = self.algorithm.object_store.read(key)
                    self.entry_time = datetime.fromisoformat(saved_time)
                except:
                    self.entry_time = self.algorithm.time
                
                self.algorithm.debug(f"Recovered MGC position: {symbol} | Regime: {self.regime} | Entry: {self.entry_time} | Qty: {holding_info.quantity}")
                return
        
        self.algorithm.debug("Not invested in MGC, nothing to recover")

    def on_data(self, slice: Slice):
        # ─── Aktualizace mapovaného NQ kontraktu ────────────────────────────
        if self.future_canonical in slice.future_chains:
            future_chain = slice.future_chains[self.future_canonical]
            if future_chain:
                sorted_contracts = sorted(future_chain, key=lambda c: c.expiry)
                new_contract = sorted_contracts[0].symbol
                
                if new_contract != self.active_future_contract:
                    if self.active_future_contract and self.algorithm.portfolio[self.active_future_contract].invested:
                        return
                    self.active_future_contract = new_contract
                    if not self.algorithm.is_warming_up:
                        self.algorithm.debug(f"New NQ contract mapped: {new_contract.value} exp: {sorted_contracts[0].expiry.date()}")
        

    def _execute_trading_logic(self):
        if self.algorithm.is_warming_up or self.active_future_contract is None:
            return

        gld_history = self.algorithm.history(TradeBar, self.gld_symbol, 210, Resolution.DAILY)
        tlt_history = self.algorithm.history(TradeBar, self.tlt_symbol, 210, Resolution.DAILY)
        invested = self.algorithm.portfolio[self.active_future_contract].invested
        atr_10 = self._atr(gld_history, 10)
        ibs = self._ibs(gld_history)
        momentum_gld = self._momentum(gld_history, 135)
        momentum_tlt = self._momentum(tlt_history, 135)
        contract    = self.active_future_contract
        three_day_high_close = self._high_breakout_close(gld_history , 4)
        ten_day_high_high = self._high_breakout_high(gld_history, 11)
        
        gld_today_close = gld_history.iloc[-1].close
        gld_yesteday_high = gld_history.iloc[-2].high

        if momentum_tlt is None or momentum_gld is None:
            return

        self.algorithm.debug(f"MGC MODULE -------- {self.algorithm.time} ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------")
        self.algorithm.debug(f"Contract: {contract} | Invested: {invested} | Momentum_TLT : {momentum_tlt:.2f} | Momentum_GLD: {momentum_gld:.2f}| price: {gld_today_close:.2f} | yesterday high: {gld_yesteday_high:.2f}")

        
        # ─── ENTRY: volatility_reversal ─────────────────────────────────────
        gld_trend_following = ((momentum_tlt > 0) and (momentum_gld > 0) and not invested)
        gld_breakout = ((tlt_history.iloc[-1].close > tlt_history.iloc[-2].close) and three_day_high_close == 1 and not invested and self.algorithm.time.weekday() != 4)
        long_pullback = (ibs < 0.15 and 
                        ten_day_high_high == 1 and
                        not invested)
        
        if gld_trend_following:
            current_price = self.algorithm.securities[self.active_future_contract].price
            
            # Výpočet velikosti pozice
            portfolio_value = self.algorithm.portfolio.total_portfolio_value
            risk_amount = portfolio_value * (self._exposure / 2)
            contract_multiplier = self.algorithm.securities[self.future_canonical].symbol_properties.contract_multiplier
            contract_value = current_price * contract_multiplier
            
            if contract_value <= 0:
                return

            qty_float = risk_amount / contract_value
            qty = max(1, int(qty_float))

            if qty <= 0:
                return

            
            order = self.algorithm.market_order(self.active_future_contract, int(qty))

            self.entry_time = self.algorithm.time
            self.regime = "long"
            self.trend_following = 1
            self._gld_trend_following = qty
            self.algorithm.debug(f"Entry Long {qty} Current price: @ ~{current_price:.1f}")

            return
        
        # ─── EXIT: trendfollowing ──────────────────────────────
        if invested:
            exit_triggered = ((momentum_tlt < 0) or (momentum_gld < 0))
            
            if exit_triggered:
                self.algorithm.liquidate(self.active_future_contract)
                self.algorithm.debug(f"EXIT LONG MGC @ GLD: {gld_today_close:.2f} | Včerejší high: {gld_yesteday_high:.2f}")
                self.entry_time = None
                self.regime = None


    def _ibs(self, bar_series):
        if len(bar_series) < 1: return None
        if bar_series['high'].iloc[-1] == bar_series['low'].iloc[-1]:
            return 0.5
        ibs = (bar_series['close'].iloc[-1] - bar_series['low'].iloc[-1]) / (bar_series['high'].iloc[-1] - bar_series['low'].iloc[-1])
        return ibs

    def _momentum(self, bar_series, period):
        if len(bar_series) < period: return None
        momentum_series = ta.mom(bar_series['close'], length=period)
        return momentum_series.iloc[-1]

    def _atr(self, bar_series, period):
        if len(bar_series) < period: return None
        atr_series = ta.atr(bar_series['high'], bar_series['low'], bar_series['close'], length=period)
        return atr_series.iloc[-1]

    def _high_breakout_close(self, bar_series, period):
        if len(bar_series) < period: return None
        recent_max = bar_series['close'].iloc[-period:].max()
        if bar_series['close'].iloc[-1] == recent_max:
            breakout = 1
        else:
            breakout = 0
        return breakout

    def _high_breakout_high(self, bar_series, period):
        if len(bar_series) < period: return None
        recent_max = bar_series['high'].iloc[-period:].max()
        if bar_series['high'].iloc[-1] == recent_max:
            breakout = 1
        else:
            breakout = 0
        return breakout
from AlgorithmImports import *
from datetime import timedelta
from datetime import time as dt_time
from MGC_module import MGC_Strategy

class AT_01(QCAlgorithm):
    
    def initialize(self):
        """Settings of Alghoritm"""
        self.set_start_date(2009, 6, 1)
        self.set_end_date(2025, 12, 1)
        self.set_cash(100000)
        self.set_warm_up(timedelta(days=300))
        self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.CASH)
        self.set_security_initializer(lambda security: security.set_fee_model(InteractiveBrokersFeeModel()))
        self.settings.seed_initial_prices = True
        benchmark = self.add_equity("SPY").symbol
        self.set_benchmark(benchmark)
        
        self.diagnostical_loging = True

        self._gold_strategy = 1
        
        if self._gold_strategy == 1: self._mgc_strategy = MGC_Strategy(self)



        self.schedule.on(
                        self.date_rules.every_day(),
                        self.time_rules.every(timedelta(minutes=60)),
                        self._hourly_sanity_check)


    def on_data(self, slice):
        if self._gold_strategy == 1:
            self._mgc_strategy.on_data(slice)


    def _hourly_sanity_check(self):
        if not self.is_warming_up and self.diagnostical_loging:
            self.debug(f"AT-01 - Main.py | Hourly Loging Sanity check - Successful | Current time: {self.time}")

    def on_order_event(self, order_event):
        """Loging/Debuging of Orders"""
        if order_event.status == OrderStatus.SUBMITTED:
            self.debug(f"NEW ORDER: {order_event.symbol} - {order_event.fill_quantity} @ ${order_event.fill_price}")
            # Save of entry time for MNQ ticker, so after restart it will recover data.
            if self.live_mode:
                if "MGC" in order_event.symbol.value:
                    key = f"entry_time_{order_event.symbol.value}"
                    self.object_store.save(key, str(self.time))

        elif order_event.status == OrderStatus.CANCELED:
            self.debug(f"Order Canceled: {order_event.symbol}")
        elif order_event.status == OrderStatus.INVALID:
            self.debug(f"Order Invalid: {order_event.symbol} - {order_event.message}")
    
    def on_brokerage_message(self, message):
        """Will Debug/Log Brokers message"""
        self.log(f"BROKERAGE MESSAGE: {message}")
        self.debug(f"BROKERAGE MESSAGE: {message}")

    def on_warmup_finished(self) -> None:
        self.debug(f"on warm up finished in MAIN CALL")
        if self._gold_strategy == 1:
            self._mgc_strategy.on_warm_up_finished()