In this project there's a lot of cool stuff going on. It is a simple order flow algorithm with a custom indicator as well. Let's dive into it.

 

We start by defining a universe of futures we want to trade and a few data structures to store useful information later: 

  1. def initialize(self):
  2. self.set_start_date(2024, 1, 1)
  3. self.set_cash(10000000)
  4. self.set_security_initializer(BrokerageModelSecurityInitializer(
  5. self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)))
  6. tickers = [
  7. Futures.Indices.NASDAQ_100_E_MINI,
  8. Futures.Indices.RUSSELL_2000_E_MINI,
  9. Futures.Indices.SP_500_E_MINI,
  10. Futures.Indices.DOW_30_E_MINI]
  11. self.futures = []
  12. self.indicators = {}
  13. self.fill_prices = {}
  14. self.tp = 0.01
  15. self.sl = 0.01
  16. for ticker in tickers:
  17. future = self.add_future(ticker,
  18. resolution=Resolution.MINUTE,
  19. extended_market_hours=False,
  20. data_normalization_mode=DataNormalizationMode.BACKWARDS_RATIO,
  21. data_mapping_mode=DataMappingMode.OPEN_INTEREST,
  22. contract_depth_offset=0)
  23. self.futures.append(future)
+ Expand

 

Next let's go over our indicator class. Instead of going through and making variables for each indicators values lets use one object with all of our information to access in one dictionary where the symbol is the key.

  1. class Indicators:
  2. def __init__(self):
  3. self.mas = SimpleMovingAverage(100)
  4. self.ens = entropy_indic()
  5. def update(self, time, price):
  6. self.mas.update(time, price)
  7. self.ens.update(price)

Next let's go through and make our entropy indicator. It will be the rolling entropy of the future and it will have a moving average value to determine if the entropy is high or low relative to the value.

  1. class entropy_indic(PythonIndicator):
  2. def __init__(self):
  3. super().__init__()
  4. self.period = 10
  5. self.ma_period = 20
  6. self.value = 0
  7. self.ma_value = 0
  8. self.queue = deque(maxlen=self.period)
  9. self.ma_queue = deque(maxlen=self.ma_period)
  10. def update(self, input_val):
  11. if not isinstance(input_val, float): return
  12. self.queue.append(input_val)
  13. if len(self.queue) == self.period:
  14. self.get_entropy()
  15. self.ma_queue.append(self.value)
  16. if self.is_ready:
  17. self.get_entropy_ma()
  18. return self.is_ready
  19. def get_entropy(self):
  20. tmp_list = list(self.queue)
  21. tmp_arr = np.array(tmp_list)
  22. base = 2
  23. value, counts = np.unique(tmp_arr, return_counts=True)
  24. probs = counts / np.sum(counts)
  25. En = entropy(probs, base=base)
  26. self.value = En
  27. def get_entropy_ma(self):
  28. tmp_list = list(self.ma_queue)
  29. tmp_arr = np.array(tmp_list)
  30. ma_val = sum(tmp_arr) / self.period
  31. self.ma_value = ma_val
  32. @property
  33. def is_ready(self):
  34. return len(self.ma_queue) == self.ma_period
+ Expand

If someone has a more efficient way to do that drop it below!

 

Next in on data we go through and initialize if necessary our indicators for each future, update them, then take and manage trades.

 

  1. def on_data(self, data):
  2. for future in self.futures:
  3. current_contract = future.mapped
  4. if current_contract not in self.indicators:
  5. self.indicators[current_contract] = Indicators()
  6. continue
  7. c = self.securities[current_contract].Close
  8. self.indicators[current_contract].mas.Update(self.Time, c)
  9. self.indicators[current_contract].ens.update(c)
  10. if not self.indicators[current_contract].mas.IsReady or not self.indicators[current_contract].ens.is_ready:
  11. trade_bars = self.history[TradeBar](current_contract, 100+1, Resolution.MINUTE)
  12. for trade_bar in trade_bars:
  13. self.indicators[current_contract].mas.update(self.time, trade_bar.close)
  14. self.indicators[current_contract].ens.update(trade_bar.close)
  15. continue
  16. if not self.portfolio[current_contract].invested:
  17. a = self.securities[current_contract].ask_size
  18. b = self.securities[current_contract].bid_size
  19. delta = b - a
  20. bid_imb = b > a * 3 and delta > 0
  21. ask_imb = a > b * 3 and delta < 0
  22. ma_val = self.indicators[current_contract].mas.Current.Value
  23. en_val = self.indicators[current_contract].ens.value
  24. en_ma_val = self.indicators[current_contract].ens.ma_value
  25. if ma_val < c and en_val < en_ma_val:
  26. if bid_imb:
  27. self.market_order(current_contract, 1, tag="LONG ENTRY")
  28. self.fill_prices[current_contract] = c
  29. elif ask_imb:
  30. self.market_order(current_contract, -1, tag="SHORT ENTRY")
  31. self.fill_prices[current_contract] = c
  32. elif ma_val > c and en_val > en_ma_val:
  33. if bid_imb:
  34. self.market_order(current_contract, -1, tag="SHORT ENTRY")
  35. self.fill_prices[current_contract] = c
  36. elif ask_imb:
  37. self.market_order(current_contract, 1, tag="LONG ENTRY")
  38. self.fill_prices[current_contract] = c
  39. else:
  40. if self.portfolio[current_contract].is_long:
  41. if c >= self.fill_prices[current_contract] * (1 + self.tp):
  42. self.liquidate(current_contract, tag="LONG EXIT")
  43. self.fill_prices[current_contract] = None
  44. elif c <= self.fill_prices[current_contract] * (1 - self.sl):
  45. self.liquidate(current_contract, tag="LONG EXIT")
  46. self.fill_prices[current_contract] = None
  47. elif self.portfolio[current_contract].is_short:
  48. if c <= self.fill_prices[current_contract] * (1 - self.tp):
  49. self.liquidate(current_contract, tag="SHORT EXIT")
  50. self.fill_prices[current_contract] = None
  51. elif c >= self.fill_prices[current_contract] * (1 + self.sl):
  52. self.liquidate(current_contract, tag="SHORT EXIT")
  53. self.fill_prices[current_contract] = None
+ Expand

In older backtests adding risk management with QC's built in models didn't work so I'm just doing it manually. using self.fill_prices in a similar way to self.indicators.

 

Hope you guys enjoyed drop any suggestions!

Author

Joseph Matteo Scorsone

September 2024