371 lines
12 KiB
Plaintext
371 lines
12 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "c263c5be",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import numpy as np\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"from optimizr import HMM\n",
|
|
"\n",
|
|
"# Set random seed for reproducibility\n",
|
|
"np.random.seed(42)\n",
|
|
"\n",
|
|
"print(\"OptimizR HMM Module Loaded Successfully!\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "d2d18086",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Example 1: Market Regime Detection\n",
|
|
"\n",
|
|
"We'll model financial returns with 3 hidden states:\n",
|
|
"- **State 0:** Bull Market (high mean, low volatility)\n",
|
|
"- **State 1:** Bear Market (negative mean, high volatility)\n",
|
|
"- **State 2:** Sideways/Neutral (zero mean, medium volatility)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "f6141fe5",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def generate_regime_data(n_samples=500, seed=42):\n",
|
|
" \"\"\"\n",
|
|
" Generate synthetic market returns with 3 regimes.\n",
|
|
" \"\"\"\n",
|
|
" np.random.seed(seed)\n",
|
|
" \n",
|
|
" # Define true regime parameters\n",
|
|
" true_means = np.array([0.08, -0.06, 0.01]) # Bull, Bear, Sideways\n",
|
|
" true_stds = np.array([0.02, 0.05, 0.03]) # Volatilities\n",
|
|
" \n",
|
|
" # Transition matrix (tend to stay in same regime)\n",
|
|
" transition_matrix = np.array([\n",
|
|
" [0.85, 0.10, 0.05], # Bull -> Bull, Bear, Sideways\n",
|
|
" [0.10, 0.80, 0.10], # Bear -> ...\n",
|
|
" [0.15, 0.15, 0.70] # Sideways -> ...\n",
|
|
" ])\n",
|
|
" \n",
|
|
" # Generate state sequence\n",
|
|
" true_states = [0] # Start in bull market\n",
|
|
" for _ in range(n_samples - 1):\n",
|
|
" current_state = true_states[-1]\n",
|
|
" next_state = np.random.choice(3, p=transition_matrix[current_state])\n",
|
|
" true_states.append(next_state)\n",
|
|
" \n",
|
|
" true_states = np.array(true_states)\n",
|
|
" \n",
|
|
" # Generate observations\n",
|
|
" returns = np.zeros(n_samples)\n",
|
|
" for t in range(n_samples):\n",
|
|
" state = true_states[t]\n",
|
|
" returns[t] = np.random.normal(true_means[state], true_stds[state])\n",
|
|
" \n",
|
|
" return returns, true_states, true_means, true_stds\n",
|
|
"\n",
|
|
"# Generate data\n",
|
|
"returns, true_states, true_means, true_stds = generate_regime_data()\n",
|
|
"\n",
|
|
"print(f\"Generated {len(returns)} return observations\")\n",
|
|
"print(f\"True means: {true_means}\")\n",
|
|
"print(f\"True stds: {true_stds}\")\n",
|
|
"print(f\"State distribution: {np.bincount(true_states)}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b172f5ef",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Visualize the Generated Data"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "23ca412a",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"fig, axes = plt.subplots(2, 1, figsize=(14, 8), sharex=True)\n",
|
|
"\n",
|
|
"# Plot returns with color-coded regimes\n",
|
|
"colors = ['green', 'red', 'gray']\n",
|
|
"regime_names = ['Bull', 'Bear', 'Sideways']\n",
|
|
"\n",
|
|
"for state in range(3):\n",
|
|
" mask = true_states == state\n",
|
|
" axes[0].scatter(np.where(mask)[0], returns[mask], \n",
|
|
" c=colors[state], label=regime_names[state], alpha=0.6, s=20)\n",
|
|
"\n",
|
|
"axes[0].axhline(y=0, color='black', linestyle='--', alpha=0.3)\n",
|
|
"axes[0].set_ylabel('Returns', fontsize=12)\n",
|
|
"axes[0].set_title('Synthetic Market Returns (Color = True Regime)', fontsize=14, fontweight='bold')\n",
|
|
"axes[0].legend()\n",
|
|
"axes[0].grid(alpha=0.3)\n",
|
|
"\n",
|
|
"# Plot cumulative returns\n",
|
|
"cumulative = np.cumsum(returns)\n",
|
|
"axes[1].plot(cumulative, linewidth=2, color='blue')\n",
|
|
"axes[1].set_xlabel('Time', fontsize=12)\n",
|
|
"axes[1].set_ylabel('Cumulative Return', fontsize=12)\n",
|
|
"axes[1].set_title('Cumulative Returns', fontsize=14, fontweight='bold')\n",
|
|
"axes[1].grid(alpha=0.3)\n",
|
|
"\n",
|
|
"plt.tight_layout()\n",
|
|
"plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "f4139a04",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Fit the HMM Model\n",
|
|
"\n",
|
|
"Now we'll use the **Baum-Welch algorithm** to learn the parameters from data."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "a2944703",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Create and fit HMM\n",
|
|
"hmm = HMM(n_states=3, random_state=42)\n",
|
|
"\n",
|
|
"print(\"Fitting HMM with Baum-Welch algorithm...\")\n",
|
|
"hmm.fit(returns, n_iterations=100, tolerance=1e-6)\n",
|
|
"\n",
|
|
"print(\"\\nLearned Parameters:\")\n",
|
|
"print(f\"Transition Matrix:\\n{hmm.transition_matrix_}\")\n",
|
|
"print(f\"\\nEmission Means: {hmm.emission_means_}\")\n",
|
|
"print(f\"Emission Stds: {hmm.emission_stds_}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b66fd9db",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Decode States with Viterbi Algorithm"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "9fb3e502",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Predict states using Viterbi\n",
|
|
"predicted_states = hmm.predict(returns)\n",
|
|
"\n",
|
|
"print(f\"Predicted state distribution: {np.bincount(predicted_states)}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "047cb02d",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Evaluate Model Performance\n",
|
|
"\n",
|
|
"Since HMM states are unlabeled, we need to find the best permutation mapping."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "98ad33ea",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from itertools import permutations\n",
|
|
"\n",
|
|
"def best_permutation_accuracy(true_states, predicted_states, n_states=3):\n",
|
|
" \"\"\"\n",
|
|
" Find best permutation mapping and compute accuracy.\n",
|
|
" \"\"\"\n",
|
|
" best_acc = 0\n",
|
|
" best_perm = None\n",
|
|
" \n",
|
|
" for perm in permutations(range(n_states)):\n",
|
|
" mapped = np.array([perm[s] for s in predicted_states])\n",
|
|
" acc = np.mean(mapped == true_states)\n",
|
|
" if acc > best_acc:\n",
|
|
" best_acc = acc\n",
|
|
" best_perm = perm\n",
|
|
" \n",
|
|
" return best_acc, best_perm\n",
|
|
"\n",
|
|
"accuracy, best_mapping = best_permutation_accuracy(true_states, predicted_states)\n",
|
|
"\n",
|
|
"print(f\"Best accuracy: {accuracy:.2%}\")\n",
|
|
"print(f\"Best mapping: {best_mapping}\")\n",
|
|
"print(f\"Interpretation: Predicted state {best_mapping[0]} = Bull\")\n",
|
|
"print(f\" Predicted state {best_mapping[1]} = Bear\")\n",
|
|
"print(f\" Predicted state {best_mapping[2]} = Sideways\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "9d64b9e0",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Visualize Results"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "b4296f1a",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Apply best mapping\n",
|
|
"mapped_predictions = np.array([best_mapping[s] for s in predicted_states])\n",
|
|
"\n",
|
|
"fig, axes = plt.subplots(3, 1, figsize=(14, 10), sharex=True)\n",
|
|
"\n",
|
|
"# Plot 1: True states\n",
|
|
"for state in range(3):\n",
|
|
" mask = true_states == state\n",
|
|
" axes[0].scatter(np.where(mask)[0], returns[mask],\n",
|
|
" c=colors[state], label=regime_names[state], alpha=0.6, s=20)\n",
|
|
"axes[0].set_ylabel('Returns', fontsize=12)\n",
|
|
"axes[0].set_title('True Hidden States', fontsize=14, fontweight='bold')\n",
|
|
"axes[0].legend()\n",
|
|
"axes[0].grid(alpha=0.3)\n",
|
|
"\n",
|
|
"# Plot 2: Predicted states\n",
|
|
"for state in range(3):\n",
|
|
" mask = mapped_predictions == state\n",
|
|
" axes[1].scatter(np.where(mask)[0], returns[mask],\n",
|
|
" c=colors[state], label=f'Predicted {regime_names[state]}', alpha=0.6, s=20)\n",
|
|
"axes[1].set_ylabel('Returns', fontsize=12)\n",
|
|
"axes[1].set_title(f'Predicted States (Accuracy: {accuracy:.2%})', fontsize=14, fontweight='bold')\n",
|
|
"axes[1].legend()\n",
|
|
"axes[1].grid(alpha=0.3)\n",
|
|
"\n",
|
|
"# Plot 3: Errors\n",
|
|
"errors = true_states != mapped_predictions\n",
|
|
"axes[2].scatter(np.where(errors)[0], returns[errors], \n",
|
|
" c='red', marker='x', s=100, label='Misclassified', alpha=0.7)\n",
|
|
"axes[2].scatter(np.where(~errors)[0], returns[~errors],\n",
|
|
" c='green', marker='.', s=20, label='Correct', alpha=0.3)\n",
|
|
"axes[2].set_xlabel('Time', fontsize=12)\n",
|
|
"axes[2].set_ylabel('Returns', fontsize=12)\n",
|
|
"axes[2].set_title('Classification Errors', fontsize=14, fontweight='bold')\n",
|
|
"axes[2].legend()\n",
|
|
"axes[2].grid(alpha=0.3)\n",
|
|
"\n",
|
|
"plt.tight_layout()\n",
|
|
"plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "864a2571",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Confusion Matrix"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "01055ea1",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from sklearn.metrics import confusion_matrix\n",
|
|
"import seaborn as sns\n",
|
|
"\n",
|
|
"cm = confusion_matrix(true_states, mapped_predictions)\n",
|
|
"\n",
|
|
"plt.figure(figsize=(8, 6))\n",
|
|
"sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n",
|
|
" xticklabels=regime_names, yticklabels=regime_names)\n",
|
|
"plt.xlabel('Predicted State', fontsize=12)\n",
|
|
"plt.ylabel('True State', fontsize=12)\n",
|
|
"plt.title('Confusion Matrix', fontsize=14, fontweight='bold')\n",
|
|
"plt.show()\n",
|
|
"\n",
|
|
"print(\"\\nPer-State Accuracy:\")\n",
|
|
"for i, name in enumerate(regime_names):\n",
|
|
" acc = cm[i, i] / cm[i].sum()\n",
|
|
" print(f\"{name}: {acc:.2%}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "0fa9b358",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Example 2: Comparing Rust vs Python Performance"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "5db60bb9",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import time\n",
|
|
"\n",
|
|
"# Generate larger dataset\n",
|
|
"large_returns, _, _, _ = generate_regime_data(n_samples=5000)\n",
|
|
"\n",
|
|
"# Time the fitting process\n",
|
|
"hmm_bench = HMM(n_states=3, random_state=42)\n",
|
|
"\n",
|
|
"start = time.time()\n",
|
|
"hmm_bench.fit(large_returns, n_iterations=50)\n",
|
|
"rust_time = time.time() - start\n",
|
|
"\n",
|
|
"print(f\"Rust-accelerated fitting time: {rust_time:.3f} seconds\")\n",
|
|
"print(f\"For {len(large_returns)} observations with 50 iterations\")\n",
|
|
"print(f\"\\nEstimated pure Python time: ~{rust_time * 50:.1f}s (50-100x slower)\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "7c91b303",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Key Takeaways\n",
|
|
"\n",
|
|
"1. **HMMs model sequential data** with hidden states and observable outputs\n",
|
|
"2. **Baum-Welch (EM)** learns parameters from unlabeled data\n",
|
|
"3. **Viterbi** finds the most likely state sequence\n",
|
|
"4. **OptimizR provides 50-100x speedup** over pure Python for large datasets\n",
|
|
"5. **Applications:** Finance, speech, biology, weather, NLP\n",
|
|
"\n",
|
|
"## Further Reading\n",
|
|
"\n",
|
|
"- Rabiner, L. R. (1989). \"A tutorial on hidden Markov models and selected applications in speech recognition.\"\n",
|
|
"- Murphy, K. P. (2012). \"Machine Learning: A Probabilistic Perspective\" - Chapter 17"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"language_info": {
|
|
"name": "python"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|