Compare commits
8 Commits
master
...
lg_branch_
| Author | SHA1 | Date | |
|---|---|---|---|
| 809f46fe36 | |||
| 413abafe0f | |||
| 5d46c1e32c | |||
| 889f7ba1c3 | |||
| 1515b2d077 | |||
| b4ae3e715d | |||
|
|
6f845d32c6 | ||
|
|
a04e8878fb |
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
||||||
|
|||||||
27
configuration/equity.cfg
Normal file
27
configuration/equity.cfg
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"security_type": "EQUITY",
|
||||||
|
"data_directory": "./data/equity",
|
||||||
|
"datafiles": [
|
||||||
|
"20250618.mktdata.ohlcv.db",
|
||||||
|
],
|
||||||
|
"db_table_name": "md_1min_bars",
|
||||||
|
"exchange_id": "ALPACA",
|
||||||
|
"instrument_id_pfx": "STOCK-",
|
||||||
|
"trading_hours": {
|
||||||
|
"begin_session": "9:30:00",
|
||||||
|
"end_session": "16:00:00",
|
||||||
|
"timezone": "America/New_York"
|
||||||
|
},
|
||||||
|
"price_column": "close",
|
||||||
|
"min_required_points": 30,
|
||||||
|
"zero_threshold": 1e-10,
|
||||||
|
"dis-equilibrium_open_trshld": 2.0,
|
||||||
|
"dis-equilibrium_close_trshld": 1.0,
|
||||||
|
"training_minutes": 120,
|
||||||
|
"funding_per_pair": 2000.0,
|
||||||
|
# "fit_method_class": "pt_trading.sliding_fit.SlidingFit",
|
||||||
|
"fit_method_class": "pt_trading.static_fit.StaticFit",
|
||||||
|
"exclude_instruments": ["CAN"],
|
||||||
|
"close_outstanding_positions": false
|
||||||
|
|
||||||
|
}
|
||||||
26
configuration/equity_lg.cfg
Normal file
26
configuration/equity_lg.cfg
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"security_type": "EQUITY",
|
||||||
|
"data_directory": "./data/equity",
|
||||||
|
"datafiles": [
|
||||||
|
"20250602.mktdata.ohlcv.db",
|
||||||
|
],
|
||||||
|
"db_table_name": "md_1min_bars",
|
||||||
|
"exchange_id": "ALPACA",
|
||||||
|
"instrument_id_pfx": "STOCK-",
|
||||||
|
"trading_hours": {
|
||||||
|
"begin_session": "9:30:00",
|
||||||
|
"end_session": "16:00:00",
|
||||||
|
"timezone": "America/New_York"
|
||||||
|
},
|
||||||
|
"price_column": "close",
|
||||||
|
"min_required_points": 30,
|
||||||
|
"zero_threshold": 1e-10,
|
||||||
|
"dis-equilibrium_open_trshld": 2.0,
|
||||||
|
"dis-equilibrium_close_trshld": 1.0,
|
||||||
|
"training_minutes": 120,
|
||||||
|
"funding_per_pair": 2000.0,
|
||||||
|
"fit_method_class": "pt_trading.fit_methods.StaticFit",
|
||||||
|
"exclude_instruments": ["CAN"]
|
||||||
|
}
|
||||||
|
# "fit_method_class": "pt_trading.fit_methods.SlidingFit",
|
||||||
|
# "fit_method_class": "pt_trading.fit_methods.StaticFit",
|
||||||
115
lg_notes.md
Normal file
115
lg_notes.md
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
07.11.2025
|
||||||
|
pairs_trading/configuration <---- directory for config
|
||||||
|
equity_lg.cfg <-------- copy of equity.cfg
|
||||||
|
How to run a Program: TRIANGLEsquare ----> triangle EQUITY backtest
|
||||||
|
Results are in > results (timestamp table for all runs)
|
||||||
|
table "...timestamp... .pt_backtest_results.equity.db"
|
||||||
|
going to table using sqlite
|
||||||
|
> sqlite3 '/home/coder/results/20250721_175750.pt_backtest_results.equity.db'
|
||||||
|
|
||||||
|
sqlite> .databases
|
||||||
|
main: /home/coder/results/20250717_180122.pt_backtest_results.equity.db r/w
|
||||||
|
sqlite> .tables
|
||||||
|
config outstanding_positions pt_bt_results
|
||||||
|
|
||||||
|
sqlite> PRAGMA table_info('pt_bt_results');
|
||||||
|
0|date|DATE|0||0
|
||||||
|
1|pair|TEXT|0||0
|
||||||
|
2|symbol|TEXT|0||0
|
||||||
|
3|open_time|DATETIME|0||0
|
||||||
|
4|open_side|TEXT|0||0
|
||||||
|
5|open_price|REAL|0||0
|
||||||
|
6|open_quantity|INTEGER|0||0
|
||||||
|
7|open_disequilibrium|REAL|0||0
|
||||||
|
8|close_time|DATETIME|0||0
|
||||||
|
9|close_side|TEXT|0||0
|
||||||
|
10|close_price|REAL|0||0
|
||||||
|
11|close_quantity|INTEGER|0||0
|
||||||
|
12|close_disequilibrium|REAL|0||0
|
||||||
|
13|symbol_return|REAL|0||0
|
||||||
|
14|pair_return|REAL|0||0
|
||||||
|
|
||||||
|
select count(*) as cnt from pt_bt_results;
|
||||||
|
8
|
||||||
|
|
||||||
|
select * from pt_bt_results;
|
||||||
|
|
||||||
|
select
|
||||||
|
date, close_time, pair, symbol, symbol_return, pair_return
|
||||||
|
from pt_bt_results ;
|
||||||
|
|
||||||
|
select date, sum(symbol_return) as daily_return
|
||||||
|
from pt_bt_results where date = '2025-06-18' group by date;
|
||||||
|
|
||||||
|
.quit
|
||||||
|
|
||||||
|
sqlite3 '/home/coder/results/20250717_172435.pt_backtest_results.equity.db'
|
||||||
|
|
||||||
|
sqlite> select date, sum(symbol_return) as daily_return
|
||||||
|
from pt_bt_results group by date;
|
||||||
|
|
||||||
|
2025-06-02|1.29845390060828
|
||||||
|
...
|
||||||
|
2025-06-18|-43.5084977104115 <========== ????? ==========>
|
||||||
|
2025-06-20|11.8605547517183
|
||||||
|
|
||||||
|
|
||||||
|
select
|
||||||
|
date, close_time, pair, symbol, symbol_return, pair_return
|
||||||
|
from pt_bt_results ;
|
||||||
|
|
||||||
|
select date, close_time, pair, symbol, symbol_return, pair_return
|
||||||
|
from pt_bt_results where date = '2025-06-18';
|
||||||
|
|
||||||
|
|
||||||
|
./scripts/load_equity_pair_intraday.sh -A NVDA -B QQQ -d 20250701 -T ./intraday_md
|
||||||
|
|
||||||
|
to inspect exactly what sources, formats, and processing steps you can open the script with:
|
||||||
|
head -n 50 ./scripts/load_equity_pair_intraday.sh
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
✓ Data file found: /home/coder/pairs_trading/data/crypto/20250605.mktdata.ohlcv.db
|
||||||
|
|
||||||
|
sqlite3 '/home/coder/results/20250722_201930.pt_backtest_results.crypto.db'
|
||||||
|
|
||||||
|
sqlite3 '/home/coder/results/xxxxxxxx_yyyyyy.pt_backtest_results.pseudo.db'
|
||||||
|
|
||||||
|
11111111
|
||||||
|
=== At your terminal, run these commands:
|
||||||
|
sqlite3 '/home/coder/results/20250722_201930.pt_backtest_results.crypto.db'
|
||||||
|
=== Then inside the SQLite prompt:
|
||||||
|
.mode csv
|
||||||
|
.headers on
|
||||||
|
.output results_20250722.csv
|
||||||
|
SELECT * FROM pt_bt_results;
|
||||||
|
.output stdout
|
||||||
|
.quit
|
||||||
|
|
||||||
|
cd /home/coder/
|
||||||
|
|
||||||
|
# === mode csv formats output as CSV
|
||||||
|
# === headers on includes column names
|
||||||
|
# === output my_table.csv directs output to that file
|
||||||
|
# === Run your SELECT query, then revert output
|
||||||
|
# === Open my_table.csv in Excel directly
|
||||||
|
|
||||||
|
# ======== Using scp (Secure Copy)
|
||||||
|
# === On your local machine, open a terminal and run:
|
||||||
|
scp cvtt@953f6e8df266:/home/coder/results_20250722.csv ~/Downloads/
|
||||||
|
|
||||||
|
|
||||||
|
# ===== convert cvs pandas dataframe ====== -->
|
||||||
|
import pandas as pd
|
||||||
|
# Replace with the actual path to your CSV file
|
||||||
|
file_path = '/home/coder/results_20250722.csv'
|
||||||
|
# Read the CSV file into a DataFrame
|
||||||
|
df = pd.read_csv(file_path)
|
||||||
|
# Show the first few rows
|
||||||
|
print(df.head())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -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"]
|
||||||
|
|
||||||
|
|||||||
@ -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(
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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_
|
||||||
|
|
||||||
|
|
||||||
@ -117,6 +188,6 @@ class VECMRollingFit(RollingFit):
|
|||||||
return VECMTradingPair(
|
return VECMTradingPair(
|
||||||
config=config,
|
config=config,
|
||||||
market_data=market_data,
|
market_data=market_data,
|
||||||
symbol_a=symbol_a,
|
symbol_a = symbol_a,
|
||||||
symbol_b=symbol_b,
|
symbol_b = symbol_b,
|
||||||
)
|
)
|
||||||
@ -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,
|
|
||||||
)
|
)
|
||||||
|
|||||||
212
requirements.txt
212
requirements.txt
@ -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
|
||||||
|
|||||||
@ -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(
|
||||||
@ -36,7 +37,7 @@ def main() -> None:
|
|||||||
"--instruments",
|
"--instruments",
|
||||||
type=str,
|
type=str,
|
||||||
required=False,
|
required=False,
|
||||||
help="Comma-separated list of instrument symbols (e.g., COIN,GBTC). If not provided, auto-detects from database.",
|
help = "Comma-separated list of instrument symbols (e.g., COIN,GBTC). If not provided, auto-detects from database.",
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|||||||
16
research/notebooks/pt_pair_backtest.ipynb
Normal file
16
research/notebooks/pt_pair_backtest.ipynb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"cells": [],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"name": "python",
|
||||||
|
"version": "3.12.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 2
|
||||||
|
}
|
||||||
7398
research/notebooks/pt_sliding.ipynb
Normal file
7398
research/notebooks/pt_sliding.ipynb
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6685
research/notebooks/single_pair_test_lg.ipynb
Normal file
6685
research/notebooks/single_pair_test_lg.ipynb
Normal file
File diff suppressed because one or more lines are too long
@ -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.
|
||||||
@ -69,9 +68,9 @@ def create_pairs(
|
|||||||
|
|
||||||
for datafile in datafiles:
|
for datafile in datafiles:
|
||||||
md_df = load_market_data(
|
md_df = load_market_data(
|
||||||
datafile=datafile,
|
datafile = datafile,
|
||||||
instruments=instruments,
|
instruments = instruments,
|
||||||
db_table_name=config_copy["market_data_loading"][instruments[0]["instrument_type"]]["db_table_name"],
|
db_table_name = config_copy["market_data_loading"][instruments[0]["instrument_type"]]["db_table_name"],
|
||||||
trading_hours=config_copy["trading_hours"],
|
trading_hours=config_copy["trading_hours"],
|
||||||
extra_minutes=extra_minutes,
|
extra_minutes=extra_minutes,
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user