170 lines
6.0 KiB
Python
170 lines
6.0 KiB
Python
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
import warnings
|
|
warnings.filterwarnings('ignore')
|
|
|
|
# Import our utility functions
|
|
from .backshift import backshift
|
|
from .smartMovingStd import smartMovingStd
|
|
from .calculateReturns import calculateReturns
|
|
from .calculateMaxDD import calculateMaxDD
|
|
from .data_loader import load_futures_data
|
|
|
|
def main():
|
|
"""
|
|
Python implementation of the gapFutures_FSTX.m gap trading strategy.
|
|
This strategy trades gaps in futures based on volatility thresholds.
|
|
"""
|
|
print("Gap Futures Trading Strategy (FSTX)")
|
|
print("=" * 40)
|
|
|
|
# Strategy parameters
|
|
entry_zscore = 0.1
|
|
|
|
print(f"Strategy Parameters:")
|
|
print(f"Entry Z-score threshold: {entry_zscore}")
|
|
|
|
try:
|
|
# Try to load real futures data (using any available futures data)
|
|
print("Loading futures data...")
|
|
# Try different futures symbols
|
|
for symbol in ['CL', 'TU', 'VX', 'HG']:
|
|
try:
|
|
data = load_futures_data(symbol)
|
|
cl = data['cl'][:, 0] # Use first contract
|
|
|
|
# For gap analysis, we need OHLC data
|
|
# Try to get from the data loader or create approximations
|
|
if 'op' in data:
|
|
op = data['op'][:, 0]
|
|
else:
|
|
# Approximate opening prices with small gaps
|
|
op = cl * (1 + np.random.normal(0, 0.005, len(cl)))
|
|
|
|
if 'hi' in data:
|
|
hi = data['hi'][:, 0]
|
|
else:
|
|
# Approximate high prices
|
|
hi = np.maximum(op, cl) * (1 + np.abs(np.random.normal(0, 0.01, len(cl))))
|
|
|
|
if 'lo' in data:
|
|
lo = data['lo'][:, 0]
|
|
else:
|
|
# Approximate low prices
|
|
lo = np.minimum(op, cl) * (1 - np.abs(np.random.normal(0, 0.01, len(cl))))
|
|
|
|
print(f"Loaded {symbol} futures data for {len(cl)} days")
|
|
print(f"Price range: {np.min(cl):.2f} to {np.max(cl):.2f}")
|
|
break
|
|
|
|
except (FileNotFoundError, KeyError):
|
|
continue
|
|
else:
|
|
raise FileNotFoundError("No futures data available")
|
|
|
|
except (FileNotFoundError, KeyError) as e:
|
|
print(f"Could not load real data ({e}), using synthetic data for demonstration...")
|
|
|
|
# Synthetic data for demonstration
|
|
np.random.seed(42)
|
|
n_days = 1000
|
|
|
|
# Generate synthetic OHLC data for FSTX
|
|
base_price = 100
|
|
returns = np.random.normal(0, 0.02, n_days)
|
|
|
|
# Create price series
|
|
cl = base_price * np.cumprod(1 + returns)
|
|
|
|
# Create OHLC data with realistic relationships
|
|
gap_factor = np.random.normal(1, 0.01, n_days) # Opening gaps
|
|
op = cl * gap_factor
|
|
|
|
# High and low based on intraday volatility
|
|
intraday_vol = np.abs(np.random.normal(0, 0.01, n_days))
|
|
hi = np.maximum(op, cl) * (1 + intraday_vol)
|
|
lo = np.minimum(op, cl) * (1 - intraday_vol)
|
|
|
|
print(f"Generated synthetic data for {n_days} days")
|
|
print(f"Price range: {np.min(cl):.2f} to {np.max(cl):.2f}")
|
|
|
|
# Calculate 90-day moving standard deviation of close-to-close returns
|
|
c2c_returns = calculateReturns(cl, 1)
|
|
stdret_c2c_90d = backshift(1, smartMovingStd(c2c_returns, 90))
|
|
|
|
# Generate trading signals based on gaps
|
|
# Long signal: opening price is significantly above previous high
|
|
longs = op >= backshift(1, hi) * (1 + entry_zscore * stdret_c2c_90d)
|
|
|
|
# Short signal: opening price is significantly below previous low
|
|
shorts = op <= backshift(1, lo) * (1 - entry_zscore * stdret_c2c_90d)
|
|
|
|
# Initialize positions
|
|
positions = np.zeros_like(cl)
|
|
positions[longs] = 1 # Long position
|
|
positions[shorts] = -1 # Short position
|
|
|
|
# Calculate returns (gap fade strategy - profit from gap closure)
|
|
# Return is from open to close, expecting gaps to fade
|
|
with np.errstate(divide='ignore', invalid='ignore'):
|
|
ret = positions * (op - cl) / op
|
|
|
|
ret = np.nan_to_num(ret, nan=0)
|
|
|
|
# Remove any infinite or NaN values
|
|
ret = ret[np.isfinite(ret)]
|
|
|
|
# Calculate performance metrics
|
|
if len(ret) > 0:
|
|
apr = np.prod(1 + ret) ** (252 / len(ret)) - 1
|
|
sharpe = np.mean(ret) * np.sqrt(252) / np.std(ret) if np.std(ret) > 0 else 0
|
|
|
|
print(f"\nFSTX Performance:")
|
|
print(f"APR: {apr:10.4f}")
|
|
print(f"Sharpe: {sharpe:4.2f}")
|
|
|
|
# Calculate cumulative returns
|
|
cumret = np.cumprod(1 + ret) - 1
|
|
|
|
# Plot results
|
|
plt.figure(figsize=(12, 6))
|
|
plt.plot(cumret)
|
|
plt.title('Gap Futures Strategy (FSTX) - Cumulative Returns')
|
|
plt.xlabel('Days')
|
|
plt.ylabel('Cumulative Return')
|
|
plt.grid(True)
|
|
plt.show()
|
|
|
|
# Calculate maximum drawdown
|
|
maxDD, maxDDD = calculateMaxDD(cumret)
|
|
print(f"Max Drawdown: {maxDD:.6f}")
|
|
print(f"Max Drawdown Duration: {int(maxDDD)} days")
|
|
|
|
# Trading statistics
|
|
num_trades = np.sum(positions != 0)
|
|
num_long_trades = np.sum(positions > 0)
|
|
num_short_trades = np.sum(positions < 0)
|
|
|
|
print(f"\nTrading Statistics:")
|
|
print(f"Total trades: {num_trades}")
|
|
print(f"Long trades: {num_long_trades}")
|
|
print(f"Short trades: {num_short_trades}")
|
|
|
|
if num_trades > 0:
|
|
win_rate = np.sum(ret > 0) / len(ret[ret != 0]) if len(ret[ret != 0]) > 0 else 0
|
|
print(f"Win rate: {win_rate:.2%}")
|
|
|
|
return {
|
|
'returns': ret,
|
|
'cumulative_returns': cumret,
|
|
'apr': apr,
|
|
'sharpe': sharpe,
|
|
'max_drawdown': maxDD,
|
|
'num_trades': num_trades
|
|
}
|
|
else:
|
|
print("No valid returns calculated")
|
|
return None
|
|
|
|
if __name__ == "__main__":
|
|
main() |