fixes
This commit is contained in:
parent
9bb36dddd7
commit
aac8b9dc50
@ -27,7 +27,7 @@
|
|||||||
# "close_outstanding_positions": false,
|
# "close_outstanding_positions": false,
|
||||||
"trading_hours": {
|
"trading_hours": {
|
||||||
"begin_session": "9:30:00",
|
"begin_session": "9:30:00",
|
||||||
"end_session": "19:00:00",
|
"end_session": "22:30:00",
|
||||||
"timezone": "America/New_York"
|
"timezone": "America/New_York"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -570,9 +570,12 @@ class BacktestResult:
|
|||||||
symbol_trades = [trade for trade in trades if trade["symbol"] == symbol]
|
symbol_trades = [trade for trade in trades if trade["symbol"] == symbol]
|
||||||
|
|
||||||
# Calculate returns for all trade combinations
|
# Calculate returns for all trade combinations
|
||||||
for i in range(len(symbol_trades) - 1):
|
for idx in range(0, len(symbol_trades), 2):
|
||||||
trade1 = trades[i]
|
trade1 = trades[idx]
|
||||||
trade2 = trades[i + 1]
|
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
|
# Calculate return based on action combination
|
||||||
trade_return = 0
|
trade_return = 0
|
||||||
@ -589,6 +592,8 @@ class BacktestResult:
|
|||||||
pair_trades.append(
|
pair_trades.append(
|
||||||
(
|
(
|
||||||
symbol,
|
symbol,
|
||||||
|
trade1["timestamp"],
|
||||||
|
trade2["timestamp"],
|
||||||
trade1["side"],
|
trade1["side"],
|
||||||
trade1["price"],
|
trade1["price"],
|
||||||
trade2["side"],
|
trade2["side"],
|
||||||
@ -596,7 +601,7 @@ class BacktestResult:
|
|||||||
trade_return,
|
trade_return,
|
||||||
trade1["scaled_disequilibrium"],
|
trade1["scaled_disequilibrium"],
|
||||||
trade2["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}:")
|
print(f" {pair}:")
|
||||||
for (
|
for (
|
||||||
symbol,
|
symbol,
|
||||||
|
trade1["timestamp"],
|
||||||
|
trade2["timestamp"],
|
||||||
trade1["side"],
|
trade1["side"],
|
||||||
trade1["price"],
|
trade1["price"],
|
||||||
trade2["side"],
|
trade2["side"],
|
||||||
@ -625,7 +632,7 @@ class BacktestResult:
|
|||||||
f" Close Dis-eq: {trade2["scaled_disequilibrium"]:.2f}"
|
f" Close Dis-eq: {trade2["scaled_disequilibrium"]:.2f}"
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f" {symbol} (Trade #{trade_num}):"
|
f" {trade2['timestamp'].time()} {symbol} (Trade #{trade_num}):"
|
||||||
f" {trade1["side"]} @ ${trade1["price"]:.2f},"
|
f" {trade1["side"]} @ ${trade1["price"]:.2f},"
|
||||||
f" {trade2["side"]} @ ${trade2["price"]:.2f},"
|
f" {trade2["side"]} @ ${trade2["price"]:.2f},"
|
||||||
f" Return: {trade_return:.2f}%{disequil_info}"
|
f" Return: {trade_return:.2f}%{disequil_info}"
|
||||||
@ -633,6 +640,7 @@ class BacktestResult:
|
|||||||
print(f" Pair Total Return: {pair_return:.2f}%")
|
print(f" Pair Total Return: {pair_return:.2f}%")
|
||||||
day_return += pair_return
|
day_return += pair_return
|
||||||
|
|
||||||
|
|
||||||
# Print day total return and add to global realized PnL
|
# Print day total return and add to global realized PnL
|
||||||
if day_return != 0:
|
if day_return != 0:
|
||||||
print(f" Day Total Return: {day_return:.2f}%")
|
print(f" Day Total Return: {day_return:.2f}%")
|
||||||
|
|||||||
@ -10,15 +10,17 @@ from statsmodels.tsa.vector_ar.vecm import VECM, VECMResults
|
|||||||
|
|
||||||
NanoPerMin = 1e9
|
NanoPerMin = 1e9
|
||||||
|
|
||||||
|
|
||||||
class RollingFit(PairsTradingFitMethod):
|
class RollingFit(PairsTradingFitMethod):
|
||||||
'''
|
"""
|
||||||
N O T E:
|
N O T E:
|
||||||
=========
|
=========
|
||||||
- This class remains to be abstract
|
- This class remains to be abstract
|
||||||
- The following methods are to be implemented in the subclass:
|
- The following methods are to be implemented in the subclass:
|
||||||
- create_trading_pair()
|
- create_trading_pair()
|
||||||
=========
|
=========
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@ -33,7 +35,8 @@ class RollingFit(PairsTradingFitMethod):
|
|||||||
|
|
||||||
pair.user_data_["state"] = PairState.INITIAL
|
pair.user_data_["state"] = PairState.INITIAL
|
||||||
# Initialize trades DataFrame with proper dtypes to avoid concatenation warnings
|
# Initialize trades DataFrame with proper dtypes to avoid concatenation warnings
|
||||||
pair.user_data_["trades"] = pd.DataFrame(columns=self.TRADES_COLUMNS).astype({
|
pair.user_data_["trades"] = pd.DataFrame(columns=self.TRADES_COLUMNS).astype(
|
||||||
|
{
|
||||||
"time": "datetime64[ns]",
|
"time": "datetime64[ns]",
|
||||||
"symbol": "string",
|
"symbol": "string",
|
||||||
"side": "string",
|
"side": "string",
|
||||||
@ -41,8 +44,9 @@ class RollingFit(PairsTradingFitMethod):
|
|||||||
"price": "float64",
|
"price": "float64",
|
||||||
"disequilibrium": "float64",
|
"disequilibrium": "float64",
|
||||||
"scaled_disequilibrium": "float64",
|
"scaled_disequilibrium": "float64",
|
||||||
"pair": "object"
|
"pair": "object",
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
training_minutes = config["training_minutes"]
|
training_minutes = config["training_minutes"]
|
||||||
curr_predicted_row_idx = 0
|
curr_predicted_row_idx = 0
|
||||||
@ -66,7 +70,9 @@ class RollingFit(PairsTradingFitMethod):
|
|||||||
# ================================ PREDICTION ================================
|
# ================================ PREDICTION ================================
|
||||||
self.pair_predict_result_ = pair.predict()
|
self.pair_predict_result_ = pair.predict()
|
||||||
except Exception as e:
|
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
|
# break
|
||||||
|
|
||||||
@ -93,7 +99,13 @@ class RollingFit(PairsTradingFitMethod):
|
|||||||
pred_row = predicted_df.iloc[curr_predicted_row_idx]
|
pred_row = predicted_df.iloc[curr_predicted_row_idx]
|
||||||
scaled_disequilibrium = pred_row["scaled_disequilibrium"]
|
scaled_disequilibrium = pred_row["scaled_disequilibrium"]
|
||||||
|
|
||||||
if pair.user_data_["state"] in [PairState.INITIAL, PairState.CLOSE, PairState.CLOSE_POSITION]:
|
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 scaled_disequilibrium >= open_threshold:
|
||||||
open_trades = self._get_open_trades(
|
open_trades = self._get_open_trades(
|
||||||
pair, row=pred_row, open_threshold=open_threshold
|
pair, row=pred_row, open_threshold=open_threshold
|
||||||
@ -121,7 +133,9 @@ class RollingFit(PairsTradingFitMethod):
|
|||||||
pair, row=pred_row, close_threshold=close_threshold
|
pair, row=pred_row, close_threshold=close_threshold
|
||||||
)
|
)
|
||||||
if close_trades is not None:
|
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}")
|
print(f"STOP CLOSE TRADES:\n{close_trades}")
|
||||||
pair.add_trades(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"]
|
||||||
@ -129,15 +143,11 @@ class RollingFit(PairsTradingFitMethod):
|
|||||||
|
|
||||||
# Outstanding positions
|
# Outstanding positions
|
||||||
if pair.user_data_["state"] == PairState.OPEN:
|
if pair.user_data_["state"] == PairState.OPEN:
|
||||||
print(
|
print(f"{pair}: *** Position is NOT CLOSED. ***")
|
||||||
f"{pair}: *** Position is NOT CLOSED. ***"
|
|
||||||
)
|
|
||||||
# outstanding positions
|
# outstanding positions
|
||||||
if config["close_outstanding_positions"]:
|
if config["close_outstanding_positions"]:
|
||||||
close_position_trades = self._get_close_trades(
|
close_position_trades = self._get_close_trades(
|
||||||
pair=pair,
|
pair=pair, row=pred_row, close_threshold=close_threshold
|
||||||
row=pred_row,
|
|
||||||
close_threshold=close_threshold
|
|
||||||
)
|
)
|
||||||
if close_position_trades is not None:
|
if close_position_trades is not None:
|
||||||
close_position_trades["status"] = PairState.CLOSE_POSITION.name
|
close_position_trades["status"] = PairState.CLOSE_POSITION.name
|
||||||
@ -223,15 +233,17 @@ class RollingFit(PairsTradingFitMethod):
|
|||||||
columns=self.TRADES_COLUMNS,
|
columns=self.TRADES_COLUMNS,
|
||||||
)
|
)
|
||||||
# Ensure consistent dtypes
|
# Ensure consistent dtypes
|
||||||
return df.astype({
|
return df.astype(
|
||||||
|
{
|
||||||
"time": "datetime64[ns]",
|
"time": "datetime64[ns]",
|
||||||
"action": "string",
|
"action": "string",
|
||||||
"symbol": "string",
|
"symbol": "string",
|
||||||
"price": "float64",
|
"price": "float64",
|
||||||
"disequilibrium": "float64",
|
"disequilibrium": "float64",
|
||||||
"scaled_disequilibrium": "float64",
|
"scaled_disequilibrium": "float64",
|
||||||
"pair": "object"
|
"pair": "object",
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def _get_close_trades(
|
def _get_close_trades(
|
||||||
self, pair: TradingPair, row: pd.Series, close_threshold: float
|
self, pair: TradingPair, row: pd.Series, close_threshold: float
|
||||||
@ -277,16 +289,17 @@ class RollingFit(PairsTradingFitMethod):
|
|||||||
columns=self.TRADES_COLUMNS,
|
columns=self.TRADES_COLUMNS,
|
||||||
)
|
)
|
||||||
# Ensure consistent dtypes
|
# Ensure consistent dtypes
|
||||||
return df.astype({
|
return df.astype(
|
||||||
|
{
|
||||||
"time": "datetime64[ns]",
|
"time": "datetime64[ns]",
|
||||||
"action": "string",
|
"action": "string",
|
||||||
"symbol": "string",
|
"symbol": "string",
|
||||||
"price": "float64",
|
"price": "float64",
|
||||||
"disequilibrium": "float64",
|
"disequilibrium": "float64",
|
||||||
"scaled_disequilibrium": "float64",
|
"scaled_disequilibrium": "float64",
|
||||||
"pair": "object"
|
"pair": "object",
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
curr_training_start_idx = 0
|
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