Qubx 0.2.72__tar.gz → 0.2.74__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Qubx might be problematic. Click here for more details.

Files changed (56) hide show
  1. {qubx-0.2.72 → qubx-0.2.74}/PKG-INFO +1 -1
  2. {qubx-0.2.72 → qubx-0.2.74}/pyproject.toml +1 -1
  3. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/backtester/ome.py +12 -3
  4. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/backtester/optimization.py +41 -5
  5. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/backtester/simulator.py +100 -7
  6. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/helpers.py +3 -3
  7. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/series.pyi +6 -1
  8. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/data/readers.py +0 -3
  9. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/trackers/riskctrl.py +10 -1
  10. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/utils/time.py +1 -1
  11. {qubx-0.2.72 → qubx-0.2.74}/README.md +0 -0
  12. {qubx-0.2.72 → qubx-0.2.74}/build.py +0 -0
  13. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/__init__.py +0 -0
  14. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/_nb_magic.py +0 -0
  15. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/backtester/__init__.py +0 -0
  16. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/backtester/queue.py +0 -0
  17. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/__init__.py +0 -0
  18. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/account.py +0 -0
  19. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/basics.py +0 -0
  20. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/context.py +0 -0
  21. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/exceptions.py +0 -0
  22. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/loggers.py +0 -0
  23. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/lookups.py +0 -0
  24. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/metrics.py +0 -0
  25. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/series.pxd +0 -0
  26. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/series.pyx +0 -0
  27. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/strategy.py +0 -0
  28. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/utils.pyi +0 -0
  29. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/core/utils.pyx +0 -0
  30. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/data/helpers.py +0 -0
  31. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/gathering/simplest.py +0 -0
  32. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/impl/ccxt_connector.py +0 -0
  33. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/impl/ccxt_customizations.py +0 -0
  34. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/impl/ccxt_trading.py +0 -0
  35. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/impl/ccxt_utils.py +0 -0
  36. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/math/__init__.py +0 -0
  37. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/math/stats.py +0 -0
  38. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/pandaz/__init__.py +0 -0
  39. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/pandaz/ta.py +0 -0
  40. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/pandaz/utils.py +0 -0
  41. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/ta/__init__.py +0 -0
  42. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/ta/indicators.pxd +0 -0
  43. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/ta/indicators.pyi +0 -0
  44. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/ta/indicators.pyx +0 -0
  45. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/trackers/__init__.py +0 -0
  46. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/trackers/composite.py +0 -0
  47. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/trackers/rebalancers.py +0 -0
  48. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/trackers/sizers.py +0 -0
  49. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/utils/__init__.py +0 -0
  50. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/utils/_pyxreloader.py +0 -0
  51. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/utils/charting/lookinglass.py +0 -0
  52. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  53. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/utils/marketdata/binance.py +0 -0
  54. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/utils/misc.py +0 -0
  55. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/utils/ntp.py +0 -0
  56. {qubx-0.2.72 → qubx-0.2.74}/src/qubx/utils/runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Qubx
3
- Version: 0.2.72
3
+ Version: 0.2.74
4
4
  Summary: Qubx - quantitative trading framework
5
5
  Home-page: https://github.com/dmarienko/Qubx
6
6
  Author: Dmitry Marienko
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "Qubx"
3
- version = "0.2.72"
3
+ version = "0.2.74"
4
4
  description = "Qubx - quantitative trading framework"
5
5
  authors = ["Dmitry Marienko <dmitry@gmail.com>", "Yuriy Arabskyy <yuriy.arabskyy@gmail.com>"]
6
6
  readme = "README.md"
@@ -43,9 +43,15 @@ class OrdersManagementEngine:
43
43
  bbo: Quote | None # current best bid/ask order book (simplest impl)
44
44
  __order_id: int
45
45
  __trade_id: int
46
+ _fill_stops_at_price: bool
46
47
 
47
48
  def __init__(
48
- self, instrument: Instrument, time_provider: ITimeProvider, tcc: TransactionCostsCalculator, debug: bool = True
49
+ self,
50
+ instrument: Instrument,
51
+ time_provider: ITimeProvider,
52
+ tcc: TransactionCostsCalculator,
53
+ fill_stop_order_at_price: bool = False, # emulate stop orders execution at order's exact limit price
54
+ debug: bool = True,
49
55
  ) -> None:
50
56
  self.instrument = instrument
51
57
  self.time_service = time_provider
@@ -57,6 +63,7 @@ class OrdersManagementEngine:
57
63
  self.bbo = None
58
64
  self.__order_id = 100000
59
65
  self.__trade_id = 100000
66
+ self._fill_stops_at_price = fill_stop_order_at_price
60
67
  if not debug:
61
68
  self._dbg = lambda message, **kwargs: None
62
69
 
@@ -96,12 +103,14 @@ class OrdersManagementEngine:
96
103
  # - processing stop orders
97
104
  for soid in list(self.stop_orders.keys()):
98
105
  so = self.stop_orders[soid]
106
+ _emulate_price_exec = self._fill_stops_at_price or so.options.get(OPTION_FILL_AT_SIGNAL_PRICE, False)
107
+
99
108
  if so.side == "BUY" and quote.ask >= so.price:
100
- _exec_price = quote.ask if not so.options.get(OPTION_FILL_AT_SIGNAL_PRICE, False) else so.price
109
+ _exec_price = quote.ask if not _emulate_price_exec else so.price
101
110
  self.stop_orders.pop(soid)
102
111
  rep.append(self._execute_order(timestamp, _exec_price, so, True))
103
112
  elif so.side == "SELL" and quote.bid <= so.price:
104
- _exec_price = quote.bid if not so.options.get(OPTION_FILL_AT_SIGNAL_PRICE, False) else so.price
113
+ _exec_price = quote.bid if not _emulate_price_exec else so.price
105
114
  self.stop_orders.pop(soid)
106
115
  rep.append(self._execute_order(timestamp, _exec_price, so, True))
107
116
 
@@ -90,7 +90,41 @@ def permutate_params(
90
90
  return _wrap_single_list(result) if wrap_as_list else result
91
91
 
92
92
 
93
- def variate(clz: Type[Any] | List[Type[Any]], *args, conditions=None, **kwargs) -> Dict[str, Any]:
93
+ def dicts_product(d1: dict, d2: dict) -> dict:
94
+ """
95
+ Product of two dictionaries.
96
+
97
+ Example:
98
+ -------
99
+
100
+ dicts_product({
101
+ 'A': 1,
102
+ 'B': 2,
103
+ }, {
104
+ 'C': 3,
105
+ 'D': 4,
106
+ })
107
+
108
+ Output:
109
+ ------
110
+ {
111
+ 'A + C': [1, 3],
112
+ 'A + D': [1, 4],
113
+ 'B + C': [2, 3],
114
+ 'B + D': [2, 4]
115
+ }
116
+
117
+ """
118
+ flatten = lambda l: [item for sublist in l for item in (sublist if isinstance(sublist, list) else [sublist])]
119
+ return {(a + " + " + b): flatten([d1[a], d2[b]]) for a, b in product(d1.keys(), d2.keys())}
120
+
121
+
122
+ class _dict(dict):
123
+ def __add__(self, other: dict) -> dict:
124
+ return _dict(dicts_product(self, other))
125
+
126
+
127
+ def variate(clz: Type[Any] | List[Type[Any]], *args, conditions=None, **kwargs) -> _dict:
94
128
  """
95
129
  Make variations of parameters for simulations (micro optimizer)
96
130
 
@@ -149,7 +183,9 @@ def variate(clz: Type[Any] | List[Type[Any]], *args, conditions=None, **kwargs)
149
183
  to_excl = [s for s, v in kwargs.items() if not isinstance(v, (list, set, tuple, range))]
150
184
  dic2str = lambda ds: [_cmprss(k) + "=" + str(v) for k, v in ds.items() if k not in to_excl]
151
185
 
152
- return {
153
- f"{sfx}_({ ','.join(dic2str(z)) })": _mk(clz, *args, **z)
154
- for z in permutate_params(kwargs, conditions=conditions)
155
- }
186
+ return _dict(
187
+ {
188
+ f"{sfx}_({ ','.join(dic2str(z)) })": _mk(clz, *args, **z)
189
+ for z in permutate_params(kwargs, conditions=conditions)
190
+ }
191
+ )
@@ -142,8 +142,8 @@ class SimulatedTrading(ITradingServiceProvider):
142
142
  First implementation of a simulated broker.
143
143
  TODO:
144
144
  1. Add margin control
145
- 2. Need to solve problem with _get_ohlcv_data_sync (actually this method must be removed from here)
146
- 3. Add support for stop orders (not urgent)
145
+ 2. Need to solve problem with _get_ohlcv_data_sync (actually this method must be removed from here) [DONE]
146
+ 3. Add support for stop orders (not urgent) [DONE]
147
147
  """
148
148
 
149
149
  _current_time: dt_64
@@ -152,13 +152,40 @@ class SimulatedTrading(ITradingServiceProvider):
152
152
  _fees_calculator: TransactionCostsCalculator | None
153
153
  _order_to_symbol: Dict[str, str]
154
154
  _half_tick_size: Dict[str, float]
155
+ _fill_stop_order_at_price: bool
155
156
 
156
157
  def __init__(
157
158
  self,
158
159
  name: str,
159
160
  commissions: str | None = None,
160
161
  simulation_initial_time: dt_64 | str = np.datetime64(0, "ns"),
162
+ accurate_stop_orders_execution: bool = False,
161
163
  ) -> None:
164
+ """
165
+ This function sets up a simulated trading environment with following parameters.
166
+
167
+ Parameters:
168
+ -----------
169
+ name : str
170
+ The name of the simulated trading environment.
171
+ commissions : str | None, optional
172
+ The commission structure to be used. If None, no commissions will be applied.
173
+ Default is None.
174
+ simulation_initial_time : dt_64 | str, optional
175
+ The initial time for the simulation. Can be a dt_64 object or a string.
176
+ Default is np.datetime64(0, "ns").
177
+ accurate_stop_orders_execution : bool, optional
178
+ If True, stop orders will be executed at the exact stop order's price.
179
+ If False, they may be executed at the next quote that could lead to
180
+ significant slippage especially if simuation run on OHLC data.
181
+ Default is False.
182
+
183
+ Raises:
184
+ -------
185
+ ValueError
186
+ If the fees configuration is not found for the given name.
187
+
188
+ """
162
189
  self._current_time = (
163
190
  np.datetime64(simulation_initial_time, "ns")
164
191
  if isinstance(simulation_initial_time, str)
@@ -168,6 +195,7 @@ class SimulatedTrading(ITradingServiceProvider):
168
195
  self._ome = {}
169
196
  self._fees_calculator = lookup.fees.find(name.lower(), commissions)
170
197
  self._half_tick_size = {}
198
+ self._fill_stop_order_at_price = accurate_stop_orders_execution
171
199
 
172
200
  self._order_to_symbol = {}
173
201
  if self._fees_calculator is None:
@@ -177,6 +205,8 @@ class SimulatedTrading(ITradingServiceProvider):
177
205
 
178
206
  # - we want to see simulate time in log messages
179
207
  QubxLogConfig.setup_logger(QubxLogConfig.get_log_level(), _SimulatedLogFormatter(self).formatter)
208
+ if self._fill_stop_order_at_price:
209
+ logger.info(f"SimulatedExchangeService emulates stop orders executions at exact price")
180
210
 
181
211
  def send_order(
182
212
  self,
@@ -195,8 +225,8 @@ class SimulatedTrading(ITradingServiceProvider):
195
225
 
196
226
  # - try to place order in OME
197
227
  report = ome.place_order(
198
- order_side.upper(),
199
- order_type.upper(),
228
+ order_side.upper(), # type: ignore
229
+ order_type.upper(), # type: ignore
200
230
  amount,
201
231
  price,
202
232
  client_id,
@@ -254,7 +284,12 @@ class SimulatedTrading(ITradingServiceProvider):
254
284
 
255
285
  if symbol not in self.acc._positions:
256
286
  # - initiolize OME for this instrument
257
- self._ome[instrument.symbol] = OrdersManagementEngine(instrument=instrument, time_provider=self, tcc=self._fees_calculator) # type: ignore
287
+ self._ome[instrument.symbol] = OrdersManagementEngine(
288
+ instrument=instrument,
289
+ time_provider=self,
290
+ tcc=self._fees_calculator, # type: ignore
291
+ fill_stop_order_at_price=self._fill_stop_order_at_price,
292
+ )
258
293
 
259
294
  # - initiolize empty position
260
295
  position = Position(instrument) # type: ignore
@@ -521,7 +556,7 @@ class SimulatedExchange(IBrokerServiceProvider):
521
556
  # we have to schedule possible crons before sending the data event itself
522
557
  if self._scheduler.check_and_run_tasks():
523
558
  # - push nothing - it will force to process last event
524
- cc.send((None, "time", None))
559
+ cc.send((None, "service_time", None))
525
560
 
526
561
  cc.send((symbol, data_type, data))
527
562
 
@@ -693,8 +728,57 @@ def simulate(
693
728
  n_jobs: int = 1,
694
729
  silent: bool = False,
695
730
  enable_event_batching: bool = True,
731
+ accurate_stop_orders_execution: bool = False,
696
732
  debug: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None = "WARNING",
697
733
  ) -> list[TradingSessionResult]:
734
+ """
735
+ Backtest utility for trading strategies or signals using historical data.
736
+
737
+ Parameters:
738
+ ----------
739
+
740
+ config (StrategyOrSignals | Dict | List[StrategyOrSignals | PositionsTracker]):
741
+ Trading strategy or signals configuration.
742
+ data (Dict[str, pd.DataFrame] | DataReader):
743
+ Historical data for simulation, either as a dictionary of DataFrames or a DataReader object.
744
+ capital (float):
745
+ Initial capital for the simulation.
746
+ instruments (List[str] | Dict[str, List[str]] | None):
747
+ List of trading instruments or a dictionary mapping exchanges to instrument lists.
748
+ subscription (Dict[str, Any]):
749
+ Subscription details for market data.
750
+ trigger (str | list[str]):
751
+ Trigger specification for strategy execution.
752
+ commissions (str):
753
+ Commission structure for trades.
754
+ start (str | pd.Timestamp):
755
+ Start time of the simulation.
756
+ stop (str | pd.Timestamp | None):
757
+ End time of the simulation. If None, simulates until the last accessible data.
758
+ fit (str | None):
759
+ Specification for strategy fitting, if applicable.
760
+ exchange (str | None):
761
+ Exchange name if not specified in the instruments list.
762
+ base_currency (str):
763
+ Base currency for the simulation, default is "USDT".
764
+ leverage (float):
765
+ Leverage factor for trading, default is 1.0.
766
+ n_jobs (int):
767
+ Number of parallel jobs for simulation, default is 1.
768
+ silent (bool):
769
+ If True, suppresses output during simulation.
770
+ enable_event_batching (bool):
771
+ If True, enables event batching for optimization.
772
+ accurate_stop_orders_execution (bool):
773
+ If True, enables more accurate stop order execution simulation.
774
+ debug (Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None):
775
+ Logging level for debugging.
776
+
777
+ Returns:
778
+ --------
779
+ list[TradingSessionResult]:
780
+ A list of TradingSessionResult objects containing the results of each simulation setup.
781
+ """
698
782
 
699
783
  # - setup logging
700
784
  QubxLogConfig.set_log_level(debug.upper() if debug else "WARNING")
@@ -755,6 +839,7 @@ def simulate(
755
839
  n_jobs=n_jobs,
756
840
  silent=silent,
757
841
  enable_event_batching=enable_event_batching,
842
+ accurate_stop_orders_execution=accurate_stop_orders_execution,
758
843
  )
759
844
 
760
845
 
@@ -818,6 +903,7 @@ def _run_setups(
818
903
  n_jobs: int = -1,
819
904
  silent: bool = False,
820
905
  enable_event_batching: bool = True,
906
+ accurate_stop_orders_execution: bool = False,
821
907
  ) -> List[TradingSessionResult]:
822
908
  # loggers don't work well with joblib and multiprocessing in general because they contain
823
909
  # open file handlers that cannot be pickled. I found a solution which requires the usage of enqueue=True
@@ -839,6 +925,7 @@ def _run_setups(
839
925
  fit=fit,
840
926
  silent=silent,
841
927
  enable_event_batching=enable_event_batching,
928
+ accurate_stop_orders_execution=accurate_stop_orders_execution,
842
929
  )
843
930
  for id, s in enumerate(setups)
844
931
  )
@@ -856,13 +943,19 @@ def _run_setup(
856
943
  fit: str | None,
857
944
  silent: bool = False,
858
945
  enable_event_batching: bool = True,
946
+ accurate_stop_orders_execution: bool = False,
859
947
  ) -> TradingSessionResult:
860
948
  _trigger = trigger
861
949
  _stop = stop
862
950
  logger.debug(
863
951
  f"<red>{pd.Timestamp(start)}</red> Initiating simulated trading for {setup.exchange} for {setup.capital} x {setup.leverage} in {setup.base_currency}..."
864
952
  )
865
- broker = SimulatedTrading(setup.exchange, setup.commissions, np.datetime64(start, "ns"))
953
+ broker = SimulatedTrading(
954
+ setup.exchange,
955
+ setup.commissions,
956
+ np.datetime64(start, "ns"),
957
+ accurate_stop_orders_execution=accurate_stop_orders_execution,
958
+ )
866
959
  exchange = SimulatedExchange(setup.exchange, broker, data_reader)
867
960
 
868
961
  # - it will store simulation results into memory
@@ -300,9 +300,9 @@ class BasicScheduler:
300
300
  next_time = iter.get_next(start_time=start_time)
301
301
  if next_time:
302
302
  self._scdlr.enterabs(next_time, 1, self._trigger, (event, prev_time, next_time))
303
- logger.debug(
304
- f"Now is <red>{_SEC2TS(self.time_sec())}</red> next ({event}) at <cyan>{_SEC2TS(next_time)}</cyan>"
305
- )
303
+ # logger.debug(
304
+ # f"Now is <red>{_SEC2TS(self.time_sec())}</red> next ({event}) at <cyan>{_SEC2TS(next_time)}</cyan>"
305
+ # )
306
306
  return True
307
307
  logger.debug(f"({event}) task is not scheduled")
308
308
  return False
@@ -4,12 +4,16 @@ from typing import Any, Tuple
4
4
  import pandas as pd
5
5
 
6
6
  class Bar:
7
+ time: int
7
8
  open: float
8
9
  high: float
9
10
  low: float
10
11
  close: float
11
12
  volume: float
13
+ bought_volume: float
12
14
  def __init__(self, time, open, high, low, close, volume, bought_volume=0): ...
15
+ def update(self, price: float, volume: float, bought_volume: float = 0) -> Bar: ...
16
+ def to_dict(self, skip_time: bool = False) -> dict: ...
13
17
 
14
18
  class Quote:
15
19
  time: int
@@ -44,8 +48,9 @@ class TimeSeries:
44
48
  max_series_length: int
45
49
  times: Indexed
46
50
  values: Indexed
47
- def __init__(self, name, timeframe, max_series_length, process_every_update=True) -> None: ...
51
+ def __init__(self, name, timeframe, max_series_length=np.inf, process_every_update=True) -> None: ...
48
52
  def __getitem__(self, idx): ...
53
+ def __len__(self) -> int: ...
49
54
  def update(self, time: int, value: float) -> bool: ...
50
55
  def copy(self, start: int, stop: int) -> "TimeSeries": ...
51
56
  def shift(self, period: int) -> TimeSeries: ...
@@ -1027,7 +1027,6 @@ class QuestDBSqlOrderBookBuilder(QuestDBSqlCandlesBuilder):
1027
1027
  Sql builder for snapshot data
1028
1028
  """
1029
1029
 
1030
- MAX_TIME_DELTA = pd.Timedelta("5h")
1031
1030
  SNAPSHOT_DELTA = pd.Timedelta("1h")
1032
1031
  MIN_DELTA = pd.Timedelta("1s")
1033
1032
 
@@ -1043,8 +1042,6 @@ class QuestDBSqlOrderBookBuilder(QuestDBSqlCandlesBuilder):
1043
1042
  raise ValueError("Start and end dates must be provided for orderbook data!")
1044
1043
  start_dt, end_dt = pd.Timestamp(start), pd.Timestamp(end)
1045
1044
  delta = end_dt - start_dt
1046
- if delta > self.MAX_TIME_DELTA:
1047
- raise ValueError(f"Time range is too big for orderbook data: {delta}, max allowed: {self.MAX_TIME_DELTA}")
1048
1045
 
1049
1046
  raw_start_dt = start_dt.floor(self.SNAPSHOT_DELTA) - self.MIN_DELTA
1050
1047
 
@@ -5,7 +5,7 @@ from typing import Dict, List, Literal
5
5
  import numpy as np
6
6
 
7
7
  from qubx import logger
8
- from qubx.core.basics import Deal, Instrument, Signal, TargetPosition
8
+ from qubx.core.basics import Deal, Instrument, OrderStatus, Signal, TargetPosition
9
9
  from qubx.core.series import Bar, Quote, Trade
10
10
  from qubx.core.strategy import IPositionSizer, PositionsTracker, StrategyContext
11
11
  from qubx.trackers.sizers import FixedRiskSizer, FixedSizer
@@ -270,6 +270,15 @@ class BrokerSideRiskController(RiskController):
270
270
  )
271
271
  order = ctx.trade(instrument, -pos, c_w.target.take)
272
272
  c_w.take_order_id = order.id
273
+
274
+ # - if order was executed immediately we don't need to send stop order
275
+ if order.status == "CLOSED":
276
+ c_w.status = State.RISK_TRIGGERED
277
+ logger.debug(
278
+ f"<yellow>{self.__class__.__name__}</yellow> <g>TAKE PROFIT</g> was exected immediately for <green>{instrument.symbol}</green> at {c_w.target.take}"
279
+ )
280
+ return
281
+
273
282
  except Exception as e:
274
283
  logger.error(
275
284
  f"<yellow>{self.__class__.__name__}</yellow> couldn't send take limit order for <green>{instrument.symbol}</green>: {str(e)}"
@@ -113,7 +113,7 @@ def infer_series_frequency(series: Union[List, pd.DataFrame, pd.Series, pd.Datet
113
113
  [
114
114
  (
115
115
  x
116
- if isinstance(x, (np.timedelta64, int))
116
+ if isinstance(x, (np.timedelta64, int, np.int64))
117
117
  else int(x) if isinstance(x, float) else int(1e9 * x.total_seconds())
118
118
  )
119
119
  for x in np.abs(np.diff(times_index))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes