Compare commits
No commits in common. "aac8b9dc50248949c5a091e6324e9066122153e7" and "31eb9f800c1904ea94e4a1ab66a466fa7f2c5268" have entirely different histories.
aac8b9dc50
...
31eb9f800c
@ -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": "22:30:00",
|
"end_session": "19:00:00",
|
||||||
"timezone": "America/New_York"
|
"timezone": "America/New_York"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -570,12 +570,9 @@ 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 idx in range(0, len(symbol_trades), 2):
|
for i in range(len(symbol_trades) - 1):
|
||||||
trade1 = trades[idx]
|
trade1 = trades[i]
|
||||||
trade2 = trades[idx + 1]
|
trade2 = trades[i + 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
|
||||||
@ -592,8 +589,6 @@ 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"],
|
||||||
@ -601,7 +596,7 @@ class BacktestResult:
|
|||||||
trade_return,
|
trade_return,
|
||||||
trade1["scaled_disequilibrium"],
|
trade1["scaled_disequilibrium"],
|
||||||
trade2["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}:")
|
print(f" {pair}:")
|
||||||
for (
|
for (
|
||||||
symbol,
|
symbol,
|
||||||
trade1["timestamp"],
|
|
||||||
trade2["timestamp"],
|
|
||||||
trade1["side"],
|
trade1["side"],
|
||||||
trade1["price"],
|
trade1["price"],
|
||||||
trade2["side"],
|
trade2["side"],
|
||||||
@ -632,14 +625,13 @@ class BacktestResult:
|
|||||||
f" Close Dis-eq: {trade2["scaled_disequilibrium"]:.2f}"
|
f" Close Dis-eq: {trade2["scaled_disequilibrium"]:.2f}"
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f" {trade2['timestamp'].time()} {symbol} (Trade #{trade_num}):"
|
f" {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}"
|
||||||
)
|
)
|
||||||
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:
|
||||||
|
|||||||
@ -9,18 +9,16 @@ from pt_trading.trading_pair import PairState, TradingPair
|
|||||||
from statsmodels.tsa.vector_ar.vecm import VECM, VECMResults
|
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__()
|
||||||
|
|
||||||
@ -35,18 +33,16 @@ 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",
|
"action": "string",
|
||||||
"action": "string",
|
"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
|
||||||
@ -70,9 +66,7 @@ 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(
|
raise RuntimeError(f"{pair}: TrainingPrediction failed: {str(e)}") from e
|
||||||
f"{pair}: TrainingPrediction failed: {str(e)}"
|
|
||||||
) from e
|
|
||||||
|
|
||||||
# break
|
# break
|
||||||
|
|
||||||
@ -82,8 +76,8 @@ class RollingFit(PairsTradingFitMethod):
|
|||||||
curr_predicted_row_idx += 1
|
curr_predicted_row_idx += 1
|
||||||
|
|
||||||
self._create_trading_signals(pair, config, bt_result)
|
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()
|
return pair.get_trades()
|
||||||
|
|
||||||
def _create_trading_signals(
|
def _create_trading_signals(
|
||||||
@ -99,14 +93,8 @@ 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 [
|
if pair.user_data_["state"] in [PairState.INITIAL, PairState.CLOSE, PairState.CLOSE_POSITION]:
|
||||||
PairState.INITIAL,
|
if scaled_disequilibrium >= open_threshold:
|
||||||
PairState.CLOSE,
|
|
||||||
PairState.CLOSE_POSITION,
|
|
||||||
PairState.CLOSE_STOP_LOSS,
|
|
||||||
PairState.CLOSE_STOP_PROFIT,
|
|
||||||
]:
|
|
||||||
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
|
||||||
)
|
)
|
||||||
@ -133,21 +121,23 @@ 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_[
|
close_trades["status"] = pair.user_data_["stop_close_state"].name
|
||||||
"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"]
|
||||||
pair.on_close_trades(close_trades)
|
pair.on_close_trades(close_trades)
|
||||||
|
|
||||||
# Outstanding positions
|
# Outstanding positions
|
||||||
if pair.user_data_["state"] == PairState.OPEN:
|
if pair.user_data_["state"] == PairState.OPEN:
|
||||||
print(f"{pair}: *** Position is NOT CLOSED. ***")
|
print(
|
||||||
|
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, row=pred_row, close_threshold=close_threshold
|
pair=pair,
|
||||||
|
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
|
||||||
@ -233,17 +223,15 @@ 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
|
||||||
@ -289,17 +277,16 @@ 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