Qubx 0.5.7__cp312-cp312-manylinux_2_39_x86_64.whl
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/__init__.py +207 -0
- qubx/_nb_magic.py +100 -0
- qubx/backtester/__init__.py +5 -0
- qubx/backtester/account.py +145 -0
- qubx/backtester/broker.py +87 -0
- qubx/backtester/data.py +296 -0
- qubx/backtester/management.py +378 -0
- qubx/backtester/ome.py +296 -0
- qubx/backtester/optimization.py +201 -0
- qubx/backtester/simulated_data.py +558 -0
- qubx/backtester/simulator.py +362 -0
- qubx/backtester/utils.py +780 -0
- qubx/cli/__init__.py +0 -0
- qubx/cli/commands.py +67 -0
- qubx/connectors/ccxt/__init__.py +0 -0
- qubx/connectors/ccxt/account.py +495 -0
- qubx/connectors/ccxt/broker.py +132 -0
- qubx/connectors/ccxt/customizations.py +193 -0
- qubx/connectors/ccxt/data.py +612 -0
- qubx/connectors/ccxt/exceptions.py +17 -0
- qubx/connectors/ccxt/factory.py +93 -0
- qubx/connectors/ccxt/utils.py +307 -0
- qubx/core/__init__.py +0 -0
- qubx/core/account.py +251 -0
- qubx/core/basics.py +850 -0
- qubx/core/context.py +420 -0
- qubx/core/exceptions.py +38 -0
- qubx/core/helpers.py +480 -0
- qubx/core/interfaces.py +1150 -0
- qubx/core/loggers.py +514 -0
- qubx/core/lookups.py +475 -0
- qubx/core/metrics.py +1512 -0
- qubx/core/mixins/__init__.py +13 -0
- qubx/core/mixins/market.py +94 -0
- qubx/core/mixins/processing.py +428 -0
- qubx/core/mixins/subscription.py +203 -0
- qubx/core/mixins/trading.py +88 -0
- qubx/core/mixins/universe.py +270 -0
- qubx/core/series.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/core/series.pxd +125 -0
- qubx/core/series.pyi +118 -0
- qubx/core/series.pyx +988 -0
- qubx/core/utils.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/core/utils.pyi +6 -0
- qubx/core/utils.pyx +62 -0
- qubx/data/__init__.py +25 -0
- qubx/data/helpers.py +416 -0
- qubx/data/readers.py +1562 -0
- qubx/data/tardis.py +100 -0
- qubx/gathering/simplest.py +88 -0
- qubx/math/__init__.py +3 -0
- qubx/math/stats.py +129 -0
- qubx/pandaz/__init__.py +23 -0
- qubx/pandaz/ta.py +2757 -0
- qubx/pandaz/utils.py +638 -0
- qubx/resources/instruments/symbols-binance.cm.json +1 -0
- qubx/resources/instruments/symbols-binance.json +1 -0
- qubx/resources/instruments/symbols-binance.um.json +1 -0
- qubx/resources/instruments/symbols-bitfinex.f.json +1 -0
- qubx/resources/instruments/symbols-bitfinex.json +1 -0
- qubx/resources/instruments/symbols-kraken.f.json +1 -0
- qubx/resources/instruments/symbols-kraken.json +1 -0
- qubx/ta/__init__.py +0 -0
- qubx/ta/indicators.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/ta/indicators.pxd +149 -0
- qubx/ta/indicators.pyi +41 -0
- qubx/ta/indicators.pyx +787 -0
- qubx/trackers/__init__.py +3 -0
- qubx/trackers/abvanced.py +236 -0
- qubx/trackers/composite.py +146 -0
- qubx/trackers/rebalancers.py +129 -0
- qubx/trackers/riskctrl.py +641 -0
- qubx/trackers/sizers.py +235 -0
- qubx/utils/__init__.py +5 -0
- qubx/utils/_pyxreloader.py +281 -0
- qubx/utils/charting/lookinglass.py +1057 -0
- qubx/utils/charting/mpl_helpers.py +1183 -0
- qubx/utils/marketdata/binance.py +284 -0
- qubx/utils/marketdata/ccxt.py +90 -0
- qubx/utils/marketdata/dukas.py +130 -0
- qubx/utils/misc.py +541 -0
- qubx/utils/ntp.py +63 -0
- qubx/utils/numbers_utils.py +7 -0
- qubx/utils/orderbook.py +491 -0
- qubx/utils/plotting/__init__.py +0 -0
- qubx/utils/plotting/dashboard.py +150 -0
- qubx/utils/plotting/data.py +137 -0
- qubx/utils/plotting/interfaces.py +25 -0
- qubx/utils/plotting/renderers/__init__.py +0 -0
- qubx/utils/plotting/renderers/plotly.py +0 -0
- qubx/utils/runner/__init__.py +1 -0
- qubx/utils/runner/_jupyter_runner.pyt +60 -0
- qubx/utils/runner/accounts.py +88 -0
- qubx/utils/runner/configs.py +65 -0
- qubx/utils/runner/runner.py +470 -0
- qubx/utils/time.py +312 -0
- qubx-0.5.7.dist-info/METADATA +105 -0
- qubx-0.5.7.dist-info/RECORD +100 -0
- qubx-0.5.7.dist-info/WHEEL +4 -0
- qubx-0.5.7.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Literal, Dict, List, Optional, Union, Any
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
PlotType = Literal["candlestick", "line", "scatter", "bar", "area", "signal", "execution", "track", "trend"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class PlotStyle:
|
|
11
|
+
"""Visual properties of a plot"""
|
|
12
|
+
|
|
13
|
+
color: Optional[str] = None
|
|
14
|
+
width: float = 1.0
|
|
15
|
+
opacity: float = 1.0
|
|
16
|
+
dash: Optional[str] = None # solid, dash, dot
|
|
17
|
+
marker: Optional[str] = None # triangle-up, triangle-down etc
|
|
18
|
+
marker_size: float = 10.0
|
|
19
|
+
fill_color: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class SeriesData:
|
|
24
|
+
"""Single data series"""
|
|
25
|
+
|
|
26
|
+
data: Union[pd.Series, pd.DataFrame]
|
|
27
|
+
type: PlotType
|
|
28
|
+
name: str
|
|
29
|
+
style: PlotStyle = field(default_factory=PlotStyle)
|
|
30
|
+
hover_text: Optional[List[str]] = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class SubplotData:
|
|
35
|
+
"""Group of series in one subplot"""
|
|
36
|
+
|
|
37
|
+
series: List[SeriesData]
|
|
38
|
+
height_ratio: float = 1.0
|
|
39
|
+
y_axis_range: Optional[tuple[float, float]] = None
|
|
40
|
+
show_legend: bool = True
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class PlotData:
|
|
45
|
+
"""Complete plot specification"""
|
|
46
|
+
|
|
47
|
+
main: SubplotData # Main chart (usually price)
|
|
48
|
+
studies: Dict[str, SubplotData] = field(default_factory=dict) # Additional studies
|
|
49
|
+
title: str = ""
|
|
50
|
+
|
|
51
|
+
def to_looking_glass(self) -> Dict[str, Any]:
|
|
52
|
+
"""Convert to LookingGlass format"""
|
|
53
|
+
return {
|
|
54
|
+
"master": self._convert_subplot(self.main),
|
|
55
|
+
"studies": (
|
|
56
|
+
{name: self._convert_subplot(study) for name, study in self.studies.items()} if self.studies else None
|
|
57
|
+
),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def _convert_subplot(self, subplot: SubplotData) -> Union[pd.DataFrame, List[Any]]:
|
|
61
|
+
"""Convert SubplotData to LookingGlass format"""
|
|
62
|
+
if not subplot.series:
|
|
63
|
+
return pd.DataFrame()
|
|
64
|
+
|
|
65
|
+
# If single OHLCV series, return as main dataframe
|
|
66
|
+
if len(subplot.series) == 1 and subplot.series[0].type == "candlestick":
|
|
67
|
+
return (
|
|
68
|
+
subplot.series[0].data
|
|
69
|
+
if isinstance(subplot.series[0].data, pd.DataFrame)
|
|
70
|
+
else subplot.series[0].data.to_frame()
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Convert multiple series into list format
|
|
74
|
+
converted = []
|
|
75
|
+
for series in subplot.series:
|
|
76
|
+
if series.type == "candlestick":
|
|
77
|
+
converted.append(
|
|
78
|
+
{
|
|
79
|
+
"data": series.data,
|
|
80
|
+
"type": "candlestick",
|
|
81
|
+
"name": series.name,
|
|
82
|
+
"style": {
|
|
83
|
+
"increasing_line_color": series.style.color or "green",
|
|
84
|
+
"decreasing_line_color": series.style.color or "red",
|
|
85
|
+
"opacity": series.style.opacity,
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
elif series.type in ["line", "area", "step"]:
|
|
91
|
+
converted.append(
|
|
92
|
+
{
|
|
93
|
+
"data": series.data,
|
|
94
|
+
"type": series.type,
|
|
95
|
+
"name": series.name,
|
|
96
|
+
"style": {
|
|
97
|
+
"color": series.style.color,
|
|
98
|
+
"width": series.style.width,
|
|
99
|
+
"dash": series.style.dash,
|
|
100
|
+
"fill": series.style.fill_color if series.type == "area" else None,
|
|
101
|
+
"opacity": series.style.opacity,
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
elif series.type in ["signal", "execution"]:
|
|
107
|
+
converted.append(
|
|
108
|
+
{
|
|
109
|
+
"data": series.data,
|
|
110
|
+
"type": "scatter",
|
|
111
|
+
"name": series.name,
|
|
112
|
+
"style": {
|
|
113
|
+
"color": series.style.color,
|
|
114
|
+
"marker": series.style.marker or "circle",
|
|
115
|
+
"size": series.style.marker_size,
|
|
116
|
+
"opacity": series.style.opacity,
|
|
117
|
+
},
|
|
118
|
+
"hover_text": series.hover_text,
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
elif series.type == "trend":
|
|
123
|
+
converted.append(
|
|
124
|
+
{
|
|
125
|
+
"data": series.data,
|
|
126
|
+
"type": "line",
|
|
127
|
+
"name": series.name,
|
|
128
|
+
"style": {
|
|
129
|
+
"color": series.style.color,
|
|
130
|
+
"width": series.style.width,
|
|
131
|
+
"dash": series.style.dash or "dash",
|
|
132
|
+
"opacity": series.style.opacity,
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return converted
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class PlotData:
|
|
7
|
+
master: dict
|
|
8
|
+
studies: dict | None = None
|
|
9
|
+
master_size: int = 3
|
|
10
|
+
study_size: int = 1
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IPlotter:
|
|
14
|
+
|
|
15
|
+
def get_plots(self) -> List[str]:
|
|
16
|
+
"""
|
|
17
|
+
Get the list of plots that this object can generate.
|
|
18
|
+
"""
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
def get_plot_data(self, plot_name: str) -> Tuple[str, dict]:
|
|
22
|
+
"""
|
|
23
|
+
Get the data for the specified plot.
|
|
24
|
+
"""
|
|
25
|
+
...
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .runner import create_strategy_context, run_strategy, run_strategy_yaml
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import qubx
|
|
2
|
+
from qubx.core.basics import Instrument
|
|
3
|
+
%qubxd
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import nest_asyncio;
|
|
7
|
+
nest_asyncio.apply()
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from qubx.core.context import StrategyContext
|
|
11
|
+
from qubx.utils.misc import dequotify, quotify
|
|
12
|
+
from qubx.utils.runner import run_strategy_yaml
|
|
13
|
+
from qubx.pandaz.utils import *
|
|
14
|
+
import qubx.pandaz.ta as pta
|
|
15
|
+
import qubx.ta.indicators as ta
|
|
16
|
+
|
|
17
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
18
|
+
account_file = Path('{account_file}') if '{account_file}' != 'None' else None
|
|
19
|
+
ctx: StrategyContext = run_strategy_yaml(Path('{config_file}'), account_file, {paper}) # type: ignore
|
|
20
|
+
assert ctx is not None, 'Strategy context is not created'
|
|
21
|
+
|
|
22
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
23
|
+
def orders(instrument: Instrument | None=None):
|
|
24
|
+
return ctx.get_orders(instrument)
|
|
25
|
+
|
|
26
|
+
def trade(instrument: Instrument, qty: float, price=None, tif='gtc'):
|
|
27
|
+
return ctx.trade(instrument, qty, price, tif)
|
|
28
|
+
|
|
29
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
30
|
+
def pnl_report(all=True):
|
|
31
|
+
from tabulate import tabulate
|
|
32
|
+
|
|
33
|
+
d = dict()
|
|
34
|
+
for s, p in ctx.get_positions().items():
|
|
35
|
+
mv = round(p.market_value_funds, 3)
|
|
36
|
+
if mv != 0.0 or all:
|
|
37
|
+
d[dequotify(s.symbol)] = dict(
|
|
38
|
+
Position=round(p.quantity, p.instrument.size_precision),
|
|
39
|
+
PnL=p.total_pnl(),
|
|
40
|
+
AvgPrice=round(p.position_avg_price_funds, p.instrument.price_precision),
|
|
41
|
+
LastPrice=round(p.last_update_price, p.instrument.price_precision),
|
|
42
|
+
MktValue=mv
|
|
43
|
+
)
|
|
44
|
+
d = pd.DataFrame.from_dict(d).T
|
|
45
|
+
# d = d[d['PnL'] != 0.0]
|
|
46
|
+
if d.empty:
|
|
47
|
+
print('-(no open positions yet)-')
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
d = d.sort_values('PnL' ,ascending=False)
|
|
51
|
+
# d = pd.concat((d, pd.Series(dict(TOTAL=d['PnL'].sum()), name='PnL'))).fillna('')
|
|
52
|
+
d = pd.concat((d, scols(pd.Series(dict(TOTAL=d['PnL'].sum()), name='PnL'), pd.Series(dict(TOTAL=d['MktValue'].sum()), name='MktValue')))).fillna('')
|
|
53
|
+
print(tabulate(d, ['Position', 'PnL', 'AvgPrice', 'LastPrice', 'MktValue'], tablefmt='rounded_grid'))
|
|
54
|
+
|
|
55
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
56
|
+
__exit = exit
|
|
57
|
+
def exit():
|
|
58
|
+
ctx.stop()
|
|
59
|
+
__exit()
|
|
60
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import toml
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ExchangeSettings(BaseModel):
|
|
9
|
+
exchange: str
|
|
10
|
+
testnet: bool = False
|
|
11
|
+
base_currency: str = "USDT"
|
|
12
|
+
commissions: str | None = None
|
|
13
|
+
initial_capital: float = 100_000
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ExchangeCredentials(ExchangeSettings):
|
|
17
|
+
name: str
|
|
18
|
+
api_key: str
|
|
19
|
+
secret: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AccountConfiguration(BaseModel):
|
|
23
|
+
defaults: list[ExchangeSettings]
|
|
24
|
+
accounts: list[ExchangeCredentials]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AccountConfigurationManager:
|
|
28
|
+
"""
|
|
29
|
+
Manages account configurations.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
account_config: Path | None = None,
|
|
35
|
+
strategy_dir: Path | None = None,
|
|
36
|
+
search_qubx_dir: bool = False,
|
|
37
|
+
):
|
|
38
|
+
self._exchange_settings: dict[str, ExchangeSettings] = {}
|
|
39
|
+
self._exchange_credentials: dict[str, ExchangeCredentials] = {}
|
|
40
|
+
self._settings_to_config: dict[str, Path] = {}
|
|
41
|
+
self._credentials_to_config: dict[str, Path] = {}
|
|
42
|
+
|
|
43
|
+
self._config_paths = [Path("~/.qubx/accounts.toml").expanduser()] if search_qubx_dir else []
|
|
44
|
+
if strategy_dir:
|
|
45
|
+
self._config_paths.append(strategy_dir / "accounts.toml")
|
|
46
|
+
if account_config:
|
|
47
|
+
self._config_paths.append(account_config)
|
|
48
|
+
self._config_paths = [config for config in self._config_paths if config.exists()]
|
|
49
|
+
for config in self._config_paths:
|
|
50
|
+
self._load(config)
|
|
51
|
+
|
|
52
|
+
def get_exchange_settings(self, exchange: str) -> ExchangeSettings:
|
|
53
|
+
"""
|
|
54
|
+
Get the basic settings for an exchange such as the base currency and commission tier.
|
|
55
|
+
"""
|
|
56
|
+
exchange = exchange.upper()
|
|
57
|
+
if exchange not in self._exchange_settings:
|
|
58
|
+
return ExchangeSettings(exchange=exchange)
|
|
59
|
+
return self._exchange_settings[exchange.upper()].model_copy()
|
|
60
|
+
|
|
61
|
+
def get_exchange_credentials(self, exchange: str) -> ExchangeCredentials:
|
|
62
|
+
"""
|
|
63
|
+
Get the api key and secret for an exchange as well as the base currency and commission tier.
|
|
64
|
+
"""
|
|
65
|
+
return self._exchange_credentials[exchange.upper()].model_copy()
|
|
66
|
+
|
|
67
|
+
def get_config_path_for_settings(self, exchange: str) -> Path:
|
|
68
|
+
return self._settings_to_config[exchange.upper()]
|
|
69
|
+
|
|
70
|
+
def get_config_path_for_credentials(self, exchange: str) -> Path:
|
|
71
|
+
return self._credentials_to_config[exchange.upper()]
|
|
72
|
+
|
|
73
|
+
def __repr__(self):
|
|
74
|
+
exchanges = set(self._exchange_credentials.keys()) | set(self._exchange_settings.keys())
|
|
75
|
+
_e_str = "\n".join([f" - {exchange}" for exchange in exchanges])
|
|
76
|
+
return f"AccountManager:\n{_e_str}"
|
|
77
|
+
|
|
78
|
+
def _load(self, config: Path):
|
|
79
|
+
config_dict = toml.load(config)
|
|
80
|
+
account_config = AccountConfiguration(**config_dict)
|
|
81
|
+
for exchange_config in account_config.defaults:
|
|
82
|
+
_exchange = exchange_config.exchange.upper()
|
|
83
|
+
self._exchange_settings[_exchange] = exchange_config
|
|
84
|
+
self._settings_to_config[_exchange] = config
|
|
85
|
+
for exchange_config in account_config.accounts:
|
|
86
|
+
_exchange = exchange_config.exchange.upper()
|
|
87
|
+
self._exchange_credentials[_exchange] = exchange_config
|
|
88
|
+
self._credentials_to_config[_exchange] = config
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import yaml
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ExchangeConfig(BaseModel):
|
|
8
|
+
connector: str
|
|
9
|
+
universe: list[str]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AuxConfig(BaseModel):
|
|
13
|
+
reader: str
|
|
14
|
+
args: dict = Field(default_factory=dict)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LoggingConfig(BaseModel):
|
|
18
|
+
logger: str
|
|
19
|
+
position_interval: str
|
|
20
|
+
portfolio_interval: str
|
|
21
|
+
heartbeat_interval: str = "1m"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class StrategyConfig(BaseModel):
|
|
25
|
+
strategy: str
|
|
26
|
+
parameters: dict = Field(default_factory=dict)
|
|
27
|
+
exchanges: dict[str, ExchangeConfig]
|
|
28
|
+
logging: LoggingConfig
|
|
29
|
+
aux: AuxConfig | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_strategy_config_from_yaml(path: Path | str, key: str | None = None) -> StrategyConfig:
|
|
33
|
+
"""
|
|
34
|
+
Loads a strategy configuration from a YAML file.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
path (str | Path): The path to the YAML file.
|
|
38
|
+
key (str | None): The key to extract from the YAML file.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
StrategyConfig: The parsed configuration.
|
|
42
|
+
"""
|
|
43
|
+
path = Path(path)
|
|
44
|
+
if not path.exists():
|
|
45
|
+
raise FileNotFoundError(f"File {path} not found.")
|
|
46
|
+
with path.open("r") as f:
|
|
47
|
+
config_dict = yaml.safe_load(f)
|
|
48
|
+
if key:
|
|
49
|
+
config_dict = config_dict[key]
|
|
50
|
+
return StrategyConfig(**config_dict)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class StrategySimulationConfig(BaseModel):
|
|
54
|
+
strategy: str | list[str]
|
|
55
|
+
parameters: dict = Field(default_factory=dict)
|
|
56
|
+
data: dict = Field(default_factory=dict)
|
|
57
|
+
simulation: dict = Field(default_factory=dict)
|
|
58
|
+
description: str | list[str] | None = None
|
|
59
|
+
variate: dict = Field(default_factory=dict)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def load_simulation_config_from_yaml(path: Path | str) -> StrategySimulationConfig:
|
|
63
|
+
with open(path, "r") as f:
|
|
64
|
+
cfg = yaml.safe_load(f)
|
|
65
|
+
return StrategySimulationConfig(**cfg)
|