progress
This commit is contained in:
+16
-80
@@ -8,7 +8,7 @@ import numpy as np
|
||||
# ============= statsmodels ===================
|
||||
from statsmodels.tsa.vector_ar.vecm import VECM
|
||||
|
||||
from tools.data_loader import get_datasets, load_market_data, transform_dataframe
|
||||
from tools.data_loader import load_market_data, transform_dataframe
|
||||
from tools.trading_pair import TradingPair
|
||||
from results import BacktestResult
|
||||
|
||||
@@ -88,8 +88,8 @@ EQT_CONFIG: Dict = {
|
||||
"price_column": "close",
|
||||
"min_required_points": 30,
|
||||
"zero_threshold": 1e-10,
|
||||
"disequilibrium_open_trshld": 5.0,
|
||||
"disequilibrium_close_trshld": 1.0,
|
||||
"disequilibrium_open_trshld": 2.0,
|
||||
"disequilibrium_close_trshld": 0.5,
|
||||
"training_minutes": 120,
|
||||
# ----- Validation
|
||||
"funding_per_pair": 2000.0,
|
||||
@@ -104,23 +104,7 @@ CONFIG = EQT_CONFIG
|
||||
BacktestResults = BacktestResult(config=CONFIG)
|
||||
|
||||
|
||||
def fit_VECM(training_pair_df, pair: TradingPair):
|
||||
vecm_model = VECM(
|
||||
training_pair_df[pair.colnames()].reset_index(drop=True), coint_rank=1
|
||||
)
|
||||
vecm_fit = vecm_model.fit()
|
||||
|
||||
# Check if the model converged properly
|
||||
if not hasattr(vecm_fit, "beta") or vecm_fit.beta is None:
|
||||
print(f"{pair}: VECM model failed to converge properly")
|
||||
|
||||
return vecm_fit
|
||||
|
||||
|
||||
|
||||
def create_trading_signals(
|
||||
vecm_fit, testing_pair_df, pair: TradingPair
|
||||
) -> pd.DataFrame:
|
||||
def create_trading_signals(pair: TradingPair) -> pd.DataFrame:
|
||||
result_columns = [
|
||||
"time",
|
||||
"action",
|
||||
@@ -131,13 +115,14 @@ def create_trading_signals(
|
||||
"pair",
|
||||
]
|
||||
|
||||
next_values = vecm_fit.predict(steps=len(testing_pair_df))
|
||||
testing_pair_df = pair.testing_df_
|
||||
next_values = pair.vecm_fit_.predict(steps=len(testing_pair_df))
|
||||
colname_a, colname_b = pair.colnames()
|
||||
|
||||
# Convert prediction to a DataFrame for readability
|
||||
predicted_df = pd.DataFrame(next_values, columns=[colname_a, colname_b])
|
||||
|
||||
beta = vecm_fit.beta
|
||||
beta = pair.vecm_fit_.beta
|
||||
|
||||
pair_result_df = pd.merge(
|
||||
testing_pair_df.reset_index(drop=True),
|
||||
@@ -149,12 +134,9 @@ def create_trading_signals(
|
||||
|
||||
pair_result_df["disequilibrium"] = pair_result_df[pair.colnames()] @ beta
|
||||
|
||||
pair_mu = pair.disequilibrium_mu_
|
||||
pair_std = pair.disequilibrium_std_
|
||||
|
||||
pair_result_df["scaled_disequilibrium"] = abs(
|
||||
pair_result_df["disequilibrium"] - pair_mu
|
||||
) / pair_std
|
||||
pair_result_df["disequilibrium"] - pair.training_mu_
|
||||
) / pair.training_std_
|
||||
|
||||
|
||||
# Reset index to ensure proper indexing
|
||||
@@ -311,54 +293,19 @@ def create_trading_signals(
|
||||
|
||||
|
||||
def run_single_pair(
|
||||
market_data: pd.DataFrame, price_column: str, pair: TradingPair
|
||||
pair: TradingPair, market_data: pd.DataFrame, price_column: str
|
||||
) -> Optional[pd.DataFrame]:
|
||||
training_pair_df, testing_pair_df = get_datasets(
|
||||
df=market_data, training_minutes=CONFIG["training_minutes"], pair=pair
|
||||
pair.get_datasets(
|
||||
market_data=market_data, training_minutes=CONFIG["training_minutes"]
|
||||
)
|
||||
|
||||
# Check if we have enough data points for a meaningful analysis
|
||||
min_required_points = CONFIG[
|
||||
"min_required_points"
|
||||
] # Minimum number of points for a reasonable VECM model
|
||||
if len(training_pair_df) < min_required_points:
|
||||
print(
|
||||
f"{pair}: Not enough data points for analysis. Found {len(training_pair_df)}, need at least {min_required_points}"
|
||||
)
|
||||
return None
|
||||
|
||||
# Check for non-finite values
|
||||
if not np.isfinite(training_pair_df).all().all():
|
||||
print(f"{pair}: Data contains non-finite values (NaN or inf)")
|
||||
return None
|
||||
|
||||
# Fit the VECM
|
||||
try:
|
||||
vecm_fit = fit_VECM(training_pair_df, pair=pair)
|
||||
pair.train_pair()
|
||||
except Exception as e:
|
||||
print(f"{pair}: VECM fitting failed: {str(e)}")
|
||||
print(f"{pair}: Training failed: {str(e)}")
|
||||
return None
|
||||
|
||||
# Add safeguard against division by zero
|
||||
if (
|
||||
abs(vecm_fit.beta[1]) < CONFIG["zero_threshold"]
|
||||
): # Small threshold to avoid division by very small numbers
|
||||
print(f"{pair}: Skipping due to near-zero beta[1] value: {vecm_fit.beta[1]}")
|
||||
return None
|
||||
diseqlbrm_series = training_pair_df[pair.colnames()] @ vecm_fit.beta
|
||||
diseqlbrm_series_mu: float = diseqlbrm_series.mean().iloc[0]
|
||||
diseqlbrm_series_std: float = diseqlbrm_series.std().iloc[0]
|
||||
pair.set_training_disequilibrium(diseqlbrm_series_mu, diseqlbrm_series_std)
|
||||
|
||||
# Normalize the disequilibrium
|
||||
training_pair_df["scaled_disequilibrium"] = (
|
||||
diseqlbrm_series - diseqlbrm_series_mu
|
||||
) / diseqlbrm_series_std
|
||||
|
||||
try:
|
||||
pair_trades = create_trading_signals(
|
||||
vecm_fit=vecm_fit,
|
||||
testing_pair_df=testing_pair_df,
|
||||
pair=pair,
|
||||
)
|
||||
except Exception as e:
|
||||
@@ -385,18 +332,10 @@ def run_pairs(config: Dict, market_data_df: pd.DataFrame, price_column: str) ->
|
||||
|
||||
pairs_trades = []
|
||||
for pair in _create_pairs(config):
|
||||
# Get the actual variable names
|
||||
# colname_a = stock_price_columns[a_index]
|
||||
# colname_b = stock_price_columns[b_index]
|
||||
|
||||
# symbol_a = colname_a[len(f"{price_column}-") :]
|
||||
# symbol_b = colname_b[len(f"{price_column}-") :]
|
||||
# pair = TradingPair(symbol_a, symbol_b, price_column)
|
||||
|
||||
single_pair_trades = run_single_pair(
|
||||
market_data=market_data_df, price_column=price_column, pair=pair
|
||||
)
|
||||
if len(single_pair_trades) > 0:
|
||||
if single_pair_trades is not None and len(single_pair_trades) > 0:
|
||||
pairs_trades.append(single_pair_trades)
|
||||
# Check if result_list has any data before concatenating
|
||||
if len(pairs_trades) == 0:
|
||||
@@ -441,10 +380,7 @@ if __name__ == "__main__":
|
||||
|
||||
print(f"Successfully processed {filename}")
|
||||
|
||||
# Print total unrealized PnL for this file
|
||||
print(
|
||||
f"\n====== TOTAL UNREALIZED PnL for {filename}: {BacktestResults.get_total_unrealized_pnl():.2f}% ======"
|
||||
)
|
||||
# No longer printing unrealized PnL since we removed that functionality
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error processing {datafile}: {str(e)}")
|
||||
|
||||
Reference in New Issue
Block a user