progress: added zscore fit
This commit is contained in:
parent
b87b40a6ed
commit
0e83142d0a
33
configuration/crypto_zscore.cfg
Normal file
33
configuration/crypto_zscore.cfg
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"security_type": "CRYPTO",
|
||||||
|
"data_directory": "./data/crypto",
|
||||||
|
"datafiles": [
|
||||||
|
"2025*.mktdata.ohlcv.db"
|
||||||
|
],
|
||||||
|
"db_table_name": "md_1min_bars",
|
||||||
|
"exchange_id": "BNBSPOT",
|
||||||
|
"instrument_id_pfx": "PAIR-",
|
||||||
|
"funding_per_pair": 2000.0,
|
||||||
|
|
||||||
|
# ====== Trading Parameters ======
|
||||||
|
"price_column": "close",
|
||||||
|
"dis-equilibrium_open_trshld": 2.0,
|
||||||
|
"dis-equilibrium_close_trshld": 0.5,
|
||||||
|
"training_minutes": 120,
|
||||||
|
"fit_method_class": "pt_trading.z-score_rolling_fit.ZScoreRollingFit",
|
||||||
|
|
||||||
|
# ====== Stop Conditions ======
|
||||||
|
"stop_close_conditions": {
|
||||||
|
"profit": 2.0,
|
||||||
|
"loss": -0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
# ====== End of Session Closeout ======
|
||||||
|
"close_outstanding_positions": true,
|
||||||
|
# "close_outstanding_positions": false,
|
||||||
|
"trading_hours": {
|
||||||
|
"begin_session": "9:30:00",
|
||||||
|
"end_session": "21:30:00",
|
||||||
|
"timezone": "America/New_York"
|
||||||
|
}
|
||||||
|
}
|
||||||
35
configuration/equity_zscore.cfg
Normal file
35
configuration/equity_zscore.cfg
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"security_type": "EQUITY",
|
||||||
|
"data_directory": "./data/equity",
|
||||||
|
"datafiles": [
|
||||||
|
"202506*.mktdata.ohlcv.db",
|
||||||
|
],
|
||||||
|
"db_table_name": "md_1min_bars",
|
||||||
|
"exchange_id": "ALPACA",
|
||||||
|
"instrument_id_pfx": "STOCK-",
|
||||||
|
"exclude_instruments": ["CAN"],
|
||||||
|
|
||||||
|
"funding_per_pair": 2000.0,
|
||||||
|
|
||||||
|
# ====== Trading Parameters ======
|
||||||
|
"price_column": "close",
|
||||||
|
"dis-equilibrium_open_trshld": 2.0,
|
||||||
|
"dis-equilibrium_close_trshld": 1.0,
|
||||||
|
"training_minutes": 120,
|
||||||
|
"fit_method_class": "pt_trading.z-score_rolling_fit.ZScoreRollingFit",
|
||||||
|
|
||||||
|
# ====== Stop Conditions ======
|
||||||
|
"stop_close_conditions": {
|
||||||
|
"profit": 2.0,
|
||||||
|
"loss": -0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
# ====== End of Session Closeout ======
|
||||||
|
"close_outstanding_positions": true,
|
||||||
|
# "close_outstanding_positions": false,
|
||||||
|
"trading_hours": {
|
||||||
|
"begin_session": "9:30:00",
|
||||||
|
"end_session": "15:30:00",
|
||||||
|
"timezone": "America/New_York"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,6 +11,14 @@ from statsmodels.tsa.vector_ar.vecm import VECM, VECMResults
|
|||||||
NanoPerMin = 1e9
|
NanoPerMin = 1e9
|
||||||
|
|
||||||
class RollingFit(PairsTradingFitMethod):
|
class RollingFit(PairsTradingFitMethod):
|
||||||
|
'''
|
||||||
|
N O T E:
|
||||||
|
=========
|
||||||
|
- This class remains to be abstract
|
||||||
|
- The following methods are to be implemented in the subclass:
|
||||||
|
- create_trading_pair()
|
||||||
|
=========
|
||||||
|
'''
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
|||||||
@ -17,9 +17,8 @@ class VECMTradingPair(TradingPair):
|
|||||||
self.pair_predict_result_ = None
|
self.pair_predict_result_ = None
|
||||||
|
|
||||||
def _train_pair(self) -> None:
|
def _train_pair(self) -> None:
|
||||||
# print('*' * 80 + '\n' + f"**************** {self} IS COINTEGRATED ****************\n" + '*' * 80)
|
|
||||||
self._fit_VECM()
|
self._fit_VECM()
|
||||||
assert self.training_df_ is not None and 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)
|
# print(diseq_series.shape)
|
||||||
self.training_mu_ = float(diseq_series[0].mean())
|
self.training_mu_ = float(diseq_series[0].mean())
|
||||||
|
|||||||
78
lib/pt_trading/z-score_rolling_fit.py
Normal file
78
lib/pt_trading/z-score_rolling_fit.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
from typing import Any, Dict, Optional, cast
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
from pt_trading.results import BacktestResult
|
||||||
|
from pt_trading.rolling_window_fit import RollingFit
|
||||||
|
from pt_trading.trading_pair import TradingPair
|
||||||
|
import statsmodels.api as sm
|
||||||
|
|
||||||
|
NanoPerMin = 1e9
|
||||||
|
class ZScoreTradingPair(TradingPair):
|
||||||
|
zscore_model_: Optional[sm.regression.linear_model.RegressionResultsWrapper]
|
||||||
|
pair_predict_result_: Optional[pd.DataFrame]
|
||||||
|
zscore_df_: Optional[pd.DataFrame]
|
||||||
|
|
||||||
|
def __init__(self, config: Dict[str, Any], market_data: pd.DataFrame, symbol_a: str, symbol_b: str, price_column: str):
|
||||||
|
super().__init__(config, market_data, symbol_a, symbol_b, price_column)
|
||||||
|
self.zscore_model_ = None
|
||||||
|
self.pair_predict_result_ = None
|
||||||
|
self.zscore_df_ = None
|
||||||
|
|
||||||
|
def _fit_zscore(self) -> None:
|
||||||
|
assert self.training_df_ is not None
|
||||||
|
a = self.training_df_[self.colnames()].iloc[:, 0]
|
||||||
|
b = self.training_df_[self.colnames()].iloc[:, 1]
|
||||||
|
|
||||||
|
a,b = a.align(b, axis=0)
|
||||||
|
|
||||||
|
|
||||||
|
X = sm.add_constant(b)
|
||||||
|
self.zscore_model_ = sm.OLS(a, X).fit()
|
||||||
|
assert self.zscore_model_ is not None
|
||||||
|
hedge_ratio = self.zscore_model_.params.iloc[1]
|
||||||
|
|
||||||
|
# Calculate spread and Z-score
|
||||||
|
spread = a - hedge_ratio * b
|
||||||
|
self.zscore_df_ = (spread - spread.mean()) / spread.std()
|
||||||
|
|
||||||
|
def predict(self) -> pd.DataFrame:
|
||||||
|
self._fit_zscore()
|
||||||
|
assert self.zscore_df_ is not None
|
||||||
|
self.training_df_["dis-equilibrium"] = self.zscore_df_
|
||||||
|
self.training_df_["scaled_dis-equilibrium"] = abs(self.zscore_df_)
|
||||||
|
|
||||||
|
assert self.testing_df_ is not None
|
||||||
|
assert self.zscore_df_ is not None
|
||||||
|
predicted_df = self.testing_df_
|
||||||
|
|
||||||
|
predicted_df["disequilibrium"] = self.zscore_df_
|
||||||
|
predicted_df["scaled_disequilibrium"] = abs(self.zscore_df_)
|
||||||
|
|
||||||
|
predicted_df = predicted_df.reset_index(drop=True)
|
||||||
|
if self.pair_predict_result_ is None:
|
||||||
|
self.pair_predict_result_ = predicted_df
|
||||||
|
else:
|
||||||
|
self.pair_predict_result_ = pd.concat([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_
|
||||||
|
|
||||||
|
|
||||||
|
class ZScoreRollingFit(RollingFit):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def run_pair(
|
||||||
|
self, pair: TradingPair, bt_result: BacktestResult
|
||||||
|
) -> Optional[pd.DataFrame]:
|
||||||
|
return super().run_pair(pair, bt_result)
|
||||||
|
def create_trading_pair(
|
||||||
|
self, config: Dict, market_data: pd.DataFrame, symbol_a: str, symbol_b: str, price_column: str
|
||||||
|
) -> TradingPair:
|
||||||
|
return ZScoreTradingPair(
|
||||||
|
config=config,
|
||||||
|
market_data=market_data,
|
||||||
|
symbol_a=symbol_a,
|
||||||
|
symbol_b=symbol_b,
|
||||||
|
price_column=price_column
|
||||||
|
)
|
||||||
@ -74,6 +74,7 @@ PyYAML>=6.0
|
|||||||
reportlab>=3.6.8
|
reportlab>=3.6.8
|
||||||
requests>=2.25.1
|
requests>=2.25.1
|
||||||
requests-file>=1.5.1
|
requests-file>=1.5.1
|
||||||
|
scipy<1.13.0
|
||||||
seaborn>=0.13.2
|
seaborn>=0.13.2
|
||||||
SecretStorage>=3.3.1
|
SecretStorage>=3.3.1
|
||||||
setproctitle>=1.2.2
|
setproctitle>=1.2.2
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user