{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "eef8836d",
   "metadata": {},
   "source": [
    "# Jupyter Notebook: Crypto Coin Price Trend Analysis and Outlier Detection\n",
    "\n",
    "## Overview\n",
    "This notebook analyzes price trends of top cryptocurrencies, quantifies similarities to a benchmark (BTC or average), and identifies outliers based on deviations. It follows the steps outlined in the query: data collection, preparation, normalization, benchmarking, and outlier detection.\n",
    "\n",
    "We use:\n",
    "- **Data Source**: CoinGecko API for historical daily closing prices (2019 to present).\n",
    "- **Coins**: BTC, ETH, BNB, XRP, ADA, SOL, DOGE, DOT, TRX, AVAX.\n",
    "- **Benchmark**: BTC as primary proxy; average of all coins as secondary.\n",
    "- **Analysis**: Daily log returns, cumulative returns, correlations, and deviation-based outlier detection.\n",
    "- **Visualization**: Plots of normalized cumulative returns to show \"identical paths\" and deviations.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7d5aecaf",
   "metadata": {},
   "outputs": [],
   "source": [
    "## Step 1: Import Libraries\n",
    "import yfinance as yf\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from scipy import stats\n",
    "from datetime import datetime\n",
    "import seaborn as sns\n",
    "\n",
    "## Step 2: Define Parameters\n",
    "# yfinance tickers\n",
    "tickers = ['BTC-USD', 'ETH-USD', 'BNB-USD', 'XRP-USD', 'ADA-USD', 'SOL-USD', 'DOGE-USD', 'DOT-USD', 'TRX-USD', 'AVAX-USD']\n",
    "\n",
    "# Date range\n",
    "start_date = '2024-01-01'\n",
    "end_date = datetime.now().strftime('%Y-%m-%d')\n",
    "\n",
    "# Function to fetch historical data from yfinance\n",
    "def fetch_coin_data(ticker):\n",
    "    try:\n",
    "        data = yf.download(ticker, start=start_date, end=end_date)\n",
    "        if data.empty:\n",
    "            print(f\"No data found for {ticker}\")\n",
    "            return None\n",
    "        #series = data['Adj Close']\n",
    "        series = data['Close']\n",
    "        series.name = ticker.split('-')[0].upper()  # e.g., 'BTC-USD' -> coin1\n",
    "        return series\n",
    "    except Exception as e:\n",
    "        print(f\"Error fetching data for {ticker}: {e}\")\n",
    "        return None\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "fec49653",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_91612/1991026065.py:21: FutureWarning: YF.download() has changed argument auto_adjust default to True\n",
      "  data = yf.download(ticker, start=start_date, end=end_date)\n",
      "[*********************100%***********************]  1 of 1 completed\n",
      "/tmp/ipykernel_91612/1991026065.py:21: FutureWarning: YF.download() has changed argument auto_adjust default to True\n",
      "  data = yf.download(ticker, start=start_date, end=end_date)\n",
      "[*********************100%***********************]  1 of 1 completed\n",
      "/tmp/ipykernel_91612/1991026065.py:21: FutureWarning: YF.download() has changed argument auto_adjust default to True\n",
      "  data = yf.download(ticker, start=start_date, end=end_date)\n",
      "[*********************100%***********************]  1 of 1 completed\n",
      "/tmp/ipykernel_91612/1991026065.py:21: FutureWarning: YF.download() has changed argument auto_adjust default to True\n",
      "  data = yf.download(ticker, start=start_date, end=end_date)\n",
      "[*********************100%***********************]  1 of 1 completed\n",
      "/tmp/ipykernel_91612/1991026065.py:21: FutureWarning: YF.download() has changed argument auto_adjust default to True\n",
      "  data = yf.download(ticker, start=start_date, end=end_date)\n",
      "[*********************100%***********************]  1 of 1 completed\n",
      "/tmp/ipykernel_91612/1991026065.py:21: FutureWarning: YF.download() has changed argument auto_adjust default to True\n",
      "  data = yf.download(ticker, start=start_date, end=end_date)\n",
      "[*********************100%***********************]  1 of 1 completed\n",
      "/tmp/ipykernel_91612/1991026065.py:21: FutureWarning: YF.download() has changed argument auto_adjust default to True\n",
      "  data = yf.download(ticker, start=start_date, end=end_date)\n",
      "[*********************100%***********************]  1 of 1 completed\n",
      "/tmp/ipykernel_91612/1991026065.py:21: FutureWarning: YF.download() has changed argument auto_adjust default to True\n",
      "  data = yf.download(ticker, start=start_date, end=end_date)\n",
      "[*********************100%***********************]  1 of 1 completed\n",
      "/tmp/ipykernel_91612/1991026065.py:21: FutureWarning: YF.download() has changed argument auto_adjust default to True\n",
      "  data = yf.download(ticker, start=start_date, end=end_date)\n",
      "[*********************100%***********************]  1 of 1 completed\n",
      "/tmp/ipykernel_91612/1991026065.py:21: FutureWarning: YF.download() has changed argument auto_adjust default to True\n",
      "  data = yf.download(ticker, start=start_date, end=end_date)\n",
      "[*********************100%***********************]  1 of 1 completed"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fetched Data Preview:\n",
      "                     BTC          ETH         BNB       XRP       ADA  \\\n",
      "Date                                                                    \n",
      "2024-01-01  44167.332031  2352.327881  314.408295  0.630082  0.623110   \n",
      "2024-01-02  44957.968750  2355.836426  312.103821  0.625363  0.605667   \n",
      "2024-01-03  42848.175781  2210.761963  316.661285  0.582580  0.557286   \n",
      "2024-01-04  44179.921875  2269.038086  323.600006  0.588226  0.570567   \n",
      "2024-01-05  44162.691406  2268.647217  317.450287  0.576486  0.541242   \n",
      "\n",
      "                   SOL      DOGE       DOT       TRX       AVAX  \n",
      "Date                                                             \n",
      "2024-01-01  109.508682  0.092024  8.598925  0.107812  41.864464  \n",
      "2024-01-02  106.611221  0.091204  8.412696  0.108364  40.645210  \n",
      "2024-01-03   98.594437  0.082042  7.641823  0.106826  36.795277  \n",
      "2024-01-04  105.077538  0.084131  7.881476  0.107085  38.737835  \n",
      "2024-01-05   99.978790  0.082773  7.445562  0.104460  36.434849  \n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "## Step 3: Collect and Prepare Data\n",
    "# Build prices_df incrementally to avoid dict-of-scalars error\n",
    "prices_df = pd.DataFrame()\n",
    "for ticker in tickers:\n",
    "    data = fetch_coin_data(ticker)\n",
    "    if data is not None:\n",
    "        prices_df[data.name] = data\n",
    "\n",
    "prices_df = prices_df.dropna(axis=1, how='all')  # Drop any failed fetches\n",
    "if prices_df.empty:\n",
    "    raise ValueError(\"No data fetched for any coins. Check tickers, dates, or internet connection.\")\n",
    "print(\"Fetched Data Preview:\")\n",
    "print(prices_df.head())\n",
    "\n",
    "# Calculate daily log returns\n",
    "returns_df = np.log(prices_df / prices_df.shift(1))\n",
    "returns_df = returns_df.dropna()  # Remove NaNs from first row\n",
    "\n",
    "# Normalize returns for visualization (min-max scaling to [0,1])\n",
    "normalized_returns = (returns_df - returns_df.min()) / (returns_df.max() - returns_df.min())\n",
    "\n",
    "# Calculate cumulative returns for trend analysis\n",
    "cum_returns = (1 + returns_df).cumprod()\n",
    "normalized_cum_returns = (cum_returns - cum_returns.min()) / (cum_returns.max() - cum_returns.min())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7b2b4bf1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Benchmark (BTC) Cumulative Returns Preview:\n",
      "Date\n",
      "2024-01-02    1.017743\n",
      "2024-01-03    0.968825\n",
      "2024-01-04    0.998478\n",
      "2024-01-05    0.998089\n",
      "2024-01-06    0.994160\n",
      "Name: BTC, dtype: float64\n",
      "\n",
      "Correlations with Benchmark (BTC):\n",
      "BTC     1.000000\n",
      "ETH     0.789045\n",
      "DOGE    0.784157\n",
      "SOL     0.753216\n",
      "AVAX    0.724280\n",
      "ADA     0.690355\n",
      "DOT     0.665794\n",
      "BNB     0.648919\n",
      "XRP     0.563232\n",
      "TRX     0.234883\n",
      "dtype: float64\n",
      "\n",
      "Outlier Flags (Proportion of Time Deviating >2 Std Devs):\n",
      "XRP     0.093697\n",
      "BNB     0.030664\n",
      "DOGE    0.025554\n",
      "TRX     0.010221\n",
      "ETH     0.006814\n",
      "BTC     0.001704\n",
      "SOL     0.000000\n",
      "ADA     0.000000\n",
      "DOT     0.000000\n",
      "AVAX    0.000000\n",
      "dtype: float64\n",
      "\n",
      "Identified Outliers (High Deviation Coins):\n",
      "Series([], dtype: float64)\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 495400 characters]",
      "text/plain": [
       "<Figure size 1400x700 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 216824 characters]",
      "text/plain": [
       "<Figure size 1400x700 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 110100 characters]",
      "text/plain": [
       "<Figure size 1000x800 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "## Step 4: Define Benchmark 'Normal' Trend\n",
    "# Primary benchmark: BTC as proxy\n",
    "benchmark_coin = coin1\n",
    "benchmark_returns = returns_df[benchmark_coin]\n",
    "benchmark_cum_returns = cum_returns[benchmark_coin]\n",
    "\n",
    "# Secondary benchmark: Average daily return of all coins\n",
    "average_returns = returns_df.mean(axis=1)\n",
    "average_cum_returns = (1 + average_returns).cumprod()\n",
    "\n",
    "print(f\"Benchmark ({benchmark_coin}) Cumulative Returns Preview:\")\n",
    "print(benchmark_cum_returns.head())\n",
    "\n",
    "## Step 5: Quantify Adherence and Identify Outliers\n",
    "# Calculate correlations with benchmark (Pearson correlation on returns)\n",
    "correlations = returns_df.corrwith(benchmark_returns)\n",
    "print(\"\\nCorrelations with Benchmark (BTC):\")\n",
    "print(correlations.sort_values(ascending=False))\n",
    "\n",
    "# Calculate deviations: Difference in cumulative returns from benchmark\n",
    "deviations = cum_returns.subtract(benchmark_cum_returns, axis=0)\n",
    "\n",
    "# Identify outliers: Coins where absolute deviation exceeds 2 std devs from mean deviation\n",
    "threshold = 2  # Z-score threshold for outliers\n",
    "\n",
    "# Custom function to compute row-wise z-scores handling NaNs and zero std\n",
    "def row_zscore(row):\n",
    "    mean = np.nanmean(row)\n",
    "    std = np.nanstd(row)\n",
    "    if std == 0 or np.isnan(std):\n",
    "        return pd.Series(np.nan, index=row.index)\n",
    "    return (row - mean) / std\n",
    "\n",
    "# Compute z-scores for deviations (preserves shape, handles NaNs)\n",
    "z_scores = deviations.apply(row_zscore, axis=1)\n",
    "\n",
    "# Flag outliers: Proportion of time |z-score| > threshold (over available data)\n",
    "outlier_flags = ((z_scores.abs() > threshold) & z_scores.notna()).sum(axis=0) / z_scores.notna().sum(axis=0)\n",
    "outliers = outlier_flags[outlier_flags > 0.1]  # Flag if >10% of the time (adjustable)\n",
    "\n",
    "print(\"\\nOutlier Flags (Proportion of Time Deviating >2 Std Devs):\")\n",
    "print(outlier_flags.sort_values(ascending=False))\n",
    "print(\"\\nIdentified Outliers (High Deviation Coins):\")\n",
    "print(outliers)\n",
    "\n",
    "## Step 6: Visualize Trends and Deviations\n",
    "# Plot 1: Normalized Cumulative Returns (to show similar paths)\n",
    "plt.figure(figsize=(14, 7))\n",
    "for coin in normalized_cum_returns.columns:\n",
    "    plt.plot(normalized_cum_returns.index, normalized_cum_returns[coin], label=coin)\n",
    "plt.plot(normalized_cum_returns.index, normalized_cum_returns[benchmark_coin], label=f'Benchmark ({benchmark_coin})', linewidth=3, color='black', linestyle='--')\n",
    "plt.title('Normalized Cumulative Returns: Similar Trends Across Coins')\n",
    "plt.xlabel('Date')\n",
    "plt.ylabel('Normalized Cumulative Return')\n",
    "plt.legend()\n",
    "plt.grid(True)\n",
    "plt.show()\n",
    "\n",
    "# Plot 2: Deviations from Benchmark\n",
    "plt.figure(figsize=(14, 7))\n",
    "for coin in deviations.columns:\n",
    "    color = 'red' if coin in outliers.index else 'blue'\n",
    "    plt.plot(deviations.index, deviations[coin], label=coin, color=color)\n",
    "plt.axhline(0, color='black', linestyle='--')\n",
    "plt.title(f'Deviations from Benchmark ({benchmark_coin}) Cumulative Returns')\n",
    "plt.xlabel('Date')\n",
    "plt.ylabel('Deviation')\n",
    "plt.legend()\n",
    "plt.grid(True)\n",
    "plt.show()\n",
    "\n",
    "# Plot 3: Correlation Heatmap (Optional, for deeper insight)\n",
    "plt.figure(figsize=(10, 8))\n",
    "sns.heatmap(returns_df.corr(), annot=True, cmap='coolwarm')\n",
    "plt.title('Correlation Matrix of Coin Returns')\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "248eee97",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "50-Day SMA Preview:\n",
      "            BTC  ETH  BNB  XRP  ADA  SOL  DOGE  DOT  TRX  AVAX\n",
      "Date                                                          \n",
      "2024-01-01  NaN  NaN  NaN  NaN  NaN  NaN   NaN  NaN  NaN   NaN\n",
      "2024-01-02  NaN  NaN  NaN  NaN  NaN  NaN   NaN  NaN  NaN   NaN\n",
      "2024-01-03  NaN  NaN  NaN  NaN  NaN  NaN   NaN  NaN  NaN   NaN\n",
      "2024-01-04  NaN  NaN  NaN  NaN  NaN  NaN   NaN  NaN  NaN   NaN\n",
      "2024-01-05  NaN  NaN  NaN  NaN  NaN  NaN   NaN  NaN  NaN   NaN\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 483212 characters]",
      "text/plain": [
       "<Figure size 1400x700 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "K-Means Clusters (0: Trend-Followers, 1: Deviators?):\n",
      "BTC     1\n",
      "ETH     1\n",
      "BNB     1\n",
      "XRP     0\n",
      "ADA     0\n",
      "SOL     1\n",
      "DOGE    1\n",
      "DOT     1\n",
      "TRX     0\n",
      "AVAX    1\n",
      "dtype: int32\n"
     ]
    }
   ],
   "source": [
    "# 4.1: Moving Averages (50-day SMA)\n",
    "\n",
    "from sklearn.cluster import KMeans\n",
    "from sklearn.linear_model import LinearRegression\n",
    "from sklearn.ensemble import IsolationForest\n",
    "from sklearn.metrics.pairwise import cosine_similarity\n",
    "from sklearn.metrics import mean_squared_error\n",
    "\n",
    "sma_window = 50\n",
    "sma_df = prices_df.pct_change().rolling(window=sma_window).mean()\n",
    "sma_df = sma_df.apply(lambda x: (x - x.min()) / (x.max() - x.min()))  # Normalize returns\n",
    "print(\"\\n50-Day SMA Preview:\")\n",
    "print(sma_df.head())\n",
    "\n",
    "# Plot SMA for comparison\n",
    "plt.figure(figsize=(14, 7))\n",
    "for coin in sma_df.columns:\n",
    "    plt.plot(sma_df.index, sma_df[coin], label=coin)\n",
    "plt.title('50-Day Simple Moving Averages (SMA) of Normalized Returns for Coins')\n",
    "plt.xlabel('Date')\n",
    "plt.ylabel('Normalized Return')\n",
    "plt.legend()\n",
    "plt.grid(True)\n",
    "plt.show()\n",
    "\n",
    "# 4.2: Clustering (K-Means on returns)\n",
    "# Prepare data: Fill NaNs, transpose for clustering (coins as samples, days as features)\n",
    "\n",
    "# Backtest parameters\n",
    "window_size = 365  # 1-year rolling windows (adjustable, e.g., 90 for quarterly)\n",
    "outlier_threshold = 2.0  # Z-score threshold\n",
    "cluster_n = 2  # For K-Means: trend-followers vs. deviators\n",
    "\n",
    "\n",
    "clustering_data = returns_df.fillna(0).T  # Transpose: rows=coins, columns=days\n",
    "\n",
    "kmeans = KMeans(n_clusters=cluster_n, random_state=42)\n",
    "clusters = kmeans.fit_predict(clustering_data)\n",
    "cluster_labels = pd.Series(clusters, index=clustering_data.index)\n",
    "print(\"\\nK-Means Clusters (0: Trend-Followers, 1: Deviators?):\")\n",
    "print(cluster_labels)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "84ea6724",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Regression Betas (Alignment to BTC):\n",
      "DOGE    1.566639\n",
      "AVAX    1.354206\n",
      "ADA     1.317561\n",
      "SOL     1.282418\n",
      "ETH     1.107412\n",
      "DOT     1.095512\n",
      "XRP     0.966135\n",
      "BNB     0.691896\n",
      "TRX     0.336323\n",
      "dtype: float64\n",
      "\n",
      "Residual-Based Outlier Proportions:\n",
      "XRP     0.061329\n",
      "ETH     0.059625\n",
      "BNB     0.051107\n",
      "DOT     0.051107\n",
      "AVAX    0.047700\n",
      "SOL     0.044293\n",
      "DOGE    0.040886\n",
      "ADA     0.034072\n",
      "TRX     0.011925\n",
      "dtype: float64\n",
      "\n",
      "Isolation Forest Outliers (Sample):\n",
      "Normal     528\n",
      "Outlier     59\n",
      "Name: count, dtype: int64\n"
     ]
    }
   ],
   "source": [
    "## Step 6: Advanced Outlier Detection Methods\n",
    "# 6.1: Residual Analysis (Linear Regression)\n",
    "residuals_dict = {}\n",
    "betas = {}\n",
    "for coin in returns_df.columns:\n",
    "    if coin == benchmark_coin:\n",
    "        continue\n",
    "    X = benchmark_returns.values.reshape(-1, 1)\n",
    "    y = returns_df[coin].values\n",
    "    model = LinearRegression().fit(X, y)\n",
    "    preds = model.predict(X)\n",
    "    residuals = y - preds\n",
    "    residuals_dict[coin] = residuals\n",
    "    betas[coin] = model.coef_[0]  # Beta against benchmark\n",
    "\n",
    "residuals_df = pd.DataFrame(residuals_dict, index=returns_df.index)\n",
    "print(\"\\nRegression Betas (Alignment to BTC):\")\n",
    "print(pd.Series(betas).sort_values(ascending=False))\n",
    "\n",
    "# Flag high residuals (e.g., |residual| > 2 * std)\n",
    "residual_outliers = (residuals_df.abs() > 2 * residuals_df.std()).mean(axis=0)\n",
    "print(\"\\nResidual-Based Outlier Proportions:\")\n",
    "print(residual_outliers.sort_values(ascending=False))\n",
    "\n",
    "# 6.2: Machine Learning - Isolation Forest\n",
    "# Prepare data: Fill NaNs, use returns\n",
    "ml_data = returns_df.fillna(0)\n",
    "iso_forest = IsolationForest(contamination=0.1, random_state=42)\n",
    "outlier_preds = iso_forest.fit_predict(ml_data)\n",
    "outlier_scores = iso_forest.decision_function(ml_data)\n",
    "ml_outliers = pd.Series(outlier_preds, index=ml_data.index).apply(lambda x: 'Outlier' if x == -1 else 'Normal')\n",
    "print(\"\\nIsolation Forest Outliers (Sample):\")\n",
    "print(ml_outliers.value_counts())\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "67c5818f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Backtest Summary (Sample Windows):\n",
      "Window 2024-01-02 to 2025-01-01: Outlier Freq=0.00, Avg Magnitude=0.42, Outliers=[]\n",
      "Window 2024-02-01 to 2025-01-31: Outlier Freq=0.20, Avg Magnitude=0.50, Outliers=['XRP', 'DOGE']\n",
      "Window 2024-03-02 to 2025-03-02: Outlier Freq=0.20, Avg Magnitude=0.39, Outliers=['BNB', 'XRP']\n",
      "Window 2024-04-01 to 2025-04-01: Outlier Freq=0.20, Avg Magnitude=0.34, Outliers=['XRP', 'TRX']\n",
      "Window 2024-05-01 to 2025-05-01: Outlier Freq=0.20, Avg Magnitude=0.40, Outliers=['XRP', 'TRX']\n",
      "\n",
      "Backtest Metrics:\n",
      "Accuracy (Proxy): 0.00\n",
      "Average Strategy Return: -0.0002\n",
      "Sharpe Ratio: -0.08\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 58228 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "## Step 7: Backtest Setup with Rolling Windows\n",
    "# Divide data into rolling windows (e.g., 1-year)\n",
    "alignments = []  # List to store per-window alignments\n",
    "outlier_metrics = []  # List to store per-window outlier data\n",
    "strategy_returns = []  # For hypothetical strategy\n",
    "\n",
    "dates = returns_df.index\n",
    "step = 30  # Step size for rolling (e.g., monthly shifts)\n",
    "for i in range(0, len(dates) - window_size, step):\n",
    "    window_start = dates[i]\n",
    "    window_end = dates[i + window_size]\n",
    "    window_returns = returns_df.loc[window_start:window_end]\n",
    "    if window_returns.empty:\n",
    "        continue\n",
    "    \n",
    "    # Benchmark: Median returns (robust to outliers)\n",
    "    benchmark_median = window_returns.median(axis=1)\n",
    "    benchmark_cum = (1 + benchmark_median).cumprod()\n",
    "    \n",
    "    # Measure alignment: Cosine similarity and regression beta\n",
    "    window_alignments = {}\n",
    "    for coin in window_returns.columns:\n",
    "        # Cosine similarity\n",
    "        sim = cosine_similarity(window_returns[coin].values.reshape(1, -1), benchmark_median.values.reshape(1, -1))[0][0]\n",
    "        # Beta\n",
    "        X = benchmark_median.values.reshape(-1, 1)\n",
    "        y = window_returns[coin].values\n",
    "        beta = LinearRegression().fit(X, y).coef_[0]\n",
    "        window_alignments[coin] = {'cosine_sim': sim, 'beta': beta}\n",
    "    alignments.append((window_start, window_end, window_alignments))\n",
    "    \n",
    "    # Identify outliers in window (Z-score on deviations)\n",
    "    window_cum = (1 + window_returns).cumprod()\n",
    "    window_dev = window_cum.subtract(benchmark_cum, axis=0)\n",
    "    window_z = window_dev.apply(row_zscore, axis=1)\n",
    "    window_flags = ((window_z.abs() > outlier_threshold) & window_z.notna()).sum(axis=0) / window_z.notna().sum(axis=0)\n",
    "    window_outliers = window_flags[window_flags > 0.1]\n",
    "    \n",
    "    # Track metrics: Frequency, magnitude\n",
    "    freq = len(window_outliers) / len(window_returns.columns)\n",
    "    magnitude = window_dev.abs().mean().mean() if not window_dev.empty else 0\n",
    "    outlier_metrics.append((window_start, window_end, freq, magnitude, window_outliers))\n",
    "    \n",
    "    # Hypothetical strategy: Short outliers (assume -1 * their next period return if flagged)\n",
    "    next_start = min(i + window_size, len(dates) - 1)\n",
    "    next_end = min(next_start + step, len(dates) - 1)\n",
    "    if next_end > next_start:\n",
    "        next_returns = returns_df.iloc[next_start:next_end]\n",
    "        strat_ret = 0\n",
    "        for coin in window_outliers.index:\n",
    "            if coin in next_returns.columns:\n",
    "                strat_ret -= next_returns[coin].mean()  # Short the outlier\n",
    "        strategy_returns.append(strat_ret / len(window_outliers) if len(window_outliers) > 0 else 0)\n",
    "\n",
    "# Summarize backtest\n",
    "print(\"\\nBacktest Summary (Sample Windows):\")\n",
    "for start, end, freq, mag, outs in outlier_metrics[:5]:  # First 5 for brevity\n",
    "    print(f\"Window {start.date()} to {end.date()}: Outlier Freq={freq:.2f}, Avg Magnitude={mag:.2f}, Outliers={outs.index.tolist()}\")\n",
    "\n",
    "# Performance Metrics\n",
    "# Accuracy: Proportion of flagged outliers with high deviation magnitude (e.g., > mean + 2SD)\n",
    "all_magnitudes = [mag for _, _, _, mag, _ in outlier_metrics]\n",
    "mag_mean = np.mean(all_magnitudes)\n",
    "mag_std = np.std(all_magnitudes)\n",
    "accuracy = np.mean([1 if mag > mag_mean + 2 * mag_std else 0 for mag in all_magnitudes])  # Simple proxy\n",
    "\n",
    "# Strategy Returns and Sharpe\n",
    "strategy_series = pd.Series(strategy_returns)\n",
    "cum_strategy = (1 + strategy_series).cumprod()\n",
    "sharpe = (strategy_series.mean() / strategy_series.std()) * np.sqrt(12) if strategy_series.std() != 0 else 0  # Annualized\n",
    "\n",
    "print(f\"\\nBacktest Metrics:\\nAccuracy (Proxy): {accuracy:.2f}\\nAverage Strategy Return: {strategy_series.mean():.4f}\\nSharpe Ratio: {sharpe:.2f}\")\n",
    "\n",
    "# Plot Hypothetical Strategy Cumulative Returns\n",
    "plt.figure(figsize=(10, 6))\n",
    "plt.plot(range(len(cum_strategy)), cum_strategy, label='Strategy Cumulative Returns')\n",
    "plt.title('Hypothetical Strategy: Short Outliers')\n",
    "plt.xlabel('Window Index')\n",
    "plt.ylabel('Cumulative Return')\n",
    "plt.legend()\n",
    "plt.grid(True)\n",
    "plt.show()\n",
    "\n",
    "## Conclusion\n",
    "# - Trends detected via SMA and clustering.\n",
    "# - Outliers identified using multiple methods.\n",
    "# - Backtest simulates rolling windows, measures alignments, and evaluates a simple strategy.\n",
    "# - Adjust parameters (e.g., window_size, threshold) for refinement."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "b6b66466",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 161644 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "## Step 8: Custom Plot Function for Comparing Two Coins' Cumulative % Returns\n",
    "def plot_coin_returns_comparison(prices_df, coin1='BTC', coin2='SOL'):\n",
    "    \"\"\"\n",
    "    Plot the cumulative percentage returns of two chosen coins from the given DataFrame.\n",
    "    \n",
    "    Args:\n",
    "        prices_df (pd.DataFrame): DataFrame containing cryptocurrency prices\n",
    "        coin1 (str): First coin symbol (default: 'BTC')\n",
    "        coin2 (str): Second coin symbol (default: 'SOL')\n",
    "    \"\"\"\n",
    "    # Check if required columns exist\n",
    "    required_coins = [coin1, coin2]\n",
    "    missing = [coin for coin in required_coins if coin not in prices_df.columns]\n",
    "    if missing:\n",
    "        raise ValueError(f\"Missing data for: {', '.join(missing)}\")\n",
    "    \n",
    "    # Calculate cumulative % returns: (current / initial - 1) * 100\n",
    "    initial_prices = prices_df.iloc[0]\n",
    "    pct_returns = ((prices_df / initial_prices) - 1) * 100\n",
    "    \n",
    "    # Create figure and axis\n",
    "    fig, ax1 = plt.subplots(figsize=(12, 6))\n",
    "    \n",
    "    # Plot first coin\n",
    "    ax1.plot(pct_returns.index, pct_returns[coin1], label=coin1, color='orange', linewidth=2)\n",
    "    ax1.set_xlabel('Date', fontsize=12)\n",
    "    ax1.set_ylabel(f'{coin1} Cumulative Return (%)', fontsize=12)\n",
    "    ax1.tick_params(axis='y', labelcolor='orange')\n",
    "    \n",
    "    # Check if secondary axis is needed (if mean returns differ by factor >50)\n",
    "    use_secondary = abs(pct_returns[coin1].mean() / pct_returns[coin2].mean()) > 50 if pct_returns[coin2].mean() != 0 else False\n",
    "    \n",
    "    if use_secondary:\n",
    "        ax2 = ax1.twinx()\n",
    "        ax2.plot(pct_returns.index, pct_returns[coin2], label=coin2, color='blue', linewidth=2)\n",
    "        ax2.set_ylabel(f'{coin2} Cumulative Return (%)', fontsize=12)\n",
    "        ax2.tick_params(axis='y', labelcolor='blue')\n",
    "        lines_1, labels_1 = ax1.get_legend_handles_labels()\n",
    "        lines_2, labels_2 = ax2.get_legend_handles_labels()\n",
    "        ax1.legend(lines_1 + lines_2, labels_1 + labels_2, fontsize=12)\n",
    "    else:\n",
    "        ax1.plot(pct_returns.index, pct_returns[coin2], label=coin2, color='blue', linewidth=2)\n",
    "        ax1.legend(fontsize=12)\n",
    "    \n",
    "    # Customize the plot\n",
    "    plt.title(f'{coin1} and {coin2} Cumulative % Returns Comparison ({start_date} to {end_date})', fontsize=14)\n",
    "    plt.grid(True, which='both', linestyle='--', linewidth=0.5)\n",
    "    \n",
    "    # Rotate x-axis labels for better readability\n",
    "    plt.xticks(rotation=45)\n",
    "    \n",
    "    # Adjust layout to prevent label cutoff\n",
    "    plt.tight_layout()\n",
    "    \n",
    "    # Show the plot\n",
    "    plt.show()\n",
    "\n",
    "plot_coin_returns_comparison(prices_df, 'ADA', 'SOL')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "venv",
   "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.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
