2025-12-03 18:16:48 +01:00
|
|
|
|
{
|
|
|
|
|
|
"cells": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "code",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"execution_count": 1,
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"id": "c263c5be",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"iopub.execute_input": "2026-02-16T15:56:24.084253Z",
|
|
|
|
|
|
"iopub.status.busy": "2026-02-16T15:56:24.083938Z",
|
|
|
|
|
|
"iopub.status.idle": "2026-02-16T15:56:27.205603Z",
|
|
|
|
|
|
"shell.execute_reply": "2026-02-16T15:56:27.204497Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"✓ OptimizR HMM Module Loaded Successfully!\n",
|
|
|
|
|
|
" Using Rust-accelerated Baum-Welch and Viterbi algorithms\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"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",
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"print(\"✓ OptimizR HMM Module Loaded Successfully!\")\n",
|
|
|
|
|
|
"print(\" Using Rust-accelerated Baum-Welch and Viterbi algorithms\")"
|
2025-12-03 18:16:48 +01:00
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"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",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"execution_count": 2,
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"id": "f6141fe5",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"iopub.execute_input": "2026-02-16T15:56:27.209928Z",
|
|
|
|
|
|
"iopub.status.busy": "2026-02-16T15:56:27.209535Z",
|
|
|
|
|
|
"iopub.status.idle": "2026-02-16T15:56:27.242263Z",
|
|
|
|
|
|
"shell.execute_reply": "2026-02-16T15:56:27.240411Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"Generated 500 return observations\n",
|
|
|
|
|
|
"True means: [ 0.08 -0.06 0.01]\n",
|
|
|
|
|
|
"True stds: [0.02 0.05 0.03]\n",
|
|
|
|
|
|
"State distribution: [199 185 116]\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"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",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"execution_count": 3,
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"id": "23ca412a",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"iopub.execute_input": "2026-02-16T15:56:27.245427Z",
|
|
|
|
|
|
"iopub.status.busy": "2026-02-16T15:56:27.245137Z",
|
|
|
|
|
|
"iopub.status.idle": "2026-02-16T15:56:27.943682Z",
|
|
|
|
|
|
"shell.execute_reply": "2026-02-16T15:56:27.942559Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAABW0AAAMWCAYAAACKoqSLAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsvXl8VNX9//+afUtmQpKZhEBCAsgWlmgUELWC2oI7blWhSm1FqkUR1Lr8qlZba1tr0Wpd+FCX+pVqxUoXFaQYrMqiEBZZRIEEAiEzSUhmMjOZ/f7+mM5lZjIzmeXemXtn3s/HgweTO2fuPffec97nfd7v93kfCcMwDAiCIAiCIAiCIAiCIAiCIAhBIM11BQiCIAiCIAiCIAiCIAiCIIhTkNGWIAiCIAiCIAiCIAiCIAhCQJDRliAIgiAIgiAIgiAIgiAIQkCQ0ZYgCIIgCIIgCIIgCIIgCEJAkNGWIAiCIAiCIAiCIAiCIAhCQJDRliAIgiAIgiAIgiAIgiAIQkCQ0ZYgCIIgCIIgCIIgCIIgCEJAkNGWIAiCIAiCIAiCIAiCIAhCQJDRliAIgiAIgiAIgiAIgiAIQkCQ0ZYgCIIgCJbXXnsNEomE/ScEZs6cydbnhz/8Ya6rk1N++MMfss9i5syZua4OIVDOPvtsSCQSqFQqHD9+PGf1oPZKZBuhjxfvvPMOW78HHngg19UhCIIgBA4ZbQmCIAiCQ9566y3Mnj0bFRUVUCgUMBgMqKurw8yZM7FkyRKsW7cuZ3Ujg2x8wo1LoX9///vfY5a98cYbB5TduHFjdivMM7/4xS/Ye6utrU3597GeZ8iIWFVVhdmzZ+PVV19FIBDgrM7h13nttdc4O6/YeO+997BlyxYAwLx58zBs2LCY5Q4fPowHHngA06dPh9FohEKhgF6vR0NDAxYvXozPPvssm9UWNdGyNZl/YjNi19bWxrwPrVaLkSNH4oYbbkBTU1Ouqyl4rr76aowaNQoA8Mc//hHt7e05rhFBEAQhZOS5rgBBEARB5As333wz3njjjYhjNpsNNpsNra2t+OSTT3DkyBHMnj07RzUUJ7fffjsuu+wyAMDEiROzdt0//vGPuPrqqyOOtbe3Y/Xq1VmrQ77h8Xhw4sQJnDhxAh999BH+85//4M0338x1tfKKRx99lP28ZMmSAd8HAgE89thjeOKJJ+D3+yO+6+vrw65du7Br1y786U9/AsMwvNeXEDf9/f1oaWlBS0sL3n77bbz88su47bbbclafXI0XySKTyfDTn/4Uy5YtQ39/P37zm9/gj3/8Y66rRRAEQQgUMtoSBEEQBAesXbs2wmDb2NiI2bNno6ioCJ2dnWhubsbmzZtzWEPxcv311+fkup988gl2796NyZMns8deeOEF+Hy+rNfFZrNBr9dn/bpc8dRTTyEQCODIkSN444030NfXBwBYtWoV7r///ohnLDaE9G42bdqEr776CgAwduxYNDQ0DCizePFivPjii+zfarUaV111FSZMmACfz4evv/4aa9euhdVqzVa1OaOvrw/FxcVZv+5ZZ52Fp556KuLY22+/jW3btrF/R39fXV0d93x+vx9utxtarZbbinLEyJEjcfvtt8Pj8WD37t3429/+xhr4H3roIdx6662QSnOzoDNX40UqfP/738c999wDhmHwxhtv4Le//S00Gk2uq0UQBEEIEYYgCIIgiIxZunQpA4ABwIwePZrx+XwDylitVuazzz5j/37kkUfY3wwfPpzx+/0R5ffs2cN+D4DZsmULwzAMs2DBAvbY+eefz7S3tzMLFy5kKisrGaVSyYwbN45ZsWIFe56WlpaI88T69+ijjzIMwzCvvvpqxHGPx8P89re/ZcaOHcsolUpm2LBhzD333MO4XK6Yz+Gf//wnc8UVVzCVlZWMQqFgSkpKmFmzZjH/7//9PyYQCLDlHn300UHr1NLSwjAMw5x//vnssQULFgy4ZltbG/Ozn/2MaWhoYIqLixmVSsVUV1czV155JfPRRx8l9f7Cn6lUKmU///jHP2bLuFwuxmg0MgAYmUwWUdempia2XHd3N3PfffcxF1xwATNixAimqKiIUSgUjMlkYi666CLmL3/5S8SzYBiGaWpqijjft99+yzz11FPMuHHjGKVSyVx55ZUx332Ivr4+5pxzzmG/KysrY7Zv385+v3PnTuaWW25hRo4cyajVakan0zENDQ3ME088wdjt9rj1iPXv1VdfTel5RqubL774YsR3f/3rXwf83uVyMc899xxz3nnnMUOGDGEUCgVTWVnJXHvttcymTZsiyoa3j1j/RowYwTDMwH4Q/s6izxPezmL9buXKlczpp5/OqNVqZsqUKQzDRLbpESNGML29vcy9997L1NTUMAqFgqmrq2OeeOKJAe/ebrczjz32GHP66aczRUVFjFwuZ4xGIzNlyhTm1ltvZT788MNBn3eIW2+9la3DQw89NOD7tWvXRtzLmDFjmMOHDw8o53A4mN/85jcDjh84cID5yU9+wowZM4bRaDSMRqNhTjvtNOa2225j9u/fP6B8vPYa4tixY8y9997LTJw4kdHpdIxKpWJGjBjBzJ8/n9m6deuA8tHPuKuri7njjjuYYcOGMVKplFm+fHlyDyoLJOoD0d+ff/75zJEjR5gf/OAHjMlkYiQSCfPee++l3WZDHDp0iLnzzjuZcePGMVqtllGr1cz48eOZ+++/n+ns7EzpfkaMGBH3XV5//fUR9Txx4sSA33d0dDAPPvggM2XKFKaoqIhRqVTMqFGjmDvuuIM5cuRIzGu2trYyN954I1NaWsrodDrmvPPOYzZs2DBgnErmmUQ/yw0bNjDPPPMMM2bMGEatVjP19fXMG2+8wTBMsE8uXbqUqaqqYlQqFdPQ0MC89957MetotVqZX//618zUqVMZvV7PKBQKprq6mlmwYAGzZ8+euM9zxowZbF3+3//7f3HLEQRBEIUNGW0JgiAIggPuvPNOdgJWXl7OHDx4cNDftLe3MwqFgv3d+++/H/F9uFF3woQJ7PHwyf7IkSOZoUOHxjRW/fnPf2YYJjOj7ezZs2OWv+mmmyLq6vf7mZtuuinhNa677jrWmM2V0fb9999niouL455jyZIlSb2/8GdaVlbGnHvuuQwARqPRMF1dXQzDMMwrr7zClrnqqqviGlO++uqrQe/tlltuibh+tLH0vPPOi/g7kdHW6XRGPCOTycTs3r2bPfcLL7zAyOXyuHWZMGECa2TJhtH2n//8Z8R369evj/jeYrEwDQ0Nca8vlUqZZ555hi2fbaNt9LuJZbQtKytjxo8fH7M+Dz/8cMR1Z86cmbD+119//aDPO0RNTQ37u3//+98Dvp8zZ07Eubdt25b0uf/2t78xarU6bj1VKtUAA3wio+0nn3zCDBkyJOF7fvrppyN+E/6My8vLmXHjxkX8JhmjbXTbHOxfLGNoMqRitD3ttNOYysrKiPKZGm3XrFnDaLXauPc1bNgwZt++fUnfTyKj7bJlyyLeW7RTb9OmTUx5eXncuhgMBua///1vxG9aWloGPJPQ+S+99NK4zzbZftzY2BizLi+88AIzderUAcclEgnzn//8J+Ja33zzDVNbW5uwT/ztb3+L+TzvueeejNsYQRAEkf9QegSCIAiC4IAzzjiD/dzV1YUxY8agoaEBZ511FhobGzFr1iyMHj064jdDhw7FNddcg7feegsAsHLlSlxyySXs9++88w77+ZZbbol53cOHD0OtVuP222+HRqPBiy++iP7+fgDA7373O/zoRz9CaWkpnnrqKWzbtg1vv/02+9vw5bozZsyIef5169axS6fffPNNtLa2AgDefPNN/OY3v0FVVRV7rVB6CIlEgmuuuQZTpkxBS0sL3njjDXi9XrzzzjtoaGjAQw89hO9973soKirCiy++iMOHDwMAzjzzzIilraWlpTHrFOLIkSO47rrr4HQ62eteccUVaGhoQGdnJz7++OOEv0/EkiVL8Nlnn6G/vx//93//hwceeIDNO1hcXIxbbrkF7733XszfSqVSjB8/HlOnTkVlZSVKSkrgcrmwY8cO/Otf/wLDMHj
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 1400x800 with 2 Axes>"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"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",
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"execution_count": 4,
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"id": "a2944703",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"iopub.execute_input": "2026-02-16T15:56:27.946979Z",
|
|
|
|
|
|
"iopub.status.busy": "2026-02-16T15:56:27.946665Z",
|
|
|
|
|
|
"iopub.status.idle": "2026-02-16T15:56:27.957082Z",
|
|
|
|
|
|
"shell.execute_reply": "2026-02-16T15:56:27.955283Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"Fitting HMM with Baum-Welch algorithm (Rust implementation)...\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"✓ Training complete!\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"Learned Parameters:\n",
|
|
|
|
|
|
"Transition Matrix:\n",
|
|
|
|
|
|
" State 0: ['0.650', '0.244', '0.107']\n",
|
|
|
|
|
|
" State 1: ['0.288', '0.501', '0.211']\n",
|
|
|
|
|
|
" State 2: ['0.115', '0.155', '0.730']\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"Emission Means: ['-0.0698', '0.0173', '0.0824']\n",
|
|
|
|
|
|
"Emission Stds: ['0.0446', '0.0302', '0.0183']\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"source": [
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"# Fit HMM using Rust-accelerated implementation\n",
|
|
|
|
|
|
"hmm = HMM(n_states=3)\n",
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"\n",
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"print(\"Fitting HMM with Baum-Welch algorithm (Rust implementation)...\")\n",
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"hmm.fit(returns, n_iterations=100, tolerance=1e-6)\n",
|
|
|
|
|
|
"\n",
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"print(\"\\n✓ Training complete!\")\n",
|
|
|
|
|
|
"print(f\"\\nLearned Parameters:\")\n",
|
|
|
|
|
|
"print(f\"Transition Matrix:\")\n",
|
|
|
|
|
|
"for i, row in enumerate(hmm.transition_matrix_):\n",
|
|
|
|
|
|
" print(f\" State {i}: {[f'{p:.3f}' for p in row]}\")\n",
|
|
|
|
|
|
"print(f\"\\nEmission Means: {[f'{m:.4f}' for m in hmm.emission_means_]}\")\n",
|
|
|
|
|
|
"print(f\"Emission Stds: {[f'{s:.4f}' for s in hmm.emission_stds_]}\")"
|
2025-12-03 18:16:48 +01:00
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
|
"id": "b66fd9db",
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"source": [
|
|
|
|
|
|
"## Decode States with Viterbi Algorithm"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "code",
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"execution_count": 5,
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"id": "9fb3e502",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"iopub.execute_input": "2026-02-16T15:56:27.960969Z",
|
|
|
|
|
|
"iopub.status.busy": "2026-02-16T15:56:27.960665Z",
|
|
|
|
|
|
"iopub.status.idle": "2026-02-16T15:56:27.966470Z",
|
|
|
|
|
|
"shell.execute_reply": "2026-02-16T15:56:27.965236Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"✓ Viterbi decoding complete!\n",
|
|
|
|
|
|
"Predicted state distribution: [179 125 196]\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"source": [
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"# Decode most likely state sequence using Viterbi algorithm (Rust)\n",
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"predicted_states = hmm.predict(returns)\n",
|
|
|
|
|
|
"\n",
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"print(f\"✓ Viterbi decoding complete!\")\n",
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"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",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"execution_count": 6,
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"id": "98ad33ea",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"iopub.execute_input": "2026-02-16T15:56:27.969492Z",
|
|
|
|
|
|
"iopub.status.busy": "2026-02-16T15:56:27.969199Z",
|
|
|
|
|
|
"iopub.status.idle": "2026-02-16T15:56:27.978475Z",
|
|
|
|
|
|
"shell.execute_reply": "2026-02-16T15:56:27.977040Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"Best accuracy: 89.40%\n",
|
|
|
|
|
|
"Best mapping: (1, 2, 0)\n",
|
|
|
|
|
|
"Interpretation: Predicted state 1 = Bull\n",
|
|
|
|
|
|
" Predicted state 2 = Bear\n",
|
|
|
|
|
|
" Predicted state 0 = Sideways\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"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",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"execution_count": 7,
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"id": "b4296f1a",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"iopub.execute_input": "2026-02-16T15:56:27.981874Z",
|
|
|
|
|
|
"iopub.status.busy": "2026-02-16T15:56:27.981566Z",
|
|
|
|
|
|
"iopub.status.idle": "2026-02-16T15:56:28.875069Z",
|
|
|
|
|
|
"shell.execute_reply": "2026-02-16T15:56:28.873742Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAABW0AAAPdCAYAAADxjUr8AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsvXl8VPW9//+afUtmkpCZCYGEhEWEAEXCrq3gBlhrEbRWuF+XKuXWUi3W9lZbtdrb8uti0bZ6hdLa5cptb7Fut4ilSNqqYIWwSEDKkkAgZCaBZNZkZjJzfn8cZzKTzExmOefMOWfez8eDB8nJLJ+zfD7v9+f1fn/eHwXDMAwIgiAIgiAIgiAIgiAIgiAIUaAsdAMIgiAIgiAIgiAIgiAIgiCIQUi0JQiCIAiCIAiCIAiCIAiCEBEk2hIEQRAEQRAEQRAEQRAEQYgIEm0JgiAIgiAIgiAIgiAIgiBEBIm2BEEQBEEQBEEQBEEQBEEQIoJEW4IgCIIgCIIgCIIgCIIgCBFBoi1BEARBEARBEARBEARBEISIINGWIAiCIAiCIAiCIAiCIAhCRJBoSxAEQRAEQRAEQRAEQRAEISJItCUIgiAIgiBkTVNTExQKRexfW1tbRu/79a9/nfC+TPnOd74Te09dXV1ujSYIgiAIgiCKGhJtCYIgCIIgJE5dXV2CuJjJv6ampkI3O2OGiqfJ2j5UmP31r38teDulxrlz5/DVr34VDQ0NMJlM0Ol0qKqqwvTp03H77bdjw4YN6OnpSXgPn4I03T+CIAiCIIhB1IVuAEEQBEEQBEHwyYQJE/CjH/0o9ntFRUUBWyMOmpubcc0118DlciUcdzgccDgcOHLkCP73f/8Xy5YtQ3l5eYFaSRAEQRAEUbyQaEsQBEEQBCFxvvWtbyWIbz09Pfj+978f+/3666/HDTfckPCeCRMmpPw8t9sNs9nMfUMLRE1NDR5++OFCN0NU3H///bFnxmQy4fbbb8f48eMRCoVw4sQJ/OMf/0B7e3uBW0kQBEEQBFG8UHkEgiAIgiAIibNmzRo8/PDDsX9r1qxJ+PvChQsT/n7rrbeitrY2odzAL3/5S8yaNQsGgwGf+tSnAAB333137DWLFi1K+MyR6sRGIhH87ne/ww033ACbzQatVgur1YpPf/rT2L59O5+XYxgjtfXMmTO44447UFFRAZPJhE996lP461//OuLnfvjhh7jppptgNpthNpuxdOlSNDc3j/g+t9uNDRs2YN68ebBYLNBqtaitrcXdd9+NlpaWYa8fWpLA5XLh61//OsaNGwetVovx48fj+9//PhiGyeh6uN1uvP/++7Hfn3vuOfzyl7/Et771LXznO9/BSy+9hLNnz+Kf//wnxo4dC2DwGj755JOx9505cyZpSYPW1lZ89atfxSc/+UnU1NTESi+MGTMGn/nMZ/DGG28ktGfRokXDagbfc889KcswOBwOPProo5g5cyZKS0uh1+sxceJEfPnLX8bZs2eHna/P58NTTz2FWbNmobS0FBqNBjabDTNnzsSaNWuwY8eOjK4bQRAEQRCEkFCmLUEQBEEQRJHz+OOP4x//+Adnn9fX14ebb755mPDZ3d2N7du3Y/v27XjooYfw9NNPc/adudLW1oYFCxags7Mzduwf//gHbrjhBixbtizl+/bt24fFixfD6/XGjr311lv429/+hiuvvDLl+06cOIEbbrhhmHDc3t6O3/zmN/j973+P3/3ud7jtttuSvt/r9WLBggU4duxY7Fhrayu+9a1vob+/H0899dRIp4yBgYGE348cOYJwOAyVSpVwfM6cOSN+VjJaWlrw7LPPDjve0dGBjo4O/N///R+efPJJPP7441l/9p49e3DzzTeju7s74fipU6fw/PPP46WXXsIbb7yBT37yk7G/3XTTTcPqIHd1daGrqwuHDh2Cx+PB0qVLs24LQRAEQRAEn5BoSxAEQRAEUeT84x//wLhx47By5UoYjUY4nc68Pm/9+vUxwVar1eLzn/88Jk2ahA8//BB//OMfwTAMfvKTn6CxsRGrVq3K+vP/8Ic/YN++fQnHTp06lVNb161blyDYfuYzn8EVV1yBN998M2VGMMMw+MIXvhATbBUKBVatWoW6ujq8/PLL2LVrV9L3hcNh3HLLLTHB1mq1YtWqVaioqMBbb72F9957D4FAAHfeeScaGxsxfvz4YZ9x8eJF9PT04M4770R1dTW2bNkSEzCfffZZfPvb34ZWq017zhUVFRg3bhzOnDkDAPjxj3+MF198EVdeeSWuuOIKLFiwAIsWLYJOp4u9J1oX+C9/+Qt27twJACgvL8ejjz4ae01U5FWr1Zg5cyZmz54Nq9UKs9kMn8+Hd999F7t37wYAfPe738W9996LMWPG4Etf+hJuuukmfP3rX4991u23347Zs2cDACwWCwA2Q3j58uWx8x03bhxuv/12GAwGbNu2DS0tLXC5XFi5ciVOnDgBi8WCY8eOxQRbpVKJO++8E5dddhm6u7vR2toqqQ35CIIgCIIoMhiCIAiCIAhCVrS2tjIAYv+eeOKJtH+vr69nenp6hn3OXXfdFXvN1VdfnfC33bt3J3xGa2srwzAMc/HiRUatVseO/+pXv0p43/333x/72xVXXJHR+bz44osJ35XJvxdffHHEtnZ0dDAKhSJ2/N/+7d9i7wkGg0xDQ0PC+6Ls2bMn4fi3v/3t2N9cLhdTWVkZ+9u4ceNif3vttddix1UqFfOvf/0r9reBgQFm+vTpsb+vX78+9rcnnngi4fueeeaZ2N9effXVhL8dPnw4o2v6pz/9KeHch/6zWCzMk08+yQwMDCS8L74t8eeWjOPHjzO///3vmZ/97GfMj3/8Y+ZHP/oRYzQaY+//7W9/m/D6VPcvyrPPPhv7e3l5OXPx4sXY37xeL2O1WmN/f/bZZxmGYZjm5ubYsSlTpjCRSCThMwcGBpi2traMrhlBEARBEISQUKYtQRAEQRBEkfPlL38ZZWVlnHzW+++/n7D8/gtf+AK+8IUvJH3twYMH4ff7YTQaOfnubNm/f39CHdjVq1fHftZoNPjc5z6HJ554Ytj7hmb5xr/PbDbjM5/5DF588cVh73v33XdjP4fDYVx22WUp2/bee+8lPa5SqbB27drY75MnT074e09PT8rPjOeWW27B22+/je9+97toampCJBJJ+LvL5cITTzyBSCSC73znOxl9ZpS2tjasXr065TlEOXfuXFafG3/9enp6MGrUqJSvfe+99/DAAw9gypQpGDVqFC5evIhjx45h4sSJuOKKK3DZZZdhxowZuO666zBu3Lis2kEQBEEQBCEEtBEZQRAEQRBEkXP55ZeP+BpmyCZXgUAg6esuXbqU8fcyDIOLFy9m/Poou3fvBsMwCf+iy+6zobe3N+F3m82W8Lvdbuf0fdlcm66urqTH7XY79Hp97Pf4EgYAhomv6Vi0aBF27dqFS5cu4c0338R3vvOdWEmCKBs3bsz486IsX758RMEWSP0MpSKX66fX6/G///u/qK2tBQCcPn0aL7/8MjZs2IA77rgDY8aMwU9+8pOs2kEQBEEQBCEElGlLEARBEARR5JhMpqTHlcrB+H5fX1/C306cOJH0PRUVFQm/r1+/HtXV1Sm/O1qvtBAMzS4eWsvX4XBk/L748071vvjX6PV6fPe7303ZtlTXRaPRJPyuUChSfkamWCwWLF26FEuXLsUTTzyBe++9F7/61a8AsHVkHQ5HSiF6KMePH8ehQ4div69atQo//OEPUV1dDYVCAZvNllKQHon46zd69Gg89NBDKV9bU1MT+/maa65Ba2srmpubcfDgQZw8eRLvvfce/vGPfyAYDOLrX/86br75ZkycODGndhEEQRAEQfABibYEQRAEQRBEUuLFyePHj6O3txdlZWVwuVx47rnnkr5n3rx5UKlUCIfDAFiR8eGHHx72ura2Nhw/fhxms5mXtmfCrFmzoFAoYlnEL730EpYuXQoACIVC+N///d+k7xuajfrSSy/FBFi324033ngj6fsWLlwY+7m/vx8NDQ1YtmzZsNe9//77wzJoueauu+7CAw8
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 1400x1000 with 3 Axes>"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"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",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"execution_count": 8,
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"id": "01055ea1",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"iopub.execute_input": "2026-02-16T15:56:28.878681Z",
|
|
|
|
|
|
"iopub.status.busy": "2026-02-16T15:56:28.878389Z",
|
|
|
|
|
|
"iopub.status.idle": "2026-02-16T15:56:33.453294Z",
|
|
|
|
|
|
"shell.execute_reply": "2026-02-16T15:56:33.452167Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"image/png": "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
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 800x600 with 2 Axes>"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"Per-State Accuracy:\n",
|
|
|
|
|
|
"Bull: 94.47%\n",
|
|
|
|
|
|
"Bear: 88.65%\n",
|
|
|
|
|
|
"Sideways: 81.90%\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"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",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"execution_count": 9,
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"id": "5db60bb9",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"iopub.execute_input": "2026-02-16T15:56:33.456943Z",
|
|
|
|
|
|
"iopub.status.busy": "2026-02-16T15:56:33.456482Z",
|
|
|
|
|
|
"iopub.status.idle": "2026-02-16T15:56:33.653325Z",
|
|
|
|
|
|
"shell.execute_reply": "2026-02-16T15:56:33.652238Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"Benchmarking Rust HMM implementation...\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"✓ Rust-accelerated fitting time: 0.021 seconds\n",
|
|
|
|
|
|
" Dataset: 5000 observations\n",
|
|
|
|
|
|
" Iterations: 50\n",
|
|
|
|
|
|
" States: 3\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"💡 Pure Python HMM libraries (hmmlearn) typically take 10-50× longer\n",
|
|
|
|
|
|
" Estimated Python time: ~0.5s\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"source": [
|
|
|
|
|
|
"import time\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Generate larger dataset\n",
|
|
|
|
|
|
"large_returns, _, _, _ = generate_regime_data(n_samples=5000)\n",
|
|
|
|
|
|
"\n",
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"# Time the Rust fitting process\n",
|
|
|
|
|
|
"print(\"Benchmarking Rust HMM implementation...\")\n",
|
|
|
|
|
|
"hmm_bench = HMM(n_states=3)\n",
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"\n",
|
|
|
|
|
|
"start = time.time()\n",
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"hmm_bench.fit(large_returns, n_iterations=50, tolerance=1e-6)\n",
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"rust_time = time.time() - start\n",
|
|
|
|
|
|
"\n",
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"print(f\"\\n✓ Rust-accelerated fitting time: {rust_time:.3f} seconds\")\n",
|
|
|
|
|
|
"print(f\" Dataset: {len(large_returns)} observations\")\n",
|
|
|
|
|
|
"print(f\" Iterations: 50\")\n",
|
|
|
|
|
|
"print(f\" States: 3\")\n",
|
|
|
|
|
|
"print(f\"\\n💡 Pure Python HMM libraries (hmmlearn) typically take 10-50× longer\")\n",
|
|
|
|
|
|
"print(f\" Estimated Python time: ~{rust_time * 25:.1f}s\")"
|
2025-12-03 18:16:48 +01:00
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"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": {
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"kernelspec": {
|
|
|
|
|
|
"display_name": "rhftlab",
|
|
|
|
|
|
"language": "python",
|
|
|
|
|
|
"name": "python3"
|
|
|
|
|
|
},
|
2025-12-03 18:16:48 +01:00
|
|
|
|
"language_info": {
|
2026-01-06 14:36:08 +01:00
|
|
|
|
"codemirror_mode": {
|
|
|
|
|
|
"name": "ipython",
|
|
|
|
|
|
"version": 3
|
|
|
|
|
|
},
|
|
|
|
|
|
"file_extension": ".py",
|
|
|
|
|
|
"mimetype": "text/x-python",
|
|
|
|
|
|
"name": "python",
|
|
|
|
|
|
"nbconvert_exporter": "python",
|
|
|
|
|
|
"pygments_lexer": "ipython3",
|
2026-02-17 09:05:43 +01:00
|
|
|
|
"version": "3.11.13"
|
2025-12-03 18:16:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"nbformat": 4,
|
|
|
|
|
|
"nbformat_minor": 5
|
|
|
|
|
|
}
|