201 lines
5.3 KiB
Plaintext
201 lines
5.3 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Backtesting with ferro-ta\n",
|
|
"\n",
|
|
"This notebook demonstrates the minimal backtesting harness and the\n",
|
|
"indicator pipeline, together with the configuration defaults API.\n",
|
|
"\n",
|
|
"Install:\n",
|
|
"```bash\n",
|
|
"pip install ferro-ta\n",
|
|
"```"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import numpy as np\n",
|
|
"\n",
|
|
"import ferro_ta.config as config\n",
|
|
"from ferro_ta import BBANDS, EMA, RSI, SMA\n",
|
|
"from ferro_ta.backtest import backtest\n",
|
|
"from ferro_ta.pipeline import Pipeline\n",
|
|
"\n",
|
|
"# Synthetic data\n",
|
|
"np.random.seed(42)\n",
|
|
"n = 300\n",
|
|
"close = np.cumprod(1 + np.random.randn(n) * 0.01) * 100\n",
|
|
"volume = np.random.randint(1000, 10000, n).astype(float)\n",
|
|
"print(f\"Generated {n} bars, final price: {close[-1]:.2f}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## RSI 30/70 Strategy"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"result = backtest(close, strategy=\"rsi_30_70\", timeperiod=14)\n",
|
|
"print(\"Strategy: RSI 30/70\")\n",
|
|
"print(f\"Final equity: {result.final_equity:.4f}\")\n",
|
|
"print(f\"Number of trades: {result.n_trades}\")\n",
|
|
"print(f\"Return: {(result.final_equity - 1.0) * 100:.2f}%\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## SMA Crossover Strategy"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"result2 = backtest(close, strategy=\"sma_crossover\", fast=10, slow=30)\n",
|
|
"print(\"Strategy: SMA Crossover (10/30)\")\n",
|
|
"print(f\"Final equity: {result2.final_equity:.4f}\")\n",
|
|
"print(f\"Number of trades: {result2.n_trades}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Configuration Defaults\n",
|
|
"\n",
|
|
"Set global defaults for indicator parameters to avoid repeating them."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Set global defaults\n",
|
|
"config.set_default(\"timeperiod\", 20) # global default for all indicators\n",
|
|
"config.set_default(\"RSI.timeperiod\", 14) # RSI-specific override\n",
|
|
"\n",
|
|
"print(\"Current defaults:\", config.list_defaults())\n",
|
|
"print(\"RSI defaults:\", config.get_defaults_for(\"RSI\"))\n",
|
|
"print(\"SMA defaults:\", config.get_defaults_for(\"SMA\"))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Context manager for temporary overrides\n",
|
|
"with config.Config(timeperiod=5):\n",
|
|
" temp_default = config.get_default(\"timeperiod\")\n",
|
|
" print(f\"Inside context: timeperiod={temp_default}\")\n",
|
|
"\n",
|
|
"print(f\"After context: timeperiod={config.get_default('timeperiod')}\") # back to 20\n",
|
|
"\n",
|
|
"# Clean up\n",
|
|
"config.reset()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Multi-indicator Pipeline for Feature Engineering"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"pipe = (\n",
|
|
" Pipeline()\n",
|
|
" .add(\"sma_10\", SMA, timeperiod=10)\n",
|
|
" .add(\"sma_30\", SMA, timeperiod=30)\n",
|
|
" .add(\"ema_10\", EMA, timeperiod=10)\n",
|
|
" .add(\"rsi_14\", RSI, timeperiod=14)\n",
|
|
" .add(\n",
|
|
" \"bb\",\n",
|
|
" BBANDS,\n",
|
|
" output_keys=[\"bb_upper\", \"bb_mid\", \"bb_lower\"],\n",
|
|
" timeperiod=20,\n",
|
|
" nbdevup=2.0,\n",
|
|
" nbdevdn=2.0,\n",
|
|
" )\n",
|
|
")\n",
|
|
"\n",
|
|
"features = pipe.run(close)\n",
|
|
"print(\"Feature columns:\", list(features.keys()))\n",
|
|
"\n",
|
|
"# Build a simple feature matrix (last 5 complete rows)\n",
|
|
"valid_start = 30 # warmup\n",
|
|
"feature_matrix = np.column_stack([v[valid_start:] for v in features.values()])\n",
|
|
"print(f\"Feature matrix shape: {feature_matrix.shape}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Simple Manual Backtest Using the Pipeline"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Signal: long when RSI < 40 AND close > SMA_30; flat otherwise\n",
|
|
"rsi_vals = features[\"rsi_14\"]\n",
|
|
"sma30_vals = features[\"sma_30\"]\n",
|
|
"\n",
|
|
"signal = np.where((rsi_vals < 40) & (close > sma30_vals), 1.0, 0.0)\n",
|
|
"position = np.roll(signal, 1) # trade on next bar open\n",
|
|
"position[0] = 0.0\n",
|
|
"\n",
|
|
"returns = np.diff(close) / close[:-1]\n",
|
|
"strategy_returns = returns * position[1:]\n",
|
|
"\n",
|
|
"equity = np.cumprod(1 + strategy_returns)\n",
|
|
"print(f\"Final equity: {equity[-1]:.4f}\")\n",
|
|
"print(f\"Number of signal bars: {int(signal.sum())}\")"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"name": "python",
|
|
"version": "3.11.0"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|