Overall Statistics
Total Orders
12378
Average Win
0.08%
Average Loss
-0.09%
Compounding Annual Return
509.596%
Drawdown
14.200%
Expectancy
0.089
Start Equity
100000.00
End Equity
168203.10
Net Profit
68.203%
Sharpe Ratio
7.419
Sortino Ratio
10.345
Probabilistic Sharpe Ratio
95.335%
Loss Rate
42%
Win Rate
58%
Profit-Loss Ratio
0.86
Alpha
2.573
Beta
0.391
Annual Standard Deviation
0.362
Annual Variance
0.131
Information Ratio
6.292
Tracking Error
0.381
Treynor Ratio
6.881
Total Fees
$0.00
Estimated Strategy Capacity
$120000.00
Lowest Capacity Asset
SOLUSD 2XR
Portfolio Turnover
2320.59%
from AlgorithmImports import *

class SymbolData:
    def __init__(self, symbol):
        self.symbol = symbol
        self.bar_data = pd.DataFrame(columns=['time', 'open', 'high', 'low', 'close', 'volume'])
        self.lower_threshold = 0.2
        self.upper_threshold = 0.8
        self.last_recalibrate_time = datetime.min
        self.consolidator = None

class CustomCryptoFeeModel(FeeModel):
    def __init__(self, fee_rate=0.00):
        self.fee_rate = fee_rate  # 0.2%

    def GetOrderFee(self, parameters: OrderFeeParameters) -> OrderFee:
        order = parameters.Order
        security = parameters.Security

        order_value = order.GetValue(security)
        fee = abs(order_value) * self.fee_rate
        return OrderFee(CashAmount(fee, security.QuoteCurrency.Symbol))


class IBSMultiAssetCrypto15Min(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2025, 4, 1)
        self.SetCash(100000)
        #self.set_brokerage_model(BrokerageName.KRAKEN, AccountType.CASH)


        self.tickers = ['BTCUSD','SOLUSD','XRPUSD','ETHUSD','SUIUSD']
        #self.tickers = ['BTCUSD']
        self.symbol_data = {}
        self.lookback_bars = 96  # Approx 1 day of 15-min bars
        self.SetWarmUp(timedelta(days=2))  # Enough for 2 days of 15-min bars

        for ticker in self.tickers:
            symbol = self.AddCrypto(ticker, Resolution.Minute).Symbol
            sym_data = SymbolData(symbol)
            self.symbol_data[symbol] = sym_data

          
            self.Securities[symbol].FeeModel = CustomCryptoFeeModel(0.00)

            consolidator = TradeBarConsolidator(timedelta(minutes=15))
            consolidator.DataConsolidated += self.OnBar
            self.SubscriptionManager.AddConsolidator(symbol, consolidator)
            sym_data.consolidator = consolidator
    
    
    def CalculateOrderQuantityFromDollar(self, symbol, dollar_amount):
        price = self.Securities[symbol].Price
        if price > 0:
            return dollar_amount / price
        return 0

    

    def get_market_status(self, symbol):
        now = self.Time

        # Access the correct Security object from self.Securities
        security = self.Securities[symbol]

        is_open_regular = security.Exchange.Hours.IsOpen(now, extendedMarketHours=False)
        is_open_extended = security.Exchange.Hours.IsOpen(now, extendedMarketHours=True)

        if is_open_regular:
            return True
        elif is_open_extended:
            return False
        else:
            return False

    def algo_status_debug(self):
        if not self.IsWarmingUp:

            positions = []
            for kvp in self.Portfolio:
                symbol = kvp.Key
                holding = kvp.Value
                if holding.Invested:
                    ticker = symbol.Value
                    avg_price = holding.AveragePrice
                    positions.append(f"{ticker}: ${avg_price:.2f}")

            self.Debug(f"Current Positions: [{', '.join(positions)}]")


    def OnBar(self, sender, bar: TradeBar):

        self.algo_status_debug()
        if self.IsWarmingUp:
            return

        sym_data = self.symbol_data[bar.Symbol]

        new_row = {
            'time': bar.EndTime,
            'open': bar.Open,
            'high': bar.High,
            'low': bar.Low,
            'close': bar.Close,
            'volume': bar.Volume
        }

        sym_data.bar_data = pd.concat([sym_data.bar_data, pd.DataFrame([new_row])], ignore_index=True)
        sym_data.bar_data = sym_data.bar_data.tail(500).reset_index(drop=True)

        if bar.High == bar.Low:
            return

        ibs = (bar.Close - bar.Low) / (bar.High - bar.Low)

 

        invested = self.Portfolio[bar.Symbol].Invested
        target_pct = 1.0 / len(self.symbol_data)

        if ibs < sym_data.lower_threshold and not invested:
            quantity = self.CalculateOrderQuantityFromDollar(bar.Symbol, 20000)
            #self.MarketOrder(bar.Symbol, quantity)

            self.set_holdings(bar.Symbol,0.20)
        elif ibs >= sym_data.upper_threshold and invested:
            self.Liquidate(bar.Symbol)

        self.Debug(f"{self.Time} | {bar.Symbol.Value} | IBS: {ibs:.2f} | LT: {sym_data.lower_threshold:.2f} | UT: {sym_data.upper_threshold:.2f} | Invested: {invested}")

    def OnEndOfAlgorithm(self):
        total_fees = sum([x.TotalFees for x in self.Portfolio.Values])
        self.Debug(f"Total fees paid: ${total_fees:.2f}")