Overall Statistics
Total Orders
914
Average Win
0.03%
Average Loss
-0.02%
Compounding Annual Return
1.207%
Drawdown
0.300%
Expectancy
0.084
Start Equity
10000000
End Equity
10080086.6
Net Profit
0.801%
Sharpe Ratio
-7.433
Sortino Ratio
-10.651
Probabilistic Sharpe Ratio
62.812%
Loss Rate
47%
Win Rate
53%
Profit-Loss Ratio
1.05
Alpha
-0.046
Beta
-0.007
Annual Standard Deviation
0.006
Annual Variance
0
Information Ratio
-1.903
Tracking Error
0.105
Treynor Ratio
6.45
Total Fees
$1965.10
Estimated Strategy Capacity
$6900000000.00
Lowest Capacity Asset
RTY YJHOAMPYKQGX
Portfolio Turnover
8.38%
#region imports
from AlgorithmImports import *
from scipy.stats import entropy
from collections import deque
import numpy as np
#endregion


class FuturesOrderFlow(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2024, 1, 1)
        self.set_cash(10000000)

        self.set_security_initializer(BrokerageModelSecurityInitializer(
            self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)))
        tickers = [
                   Futures.Indices.NASDAQ_100_E_MINI, 
                   Futures.Indices.RUSSELL_2000_E_MINI, 
                   Futures.Indices.SP_500_E_MINI, 
                   Futures.Indices.DOW_30_E_MINI]
        
        self.futures = []
        self.indicators = {}
        self.fill_prices = {}
        self.tp = 0.01
        self.sl = 0.01
        
        for ticker in tickers:
            future = self.add_future(ticker,
                resolution=Resolution.MINUTE,
                extended_market_hours=False,
                data_normalization_mode=DataNormalizationMode.BACKWARDS_RATIO,
                data_mapping_mode=DataMappingMode.OPEN_INTEREST,
                contract_depth_offset=0)
            self.futures.append(future)
        
        

    def on_data(self, data):
        for future in self.futures:

            current_contract = future.mapped

            if current_contract not in self.indicators:
                self.indicators[current_contract] = Indicators()
                continue

            c = self.securities[current_contract].Close

            self.indicators[current_contract].mas.Update(self.Time, c)
            self.indicators[current_contract].ens.update(c)
            
            if not self.indicators[current_contract].mas.IsReady or not self.indicators[current_contract].ens.is_ready:

                trade_bars = self.history[TradeBar](current_contract, 100+1, Resolution.MINUTE)
                for trade_bar in trade_bars:
                    self.indicators[current_contract].mas.update(self.time, trade_bar.close)
                    self.indicators[current_contract].ens.update(trade_bar.close)

                continue


            
            if not self.portfolio[current_contract].invested:

                a = self.securities[current_contract].ask_size
                b = self.securities[current_contract].bid_size
                delta = b - a

                bid_imb = b > a * 3 and delta > 0
                ask_imb = a > b * 3 and delta < 0

                ma_val = self.indicators[current_contract].mas.Current.Value
                en_val = self.indicators[current_contract].ens.value
                en_ma_val = self.indicators[current_contract].ens.ma_value

                if ma_val < c and en_val < en_ma_val:

                    if bid_imb:
                        self.market_order(current_contract, 1, tag="LONG ENTRY")
                        self.fill_prices[current_contract] = c

                    elif ask_imb:
                        self.market_order(current_contract, -1, tag="SHORT ENTRY")
                        self.fill_prices[current_contract] = c

                elif ma_val > c and en_val > en_ma_val:

                    if bid_imb:
                        self.market_order(current_contract, -1, tag="SHORT ENTRY")
                        self.fill_prices[current_contract] = c

                    elif ask_imb:
                        self.market_order(current_contract, 1, tag="LONG ENTRY")
                        self.fill_prices[current_contract] = c

            else:
                
                if self.portfolio[current_contract].is_long:

                    if c >= self.fill_prices[current_contract] * (1 + self.tp):
                        self.liquidate(current_contract, tag="LONG EXIT")
                        self.fill_prices[current_contract] = None

                    elif c <= self.fill_prices[current_contract] * (1 - self.sl):
                        self.liquidate(current_contract, tag="LONG EXIT")
                        self.fill_prices[current_contract] = None

                elif self.portfolio[current_contract].is_short:
                    
                    if c <= self.fill_prices[current_contract] * (1 - self.tp):
                        self.liquidate(current_contract, tag="SHORT EXIT")
                        self.fill_prices[current_contract] = None

                    elif c >= self.fill_prices[current_contract] * (1 + self.sl):
                        self.liquidate(current_contract, tag="SHORT EXIT")
                        self.fill_prices[current_contract] = None




class Indicators:
    def __init__(self):
        self.mas = SimpleMovingAverage(100)
        self.ens = entropy_indic()

    def update(self, time, price):
        self.mas.update(time, price)
        self.ens.update(price)
        

class entropy_indic(PythonIndicator):

    def __init__(self):
        super().__init__()

        self.period = 10
        self.ma_period = 20

        self.value = 0
        self.ma_value = 0

        self.queue = deque(maxlen=self.period)
        self.ma_queue = deque(maxlen=self.ma_period)

    def update(self, input_val):

        if not isinstance(input_val, float): return

        self.queue.append(input_val)

        if len(self.queue) == self.period:
            self.get_entropy()
            self.ma_queue.append(self.value)

        if self.is_ready:
            self.get_entropy_ma()

        return self.is_ready

    def get_entropy(self):

        tmp_list = list(self.queue)
        tmp_arr = np.array(tmp_list)

        base = 2
        value, counts = np.unique(tmp_arr, return_counts=True)
        probs = counts / np.sum(counts)
        En = entropy(probs, base=base)

        self.value = En

    def get_entropy_ma(self):

        tmp_list = list(self.ma_queue)
        tmp_arr = np.array(tmp_list)

        ma_val = sum(tmp_arr) / self.period

        self.ma_value = ma_val

    @property
    def is_ready(self):
        return len(self.ma_queue) == self.ma_period