Qubx 0.2.73__tar.gz → 0.2.75__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.
- {qubx-0.2.73 → qubx-0.2.75}/PKG-INFO +1 -1
- {qubx-0.2.73 → qubx-0.2.75}/pyproject.toml +1 -1
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/backtester/optimization.py +41 -5
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/backtester/simulator.py +1 -1
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/helpers.py +3 -3
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/data/readers.py +17 -3
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/trackers/riskctrl.py +10 -1
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/utils/time.py +1 -1
- {qubx-0.2.73 → qubx-0.2.75}/README.md +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/build.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/__init__.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/_nb_magic.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/backtester/ome.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/backtester/queue.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/__init__.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/account.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/basics.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/context.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/exceptions.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/loggers.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/lookups.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/metrics.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/series.pxd +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/series.pyi +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/series.pyx +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/strategy.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/utils.pyi +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/core/utils.pyx +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/data/helpers.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/gathering/simplest.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/impl/ccxt_connector.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/impl/ccxt_customizations.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/impl/ccxt_trading.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/impl/ccxt_utils.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/math/__init__.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/math/stats.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/pandaz/ta.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/pandaz/utils.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/ta/__init__.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/trackers/__init__.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/trackers/composite.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/trackers/sizers.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/utils/__init__.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/utils/misc.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.2.73 → qubx-0.2.75}/src/qubx/utils/runner.py +0 -0
|
@@ -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
|
|
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
|
-
|
|
154
|
-
|
|
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
|
+
)
|
|
@@ -556,7 +556,7 @@ class SimulatedExchange(IBrokerServiceProvider):
|
|
|
556
556
|
# we have to schedule possible crons before sending the data event itself
|
|
557
557
|
if self._scheduler.check_and_run_tasks():
|
|
558
558
|
# - push nothing - it will force to process last event
|
|
559
|
-
cc.send((None, "
|
|
559
|
+
cc.send((None, "service_time", None))
|
|
560
560
|
|
|
561
561
|
cc.send((symbol, data_type, data))
|
|
562
562
|
|
|
@@ -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
|
-
|
|
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
|
|
@@ -945,6 +945,23 @@ class QuestDBConnector(DataReader):
|
|
|
945
945
|
return pd.Series()
|
|
946
946
|
return vol_stats.set_index("symbol")["quote_volume"]
|
|
947
947
|
|
|
948
|
+
def get_fundamental_data(
|
|
949
|
+
self, exchange: str, start: str | pd.Timestamp | None = None, stop: str | pd.Timestamp | None = None
|
|
950
|
+
) -> pd.DataFrame:
|
|
951
|
+
table_name = {"BINANCE.UM": "binance.umfutures.fundamental"}[exchange]
|
|
952
|
+
query = f"select * from {table_name}"
|
|
953
|
+
if start or stop:
|
|
954
|
+
conditions = []
|
|
955
|
+
if start:
|
|
956
|
+
conditions.append(f"timestamp >= '{start}'")
|
|
957
|
+
if stop:
|
|
958
|
+
conditions.append(f"timestamp < '{stop}'")
|
|
959
|
+
query += " where " + " and ".join(conditions)
|
|
960
|
+
df = self.execute(query)
|
|
961
|
+
if df.empty:
|
|
962
|
+
return pd.DataFrame()
|
|
963
|
+
return df.set_index(["timestamp", "symbol", "metric"]).value.unstack("metric")
|
|
964
|
+
|
|
948
965
|
def get_names(self) -> List[str]:
|
|
949
966
|
return self._get_names(self._builder)
|
|
950
967
|
|
|
@@ -1027,7 +1044,6 @@ class QuestDBSqlOrderBookBuilder(QuestDBSqlCandlesBuilder):
|
|
|
1027
1044
|
Sql builder for snapshot data
|
|
1028
1045
|
"""
|
|
1029
1046
|
|
|
1030
|
-
MAX_TIME_DELTA = pd.Timedelta("5h")
|
|
1031
1047
|
SNAPSHOT_DELTA = pd.Timedelta("1h")
|
|
1032
1048
|
MIN_DELTA = pd.Timedelta("1s")
|
|
1033
1049
|
|
|
@@ -1043,8 +1059,6 @@ class QuestDBSqlOrderBookBuilder(QuestDBSqlCandlesBuilder):
|
|
|
1043
1059
|
raise ValueError("Start and end dates must be provided for orderbook data!")
|
|
1044
1060
|
start_dt, end_dt = pd.Timestamp(start), pd.Timestamp(end)
|
|
1045
1061
|
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
1062
|
|
|
1049
1063
|
raw_start_dt = start_dt.floor(self.SNAPSHOT_DELTA) - self.MIN_DELTA
|
|
1050
1064
|
|
|
@@ -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
|
|
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
|