quantex 0.5.2__tar.gz → 0.6.1__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.
- quantex-0.6.1/PKG-INFO +83 -0
- quantex-0.6.1/README.md +57 -0
- {quantex-0.5.2 → quantex-0.6.1}/pyproject.toml +7 -2
- quantex-0.6.1/src/quantex/__init__.py +0 -0
- quantex-0.6.1/src/quantex/backtester.py +288 -0
- quantex-0.6.1/src/quantex/broker.py +470 -0
- quantex-0.6.1/src/quantex/commission.py +58 -0
- quantex-0.6.1/src/quantex/datasource.py +135 -0
- quantex-0.6.1/src/quantex/strategy.py +92 -0
- quantex-0.5.2/PKG-INFO +0 -314
- quantex-0.5.2/README.md +0 -290
- quantex-0.5.2/src/quantex/__init__.py +0 -12
- quantex-0.5.2/src/quantex/backtester/__init__.py +0 -58
- quantex-0.5.2/src/quantex/backtester/backtester.py +0 -1710
- quantex-0.5.2/src/quantex/backtester/constants.py +0 -10
- quantex-0.5.2/src/quantex/backtester/data_splits.py +0 -100
- quantex-0.5.2/src/quantex/backtester/metrics.py +0 -128
- quantex-0.5.2/src/quantex/backtester/montecarlo.py +0 -776
- quantex-0.5.2/src/quantex/backtester/parallel.py +0 -206
- quantex-0.5.2/src/quantex/backtester/reports.py +0 -357
- quantex-0.5.2/src/quantex/backtester/walk_forward.py +0 -930
- quantex-0.5.2/src/quantex/broker/__init__.py +0 -11
- quantex-0.5.2/src/quantex/broker/broker.py +0 -788
- quantex-0.5.2/src/quantex/broker/types.py +0 -93
- quantex-0.5.2/src/quantex/datasource.py +0 -260
- quantex-0.5.2/src/quantex/helpers.py +0 -299
- quantex-0.5.2/src/quantex/indicators.py +0 -626
- quantex-0.5.2/src/quantex/strategy.py +0 -163
- {quantex-0.5.2 → quantex-0.6.1}/LICENSE.md +0 -0
quantex-0.6.1/PKG-INFO
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: quantex
|
|
3
|
+
Version: 0.6.1
|
|
4
|
+
Summary: A simple quant strategy creation and backtesting package.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE.md
|
|
7
|
+
Author: Daniel Green
|
|
8
|
+
Author-email: dangreen07@outlook.com
|
|
9
|
+
Requires-Python: >=3.11,<4
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Dist: fastparquet (>=2024.11.0,<2025.0.0)
|
|
16
|
+
Requires-Dist: matplotlib (>=3.10.8,<4.0.0)
|
|
17
|
+
Requires-Dist: mkdocs (>=1.6.1,<2.0.0)
|
|
18
|
+
Requires-Dist: mplfinance (>=0.12.10b0,<0.13.0)
|
|
19
|
+
Requires-Dist: numpy (>=2.4.3,<3.0.0)
|
|
20
|
+
Requires-Dist: optuna (>=4.8.0,<5.0.0)
|
|
21
|
+
Requires-Dist: pandas (>=2.3.0,<3.0.0)
|
|
22
|
+
Requires-Dist: ta-lib (>=0.7.1,<0.8.0)
|
|
23
|
+
Requires-Dist: tqdm (>=4.67.1,<5.0.0)
|
|
24
|
+
Requires-Dist: yfinance (>=1.7.0,<2.0.0)
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# QuantEx Documentation
|
|
28
|
+
|
|
29
|
+
QuantEx is a Python backtesting library for OHLCV data. It is designed to be easy to use and extend, and to provide a simple interface for backtesting trading strategies.
|
|
30
|
+
|
|
31
|
+
## Getting Started
|
|
32
|
+
|
|
33
|
+
To get started with QuantEx, you can install it using pip:
|
|
34
|
+
```bash
|
|
35
|
+
pip install quantex
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
QuantEx is designed to be used by writing your strategy as event-driven code. An example buy and hold strategy is shown below:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from quantex.strategy import Strategy
|
|
42
|
+
|
|
43
|
+
class BuyAndHold(Strategy):
|
|
44
|
+
def init(self):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
def next(self):
|
|
48
|
+
if self.broker.is_closed():
|
|
49
|
+
self.broker.buy(amount=50)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This strategy will buy 50 shares of the stock when there are no open positions. To run this strategy, we must first get some data. Currently, QuantEx supports Yahoo Finance and pandas DataFrames as data sources.
|
|
53
|
+
To run the strategy with the data, we can use the Backtester class:
|
|
54
|
+
```python
|
|
55
|
+
from quantex.backtester import Backtester
|
|
56
|
+
from quantex.datasource import YahooDataSource
|
|
57
|
+
|
|
58
|
+
bt = Backtester(BuyAndHold)
|
|
59
|
+
source = YahooDataSource("NVDA", start="2020-01-01", end="2025-12-31")
|
|
60
|
+
bt.add_data(source, "NVDA")
|
|
61
|
+
result = bt.run()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The result object contains information about the strategy, such as the total return, annualized return, and max drawdown.
|
|
65
|
+
|
|
66
|
+
If you would like to include commissions in your strategy, you can use the Commission class:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from quantex.commission import Commission, ApplicationType
|
|
70
|
+
from quantex.backtester import Backtester
|
|
71
|
+
from quantex.datasource import YahooDataSource
|
|
72
|
+
|
|
73
|
+
bt = Backtester(BuyAndHold, commission=Commission(ApplicationType.PER_ORDER, fixed=1))
|
|
74
|
+
source = YahooDataSource("NVDA", start="2020-01-01", end="2025-12-31")
|
|
75
|
+
bt.add_data(source, "NVDA")
|
|
76
|
+
result = bt.run()
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This will charge a fixed commission of 1 dollar (or whatever the currency price is quoted in) per order.
|
|
80
|
+
|
|
81
|
+
## Documentation
|
|
82
|
+
|
|
83
|
+
The documentation for QuantEx can be found [here](https://dangreen07.github.io/quantex/).
|
quantex-0.6.1/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# QuantEx Documentation
|
|
2
|
+
|
|
3
|
+
QuantEx is a Python backtesting library for OHLCV data. It is designed to be easy to use and extend, and to provide a simple interface for backtesting trading strategies.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
To get started with QuantEx, you can install it using pip:
|
|
8
|
+
```bash
|
|
9
|
+
pip install quantex
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
QuantEx is designed to be used by writing your strategy as event-driven code. An example buy and hold strategy is shown below:
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from quantex.strategy import Strategy
|
|
16
|
+
|
|
17
|
+
class BuyAndHold(Strategy):
|
|
18
|
+
def init(self):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def next(self):
|
|
22
|
+
if self.broker.is_closed():
|
|
23
|
+
self.broker.buy(amount=50)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This strategy will buy 50 shares of the stock when there are no open positions. To run this strategy, we must first get some data. Currently, QuantEx supports Yahoo Finance and pandas DataFrames as data sources.
|
|
27
|
+
To run the strategy with the data, we can use the Backtester class:
|
|
28
|
+
```python
|
|
29
|
+
from quantex.backtester import Backtester
|
|
30
|
+
from quantex.datasource import YahooDataSource
|
|
31
|
+
|
|
32
|
+
bt = Backtester(BuyAndHold)
|
|
33
|
+
source = YahooDataSource("NVDA", start="2020-01-01", end="2025-12-31")
|
|
34
|
+
bt.add_data(source, "NVDA")
|
|
35
|
+
result = bt.run()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The result object contains information about the strategy, such as the total return, annualized return, and max drawdown.
|
|
39
|
+
|
|
40
|
+
If you would like to include commissions in your strategy, you can use the Commission class:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from quantex.commission import Commission, ApplicationType
|
|
44
|
+
from quantex.backtester import Backtester
|
|
45
|
+
from quantex.datasource import YahooDataSource
|
|
46
|
+
|
|
47
|
+
bt = Backtester(BuyAndHold, commission=Commission(ApplicationType.PER_ORDER, fixed=1))
|
|
48
|
+
source = YahooDataSource("NVDA", start="2020-01-01", end="2025-12-31")
|
|
49
|
+
bt.add_data(source, "NVDA")
|
|
50
|
+
result = bt.run()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This will charge a fixed commission of 1 dollar (or whatever the currency price is quoted in) per order.
|
|
54
|
+
|
|
55
|
+
## Documentation
|
|
56
|
+
|
|
57
|
+
The documentation for QuantEx can be found [here](https://dangreen07.github.io/quantex/).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "quantex"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.1"
|
|
4
4
|
description = "A simple quant strategy creation and backtesting package."
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Daniel Green",email = "dangreen07@outlook.com"}
|
|
@@ -11,12 +11,14 @@ license = "MIT"
|
|
|
11
11
|
dependencies = [
|
|
12
12
|
"pandas (>=2.3.0,<3.0.0)",
|
|
13
13
|
"fastparquet (>=2024.11.0,<2025.0.0)",
|
|
14
|
-
"pyarrow (>=20.0.0,<21.0.0)",
|
|
15
14
|
"tqdm (>=4.67.1,<5.0.0)",
|
|
16
15
|
"numpy (>=2.4.3,<3.0.0)",
|
|
17
16
|
"optuna (>=4.8.0,<5.0.0)",
|
|
18
17
|
"matplotlib (>=3.10.8,<4.0.0)",
|
|
19
18
|
"mplfinance (>=0.12.10b0,<0.13.0)",
|
|
19
|
+
"yfinance (>=1.7.0,<2.0.0)",
|
|
20
|
+
"ta-lib (>=0.7.1,<0.8.0)",
|
|
21
|
+
"mkdocs (>=1.6.1,<2.0.0)",
|
|
20
22
|
]
|
|
21
23
|
|
|
22
24
|
[tool.poetry]
|
|
@@ -44,6 +46,9 @@ pytest-xdist = "^3.8.0"
|
|
|
44
46
|
matplotlib = "^3.10.3"
|
|
45
47
|
ipykernel = "^7.2.0"
|
|
46
48
|
|
|
49
|
+
[tool.pytest.ini_options]
|
|
50
|
+
pythonpath = ["src"]
|
|
51
|
+
|
|
47
52
|
[build-system]
|
|
48
53
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
49
54
|
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
from quantex.commission import Commission
|
|
2
|
+
from quantex.datasource import DataSource, PricingData
|
|
3
|
+
from quantex.strategy import Indicator, Strategy
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from itertools import product
|
|
7
|
+
from enum import Enum
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import numpy as np
|
|
10
|
+
import optuna
|
|
11
|
+
import tqdm
|
|
12
|
+
import math
|
|
13
|
+
import copy
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SearchType(Enum):
|
|
17
|
+
GRID = 1
|
|
18
|
+
OPTUNA = 2
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Result:
|
|
23
|
+
"""
|
|
24
|
+
This class contains the result of a backtest.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
equity: np.ndarray,
|
|
30
|
+
starting_cash: float,
|
|
31
|
+
run_strategy: Strategy,
|
|
32
|
+
total_trades: int = 0,
|
|
33
|
+
):
|
|
34
|
+
self.equity = equity
|
|
35
|
+
self.starting_cash = starting_cash
|
|
36
|
+
self.run_strategy = run_strategy
|
|
37
|
+
self.total_trades = total_trades
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def timedelta(self) -> pd.Timedelta:
|
|
41
|
+
"""
|
|
42
|
+
The amount of time elapsed between the start and end of the backtest.
|
|
43
|
+
"""
|
|
44
|
+
return (
|
|
45
|
+
self.run_strategy.data.Timestamp[-1] - self.run_strategy.data.Timestamp[0]
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def start(self) -> pd.Timestamp:
|
|
50
|
+
"""
|
|
51
|
+
The start time of the backtest.
|
|
52
|
+
"""
|
|
53
|
+
return self.run_strategy.data.Timestamp[0]
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def end(self) -> pd.Timestamp:
|
|
57
|
+
"""
|
|
58
|
+
The end time of the backtest.
|
|
59
|
+
"""
|
|
60
|
+
return self.run_strategy.data.Timestamp[-1]
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def total_return(self) -> float:
|
|
64
|
+
"""
|
|
65
|
+
The total return of the backtest as a percentage.
|
|
66
|
+
"""
|
|
67
|
+
return (self.equity[-1] - self.starting_cash) / self.starting_cash
|
|
68
|
+
|
|
69
|
+
def sharpe_ratio(self, risk_free_rate: float = 0.04) -> float:
|
|
70
|
+
"""
|
|
71
|
+
The yearly sharpe ratio of the backtest.
|
|
72
|
+
|
|
73
|
+
Parameters:
|
|
74
|
+
risk_free_rate:
|
|
75
|
+
The risk free rate to be used in the calculation.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The yearly sharpe ratio of the backtest.
|
|
79
|
+
"""
|
|
80
|
+
total_time = (
|
|
81
|
+
self.run_strategy.data.Timestamp[-1] - self.run_strategy.data.Timestamp[0]
|
|
82
|
+
)
|
|
83
|
+
years = total_time.days / 365.25
|
|
84
|
+
periods_per_year = len(self.equity) / years
|
|
85
|
+
returns = pd.Series(
|
|
86
|
+
self.equity, index=self.run_strategy.data.Timestamp
|
|
87
|
+
).pct_change()
|
|
88
|
+
risk_free = (1 + risk_free_rate) ** (1 / periods_per_year) - 1
|
|
89
|
+
std = returns.std()
|
|
90
|
+
if std == 0:
|
|
91
|
+
return 0
|
|
92
|
+
sharpe = (returns.mean() - risk_free) / std * np.sqrt(periods_per_year)
|
|
93
|
+
return sharpe
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def annualized_return(self):
|
|
97
|
+
"""
|
|
98
|
+
The annualized return of the backtest.
|
|
99
|
+
"""
|
|
100
|
+
total_time = (
|
|
101
|
+
self.run_strategy.data.Timestamp[-1] - self.run_strategy.data.Timestamp[0]
|
|
102
|
+
)
|
|
103
|
+
years = total_time.days / (365.25)
|
|
104
|
+
return (1 + self.total_return) ** (1 / years) - 1
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def max_drawdown(self) -> tuple[float, float]:
|
|
108
|
+
"""
|
|
109
|
+
The maximum drawdown of the backtest
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
The maximum drawdown in dollars and percentage, respectively.
|
|
113
|
+
"""
|
|
114
|
+
peak = self.equity[0]
|
|
115
|
+
max_drawdown_dollars = 0
|
|
116
|
+
max_drawdown_percent = 0
|
|
117
|
+
for value in self.equity:
|
|
118
|
+
drawdown_dollars = value - peak
|
|
119
|
+
drawdown_percent = (value - peak) / peak
|
|
120
|
+
max_drawdown_dollars = min(
|
|
121
|
+
max_drawdown_dollars,
|
|
122
|
+
drawdown_dollars,
|
|
123
|
+
)
|
|
124
|
+
max_drawdown_percent = min(
|
|
125
|
+
max_drawdown_percent,
|
|
126
|
+
drawdown_percent,
|
|
127
|
+
)
|
|
128
|
+
return abs(max_drawdown_dollars), abs(max_drawdown_percent)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class Backtester:
|
|
132
|
+
def __init__(
|
|
133
|
+
self,
|
|
134
|
+
strategy: type[Strategy],
|
|
135
|
+
commission: Commission | None = None,
|
|
136
|
+
cash: float = 10_000,
|
|
137
|
+
):
|
|
138
|
+
"""
|
|
139
|
+
This class is used to run a backtest on a strategy.
|
|
140
|
+
|
|
141
|
+
Parameters:
|
|
142
|
+
strategy:
|
|
143
|
+
The strategy to be run.
|
|
144
|
+
commission:
|
|
145
|
+
The commission to be applied to the strategy. If None, no commission will be applied.
|
|
146
|
+
cash:
|
|
147
|
+
The starting cash to be used in the backtest.
|
|
148
|
+
"""
|
|
149
|
+
self.strategy = strategy
|
|
150
|
+
self.data = PricingData()
|
|
151
|
+
self.cash = cash
|
|
152
|
+
self.commission = commission or Commission()
|
|
153
|
+
|
|
154
|
+
def add_data(self, data: DataSource, name: str | None = None):
|
|
155
|
+
"""
|
|
156
|
+
Adds a data source to the backtest.
|
|
157
|
+
|
|
158
|
+
Parameters:
|
|
159
|
+
data:
|
|
160
|
+
The data source to be added.
|
|
161
|
+
name:
|
|
162
|
+
The name of the data source. If None, the name will be the same as the data source.
|
|
163
|
+
"""
|
|
164
|
+
if name is None:
|
|
165
|
+
name = data.name
|
|
166
|
+
self.data.add_data(data, name)
|
|
167
|
+
|
|
168
|
+
def run(self, params: dict | None = None) -> Result:
|
|
169
|
+
"""
|
|
170
|
+
Runs the backtest.
|
|
171
|
+
|
|
172
|
+
Parameters:
|
|
173
|
+
params: The parameters to use for the backtest. If None, the default parameters will be used.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Result: The result of the backtest.
|
|
177
|
+
"""
|
|
178
|
+
data = copy.deepcopy(self.data)
|
|
179
|
+
strat = self.strategy(data, cash=self.cash)
|
|
180
|
+
if params is not None:
|
|
181
|
+
for name, value in params.items():
|
|
182
|
+
setattr(strat, name, value)
|
|
183
|
+
strat.init()
|
|
184
|
+
indicators = [
|
|
185
|
+
i for i in strat.__dict__.keys() if isinstance(strat.__dict__[i], Indicator)
|
|
186
|
+
]
|
|
187
|
+
for name in data.datas.keys():
|
|
188
|
+
data.datas[name]._current = 0 ## Reset the current index
|
|
189
|
+
for indicator in indicators:
|
|
190
|
+
strat.__dict__[indicator]._current = 0
|
|
191
|
+
initial_cash = strat.broker.cash
|
|
192
|
+
equity = np.full(len(data.index), initial_cash, dtype=np.float64)
|
|
193
|
+
for i in range(len(data.index)):
|
|
194
|
+
for name in data.datas.keys():
|
|
195
|
+
data.datas[
|
|
196
|
+
name
|
|
197
|
+
]._current += (
|
|
198
|
+
1 ## TODO: Handle multiple data sources with different indexes
|
|
199
|
+
)
|
|
200
|
+
skip_nan = False
|
|
201
|
+
for indicator in indicators:
|
|
202
|
+
strat.__dict__[
|
|
203
|
+
indicator
|
|
204
|
+
]._current += 1 ## Shouldn't have to handle multiple indexers as they should all be the same
|
|
205
|
+
ind: Indicator = strat.__dict__[indicator]
|
|
206
|
+
if np.isnan(ind[-1]):
|
|
207
|
+
skip_nan = True
|
|
208
|
+
if skip_nan:
|
|
209
|
+
continue
|
|
210
|
+
strat.broker.__process_orders__()
|
|
211
|
+
strat.next()
|
|
212
|
+
equity[i] = strat.broker.equity()
|
|
213
|
+
result = Result(equity, initial_cash, strat, strat.broker.total_trades)
|
|
214
|
+
return result
|
|
215
|
+
|
|
216
|
+
def optimize(
|
|
217
|
+
self,
|
|
218
|
+
params: dict[str, list],
|
|
219
|
+
constraint: Callable[[dict], bool] | None = None,
|
|
220
|
+
max_trials: int = 100,
|
|
221
|
+
search_type: SearchType = SearchType.GRID,
|
|
222
|
+
risk_free_rate: float = 0.04,
|
|
223
|
+
seed: int = 0,
|
|
224
|
+
) -> tuple[float, dict]:
|
|
225
|
+
"""
|
|
226
|
+
Optimizes the backtest with the given parameters.
|
|
227
|
+
|
|
228
|
+
Parameters:
|
|
229
|
+
params: The parameters to optimize.
|
|
230
|
+
max_trials: The maximum number of trials to run.
|
|
231
|
+
search_type: The search type to use.
|
|
232
|
+
"""
|
|
233
|
+
self.results = []
|
|
234
|
+
self.max_sharpe = -np.inf
|
|
235
|
+
self.best_trial = {}
|
|
236
|
+
if search_type == SearchType.GRID:
|
|
237
|
+
search_space = (
|
|
238
|
+
dict(zip(params.keys(), values)) for values in product(*params.values())
|
|
239
|
+
)
|
|
240
|
+
search_space = list(search_space)
|
|
241
|
+
if constraint is not None:
|
|
242
|
+
search_space = [trial for trial in search_space if constraint(trial)]
|
|
243
|
+
search_space = search_space[:max_trials]
|
|
244
|
+
|
|
245
|
+
def run_trial(trial):
|
|
246
|
+
result = self.run(params=trial)
|
|
247
|
+
sharpe = result.sharpe_ratio(risk_free_rate=risk_free_rate)
|
|
248
|
+
if sharpe > self.max_sharpe:
|
|
249
|
+
self.max_sharpe = sharpe
|
|
250
|
+
self.best_trial = trial
|
|
251
|
+
self.results.append((trial, sharpe))
|
|
252
|
+
|
|
253
|
+
for trial in tqdm.tqdm(search_space, desc="Processing"):
|
|
254
|
+
run_trial(trial)
|
|
255
|
+
elif search_type == SearchType.OPTUNA:
|
|
256
|
+
optuna.logging.set_verbosity(optuna.logging.WARNING)
|
|
257
|
+
sampler = optuna.samplers.TPESampler(seed=seed)
|
|
258
|
+
study = optuna.create_study(
|
|
259
|
+
direction="maximize",
|
|
260
|
+
sampler=sampler,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
def objective(optuna_trial: optuna.Trial):
|
|
264
|
+
trial_params = {
|
|
265
|
+
name: optuna_trial.suggest_categorical(name, values)
|
|
266
|
+
for name, values in params.items()
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if constraint is not None and not constraint(trial_params):
|
|
270
|
+
raise optuna.TrialPruned("Parameter constraint failed")
|
|
271
|
+
|
|
272
|
+
result = self.run(params=trial_params)
|
|
273
|
+
sharpe = result.sharpe_ratio(risk_free_rate=risk_free_rate)
|
|
274
|
+
|
|
275
|
+
if sharpe is None or not math.isfinite(sharpe):
|
|
276
|
+
raise optuna.TrialPruned("Invalid Sharpe ratio")
|
|
277
|
+
|
|
278
|
+
self.results.append((trial_params, sharpe))
|
|
279
|
+
return sharpe
|
|
280
|
+
|
|
281
|
+
study.optimize(objective, n_trials=max_trials, show_progress_bar=True)
|
|
282
|
+
|
|
283
|
+
if study.best_trial is None:
|
|
284
|
+
raise ValueError("No valid parameter combination was found")
|
|
285
|
+
|
|
286
|
+
self.best_trial = study.best_params
|
|
287
|
+
self.max_sharpe = study.best_value
|
|
288
|
+
return (self.max_sharpe, self.best_trial)
|