Compare commits

..

2 Commits

Author SHA1 Message Date
Oleg Sheynin
aac8b9dc50 fixes 2025-07-22 18:04:23 +00:00
Oleg Sheynin
9bb36dddd7 notebook fixes 2025-07-22 17:42:14 +00:00
4 changed files with 4179 additions and 2202 deletions

View File

@ -27,7 +27,7 @@
# "close_outstanding_positions": false,
"trading_hours": {
"begin_session": "9:30:00",
"end_session": "19:00:00",
"end_session": "22:30:00",
"timezone": "America/New_York"
}
}

View File

@ -570,9 +570,12 @@ class BacktestResult:
symbol_trades = [trade for trade in trades if trade["symbol"] == symbol]
# Calculate returns for all trade combinations
for i in range(len(symbol_trades) - 1):
trade1 = trades[i]
trade2 = trades[i + 1]
for idx in range(0, len(symbol_trades), 2):
trade1 = trades[idx]
trade2 = trades[idx + 1]
assert trade1["timestamp"] < trade2["timestamp"], f"Trade 1: {trade1['timestamp']} is not less than Trade 2: {trade2['timestamp']}"
assert trade1["action"] == "OPEN" and trade2["action"] == "CLOSE", f"Trade 1: {trade1['action']} and Trade 2: {trade2['action']} are the same"
# Calculate return based on action combination
trade_return = 0
@ -589,6 +592,8 @@ class BacktestResult:
pair_trades.append(
(
symbol,
trade1["timestamp"],
trade2["timestamp"],
trade1["side"],
trade1["price"],
trade2["side"],
@ -596,7 +601,7 @@ class BacktestResult:
trade_return,
trade1["scaled_disequilibrium"],
trade2["scaled_disequilibrium"],
i + 1, # Trade sequence number
f"{idx + 1}", # Trade sequence number
)
)
@ -607,6 +612,8 @@ class BacktestResult:
print(f" {pair}:")
for (
symbol,
trade1["timestamp"],
trade2["timestamp"],
trade1["side"],
trade1["price"],
trade2["side"],
@ -625,13 +632,14 @@ class BacktestResult:
f" Close Dis-eq: {trade2["scaled_disequilibrium"]:.2f}"
print(
f" {symbol} (Trade #{trade_num}):"
f" {trade2['timestamp'].time()} {symbol} (Trade #{trade_num}):"
f" {trade1["side"]} @ ${trade1["price"]:.2f},"
f" {trade2["side"]} @ ${trade2["price"]:.2f},"
f" Return: {trade_return:.2f}%{disequil_info}"
)
print(f" Pair Total Return: {pair_return:.2f}%")
day_return += pair_return
# Print day total return and add to global realized PnL
if day_return != 0:

View File

@ -9,16 +9,18 @@ from pt_trading.trading_pair import PairState, TradingPair
from statsmodels.tsa.vector_ar.vecm import VECM, VECMResults
NanoPerMin = 1e9
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:
super().__init__()
@ -33,16 +35,18 @@ class RollingFit(PairsTradingFitMethod):
pair.user_data_["state"] = PairState.INITIAL
# Initialize trades DataFrame with proper dtypes to avoid concatenation warnings
pair.user_data_["trades"] = pd.DataFrame(columns=self.TRADES_COLUMNS).astype({
"time": "datetime64[ns]",
"symbol": "string",
"side": "string",
"action": "string",
"price": "float64",
"disequilibrium": "float64",
"scaled_disequilibrium": "float64",
"pair": "object"
})
pair.user_data_["trades"] = pd.DataFrame(columns=self.TRADES_COLUMNS).astype(
{
"time": "datetime64[ns]",
"symbol": "string",
"side": "string",
"action": "string",
"price": "float64",
"disequilibrium": "float64",
"scaled_disequilibrium": "float64",
"pair": "object",
}
)
training_minutes = config["training_minutes"]
curr_predicted_row_idx = 0
@ -66,7 +70,9 @@ class RollingFit(PairsTradingFitMethod):
# ================================ PREDICTION ================================
self.pair_predict_result_ = pair.predict()
except Exception as e:
raise RuntimeError(f"{pair}: TrainingPrediction failed: {str(e)}") from e
raise RuntimeError(
f"{pair}: TrainingPrediction failed: {str(e)}"
) from e
# break
@ -76,8 +82,8 @@ class RollingFit(PairsTradingFitMethod):
curr_predicted_row_idx += 1
self._create_trading_signals(pair, config, bt_result)
print(f"***{pair}*** FINISHED *** Num Trades:{len(pair.user_data_['trades'])}")
print(f"***{pair}*** FINISHED *** Num Trades:{len(pair.user_data_['trades'])}")
return pair.get_trades()
def _create_trading_signals(
@ -93,8 +99,14 @@ class RollingFit(PairsTradingFitMethod):
pred_row = predicted_df.iloc[curr_predicted_row_idx]
scaled_disequilibrium = pred_row["scaled_disequilibrium"]
if pair.user_data_["state"] in [PairState.INITIAL, PairState.CLOSE, PairState.CLOSE_POSITION]:
if scaled_disequilibrium >= open_threshold:
if pair.user_data_["state"] in [
PairState.INITIAL,
PairState.CLOSE,
PairState.CLOSE_POSITION,
PairState.CLOSE_STOP_LOSS,
PairState.CLOSE_STOP_PROFIT,
]:
if scaled_disequilibrium >= open_threshold:
open_trades = self._get_open_trades(
pair, row=pred_row, open_threshold=open_threshold
)
@ -121,23 +133,21 @@ class RollingFit(PairsTradingFitMethod):
pair, row=pred_row, close_threshold=close_threshold
)
if close_trades is not None:
close_trades["status"] = pair.user_data_["stop_close_state"].name
close_trades["status"] = pair.user_data_[
"stop_close_state"
].name
print(f"STOP CLOSE TRADES:\n{close_trades}")
pair.add_trades(close_trades)
pair.user_data_["state"] = pair.user_data_["stop_close_state"]
pair.user_data_["state"] = pair.user_data_["stop_close_state"]
pair.on_close_trades(close_trades)
# Outstanding positions
if pair.user_data_["state"] == PairState.OPEN:
print(
f"{pair}: *** Position is NOT CLOSED. ***"
)
print(f"{pair}: *** Position is NOT CLOSED. ***")
# outstanding positions
if config["close_outstanding_positions"]:
close_position_trades = self._get_close_trades(
pair=pair,
row=pred_row,
close_threshold=close_threshold
pair=pair, row=pred_row, close_threshold=close_threshold
)
if close_position_trades is not None:
close_position_trades["status"] = PairState.CLOSE_POSITION.name
@ -223,15 +233,17 @@ class RollingFit(PairsTradingFitMethod):
columns=self.TRADES_COLUMNS,
)
# Ensure consistent dtypes
return df.astype({
"time": "datetime64[ns]",
"action": "string",
"symbol": "string",
"price": "float64",
"disequilibrium": "float64",
"scaled_disequilibrium": "float64",
"pair": "object"
})
return df.astype(
{
"time": "datetime64[ns]",
"action": "string",
"symbol": "string",
"price": "float64",
"disequilibrium": "float64",
"scaled_disequilibrium": "float64",
"pair": "object",
}
)
def _get_close_trades(
self, pair: TradingPair, row: pd.Series, close_threshold: float
@ -277,16 +289,17 @@ class RollingFit(PairsTradingFitMethod):
columns=self.TRADES_COLUMNS,
)
# Ensure consistent dtypes
return df.astype({
"time": "datetime64[ns]",
"action": "string",
"symbol": "string",
"price": "float64",
"disequilibrium": "float64",
"scaled_disequilibrium": "float64",
"pair": "object"
})
return df.astype(
{
"time": "datetime64[ns]",
"action": "string",
"symbol": "string",
"price": "float64",
"disequilibrium": "float64",
"scaled_disequilibrium": "float64",
"pair": "object",
}
)
def reset(self) -> None:
curr_training_start_idx = 0

File diff suppressed because one or more lines are too long