mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 15:37:44 +00:00
412 lines
14 KiB
Plaintext
412 lines
14 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# PREDIX Quickstart Tutorial\n",
|
||
"\n",
|
||
"Willkommen zu PREDIX – deiner Plattform für algorithmisches EUR/USD Trading!\n",
|
||
"\n",
|
||
"In diesem Notebook lernst du:\n",
|
||
"1. **Daten laden** – EUR/USD 1-Minute Daten vorbereiten\n",
|
||
"2. **Faktoren generieren** – Einfache Trading-Faktoren berechnen\n",
|
||
"3. **Strategie kombinieren** – Mehrere Faktoren zu einer Strategie verbinden\n",
|
||
"4. **Backtest durchführen** – Historische Performance testen\n",
|
||
"5. **Ergebnisse visualisieren** – Equity Curve und Metriken\n",
|
||
"\n",
|
||
"## Voraussetzungen\n",
|
||
"\n",
|
||
"```bash\n",
|
||
"pip install -e \".[all]\"\n",
|
||
"```"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 1. Setup & Daten laden\n",
|
||
"\n",
|
||
"Zuerst importieren wir die benötigten Bibliotheken und laden die EUR/USD Daten."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import pandas as pd\n",
|
||
"import numpy as np\n",
|
||
"import matplotlib.pyplot as plt\n",
|
||
"import warnings\n",
|
||
"warnings.filterwarnings('ignore')\n",
|
||
"\n",
|
||
"# Plotly für interaktive Charts (optional)\n",
|
||
"try:\n",
|
||
" import plotly.graph_objects as go\n",
|
||
" from plotly.subplots import make_subplots\n",
|
||
" HAS_PLOTLY = True\n",
|
||
"except ImportError:\n",
|
||
" HAS_PLOTLY = False\n",
|
||
"\n",
|
||
"print(\"✓ Imports erfolgreich!\")\n",
|
||
"print(f\" Pandas: {pd.__version__}\")\n",
|
||
"print(f\" NumPy: {np.__version__}\")\n",
|
||
"print(f\" Plotly: {'ja' if HAS_PLOTLY else 'nein (pip install plotly)'}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Daten-Simulation\n",
|
||
"\n",
|
||
"Für dieses Tutorial simulieren wir EUR/USD Daten (in Produktion: Echte Daten aus Qlib)."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# Simuliere EUR/USD 1-Minute Daten (1 Jahr)\n",
|
||
"np.random.seed(42)\n",
|
||
"n_bars = 525600 # 525600 Minuten pro Jahr\n",
|
||
"\n",
|
||
"# Datetime-Index (24/7 Trading)\n",
|
||
"dates = pd.date_range('2024-01-01', periods=n_bars, freq='min')\n",
|
||
"\n",
|
||
"# Simulierte Preise (Geometric Brownian Motion)\n",
|
||
"dt = 1/525600\n",
|
||
"mu = 0.00002 # Drift\n",
|
||
"sigma = 0.0003 # Volatilität\n",
|
||
"returns = np.random.normal(mu, sigma, n_bars)\n",
|
||
"prices = 1.0850 * np.exp(np.cumsum(returns)) # Start bei 1.0850\n",
|
||
"\n",
|
||
# OHLCV erstellen\n",
|
||
"df = pd.DataFrame({\n",
|
||
" 'open': prices + np.random.normal(0, 0.0001, n_bars),\n",
|
||
" 'high': prices + np.abs(np.random.normal(0, 0.0002, n_bars)),\n",
|
||
" 'low': prices - np.abs(np.random.normal(0, 0.0002, n_bars)),\n",
|
||
" 'close': prices,\n",
|
||
" 'volume': np.random.exponential(100, n_bars).astype(int)\n",
|
||
"}, index=dates)\n",
|
||
"\n",
|
||
"print(f\"✓ Daten generiert: {len(df)} Bars\")\n",
|
||
"print(f\" Zeitraum: {df.index[0]} bis {df.index[-1]}\")\n",
|
||
"print(f\"\\nErste 5 Zeilen:\")\n",
|
||
"df.head()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 2. Trading-Faktoren berechnen\n",
|
||
"\n",
|
||
"Jetzt berechnen wir verschiedene Trading-Faktoren:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def calculate_momentum(close: pd.Series, window: int) -> pd.Series:\n",
|
||
" \"\"\"Momentum-Faktor: Prozentuale Veränderung über window Bars.\"\"\"\n",
|
||
" return close.pct_change(window)\n",
|
||
"\n",
|
||
"def calculate_rsi(close: pd.Series, period: int = 14) -> pd.Series:\n",
|
||
" \"\"\"RSI (Relative Strength Index).\"\"\"\n",
|
||
" delta = close.diff()\n",
|
||
" gain = delta.where(delta > 0, 0).rolling(period).mean()\n",
|
||
" loss = (-delta.where(delta < 0, 0)).rolling(period).mean()\n",
|
||
" rs = gain / (loss + 1e-8)\n",
|
||
" return 100 - (100 / (1 + rs))\n",
|
||
"\n",
|
||
"def calculate_hl_range(high: pd.Series, low: pd.Series, close: pd.Series) -> pd.Series:\n",
|
||
" \"\"\"High-Low Range als Volatilitäts-Proxy.\"\"\"\n",
|
||
" return (high - low) / close\n",
|
||
"\n",
|
||
"def calculate_session_flag(index: pd.DatetimeIndex, session: str) -> pd.Series:\n",
|
||
" \"\"\"Session-Filter (London, NY, Asian).\"\"\"\n",
|
||
" hour = index.hour\n",
|
||
" if session == 'london':\n",
|
||
" return ((hour >= 8) & (hour < 16)).astype(float)\n",
|
||
" elif session == 'ny':\n",
|
||
" return ((hour >= 13) & (hour < 21)).astype(float)\n",
|
||
" elif session == 'overlap':\n",
|
||
" return ((hour >= 13) & (hour < 16)).astype(float)\n",
|
||
" return pd.Series(1, index=index)\n",
|
||
"\n",
|
||
"# Faktoren berechnen\n",
|
||
"factors = pd.DataFrame(index=df.index)\n",
|
||
"factors['momentum_16'] = calculate_momentum(df['close'], 16)\n",
|
||
"factors['momentum_96'] = calculate_momentum(df['close'], 96)\n",
|
||
"factors['rsi_14'] = calculate_rsi(df['close'], 14)\n",
|
||
"factors['hl_range'] = calculate_hl_range(df['high'], df['low'], df['close'])\n",
|
||
"factors['is_london'] = calculate_session_flag(df.index, 'london')\n",
|
||
"factors['is_ny'] = calculate_session_flag(df.index, 'ny')\n",
|
||
"\n",
|
||
"# NaN entfernen\n",
|
||
"factors = factors.dropna()\n",
|
||
"\n",
|
||
"print(f\"✓ {len(factors.columns)} Faktoren berechnet:\")\n",
|
||
"for col in factors.columns:\n",
|
||
" print(f\" - {col:15s} | Mean: {factors[col].mean():+.4f} | Std: {factors[col].std():.4f}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 3. Strategie kombinieren\n",
|
||
"\n",
|
||
"Wir kombinieren die Faktoren zu einer IC-weighted Strategie:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# Simulierte IC-Werte (Information Coefficient)\n",
|
||
"ic_values = {\n",
|
||
" 'momentum_16': 0.074, # Positiv: Trend-following\n",
|
||
" 'momentum_96': 0.051, # Positiv: Langfristiger Trend\n",
|
||
" 'rsi_14': -0.045, # Negativ: Mean-reversion\n",
|
||
" 'hl_range': -0.032 # Negativ: Volatilitäts-Fade\n",
|
||
"}\n",
|
||
"\n",
|
||
"# Z-Score Normalisierung\n",
|
||
"z_scores = (factors[list(ic_values.keys())] - factors[list(ic_values.keys())].rolling(20).mean()) / (\n",
|
||
" factors[list(ic_values.keys())].rolling(20).std() + 1e-8\n",
|
||
")\n",
|
||
"\n",
|
||
"# IC-Weights (normalisieren)\n",
|
||
"total_abs_ic = sum(abs(ic) for ic in ic_values.values())\n",
|
||
"weights = {k: v / total_abs_ic for k, v in ic_values.items()}\n",
|
||
"\n",
|
||
"# Composite Signal\n",
|
||
"composite = pd.Series(0.0, index=z_scores.index)\n",
|
||
"for factor_name, weight in weights.items():\n",
|
||
" composite += weight * z_scores[factor_name]\n",
|
||
"\n",
|
||
"# Signale generieren (Thresholds)\n",
|
||
"signal = pd.Series(0, index=composite.index)\n",
|
||
"signal[composite > 0.5] = 1 # LONG\n",
|
||
"signal[composite < -0.5] = -1 # SHORT\n",
|
||
"\n",
|
||
"print(f\"✓ Strategie generiert\")\n",
|
||
"print(f\"\\nSignal-Verteilung:\")\n",
|
||
"print(f\" LONG: {(signal == 1).sum():6d} ({(signal == 1).mean()*100:.1f}%)\")\n",
|
||
"print(f\" SHORT: {(signal == -1).sum():6d} ({(signal == -1).mean()*100:.1f}%)\")\n",
|
||
"print(f\" NEUTRAL: {(signal == 0).sum():6d} ({(signal == 0).mean()*100:.1f}%)\")\n",
|
||
"\n",
|
||
"# IC-Weights anzeigen\n",
|
||
"print(f\"\\nIC-Weights:\")\n",
|
||
"for factor_name, weight in weights.items():\n",
|
||
" print(f\" {factor_name:15s}: {weight:+.4f} (IC: {ic_values[factor_name]:+.4f})\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 4. Backtest\n",
|
||
"\n",
|
||
"Simulieren wir einen einfachen Backtest mit Spread-Kosten:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# Backtest-Parameter\n",
|
||
"spread_cost = 0.00015 # 1.5 bps\n",
|
||
"initial_capital = 100000\n",
|
||
"position_size = 0.1 # 10% des Kapitals pro Trade\n",
|
||
"\n",
|
||
"# Nur London/NY Session handeln\n",
|
||
"active_mask = (factors['is_london'] == 1) | (factors['is_ny'] == 1)\n",
|
||
"\n",
|
||
"# Returns berechnen\n",
|
||
"close = df.loc[signal.index, 'close']\n",
|
||
"returns = close.pct_change()\n",
|
||
"\n",
|
||
"# Strategie-Returns\n",
|
||
"strategy_returns = signal.shift(1) * returns # Signal vom Vortag\n",
|
||
"strategy_returns = strategy_returns[active_mask]\n",
|
||
"\n",
|
||
"# Spread-Kosten abziehen\n",
|
||
"trade_costs = (signal.shift(1) != signal).astype(float) * spread_cost\n",
|
||
"strategy_returns = strategy_returns - trade_costs\n",
|
||
"\n",
|
||
"# Kumulierte Returns\n",
|
||
"equity = initial_capital * (1 + strategy_returns).cumprod()\n",
|
||
"benchmark_equity = initial_capital * (1 + returns[active_mask]).cumprod()\n",
|
||
"\n",
|
||
"# Metriken berechnen\n",
|
||
"total_return = (equity.iloc[-1] / initial_capital - 1) * 100\n",
|
||
"years = len(strategy_returns) / 525600\n",
|
||
"arr = ((equity.iloc[-1] / initial_capital) ** (1/max(years, 0.001)) - 1) * 100\n",
|
||
"sharpe = strategy_returns.mean() / (strategy_returns.std() + 1e-8) * np.sqrt(525600)\n",
|
||
"\n",
|
||
"# Max Drawdown\n",
|
||
"rolling_max = equity.cummax()\n",
|
||
"drawdown = (equity - rolling_max) / rolling_max\n",
|
||
"max_dd = drawdown.min() * 100\n",
|
||
"\n",
|
||
"print(f\"=\" * 50)\n",
|
||
"print(f\"BACKTEST ERGEBNISSE\")\n",
|
||
"print(f\"=\" * 50)\n",
|
||
"print(f\" Initial Capital: ${initial_capital:,.0f}\")\n",
|
||
"print(f\" Final Capital: ${equity.iloc[-1]:,.0f}\")\n",
|
||
"print(f\" Total Return: {total_return:+.2f}%\")\n",
|
||
"print(f\" ARR: {arr:+.2f}%\")\n",
|
||
"print(f\" Sharpe Ratio: {sharpe:.2f}\")\n",
|
||
"print(f\" Max Drawdown: {max_dd:.2f}%\")\n",
|
||
"print(f\" Trades: {(signal.shift(1) != signal).sum()}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 5. Visualisierung\n",
|
||
"\n",
|
||
"Jetzt visualisieren wir die Equity Curve und die Drawdowns."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"if HAS_PLOTLY:\n",
|
||
" # Subplots: Equity + Drawdown\n",
|
||
" fig = make_subplots(\n",
|
||
" rows=2, cols=1,\n",
|
||
" shared_xaxes=True,\n",
|
||
" vertical_spacing=0.05,\n",
|
||
" row_heights=[0.7, 0.3],\n",
|
||
" subplot_titles=('Equity Curve', 'Drawdown')\n",
|
||
" )\n",
|
||
" \n",
|
||
" # Equity Curve\n",
|
||
" fig.add_trace(\n",
|
||
" go.Scatter(x=equity.index, y=equity.values, name='Strategy', line=dict(color='#2E86AB', width=2)),\n",
|
||
" row=1, col=1\n",
|
||
" )\n",
|
||
" fig.add_trace(\n",
|
||
" go.Scatter(x=benchmark_equity.index, y=benchmark_equity.values, name='Benchmark', line=dict(color='#A23B72', width=1, dash='dot')),\n",
|
||
" row=1, col=1\n",
|
||
" )\n",
|
||
" \n",
|
||
" # Drawdown\n",
|
||
" fig.add_trace(\n",
|
||
" go.Scatter(x=drawdown.index, y=drawdown.values*100, name='Drawdown',\n",
|
||
" fill='tozeroy', line=dict(color='#F18F01', width=1)),\n",
|
||
" row=2, col=1\n",
|
||
" )\n",
|
||
" \n",
|
||
" fig.update_layout(\n",
|
||
" title='PREDIX Backtest - EUR/USD 1-Minute',\n",
|
||
" template='plotly_dark',\n",
|
||
" height=700,\n",
|
||
" showlegend=True\n",
|
||
" )\n",
|
||
" \n",
|
||
" fig.show()\n",
|
||
"else:\n",
|
||
" # Matplotlib Fallback\n",
|
||
" fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 8), sharex=True, gridspec_kw={'height_ratios': [3, 1]})\n",
|
||
" \n",
|
||
" ax1.plot(equity.index, equity.values, label='Strategy', color='#2E86AB', linewidth=2)\n",
|
||
" ax1.plot(benchmark_equity.index, benchmark_equity.values, label='Benchmark', color='#A23B72', linewidth=1, linestyle='--')\n",
|
||
" ax1.set_title('Equity Curve')\n",
|
||
" ax1.legend()\n",
|
||
" ax1.grid(True, alpha=0.3)\n",
|
||
" \n",
|
||
" ax2.fill_between(drawdown.index, drawdown.values*100, 0, color='#F18F01', alpha=0.5)\n",
|
||
" ax2.set_title('Drawdown')\n",
|
||
" ax2.grid(True, alpha=0.3)\n",
|
||
" \n",
|
||
" plt.tight_layout()\n",
|
||
" plt.savefig('equity_curve.png', dpi=150)\n",
|
||
" plt.show()\n",
|
||
" print(\"✓ Chart gespeichert: equity_curve.png\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 6. Nächste Schritte\n",
|
||
"\n",
|
||
"🎉 Glückwunsch! Du hast deinen ersten PREDIX-Backtest durchgeführt.\n",
|
||
"\n",
|
||
"### Weiterführende Beispiele:\n",
|
||
"\n",
|
||
"| Beispiel | Beschreibung |\n",
|
||
"|----------|-------------|\n",
|
||
"| `01_factor_discovery.py` | Automatische Faktor-Generierung mit LLM |\n",
|
||
"| `02_factor_evolution.py` | Faktor-Optimierung mit Session/Regime Filters |\n",
|
||
"| `05_model_training.py` | ML-Modelle (LSTM/XGBoost) trainieren |\n",
|
||
"| `06_rl_trading_agent.py` | Reinforcement Learning Agent |\n",
|
||
"\n",
|
||
"### CLI Commands:\n",
|
||
"\n",
|
||
"```bash\n",
|
||
"# Alle Commands anzeigen\n",
|
||
"rdagent --help\n",
|
||
"\n",
|
||
"# Faktor-Generierung starten\n",
|
||
"rdagent quant --loop-n 10\n",
|
||
"\n",
|
||
"# Faktoren evaluieren\n",
|
||
"rdagent evaluate\n",
|
||
"\n",
|
||
"# Top-Faktoren anzeigen\n",
|
||
"rdagent top --n 10\n",
|
||
"```\n",
|
||
"\n",
|
||
"### Ressourcen:\n",
|
||
"\n",
|
||
"- 📚 [Dokumentation](../docs/)\n",
|
||
"- 💬 [GitHub Discussions](https://github.com/nico/NexQuant/discussions)\n",
|
||
"- 🐛 [Issues melden](https://github.com/nico/NexQuant/issues)"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": "Python 3 (ipykernel)",
|
||
"language": "python",
|
||
"name": "python3"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": {
|
||
"name": "ipython",
|
||
"version": 3
|
||
},
|
||
"file_extension": ".py",
|
||
"mimetype": "text/x-python",
|
||
"name": "python",
|
||
"nbconvert_exporter": "python",
|
||
"pygments_lexer": "ipython3",
|
||
"version": "3.10.0"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 4
|
||
}
|