My First Commit

This commit is contained in:
Leonid Gloubchnin 2025-11-04 17:55:08 +00:00
parent 5d46c1e32c
commit 413abafe0f
11 changed files with 7076 additions and 290 deletions

3
.gitignore vendored
View File

@ -1,11 +1,10 @@
# SpecStory explanation file # SpecStory explanation file
__pycache__/ __pycache__/
__OLD__/ __OLD__/
.specstory/
.history/ .history/
.cursorindexingignore .cursorindexingignore
data data
.vscode/ ####.vscode/
cvttpy cvttpy
# SpecStory explanation file # SpecStory explanation file
.specstory/.what-is-this.md .specstory/.what-is-this.md

View File

@ -427,14 +427,16 @@ class BacktestResult:
trd["open_scaled_disequilibrium"] is not None trd["open_scaled_disequilibrium"] is not None
and trd["open_scaled_disequilibrium"] is not None and trd["open_scaled_disequilibrium"] is not None
): ):
disequil_info = f" | Open Dis-eq: {trd['open_scaled_disequilibrium']:.2f}," disequil_info = (
f" Close Dis-eq: {trd['open_scaled_disequilibrium']:.2f}" f' | Open Dis-eq: {trd["open_scaled_disequilibrium"]:.2f},'
f' Close Dis-eq: {trd["close_scaled_disequilibrium"]:.2f}'
)
print( print(
f" {trd['open_time'].time()}-{trd['close_time'].time()} {trd['symbol']}: " f' {trd["open_time"].time()}-{trd["close_time"].time()} {trd["symbol"]}: '
f" {trd['open_side']} @ ${trd['open_price']:.2f}," f' {trd["open_side"]} @ ${trd["open_price"]:.2f},'
f" {trd["close_side"]} @ ${trd["close_price"]:.2f}," f' {trd["close_side"]} @ ${trd["close_price"]:.2f},'
f" Return: {trd['symbol_return']:.2f}%{disequil_info}" f' Return: {trd["symbol_return"]:.2f}%{disequil_info}'
) )
pair_return += trd["symbol_return"] pair_return += trd["symbol_return"]

View File

@ -188,7 +188,8 @@ class RollingFit(PairsTradingFitMethod):
open_px_b = open_row[f"{colname_b}"] open_px_b = open_row[f"{colname_b}"]
# creating the trades # creating the trades
print(f"OPEN_TRADES: {row["tstamp"]} {open_scaled_disequilibrium=}") # use outer single quotes so we can reference DataFrame keys with double quotes inside
print(f'OPEN_TRADES: {open_tstamp} open_scaled_disequilibrium={open_scaled_disequilibrium}')
if open_disequilibrium > 0: if open_disequilibrium > 0:
open_side_a = "SELL" open_side_a = "SELL"
open_side_b = "BUY" open_side_b = "BUY"
@ -237,10 +238,7 @@ class RollingFit(PairsTradingFitMethod):
), ),
] ]
# Create DataFrame with explicit dtypes to avoid concatenation warnings # Create DataFrame with explicit dtypes to avoid concatenation warnings
df = pd.DataFrame( df = pd.DataFrame(trd_signal_tuples, columns=self.TRADES_COLUMNS)
trd_signal_tuples,
columns=self.TRADES_COLUMNS,
)
# Ensure consistent dtypes # Ensure consistent dtypes
return df.astype( return df.astype(
{ {

View File

@ -1,14 +1,47 @@
# original script moved to vecm_rolling_fit_01.py
# 09.09.25 Added GARCH model - predicting volatility
# Rule of thumb:
# alpha + beta ≈ 1 → strong volatility clustering, persistence.
# If much lower → volatility mean reverts quickly.
# If > 1 → model is unstable / non-stationary (bad).
# the VECM disequilibrium (mean reversion signal) and
# the GARCH volatility forecast (risk measure).
# combine them → e.g., only enter trades when:
# high_volatility = 1 → persistence > 0.95 or volatility > 2 (rule of thumb: unstable / risky regime).
# high_volatility = 0 → stable regime.
# VECM disequilibrium z-score > threshold and
# GARCH-forecasted volatility is not too high (avoid noise-driven signals).
# This creates a volatility-adjusted pairs trading strategy, more robust than plain VECM
# now pair_predict_result_ DataFrame includes:
# disequilibrium, scaled_disequilibrium, z-scores, garch_alpha, garch_beta, garch_persistence (α+β rule-of-thumb)
# garch_vol_forecast (1-step volatility forecast)
# Would you like me to also add a warning flag column
# (e.g., "high_volatility" = 1 if persistence > 0.95 or vol_forecast > threshold)
# so you can easily detect unstable regimes?
# VECM/GARCH
# vecm_rolling_fit.py:
from typing import Any, Dict, Optional, cast from typing import Any, Dict, Optional, cast
import numpy as np
import pandas as pd import pandas as pd
from typing import Any, Dict, Optional
from pt_trading.results import BacktestResult from pt_trading.results import BacktestResult
from pt_trading.rolling_window_fit import RollingFit from pt_trading.rolling_window_fit import RollingFit
from pt_trading.trading_pair import TradingPair from pt_trading.trading_pair import TradingPair
from statsmodels.tsa.vector_ar.vecm import VECM, VECMResults from statsmodels.tsa.vector_ar.vecm import VECM, VECMResults
from arch import arch_model
NanoPerMin = 1e9 NanoPerMin = 1e9
class VECMTradingPair(TradingPair): class VECMTradingPair(TradingPair):
vecm_fit_: Optional[VECMResults] vecm_fit_: Optional[VECMResults]
pair_predict_result_: Optional[pd.DataFrame] pair_predict_result_: Optional[pd.DataFrame]
@ -23,20 +56,23 @@ class VECMTradingPair(TradingPair):
super().__init__(config, market_data, symbol_a, symbol_b) super().__init__(config, market_data, symbol_a, symbol_b)
self.vecm_fit_ = None self.vecm_fit_ = None
self.pair_predict_result_ = None self.pair_predict_result_ = None
self.garch_fit_ = None
self.sigma_spread_forecast_ = None
self.garch_alpha_ = None
self.garch_beta_ = None
self.garch_persistence_ = None
self.high_volatility_flag_ = None
def _train_pair(self) -> None: def _train_pair(self) -> None:
self._fit_VECM() self._fit_VECM()
assert self.vecm_fit_ is not None assert self.vecm_fit_ is not None
diseq_series = self.training_df_[self.colnames()] @ self.vecm_fit_.beta diseq_series = self.training_df_[self.colnames()] @ self.vecm_fit_.beta
# print(diseq_series.shape)
self.training_mu_ = float(diseq_series[0].mean()) self.training_mu_ = float(diseq_series[0].mean())
self.training_std_ = float(diseq_series[0].std()) self.training_std_ = float(diseq_series[0].std())
self.training_df_["dis-equilibrium"] = ( self.training_df_["disequilibrium"] = diseq_series
self.training_df_[self.colnames()] @ self.vecm_fit_.beta self.training_df_["scaled_disequilibrium"] = (
)
# Normalize the dis-equilibrium
self.training_df_["scaled_dis-equilibrium"] = (
diseq_series - self.training_mu_ diseq_series - self.training_mu_
) / self.training_std_ ) / self.training_std_
@ -45,61 +81,96 @@ class VECMTradingPair(TradingPair):
vecm_df = self.training_df_[self.colnames()].reset_index(drop=True) vecm_df = self.training_df_[self.colnames()].reset_index(drop=True)
vecm_model = VECM(vecm_df, coint_rank=1) vecm_model = VECM(vecm_df, coint_rank=1)
vecm_fit = vecm_model.fit() vecm_fit = vecm_model.fit()
assert vecm_fit is not None
# URGENT check beta and alpha
# Check if the model converged properly
if not hasattr(vecm_fit, "beta") or vecm_fit.beta is None:
print(f"{self}: VECM model failed to converge properly")
self.vecm_fit_ = vecm_fit self.vecm_fit_ = vecm_fit
pass
# Error Correction Term (spread)
ect_series = (vecm_df @ vecm_fit.beta).iloc[:, 0]
# Difference the spread for stationarity
dz = ect_series.diff().dropna()
if len(dz) < 30:
print("Not enough data for GARCH fitting.")
return
# Rescale if variance too small
if dz.std() < 0.1:
dz = dz * 1000
# print("Scale check:", dz.std())
try:
garch = arch_model(dz, vol="GARCH", p=1, q=1, mean="Zero", dist="normal")
garch_fit = garch.fit(disp="off")
self.garch_fit_ = garch_fit
# Extract parameters
params = garch_fit.params
self.garch_alpha_ = params.get("alpha[1]", np.nan)
self.garch_beta_ = params.get("beta[1]", np.nan)
self.garch_persistence_ = self.garch_alpha_ + self.garch_beta_
# print (f"GARCH α: {self.garch_alpha_:.4f}, β: {self.garch_beta_:.4f}, "
# f"α+β (persistence): {self.garch_persistence_:.4f}")
# One-step-ahead volatility forecast
forecast = garch_fit.forecast(horizon=1)
sigma_next = np.sqrt(forecast.variance.iloc[-1, 0])
self.sigma_spread_forecast_ = float(sigma_next)
# print("GARCH sigma forecast:", self.sigma_spread_forecast_)
# Rule of thumb: persistence close to 1 or large volatility forecast
self.high_volatility_flag_ = int(
(self.garch_persistence_ is not None and self.garch_persistence_ > 0.95)
or (self.sigma_spread_forecast_ is not None and self.sigma_spread_forecast_ > 2)
)
except Exception as e:
print(f"GARCH fit failed: {e}")
self.garch_fit_ = None
self.sigma_spread_forecast_ = None
self.high_volatility_flag_ = None
def predict(self) -> pd.DataFrame: def predict(self) -> pd.DataFrame:
self._train_pair() self._train_pair()
assert self.testing_df_ is not None assert self.testing_df_ is not None
assert self.vecm_fit_ is not None assert self.vecm_fit_ is not None
# VECM predictions
predicted_prices = self.vecm_fit_.predict(steps=len(self.testing_df_)) predicted_prices = self.vecm_fit_.predict(steps=len(self.testing_df_))
# Convert prediction to a DataFrame for readability
predicted_df = pd.DataFrame(
predicted_prices, columns=pd.Index(self.colnames()), dtype=float
)
predicted_df = pd.merge( predicted_df = pd.merge(
self.testing_df_.reset_index(drop=True), self.testing_df_.reset_index(drop=True),
pd.DataFrame( pd.DataFrame(predicted_prices, columns=pd.Index(self.colnames()), dtype=float),
predicted_prices, columns=pd.Index(self.colnames()), dtype=float
),
left_index=True, left_index=True,
right_index=True, right_index=True,
suffixes=("", "_pred"), suffixes=("", "_pred"),
).dropna() ).dropna()
# Disequilibrium and z-scores
predicted_df["disequilibrium"] = ( predicted_df["disequilibrium"] = (
predicted_df[self.colnames()] @ self.vecm_fit_.beta predicted_df[self.colnames()] @ self.vecm_fit_.beta
) )
predicted_df["signed_scaled_disequilibrium"] = ( predicted_df["signed_scaled_disequilibrium"] = (
predicted_df["disequilibrium"] - self.training_mu_ predicted_df["disequilibrium"] - self.training_mu_
) / self.training_std_ ) / self.training_std_
predicted_df["scaled_disequilibrium"] = abs( predicted_df["scaled_disequilibrium"] = abs(
predicted_df["signed_scaled_disequilibrium"] predicted_df["signed_scaled_disequilibrium"]
) )
predicted_df = predicted_df.reset_index(drop=True) # Add GARCH parameters + volatility forecast
predicted_df["garch_alpha"] = self.garch_alpha_
predicted_df["garch_beta"] = self.garch_beta_
predicted_df["garch_persistence"] = self.garch_persistence_
predicted_df["garch_vol_forecast"] = self.sigma_spread_forecast_
predicted_df["high_volatility"] = self.high_volatility_flag_
# Save results
if self.pair_predict_result_ is None: if self.pair_predict_result_ is None:
self.pair_predict_result_ = predicted_df self.pair_predict_result_ = predicted_df
else: else:
self.pair_predict_result_ = pd.concat( self.pair_predict_result_ = pd.concat(
[self.pair_predict_result_, predicted_df], ignore_index=True [self.pair_predict_result_, predicted_df], ignore_index=True
) )
# Reset index to ensure proper indexing
self.pair_predict_result_ = self.pair_predict_result_.reset_index(drop=True)
return self.pair_predict_result_ return self.pair_predict_result_

View File

@ -1,18 +1,28 @@
from typing import Any, Dict, Optional, cast from typing import Any, Dict, Optional
import pandas as pd import pandas as pd
from pt_trading.results import BacktestResult import statsmodels.api as sm
from pt_trading.rolling_window_fit import RollingFit from pt_trading.rolling_window_fit import RollingFit
from pt_trading.trading_pair import TradingPair from pt_trading.trading_pair import TradingPair
import statsmodels.api as sm
NanoPerMin = 1e9 NanoPerMin = 1e9
class ZScoreTradingPair(TradingPair): class ZScoreTradingPair(TradingPair):
"""TradingPair implementation that fits a hedge ratio with OLS and
computes a standardized spread (z-score).
The class stores training spread mean/std and hedge ratio so the model
can be applied to testing data consistently.
"""
zscore_model_: Optional[sm.regression.linear_model.RegressionResultsWrapper] zscore_model_: Optional[sm.regression.linear_model.RegressionResultsWrapper]
pair_predict_result_: Optional[pd.DataFrame] pair_predict_result_: Optional[pd.DataFrame]
zscore_df_: Optional[pd.DataFrame] zscore_df_: Optional[pd.Series]
hedge_ratio_: Optional[float]
spread_mean_: Optional[float]
spread_std_: Optional[float]
def __init__( def __init__(
self, self,
@ -25,47 +35,79 @@ class ZScoreTradingPair(TradingPair):
self.zscore_model_ = None self.zscore_model_ = None
self.pair_predict_result_ = None self.pair_predict_result_ = None
self.zscore_df_ = None self.zscore_df_ = None
self.hedge_ratio_ = None
self.spread_mean_ = None
self.spread_std_ = None
def _fit_zscore(self) -> None: def _fit_zscore(self) -> None:
"""Fit OLS on the training window and compute training z-score."""
assert self.training_df_ is not None assert self.training_df_ is not None
symbol_a_px_series = self.training_df_[self.colnames()].iloc[:, 0]
symbol_b_px_series = self.training_df_[self.colnames()].iloc[:, 1]
symbol_a_px_series, symbol_b_px_series = symbol_a_px_series.align( # Extract price series for the two symbols from the training frame.
symbol_b_px_series, axis=0 px_df = self.training_df_[self.colnames()]
) symbol_a_px = px_df.iloc[:, 0]
symbol_b_px = px_df.iloc[:, 1]
X = sm.add_constant(symbol_b_px_series) # Align indexes and fit OLS: symbol_a ~ const + symbol_b
self.zscore_model_ = sm.OLS(symbol_a_px_series, X).fit() symbol_a_px, symbol_b_px = symbol_a_px.align(symbol_b_px, join="inner")
assert self.zscore_model_ is not None X = sm.add_constant(symbol_b_px)
hedge_ratio = self.zscore_model_.params.iloc[1] self.zscore_model_ = sm.OLS(symbol_a_px, X).fit()
# Calculate spread and Z-score # Hedge ratio is the slope on symbol_b
spread = symbol_a_px_series - hedge_ratio * symbol_b_px_series params = self.zscore_model_.params
self.zscore_df_ = (spread - spread.mean()) / spread.std() self.hedge_ratio_ = float(params.iloc[1]) if len(params) > 1 else 0.0
# Training spread and its standardized z-score
spread = symbol_a_px - self.hedge_ratio_ * symbol_b_px
self.spread_mean_ = float(spread.mean())
self.spread_std_ = float(spread.std(ddof=0)) if spread.std(ddof=0) != 0 else 1.0
self.zscore_df_ = (spread - self.spread_mean_) / self.spread_std_
def predict(self) -> pd.DataFrame: def predict(self) -> pd.DataFrame:
"""Apply fitted hedge ratio to the testing frame and return a
dataframe with canonical columns:
- disequilibrium: signed z-score
- scaled_disequilibrium: absolute z-score
- signed_scaled_disequilibrium: same as disequilibrium (keeps sign)
"""
# Fit on training window
self._fit_zscore() self._fit_zscore()
assert self.zscore_df_ is not None assert self.zscore_df_ is not None
self.training_df_["dis-equilibrium"] = self.zscore_df_ assert self.hedge_ratio_ is not None
self.training_df_["scaled_dis-equilibrium"] = abs(self.zscore_df_) assert self.spread_mean_ is not None and self.spread_std_ is not None
# Keep training columns for inspection
self.training_df_["disequilibrium"] = self.zscore_df_
self.training_df_["scaled_disequilibrium"] = self.zscore_df_.abs()
# Apply model to testing frame
assert self.testing_df_ is not None assert self.testing_df_ is not None
assert self.zscore_df_ is not None test_df = self.testing_df_.copy()
predicted_df = self.testing_df_ px_test = test_df[self.colnames()]
a_test = px_test.iloc[:, 0]
b_test = px_test.iloc[:, 1]
a_test, b_test = a_test.align(b_test, join="inner")
predicted_df["disequilibrium"] = self.zscore_df_ # Compute test spread and standardize using training mean/std
predicted_df["signed_scaled_disequilibrium"] = self.zscore_df_ test_spread = a_test - self.hedge_ratio_ * b_test
predicted_df["scaled_disequilibrium"] = abs(self.zscore_df_) test_zscore = (test_spread - self.spread_mean_) / self.spread_std_
predicted_df = predicted_df.reset_index(drop=True) # Attach canonical columns
# Align back to test_df index if needed
test_zscore = test_zscore.reindex(test_df.index)
test_df["disequilibrium"] = test_zscore
test_df["signed_scaled_disequilibrium"] = test_zscore
test_df["scaled_disequilibrium"] = test_zscore.abs()
# Reset index and accumulate results across windows
test_df = test_df.reset_index(drop=True)
if self.pair_predict_result_ is None: if self.pair_predict_result_ is None:
self.pair_predict_result_ = predicted_df self.pair_predict_result_ = test_df
else: else:
self.pair_predict_result_ = pd.concat( self.pair_predict_result_ = pd.concat(
[self.pair_predict_result_, predicted_df], ignore_index=True [self.pair_predict_result_, test_df], ignore_index=True
) )
# Reset index to ensure proper indexing
self.pair_predict_result_ = self.pair_predict_result_.reset_index(drop=True) self.pair_predict_result_ = self.pair_predict_result_.reset_index(drop=True)
return self.pair_predict_result_.dropna() return self.pair_predict_result_.dropna()
@ -78,8 +120,5 @@ class ZScoreRollingFit(RollingFit):
self, config: Dict, market_data: pd.DataFrame, symbol_a: str, symbol_b: str self, config: Dict, market_data: pd.DataFrame, symbol_a: str, symbol_b: str
) -> TradingPair: ) -> TradingPair:
return ZScoreTradingPair( return ZScoreTradingPair(
config=config, config=config, market_data=market_data, symbol_a=symbol_a, symbol_b=symbol_b
market_data=market_data,
symbol_a=symbol_a,
symbol_b=symbol_b,
) )

View File

@ -61,7 +61,7 @@ protobuf>=3.12.4
psutil>=5.9.0 psutil>=5.9.0
ptyprocess>=0.7.0 ptyprocess>=0.7.0
pycurl>=7.44.1 pycurl>=7.44.1
pyelftools>=0.27 # pyelftools>=0.27
Pygments>=2.11.2 Pygments>=2.11.2
pyparsing>=2.4.7 pyparsing>=2.4.7
pyrsistent>=0.18.1 pyrsistent>=0.18.1
@ -69,7 +69,7 @@ python-debian>=0.1.43 #+ubuntu1.1
python-dotenv>=0.19.2 python-dotenv>=0.19.2
python-magic>=0.4.24 python-magic>=0.4.24
python-xlib>=0.29 python-xlib>=0.29
pyxdg>=0.27 # pyxdg>=0.27
PyYAML>=6.0 PyYAML>=6.0
reportlab>=3.6.8 reportlab>=3.6.8
requests>=2.25.1 requests>=2.25.1
@ -82,113 +82,113 @@ six>=1.16.0
soupsieve>=2.3.1 soupsieve>=2.3.1
ssh-import-id>=5.11 ssh-import-id>=5.11
statsmodels>=0.14.4 statsmodels>=0.14.4
texttable>=1.6.4 # texttable>=1.6.4
tldextract>=3.1.2 tldextract>=3.1.2
tomli>=1.2.2 tomli>=1.2.2
######## typed-ast>=1.4.3 ######## typed-ast>=1.4.3
types-aiofiles>=0.1 # types-aiofiles>=0.1
types-annoy>=1.17 # types-annoy>=1.17
types-appdirs>=1.4 # types-appdirs>=1.4
types-atomicwrites>=1.4 # types-atomicwrites>=1.4
types-aws-xray-sdk>=2.8 # types-aws-xray-sdk>=2.8
types-babel>=2.9 # types-babel>=2.9
types-backports-abc>=0.5 # types-backports-abc>=0.5
types-backports.ssl-match-hostname>=3.7 # types-backports.ssl-match-hostname>=3.7
types-beautifulsoup4>=4.10 # types-beautifulsoup4>=4.10
types-bleach>=4.1 # types-bleach>=4.1
types-boto>=2.49 # types-boto>=2.49
types-braintree>=4.11 # types-braintree>=4.11
types-cachetools>=4.2 # types-cachetools>=4.2
types-caldav>=0.8 # types-caldav>=0.8
types-certifi>=2020.4 # types-certifi>=2020.4
types-characteristic>=14.3 # types-characteristic>=14.3
types-chardet>=4.0 # types-chardet>=4.0
types-click>=7.1 # types-click>=7.1
types-click-spinner>=0.1 # types-click-spinner>=0.1
types-colorama>=0.4 # types-colorama>=0.4
types-commonmark>=0.9 # types-commonmark>=0.9
types-contextvars>=0.1 # types-contextvars>=0.1
types-croniter>=1.0 # types-croniter>=1.0
types-cryptography>=3.3 # types-cryptography>=3.3
types-dataclasses>=0.1 # types-dataclasses>=0.1
types-dateparser>=1.0 # types-dateparser>=1.0
types-DateTimeRange>=0.1 # types-DateTimeRange>=0.1
types-decorator>=0.1 # types-decorator>=0.1
types-Deprecated>=1.2 # types-Deprecated>=1.2
types-docopt>=0.6 # types-docopt>=0.6
types-docutils>=0.17 # types-docutils>=0.17
types-editdistance>=0.5 # types-editdistance>=0.5
types-emoji>=1.2 # types-emoji>=1.2
types-entrypoints>=0.3 # types-entrypoints>=0.3
types-enum34>=1.1 # types-enum34>=1.1
types-filelock>=3.2 # types-filelock>=3.2
types-first>=2.0 # types-first>=2.0
types-Flask>=1.1 # types-Flask>=1.1
types-freezegun>=1.1 # types-freezegun>=1.1
types-frozendict>=0.1 # types-frozendict>=0.1
types-futures>=3.3 # types-futures>=3.3
types-html5lib>=1.1 # types-html5lib>=1.1
types-httplib2>=0.19 # types-httplib2>=0.19
types-humanfriendly>=9.2 # types-humanfriendly>=9.2
types-ipaddress>=1.0 # types-ipaddress>=1.0
types-itsdangerous>=1.1 # types-itsdangerous>=1.1
types-JACK-Client>=0.1 # types-JACK-Client>=0.1
types-Jinja2>=2.11 # types-Jinja2>=2.11
types-jmespath>=0.10 # types-jmespath>=0.10
types-jsonschema>=3.2 # types-jsonschema>=3.2
types-Markdown>=3.3 # types-Markdown>=3.3
types-MarkupSafe>=1.1 # types-MarkupSafe>=1.1
types-mock>=4.0 # types-mock>=4.0
types-mypy-extensions>=0.4 # types-mypy-extensions>=0.4
types-mysqlclient>=2.0 # types-mysqlclient>=2.0
types-oauthlib>=3.1 # types-oauthlib>=3.1
types-orjson>=3.6 # types-orjson>=3.6
types-paramiko>=2.7 # types-paramiko>=2.7
types-Pillow>=8.3 # types-Pillow>=8.3
types-polib>=1.1 # types-polib>=1.1
types-prettytable>=2.1 # types-prettytable>=2.1
types-protobuf>=3.17 # types-protobuf>=3.17
types-psutil>=5.8 # types-psutil>=5.8
types-psycopg2>=2.9 # types-psycopg2>=2.9
types-pyaudio>=0.2 # types-pyaudio>=0.2
types-pycurl>=0.1 # types-pycurl>=0.1
types-pyfarmhash>=0.2 # types-pyfarmhash>=0.2
types-Pygments>=2.9 # types-Pygments>=2.9
types-PyMySQL>=1.0 # types-PyMySQL>=1.0
types-pyOpenSSL>=20.0 # types-pyOpenSSL>=20.0
types-pyRFC3339>=0.1 # types-pyRFC3339>=0.1
types-pysftp>=0.2 # types-pysftp>=0.2
types-pytest-lazy-fixture>=0.6 # types-pytest-lazy-fixture>=0.6
types-python-dateutil>=2.8 # types-python-dateutil>=2.8
types-python-gflags>=3.1 # types-python-gflags>=3.1
types-python-nmap>=0.6 # types-python-nmap>=0.6
types-python-slugify>=5.0 # types-python-slugify>=5.0
types-pytz>=2021.1 # types-pytz>=2021.1
types-pyvmomi>=7.0 # types-pyvmomi>=7.0
types-PyYAML>=5.4 # types-PyYAML>=5.4
types-redis>=3.5 # types-redis>=3.5
types-requests>=2.25 # types-requests>=2.25
types-retry>=0.9 # types-retry>=0.9
types-selenium>=3.141 # types-selenium>=3.141
types-Send2Trash>=1.8 # types-Send2Trash>=1.8
types-setuptools>=57.4 # types-setuptools>=57.4
types-simplejson>=3.17 # types-simplejson>=3.17
types-singledispatch>=3.7 # types-singledispatch>=3.7
types-six>=1.16 # types-six>=1.16
types-slumber>=0.7 # types-slumber>=0.7
types-stripe>=2.59 # types-stripe>=2.59
types-tabulate>=0.8 # types-tabulate>=0.8
types-termcolor>=1.1 # types-termcolor>=1.1
types-toml>=0.10 # types-toml>=0.10
types-toposort>=1.6 # types-toposort>=1.6
types-ttkthemes>=3.2 # types-ttkthemes>=3.2
types-typed-ast>=1.4 # types-typed-ast>=1.4
types-tzlocal>=0.1 # types-tzlocal>=0.1
types-ujson>=0.1 # types-ujson>=0.1
types-vobject>=0.9 # types-vobject>=0.9
types-waitress>=0.1 # types-waitress>=0.1
types-Werkzeug>=1.0 #types-Werkzeug>=1.0
types-xxhash>=2.0 #types-xxhash>=2.0
typing-extensions>=3.10.0.2 typing-extensions>=3.10.0.2
Unidecode>=1.3.3 Unidecode>=1.3.3
urllib3>=1.26.5 urllib3>=1.26.5

View File

@ -9,18 +9,19 @@ import pandas as pd
from tools.config import expand_filename, load_config from tools.config import expand_filename, load_config
from tools.data_loader import get_available_instruments_from_db from tools.data_loader import get_available_instruments_from_db
from pt_trading.results import ( from pt_trading.results import (
BacktestResult, BacktestResult,
create_result_database, create_result_database,
store_config_in_database, store_config_in_database,
store_results_in_database, store_results_in_database,
) )
from pt_trading.fit_method import PairsTradingFitMethod from pt_trading.fit_method import PairsTradingFitMethod
from pt_trading.trading_pair import TradingPair from pt_trading.trading_pair import TradingPair
from research.research_tools import create_pairs, resolve_datafiles from research.research_tools import create_pairs, resolve_datafiles
def main() -> None: def main() -> None:
parser = argparse.ArgumentParser(description="Run pairs trading backtest.") parser = argparse.ArgumentParser(description="Run pairs trading backtest.")
parser.add_argument( parser.add_argument(

View File

@ -7390,7 +7390,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.12.11" "version": "3.12.4"
} }
}, },
"nbformat": 4, "nbformat": 4,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,6 @@ from typing import Dict, List, Optional
import pandas as pd import pandas as pd
from pt_trading.fit_method import PairsTradingFitMethod from pt_trading.fit_method import PairsTradingFitMethod
def resolve_datafiles(config: Dict, cli_datafiles: Optional[str] = None) -> List[str]: def resolve_datafiles(config: Dict, cli_datafiles: Optional[str] = None) -> List[str]:
""" """
Resolve the list of data files to process. Resolve the list of data files to process.