Compare commits
2 Commits
31eb9f800c
...
aac8b9dc50
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aac8b9dc50 | ||
|
|
9bb36dddd7 |
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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:
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user