progress
This commit is contained in:
parent
5c6841820b
commit
bb4bb90594
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,7 +1,10 @@
|
|||||||
# SpecStory explanation file
|
# SpecStory explanation file
|
||||||
|
__pycache__/
|
||||||
.specstory/
|
.specstory/
|
||||||
.history/
|
.history/
|
||||||
.cursorindexingignore
|
.cursorindexingignore
|
||||||
data
|
data
|
||||||
.vscode/
|
.vscode/
|
||||||
cvttpy
|
cvttpy
|
||||||
|
# SpecStory explanation file
|
||||||
|
.specstory/.what-is-this.md
|
||||||
|
|||||||
@ -1,43 +0,0 @@
|
|||||||
import pandas as pd
|
|
||||||
|
|
||||||
# This example shows how to transform your dataframe to have columns like close-COIN, close-PYPL, etc.
|
|
||||||
|
|
||||||
# Assuming df is your input dataframe with the structure shown in your question
|
|
||||||
def transform_dataframe(df):
|
|
||||||
# Select only the columns we need
|
|
||||||
selected_columns = ["tstamp", "symbol", "close"]
|
|
||||||
df_selected = df[selected_columns]
|
|
||||||
|
|
||||||
# Start with unique timestamps
|
|
||||||
result_df = df_selected["tstamp"].drop_duplicates().reset_index(drop=True)
|
|
||||||
|
|
||||||
# For each unique symbol, add a corresponding close price column
|
|
||||||
for symbol in df_selected["symbol"].unique():
|
|
||||||
# Filter rows for this symbol
|
|
||||||
df_symbol = df_selected[df_selected["symbol"] == symbol].reset_index(drop=True)
|
|
||||||
|
|
||||||
# Create column name like "close-COIN"
|
|
||||||
price_column = f"close-{symbol}"
|
|
||||||
|
|
||||||
# Create temporary dataframe with timestamp and price
|
|
||||||
temp_df = pd.DataFrame({
|
|
||||||
"tstamp": df_symbol["tstamp"],
|
|
||||||
price_column: df_symbol["close"]
|
|
||||||
})
|
|
||||||
|
|
||||||
# Join with our result dataframe
|
|
||||||
result_df = pd.merge(result_df, temp_df, on="tstamp", how="left")
|
|
||||||
|
|
||||||
return result_df
|
|
||||||
|
|
||||||
# Example usage (assuming df is your input dataframe):
|
|
||||||
# result_df = transform_dataframe(df)
|
|
||||||
# print(result_df.head())
|
|
||||||
|
|
||||||
"""
|
|
||||||
The resulting dataframe will look like:
|
|
||||||
tstamp close-COIN close-GBTC close-HOOD close-MSTR close-PYPL
|
|
||||||
0 2025-05-20 14:30:00 262.3650 45.1234 21.567 935.42 72.1611
|
|
||||||
1 2025-05-20 14:31:00 262.5850 45.2100 21.589 935.67 72.1611
|
|
||||||
...
|
|
||||||
"""
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
import pandas as pd
|
|
||||||
|
|
||||||
# Assuming your dataframe is named 'df'
|
|
||||||
def pivot_dataframe(df):
|
|
||||||
# Convert timestamp to datetime if it's not already
|
|
||||||
df['tstamp'] = pd.to_datetime(df['tstamp'])
|
|
||||||
|
|
||||||
# Create a pivot table with 'tstamp' as index and 'symbol' as columns
|
|
||||||
# You can choose any aggregation function (mean, first, last, etc.)
|
|
||||||
# if there are multiple values per timestamp/symbol
|
|
||||||
pivoted_df = pd.pivot_table(
|
|
||||||
df,
|
|
||||||
values=['close', 'open', 'high', 'low', 'volume'],
|
|
||||||
index='tstamp',
|
|
||||||
columns='symbol',
|
|
||||||
aggfunc='first' # Use 'first' if there's only one value per timestamp/symbol
|
|
||||||
)
|
|
||||||
|
|
||||||
# Flatten the multi-level column names
|
|
||||||
pivoted_df.columns = [f"{col[0]}-{col[1]}" for col in pivoted_df.columns]
|
|
||||||
|
|
||||||
# Reset index to make tstamp a column
|
|
||||||
pivoted_df = pivoted_df.reset_index()
|
|
||||||
|
|
||||||
return pivoted_df
|
|
||||||
|
|
||||||
# Example usage:
|
|
||||||
# pivoted_df = pivot_dataframe(your_dataframe)
|
|
||||||
# print(pivoted_df.head())
|
|
||||||
|
|
||||||
# Alternative approach (similar to your code):
|
|
||||||
def pivot_alternative(df):
|
|
||||||
# Create an empty dataframe with just the timestamp
|
|
||||||
result_df = df['tstamp'].drop_duplicates().reset_index(drop=True)
|
|
||||||
|
|
||||||
# For each symbol, create a separate column for each metric
|
|
||||||
for symbol in df['symbol'].unique():
|
|
||||||
# Filter dataframe for this symbol
|
|
||||||
df_symbol = df[df['symbol'] == symbol].reset_index(drop=True)
|
|
||||||
|
|
||||||
# Create columns for each price/volume metric
|
|
||||||
for metric in ['close', 'open', 'high', 'low', 'volume']:
|
|
||||||
column_name = f"{metric}-{symbol}"
|
|
||||||
# Create a temporary dataframe with tstamp and the new column
|
|
||||||
temp_df = pd.DataFrame({
|
|
||||||
'tstamp': df_symbol['tstamp'],
|
|
||||||
column_name: df_symbol[metric]
|
|
||||||
})
|
|
||||||
|
|
||||||
# Merge with the result dataframe
|
|
||||||
result_df = pd.merge(result_df, temp_df, on='tstamp', how='left')
|
|
||||||
|
|
||||||
return result_df
|
|
||||||
|
|
||||||
# Example using the code similar to what's in your main code:
|
|
||||||
"""
|
|
||||||
# Selected columns from original dataframe
|
|
||||||
selected_columns = ["tstamp", "symbol", "close"]
|
|
||||||
df_selected = df[selected_columns]
|
|
||||||
|
|
||||||
# Create result dataframe starting with timestamps
|
|
||||||
result_df = df_selected["tstamp"].drop_duplicates().reset_index(drop=True)
|
|
||||||
|
|
||||||
# For each symbol, add a column with its close price
|
|
||||||
for symbol in df_selected["symbol"].unique():
|
|
||||||
df_symbol = df_selected[df_selected["symbol"] == symbol].reset_index(drop=True)
|
|
||||||
price_column = f"close-{symbol}"
|
|
||||||
|
|
||||||
# Create temp dataframe with tstamp and the price column
|
|
||||||
temp_df = pd.DataFrame({
|
|
||||||
"tstamp": df_symbol["tstamp"],
|
|
||||||
price_column: df_symbol["close"]
|
|
||||||
})
|
|
||||||
|
|
||||||
# Merge with result dataframe
|
|
||||||
result_df = pd.merge(result_df, temp_df, on="tstamp", how="left")
|
|
||||||
"""
|
|
||||||
@ -41,9 +41,11 @@ CONFIG: Dict = {
|
|||||||
"price_column": "close",
|
"price_column": "close",
|
||||||
"min_required_points": 30,
|
"min_required_points": 30,
|
||||||
"zero_threshold": 1e-10,
|
"zero_threshold": 1e-10,
|
||||||
"equilibrium_threshold": 10.0,
|
"equilibrium_threshold_open": 5.0,
|
||||||
# "training_minutes": 120,
|
"equilibrium_threshold_close": 1.0,
|
||||||
"training_minutes": 120,
|
"training_minutes": 120,
|
||||||
|
|
||||||
|
"funding_per_pair": 2000.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
# ====== later ===================
|
# ====== later ===================
|
||||||
@ -66,6 +68,9 @@ CONFIG: Dict = {
|
|||||||
# ------------------------ Settings ------------------------
|
# ------------------------ Settings ------------------------
|
||||||
|
|
||||||
TRADES = {}
|
TRADES = {}
|
||||||
|
TOTAL_UNREALIZED_PNL = 0.0 # Global variable to track total unrealized PnL
|
||||||
|
TOTAL_REALIZED_PNL = 0.0 # Global variable to track total realized PnL
|
||||||
|
OUTSTANDING_POSITIONS = [] # Global list to track outstanding positions with share quantities
|
||||||
class Pair:
|
class Pair:
|
||||||
symbol_a_: str
|
symbol_a_: str
|
||||||
symbol_b_: str
|
symbol_b_: str
|
||||||
@ -76,11 +81,9 @@ class Pair:
|
|||||||
self.symbol_b_ = symbol_b
|
self.symbol_b_ = symbol_b
|
||||||
self.price_column_ = price_column
|
self.price_column_ = price_column
|
||||||
|
|
||||||
def colname_a(self) -> str:
|
|
||||||
return f"{self.price_column_}_{self.symbol_a_}"
|
|
||||||
|
|
||||||
def colname_b(self) -> str:
|
def colnames(self) -> List[str]:
|
||||||
return f"{self.price_column_}_{self.symbol_b_}"
|
return [f"{self.price_column_}_{self.symbol_a_}", f"{self.price_column_}_{self.symbol_b_}"]
|
||||||
|
|
||||||
def __repr__(self) ->str:
|
def __repr__(self) ->str:
|
||||||
return f"{self.symbol_a_} & {self.symbol_b_}"
|
return f"{self.symbol_a_} & {self.symbol_b_}"
|
||||||
@ -148,8 +151,7 @@ def transform_dataframe(df: pd.DataFrame, price_column: str):
|
|||||||
|
|
||||||
def get_datasets(df: pd.DataFrame, training_minutes: int, pair: Pair) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
def get_datasets(df: pd.DataFrame, training_minutes: int, pair: Pair) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
||||||
# Training dataset
|
# Training dataset
|
||||||
colname_a = pair.colname_a()
|
colname_a, colname_b = pair.colnames()
|
||||||
colname_b = pair.colname_b()
|
|
||||||
df = df[["tstamp", colname_a, colname_b]]
|
df = df[["tstamp", colname_a, colname_b]]
|
||||||
df = df.dropna()
|
df = df.dropna()
|
||||||
|
|
||||||
@ -163,7 +165,7 @@ def get_datasets(df: pd.DataFrame, training_minutes: int, pair: Pair) -> Tuple[p
|
|||||||
return (training_df, testing_df)
|
return (training_df, testing_df)
|
||||||
|
|
||||||
def fit_VECM(training_pair_df, pair: Pair):
|
def fit_VECM(training_pair_df, pair: Pair):
|
||||||
vecm_model = VECM(training_pair_df[[pair.colname_a(), pair.colname_b()]].reset_index(drop=True), coint_rank=1)
|
vecm_model = VECM(training_pair_df[pair.colnames()].reset_index(drop=True), coint_rank=1)
|
||||||
vecm_fit = vecm_model.fit()
|
vecm_fit = vecm_model.fit()
|
||||||
|
|
||||||
# Check if the model converged properly
|
# Check if the model converged properly
|
||||||
@ -172,17 +174,18 @@ def fit_VECM(training_pair_df, pair: Pair):
|
|||||||
|
|
||||||
return vecm_fit
|
return vecm_fit
|
||||||
|
|
||||||
def create_trading_signals(vecm_fit, testing_pair_df, pair: Pair, colname_a, colname_b) -> pd.DataFrame:
|
def create_trading_signals(vecm_fit, testing_pair_df, pair: Pair) -> pd.DataFrame:
|
||||||
result_columns = [
|
result_columns = [
|
||||||
"time",
|
"time",
|
||||||
"action",
|
"action",
|
||||||
"symbol",
|
"symbol",
|
||||||
"price",
|
"price",
|
||||||
"divergence",
|
"equilibrium",
|
||||||
"pair",
|
"pair",
|
||||||
]
|
]
|
||||||
|
|
||||||
next_values = vecm_fit.predict(steps=len(testing_pair_df))
|
next_values = vecm_fit.predict(steps=len(testing_pair_df))
|
||||||
|
colname_a, colname_b = pair.colnames()
|
||||||
|
|
||||||
# Convert prediction to a DataFrame for readability
|
# Convert prediction to a DataFrame for readability
|
||||||
predicted_df = pd.DataFrame(next_values, columns=[colname_a, colname_b])
|
predicted_df = pd.DataFrame(next_values, columns=[colname_a, colname_b])
|
||||||
@ -198,52 +201,57 @@ def create_trading_signals(vecm_fit, testing_pair_df, pair: Pair, colname_a, col
|
|||||||
testing_pair_df.reset_index(drop=True), predicted_df, left_index=True, right_index=True, suffixes=('', '_pred')
|
testing_pair_df.reset_index(drop=True), predicted_df, left_index=True, right_index=True, suffixes=('', '_pred')
|
||||||
).dropna()
|
).dropna()
|
||||||
|
|
||||||
pair_result_df["testing_eqlbrm_term"] = (
|
pair_result_df["equilibrium"] = (
|
||||||
beta[0] * pair_result_df[colname_a]
|
beta[0] * pair_result_df[colname_a]
|
||||||
+ beta[1] * pair_result_df[colname_b]
|
+ beta[1] * pair_result_df[colname_b]
|
||||||
)
|
)
|
||||||
|
|
||||||
pair_result_df["abs_testing_eqlbrm_term"] = np.abs(pair_result_df["testing_eqlbrm_term"])
|
pair_result_df["abs_equilibrium"] = np.abs(pair_result_df["equilibrium"])
|
||||||
|
|
||||||
# Check if the first value is non-zero to avoid division by zero
|
# Reset index to ensure proper indexing
|
||||||
pair_result_df = pair_result_df.reset_index()
|
pair_result_df = pair_result_df.reset_index()
|
||||||
initial_abs_term = pair_result_df["abs_testing_eqlbrm_term"][0]
|
|
||||||
if (
|
# Iterate through the testing dataset to find the first trading opportunity
|
||||||
initial_abs_term < CONFIG["zero_threshold"]
|
open_row_index = None
|
||||||
): # Small threshold to avoid division by very small numbers
|
initial_abs_term = None
|
||||||
print(
|
|
||||||
f"{pair}: Skipping pair due to near-zero initial equilibrium: {initial_abs_term}"
|
for row_idx in range(len(pair_result_df)):
|
||||||
)
|
current_abs_term = pair_result_df["abs_equilibrium"][row_idx]
|
||||||
|
|
||||||
|
# Check if current row has sufficient equilibrium (not near-zero)
|
||||||
|
if current_abs_term >= CONFIG["equilibrium_threshold_open"]:
|
||||||
|
open_row_index = row_idx
|
||||||
|
initial_abs_term = current_abs_term
|
||||||
|
break
|
||||||
|
|
||||||
|
# If no row with sufficient equilibrium found, skip this pair
|
||||||
|
if open_row_index is None:
|
||||||
|
print(f"{pair}: Skipping pair - no rows with sufficient equilibrium found in testing dataset")
|
||||||
return pd.DataFrame()
|
return pd.DataFrame()
|
||||||
|
|
||||||
|
# Look for close signal starting from the open position
|
||||||
trading_signals_df = (
|
trading_signals_df = (
|
||||||
pair_result_df["abs_testing_eqlbrm_term"]
|
pair_result_df["abs_equilibrium"][open_row_index:]
|
||||||
< initial_abs_term / CONFIG["equilibrium_threshold"]
|
# < initial_abs_term / CONFIG["equilibrium_threshold_close"]
|
||||||
)
|
< CONFIG["equilibrium_threshold_close"]
|
||||||
close_row_index = next(
|
|
||||||
(index for index, value in trading_signals_df.items() if value), None
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if close_row_index is None:
|
# Adjust indices to account for the offset from open_row_index
|
||||||
print(f"{pair}: NO SIGNAL FOUND")
|
close_row_index = None
|
||||||
return pd.DataFrame()
|
for idx, value in trading_signals_df.items():
|
||||||
|
if value:
|
||||||
open_row = pair_result_df.loc[0]
|
close_row_index = idx
|
||||||
close_row = pair_result_df.loc[close_row_index]
|
break
|
||||||
|
|
||||||
|
open_row = pair_result_df.loc[open_row_index]
|
||||||
open_tstamp = open_row["tstamp"]
|
open_tstamp = open_row["tstamp"]
|
||||||
open_eqlbrm = open_row["testing_eqlbrm_term"]
|
open_eqlbrm = open_row["equilibrium"]
|
||||||
open_px_a = open_row[f"{colname_a}"]
|
open_px_a = open_row[f"{colname_a}"]
|
||||||
open_px_b = open_row[f"{colname_b}"]
|
open_px_b = open_row[f"{colname_b}"]
|
||||||
|
|
||||||
close_tstamp = close_row["tstamp"]
|
|
||||||
close_eqlbrm = close_row["testing_eqlbrm_term"]
|
|
||||||
close_px_a = close_row[f"{colname_a}"]
|
|
||||||
close_px_b = close_row[f"{colname_b}"]
|
|
||||||
|
|
||||||
abs_beta = abs(beta[1])
|
abs_beta = abs(beta[1])
|
||||||
pred_px_b = pair_result_df.loc[0][f"{colname_b}_pred"]
|
pred_px_b = pair_result_df.loc[open_row_index][f"{colname_b}_pred"]
|
||||||
pred_px_a = pair_result_df.loc[0][f"{colname_a}_pred"]
|
pred_px_a = pair_result_df.loc[open_row_index][f"{colname_a}_pred"]
|
||||||
|
|
||||||
if pred_px_b * abs_beta - pred_px_a > 0:
|
if pred_px_b * abs_beta - pred_px_a > 0:
|
||||||
open_side_a = "BUY"
|
open_side_a = "BUY"
|
||||||
@ -256,40 +264,136 @@ def create_trading_signals(vecm_fit, testing_pair_df, pair: Pair, colname_a, col
|
|||||||
close_side_b = "SELL"
|
close_side_b = "SELL"
|
||||||
close_side_a = "BUY"
|
close_side_a = "BUY"
|
||||||
|
|
||||||
trd_signal_tuples = [
|
# If no close signal found, print position and unrealized PnL
|
||||||
(
|
if close_row_index is None:
|
||||||
open_tstamp,
|
global TOTAL_UNREALIZED_PNL, OUTSTANDING_POSITIONS
|
||||||
open_side_a,
|
|
||||||
pair.symbol_a_,
|
last_row_index = len(pair_result_df) - 1
|
||||||
open_px_a,
|
last_row = pair_result_df.loc[last_row_index]
|
||||||
open_eqlbrm,
|
last_tstamp = last_row["tstamp"]
|
||||||
pair,
|
last_px_a = last_row[f"{colname_a}"]
|
||||||
),
|
last_px_b = last_row[f"{colname_b}"]
|
||||||
(
|
|
||||||
open_tstamp,
|
# Calculate share quantities based on $1000 funding per pair
|
||||||
open_side_b,
|
# Split $1000 equally between the two positions ($500 each)
|
||||||
pair.symbol_b_,
|
funding_per_position = CONFIG["funding_per_pair"] / 2
|
||||||
open_px_b,
|
shares_a = funding_per_position / open_px_a
|
||||||
open_eqlbrm,
|
shares_b = funding_per_position / open_px_b
|
||||||
pair,
|
|
||||||
),
|
# Calculate unrealized PnL for each position
|
||||||
(
|
if open_side_a == "BUY":
|
||||||
close_tstamp,
|
unrealized_pnl_a = (last_px_a - open_px_a) / open_px_a * 100
|
||||||
close_side_a,
|
unrealized_dollar_a = shares_a * (last_px_a - open_px_a)
|
||||||
pair.symbol_a_,
|
else: # SELL
|
||||||
close_px_a,
|
unrealized_pnl_a = (open_px_a - last_px_a) / open_px_a * 100
|
||||||
close_eqlbrm,
|
unrealized_dollar_a = shares_a * (open_px_a - last_px_a)
|
||||||
pair,
|
|
||||||
),
|
if open_side_b == "BUY":
|
||||||
(
|
unrealized_pnl_b = (last_px_b - open_px_b) / open_px_b * 100
|
||||||
close_tstamp,
|
unrealized_dollar_b = shares_b * (last_px_b - open_px_b)
|
||||||
close_side_b,
|
else: # SELL
|
||||||
pair.symbol_b_,
|
unrealized_pnl_b = (open_px_b - last_px_b) / open_px_b * 100
|
||||||
close_px_b,
|
unrealized_dollar_b = shares_b * (open_px_b - last_px_b)
|
||||||
close_eqlbrm,
|
|
||||||
pair,
|
total_unrealized_pnl = unrealized_pnl_a + unrealized_pnl_b
|
||||||
),
|
total_unrealized_dollar = unrealized_dollar_a + unrealized_dollar_b
|
||||||
]
|
|
||||||
|
# Add to global total
|
||||||
|
TOTAL_UNREALIZED_PNL += total_unrealized_pnl
|
||||||
|
|
||||||
|
# Store outstanding positions
|
||||||
|
OUTSTANDING_POSITIONS.append({
|
||||||
|
'pair': str(pair),
|
||||||
|
'symbol_a': pair.symbol_a_,
|
||||||
|
'symbol_b': pair.symbol_b_,
|
||||||
|
'side_a': open_side_a,
|
||||||
|
'side_b': open_side_b,
|
||||||
|
'shares_a': shares_a,
|
||||||
|
'shares_b': shares_b,
|
||||||
|
'open_px_a': open_px_a,
|
||||||
|
'open_px_b': open_px_b,
|
||||||
|
'current_px_a': last_px_a,
|
||||||
|
'current_px_b': last_px_b,
|
||||||
|
'unrealized_dollar_a': unrealized_dollar_a,
|
||||||
|
'unrealized_dollar_b': unrealized_dollar_b,
|
||||||
|
'total_unrealized_dollar': total_unrealized_dollar,
|
||||||
|
'open_time': open_tstamp,
|
||||||
|
'last_time': last_tstamp,
|
||||||
|
'initial_abs_term': initial_abs_term,
|
||||||
|
'current_abs_term': pair_result_df.loc[last_row_index, "abs_equilibrium"],
|
||||||
|
'closing_threshold': initial_abs_term / CONFIG["equilibrium_threshold_close"],
|
||||||
|
'equilibrium_ratio': pair_result_df.loc[last_row_index, "abs_equilibrium"] / (initial_abs_term / CONFIG["equilibrium_threshold_close"])
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"{pair}: NO CLOSE SIGNAL FOUND - Position held until end of session")
|
||||||
|
print(f" Open: {open_tstamp} | Last: {last_tstamp}")
|
||||||
|
print(f" {pair.symbol_a_}: {open_side_a} {shares_a:.2f} shares @ ${open_px_a:.2f} -> ${last_px_a:.2f} | Unrealized: ${unrealized_dollar_a:.2f} ({unrealized_pnl_a:.2f}%)")
|
||||||
|
print(f" {pair.symbol_b_}: {open_side_b} {shares_b:.2f} shares @ ${open_px_b:.2f} -> ${last_px_b:.2f} | Unrealized: ${unrealized_dollar_b:.2f} ({unrealized_pnl_b:.2f}%)")
|
||||||
|
print(f" Total Unrealized: ${total_unrealized_dollar:.2f} ({total_unrealized_pnl:.2f}%)")
|
||||||
|
|
||||||
|
# Return only open trades (no close trades)
|
||||||
|
trd_signal_tuples = [
|
||||||
|
(
|
||||||
|
open_tstamp,
|
||||||
|
open_side_a,
|
||||||
|
pair.symbol_a_,
|
||||||
|
open_px_a,
|
||||||
|
open_eqlbrm,
|
||||||
|
pair,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
open_tstamp,
|
||||||
|
open_side_b,
|
||||||
|
pair.symbol_b_,
|
||||||
|
open_px_b,
|
||||||
|
open_eqlbrm,
|
||||||
|
pair,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
# Close signal found - create complete trade
|
||||||
|
close_row = pair_result_df.loc[close_row_index]
|
||||||
|
close_tstamp = close_row["tstamp"]
|
||||||
|
close_eqlbrm = close_row["equilibrium"]
|
||||||
|
close_px_a = close_row[f"{colname_a}"]
|
||||||
|
close_px_b = close_row[f"{colname_b}"]
|
||||||
|
|
||||||
|
print(f"{pair}: Close signal found at index {close_row_index}")
|
||||||
|
|
||||||
|
trd_signal_tuples = [
|
||||||
|
(
|
||||||
|
open_tstamp,
|
||||||
|
open_side_a,
|
||||||
|
pair.symbol_a_,
|
||||||
|
open_px_a,
|
||||||
|
open_eqlbrm,
|
||||||
|
pair,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
open_tstamp,
|
||||||
|
open_side_b,
|
||||||
|
pair.symbol_b_,
|
||||||
|
open_px_b,
|
||||||
|
open_eqlbrm,
|
||||||
|
pair,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
close_tstamp,
|
||||||
|
close_side_a,
|
||||||
|
pair.symbol_a_,
|
||||||
|
close_px_a,
|
||||||
|
close_eqlbrm,
|
||||||
|
pair,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
close_tstamp,
|
||||||
|
close_side_b,
|
||||||
|
pair.symbol_b_,
|
||||||
|
close_px_b,
|
||||||
|
close_eqlbrm,
|
||||||
|
pair,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
# Add tuples to data frame
|
# Add tuples to data frame
|
||||||
return pd.DataFrame(
|
return pd.DataFrame(
|
||||||
@ -297,7 +401,6 @@ def create_trading_signals(vecm_fit, testing_pair_df, pair: Pair, colname_a, col
|
|||||||
columns=result_columns,
|
columns=result_columns,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def run_single_pair(market_data: pd.DataFrame, price_column:str, pair: Pair) -> Optional[pd.DataFrame]:
|
def run_single_pair(market_data: pd.DataFrame, price_column:str, pair: Pair) -> Optional[pd.DataFrame]:
|
||||||
colname_a = f"{price_column}_{pair.symbol_a_}"
|
colname_a = f"{price_column}_{pair.symbol_a_}"
|
||||||
colname_b = f"{price_column}_{pair.symbol_b_}"
|
colname_b = f"{price_column}_{pair.symbol_b_}"
|
||||||
@ -337,8 +440,6 @@ def run_single_pair(market_data: pd.DataFrame, price_column:str, pair: Pair) ->
|
|||||||
vecm_fit=vecm_fit,
|
vecm_fit=vecm_fit,
|
||||||
testing_pair_df=testing_pair_df,
|
testing_pair_df=testing_pair_df,
|
||||||
pair=pair,
|
pair=pair,
|
||||||
colname_a=colname_a,
|
|
||||||
colname_b=colname_b
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"{pair}: Prediction failed: {str(e)}")
|
print(f"{pair}: Prediction failed: {str(e)}")
|
||||||
@ -346,49 +447,6 @@ def run_single_pair(market_data: pd.DataFrame, price_column:str, pair: Pair) ->
|
|||||||
|
|
||||||
return pair_trades
|
return pair_trades
|
||||||
|
|
||||||
|
|
||||||
def run_pairs(summaries_df: pd.DataFrame, price_column: str) -> None:
|
|
||||||
|
|
||||||
result_df = transform_dataframe(df=summaries_df, price_column=price_column)
|
|
||||||
|
|
||||||
stock_price_columns = [
|
|
||||||
column
|
|
||||||
for column in result_df.columns
|
|
||||||
if column.startswith(f"{price_column}_")
|
|
||||||
]
|
|
||||||
|
|
||||||
# Find the starting indices for A and B
|
|
||||||
all_indexes = range(len(stock_price_columns))
|
|
||||||
unique_index_pairs = [(i, j) for i in all_indexes for j in all_indexes if i < j]
|
|
||||||
|
|
||||||
pairs_trades = []
|
|
||||||
for a_index, b_index in unique_index_pairs:
|
|
||||||
# Get the actual variable names
|
|
||||||
colname_a = stock_price_columns[a_index]
|
|
||||||
colname_b = stock_price_columns[b_index]
|
|
||||||
|
|
||||||
symbol_a = colname_a[len(f"{price_column}-") :]
|
|
||||||
symbol_b = colname_b[len(f"{price_column}-") :].replace(
|
|
||||||
"STOCK-", ""
|
|
||||||
)
|
|
||||||
pair = Pair(symbol_a, symbol_b, price_column)
|
|
||||||
|
|
||||||
single_pair_trades = run_single_pair(market_data=result_df, price_column=price_column, pair=pair)
|
|
||||||
if single_pair_trades is not None:
|
|
||||||
pairs_trades.append(single_pair_trades)
|
|
||||||
# Check if result_list has any data before concatenating
|
|
||||||
if not pairs_trades:
|
|
||||||
print("No trading signals found for any pairs")
|
|
||||||
return None
|
|
||||||
|
|
||||||
result = pd.concat(pairs_trades, ignore_index=True)
|
|
||||||
result["time"] = pd.to_datetime(result["time"])
|
|
||||||
result = result.set_index("time").sort_index()
|
|
||||||
|
|
||||||
collect_single_day_results(result)
|
|
||||||
# print_single_day_results(result)
|
|
||||||
|
|
||||||
|
|
||||||
def add_trade(pair, symbol, action, price):
|
def add_trade(pair, symbol, action, price):
|
||||||
# Ensure we always use clean names without STOCK- prefix
|
# Ensure we always use clean names without STOCK- prefix
|
||||||
pair = str(pair).replace("STOCK-", "")
|
pair = str(pair).replace("STOCK-", "")
|
||||||
@ -431,7 +489,9 @@ def print_results_suummary(all_results):
|
|||||||
)
|
)
|
||||||
print(f"{filename}: {trade_count} trades")
|
print(f"{filename}: {trade_count} trades")
|
||||||
|
|
||||||
|
|
||||||
def calculate_returns(all_results: Dict):
|
def calculate_returns(all_results: Dict):
|
||||||
|
global TOTAL_REALIZED_PNL
|
||||||
print("\n====== Returns By Day and Pair ======")
|
print("\n====== Returns By Day and Pair ======")
|
||||||
|
|
||||||
for filename, data in all_results.items():
|
for filename, data in all_results.items():
|
||||||
@ -488,21 +548,101 @@ def calculate_returns(all_results: Dict):
|
|||||||
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
|
# 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}%")
|
||||||
|
TOTAL_REALIZED_PNL += day_return
|
||||||
|
|
||||||
|
def run_pairs(summaries_df: pd.DataFrame, price_column: str) -> None:
|
||||||
|
|
||||||
|
result_df = transform_dataframe(df=summaries_df, price_column=price_column)
|
||||||
|
|
||||||
|
stock_price_columns = [
|
||||||
|
column
|
||||||
|
for column in result_df.columns
|
||||||
|
if column.startswith(f"{price_column}_")
|
||||||
|
]
|
||||||
|
|
||||||
|
# Find the starting indices for A and B
|
||||||
|
all_indexes = range(len(stock_price_columns))
|
||||||
|
unique_index_pairs = [(i, j) for i in all_indexes for j in all_indexes if i < j]
|
||||||
|
|
||||||
|
pairs_trades = []
|
||||||
|
for a_index, b_index in unique_index_pairs:
|
||||||
|
# Get the actual variable names
|
||||||
|
colname_a = stock_price_columns[a_index]
|
||||||
|
colname_b = stock_price_columns[b_index]
|
||||||
|
|
||||||
|
symbol_a = colname_a[len(f"{price_column}-") :]
|
||||||
|
symbol_b = colname_b[len(f"{price_column}-") :].replace(
|
||||||
|
"STOCK-", ""
|
||||||
|
)
|
||||||
|
pair = Pair(symbol_a, symbol_b, price_column)
|
||||||
|
|
||||||
|
single_pair_trades = run_single_pair(market_data=result_df, price_column=price_column, pair=pair)
|
||||||
|
if len(single_pair_trades) > 0:
|
||||||
|
pairs_trades.append(single_pair_trades)
|
||||||
|
# Check if result_list has any data before concatenating
|
||||||
|
if len(pairs_trades) == 0:
|
||||||
|
print("No trading signals found for any pairs")
|
||||||
|
return None
|
||||||
|
|
||||||
|
result = pd.concat(pairs_trades, ignore_index=True)
|
||||||
|
result["time"] = pd.to_datetime(result["time"])
|
||||||
|
result = result.set_index("time").sort_index()
|
||||||
|
|
||||||
|
collect_single_day_results(result)
|
||||||
|
# print_single_day_results(result)
|
||||||
|
|
||||||
|
def print_outstanding_positions():
|
||||||
|
"""Print all outstanding positions with share quantities and unrealized PnL"""
|
||||||
|
if not OUTSTANDING_POSITIONS:
|
||||||
|
print("\n====== NO OUTSTANDING POSITIONS ======")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n====== OUTSTANDING POSITIONS ======")
|
||||||
|
print(f"{'Pair':<15} {'Symbol':<6} {'Side':<4} {'Shares':<10} {'Open $':<8} {'Current $':<10} {'Unrealized $':<12} {'%':<8} {'Close Eq':<10}")
|
||||||
|
print("-" * 105)
|
||||||
|
|
||||||
|
total_unrealized_dollar = 0.0
|
||||||
|
|
||||||
|
for pos in OUTSTANDING_POSITIONS:
|
||||||
|
# Print position A
|
||||||
|
print(f"{pos['pair']:<15} {pos['symbol_a']:<6} {pos['side_a']:<4} {pos['shares_a']:<10.2f} {pos['open_px_a']:<8.2f} {pos['current_px_a']:<10.2f} {pos['unrealized_dollar_a']:<12.2f} {pos['unrealized_dollar_a']/500*100:<8.2f} {'':<10}")
|
||||||
|
|
||||||
|
# Print position B
|
||||||
|
print(f"{'':<15} {pos['symbol_b']:<6} {pos['side_b']:<4} {pos['shares_b']:<10.2f} {pos['open_px_b']:<8.2f} {pos['current_px_b']:<10.2f} {pos['unrealized_dollar_b']:<12.2f} {pos['unrealized_dollar_b']/500*100:<8.2f} {'':<10}")
|
||||||
|
|
||||||
|
# Print pair totals with equilibrium info
|
||||||
|
equilibrium_status = "CLOSE" if pos['current_abs_term'] < pos['closing_threshold'] else f"{pos['equilibrium_ratio']:.2f}x"
|
||||||
|
print(f"{'':<15} {'PAIR':<6} {'TOT':<4} {'':<10} {'':<8} {'':<10} {pos['total_unrealized_dollar']:<12.2f} {pos['total_unrealized_dollar']/1000*100:<8.2f} {equilibrium_status:<10}")
|
||||||
|
|
||||||
|
# Print equilibrium details
|
||||||
|
print(f"{'':<15} {'EQ':<6} {'INFO':<4} {'':<10} {'':<8} {'':<10} {'Curr:':<6}{pos['current_abs_term']:<6.4f} {'Thresh:':<7}{pos['closing_threshold']:<6.4f} {'':<10}")
|
||||||
|
print("-" * 105)
|
||||||
|
|
||||||
|
total_unrealized_dollar += pos['total_unrealized_dollar']
|
||||||
|
|
||||||
|
print(f"{'TOTAL OUTSTANDING':<80} ${total_unrealized_dollar:<12.2f}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Initialize a dictionary to store all trade results
|
# Initialize a dictionary to store all trade results
|
||||||
all_results = {}
|
all_results = {}
|
||||||
|
|
||||||
|
# Initialize global PnL tracking variables
|
||||||
|
TOTAL_REALIZED_PNL = 0.0
|
||||||
|
TOTAL_UNREALIZED_PNL = 0.0
|
||||||
|
OUTSTANDING_POSITIONS = []
|
||||||
|
|
||||||
# Process each data file
|
# Process each data file
|
||||||
price_column = CONFIG["price_column"]
|
price_column = CONFIG["price_column"]
|
||||||
for datafile in CONFIG["datafiles"]:
|
for datafile in CONFIG["datafiles"]:
|
||||||
print(f"\n====== Processing {datafile} ======")
|
print(f"\n====== Processing {datafile} ======")
|
||||||
|
|
||||||
# Clear the TRADES global dictionary for the new file
|
# Clear the TRADES global dictionary and reset unrealized PnL for the new file
|
||||||
TRADES.clear()
|
TRADES.clear()
|
||||||
|
TOTAL_UNREALIZED_PNL = 0.0
|
||||||
|
TOTAL_REALIZED_PNL = 0.0
|
||||||
|
|
||||||
# Process data for this file
|
# Process data for this file
|
||||||
try:
|
try:
|
||||||
@ -516,8 +656,23 @@ if __name__ == "__main__":
|
|||||||
all_results[filename] = {"trades": TRADES.copy()}
|
all_results[filename] = {"trades": TRADES.copy()}
|
||||||
|
|
||||||
print(f"Successfully processed {filename}")
|
print(f"Successfully processed {filename}")
|
||||||
|
|
||||||
|
# Print total unrealized PnL for this file
|
||||||
|
if TOTAL_UNREALIZED_PNL != 0:
|
||||||
|
print(f"\n====== TOTAL UNREALIZED PnL for {filename}: {TOTAL_UNREALIZED_PNL:.2f}% ======")
|
||||||
|
else:
|
||||||
|
print(f"\n====== No unrealized positions for {filename} ======")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing {datafile}: {str(e)}")
|
print(f"Error processing {datafile}: {str(e)}")
|
||||||
|
|
||||||
# print_results_suummary(all_results)
|
# print_results_suummary(all_results)
|
||||||
calculate_returns(all_results)
|
calculate_returns(all_results)
|
||||||
|
|
||||||
|
# Print grand totals
|
||||||
|
print(f"\n====== GRAND TOTALS ACROSS ALL PAIRS ======")
|
||||||
|
print(f"Total Realized PnL: {TOTAL_REALIZED_PNL:.2f}%")
|
||||||
|
print(f"Total Unrealized PnL: {TOTAL_UNREALIZED_PNL:.2f}%")
|
||||||
|
print(f"Combined Total PnL: {TOTAL_REALIZED_PNL + TOTAL_UNREALIZED_PNL:.2f}%")
|
||||||
|
|
||||||
|
print_outstanding_positions()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user