Compare commits

..

No commits in common. "aac8b9dc50248949c5a091e6324e9066122153e7" and "31eb9f800c1904ea94e4a1ab66a466fa7f2c5268" have entirely different histories.

4 changed files with 2202 additions and 4179 deletions

View File

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

View File

@ -570,12 +570,9 @@ class BacktestResult:
symbol_trades = [trade for trade in trades if trade["symbol"] == symbol]
# Calculate returns for all trade combinations
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"
for i in range(len(symbol_trades) - 1):
trade1 = trades[i]
trade2 = trades[i + 1]
# Calculate return based on action combination
trade_return = 0
@ -592,8 +589,6 @@ class BacktestResult:
pair_trades.append(
(
symbol,
trade1["timestamp"],
trade2["timestamp"],
trade1["side"],
trade1["price"],
trade2["side"],
@ -601,7 +596,7 @@ class BacktestResult:
trade_return,
trade1["scaled_disequilibrium"],
trade2["scaled_disequilibrium"],
f"{idx + 1}", # Trade sequence number
i + 1, # Trade sequence number
)
)
@ -612,8 +607,6 @@ class BacktestResult:
print(f" {pair}:")
for (
symbol,
trade1["timestamp"],
trade2["timestamp"],
trade1["side"],
trade1["price"],
trade2["side"],
@ -632,14 +625,13 @@ class BacktestResult:
f" Close Dis-eq: {trade2["scaled_disequilibrium"]:.2f}"
print(
f" {trade2['timestamp'].time()} {symbol} (Trade #{trade_num}):"
f" {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,18 +9,16 @@ 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__()
@ -35,18 +33,16 @@ 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
@ -70,9 +66,7 @@ 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
@ -82,8 +76,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(
@ -99,14 +93,8 @@ 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,
PairState.CLOSE_STOP_LOSS,
PairState.CLOSE_STOP_PROFIT,
]:
if scaled_disequilibrium >= open_threshold:
if pair.user_data_["state"] in [PairState.INITIAL, PairState.CLOSE, PairState.CLOSE_POSITION]:
if scaled_disequilibrium >= open_threshold:
open_trades = self._get_open_trades(
pair, row=pred_row, open_threshold=open_threshold
)
@ -133,21 +121,23 @@ 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
@ -233,17 +223,15 @@ 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
@ -289,17 +277,16 @@ 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