{ "cells": [ { "cell_type": "code", "execution_count": 27, "id": "87f54819", "metadata": {}, "outputs": [], "source": [ "# Data and analysis libraries\n", "import polars as pl # Fast dataframes for financial data\n", "import numpy as np # Numerical computing library\n", "from datetime import datetime, timedelta # Date and time operations\n", "import random\n", "\n", "# Machine learning libraries \n", "import torch # PyTorch framework\n", "import torch.nn as nn # Neural network modules\n", "import torch.optim as optim # Optimization algorithms\n", "import research # Model building and training utilities\n", "\n", "# Visualization and \n", "import altair as alt # Interactive visualization library\n", "\n", "# data sources\n", "import binance # Binance market data utilities" ] }, { "cell_type": "markdown", "id": "8e8cc3fb", "metadata": {}, "source": [ "# Part 3 - Implementation" ] }, { "cell_type": "markdown", "id": "2718194d", "metadata": {}, "source": [ "## Recap" ] }, { "cell_type": "markdown", "id": "4fd78781", "metadata": {}, "source": [ "### Part 1: Reasearch" ] }, { "cell_type": "code", "execution_count": 28, "id": "ced8e0ab", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "LinearModel(\n", " (linear): Linear(in_features=3, out_features=1, bias=True)\n", ")" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import models\n", "\n", "model = models.LinearModel(3)\n", "model.load_state_dict(torch.load('model_weights.pth', weights_only=True))\n", "model.eval()" ] }, { "cell_type": "markdown", "id": "0c43665f", "metadata": {}, "source": [ "### AR(3) Model to predict future log return - 12h forecast horizon" ] }, { "cell_type": "code", "execution_count": 29, "id": "d26b6d5a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "linear.weight:\n", "[[-0.02849757 -0.08180149 -0.05941094]]\n", "linear.bias:\n", "[0.00058626]\n" ] } ], "source": [ "research.print_model_params(model)" ] }, { "cell_type": "markdown", "id": "d8250c71", "metadata": {}, "source": [ "## Part 2: Strategy Recap" ] }, { "cell_type": "code", "execution_count": 30, "id": "13a8bba8", "metadata": {}, "outputs": [], "source": [ "# ~14% without any optimization\n", "# 1. Compounding Trade Sizing\n", "# 2. Leverage\n", "# ~14% to >40%" ] }, { "cell_type": "markdown", "id": "4c0d6f13", "metadata": {}, "source": [ "## Tick" ] }, { "cell_type": "code", "execution_count": 31, "id": "8c0d69f4", "metadata": {}, "outputs": [], "source": [ "from abc import ABC, abstractmethod\n", "from typing import Generic, TypeVar\n", "\n", "T = TypeVar('T') # input type\n", "R = TypeVar('R') # output type\n", "\n", "class Tick(ABC, Generic[T, R]):\n", " @abstractmethod\n", " def on_tick(self, val: T) -> R:\n", " \"\"\"Handle a new tick and optionally return a result.\"\"\"\n", " pass" ] }, { "cell_type": "markdown", "id": "4c684fbf", "metadata": {}, "source": [ "## Sliding Window" ] }, { "cell_type": "code", "execution_count": 32, "id": "9d331f96", "metadata": {}, "outputs": [], "source": [ "from collections import deque\n", "from typing import Deque, Optional\n", "import numpy as np\n", "\n", "class DequeWindow(Tick[T, Optional[T]], Generic[T]):\n", " def __init__(self, n: int):\n", " self._data: Deque[T] = deque(maxlen=n)\n", "\n", " def on_tick(self, val: T) -> Optional[T]:\n", " \"\"\"Append a value and return the oldest value dropped (if any).\"\"\"\n", " dropped = None\n", " if self.is_full():\n", " dropped = self._data[0]\n", " self._data.append(val)\n", " return dropped\n", " \n", " def is_full(self) -> bool:\n", " return self._data.maxlen == len(self._data)\n", "\n", " def append_left(self, val: T) -> Optional[T]:\n", " dropped = None\n", " if self.is_full():\n", " dropped = self._data[-1]\n", " self._data.appendleft(val)\n", " return dropped\n", " \n", " def to_numpy(self) -> np.ndarray:\n", " return np.array(self._data)\n", " \n", " def __repr__(self) -> str:\n", " cls_name = self.__class__.__name__\n", " return f\"{cls_name}(capacity={self._data.maxlen}, values={list(self._data)})\"" ] }, { "cell_type": "markdown", "id": "6b1af130", "metadata": {}, "source": [ "### Array based window" ] }, { "cell_type": "code", "execution_count": 33, "id": "f4fc79a5", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "from typing import Optional\n", "\n", "class NumpyWindow(Tick[T, Optional[T]]):\n", " def __init__(self, n: int, dtype=np.float64):\n", " if n <= 0:\n", " raise ValueError(\"Capacity must be positive.\")\n", " self._capacity = n\n", " self._data = np.zeros(n, dtype=dtype)\n", " self._size = 0\n", "\n", " def on_tick(self, val: float) -> Optional[float]:\n", " dropped = None\n", "\n", " if self._size < self._capacity:\n", " self._data[self._size] = val\n", " self._size += 1\n", " else:\n", " dropped = self._data[0]\n", " # shift left in-place\n", " for i in range(1, self._capacity):\n", " self._data[i - 1] = self._data[i]\n", " self._data[-1] = val\n", "\n", " return dropped\n", "\n", "\n", " def __getitem__(self, idx: int) -> float:\n", " \"\"\"Index access (0 = oldest).\"\"\"\n", " if not 0 <= idx < self._size:\n", " raise IndexError(\"Index out of range.\")\n", " return self._data[idx]\n", "\n", " def __len__(self) -> int:\n", " return self._size\n", "\n", " def capacity(self) -> int:\n", " return self._capacity\n", "\n", " def is_full(self) -> bool:\n", " return self._size == self._capacity\n", "\n", " def values(self) -> np.ndarray:\n", " return self._data[:self._size]\n", "\n", " def __repr__(self) -> str:\n", " vals = self.values().tolist()\n", " return f\"{self.__class__.__name__}(capacity={self._capacity}, size={self._size}, values={vals})\"" ] }, { "cell_type": "markdown", "id": "c9213b19", "metadata": {}, "source": [ "### Stream the Last Known Value" ] }, { "cell_type": "code", "execution_count": 34, "id": "e3b6ae13", "metadata": {}, "outputs": [], "source": [ "class Last(Tick[T, T], Generic[T]):\n", " def __init__(self):\n", " self._value: Optional[T] = None\n", "\n", " def on_tick(self, val: T) -> Optional[T]:\n", " self._value = val\n", " return val\n", "\n", " def __repr__(self) -> str:\n", " cls_name = self.__class__.__name__\n", " return f\"{cls_name}(value={self._value})\"" ] }, { "cell_type": "markdown", "id": "d93282e7", "metadata": {}, "source": [ "## Streaming Log Returns" ] }, { "cell_type": "code", "execution_count": 35, "id": "e14f546c", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "class LogReturn(Tick[float, Optional[float]], Generic[T]):\n", " def __init__(self):\n", " self._window = NumpyWindow(2)\n", "\n", " def on_tick(self, val: float) -> Optional[float]:\n", " self._window.on_tick(val)\n", " if self._window.is_full():\n", " return np.log(self._window[1] / self._window[0])\n", " else:\n", " return None\n", " \n", " def __repr__(self) -> str:\n", " cls_name = self.__class__.__name__\n", " return f\"{cls_name}(window={self._window})\"" ] }, { "cell_type": "markdown", "id": "8c2ef8a2", "metadata": {}, "source": [ "## Streaming Auto-Regressive Log Returns Lags" ] }, { "cell_type": "code", "execution_count": 36, "id": "cf955e23", "metadata": {}, "outputs": [], "source": [ "class LogReturnLags(Tick[float, torch.Tensor]):\n", " def __init__(self, no_lags: int):\n", " self._lags = DequeWindow(no_lags)\n", " self._log_return = LogReturn()\n", " \n", " def on_tick(self, val: float) -> torch.Tensor | None:\n", " log_ret = self._log_return.on_tick(val)\n", " if log_ret is not None:\n", " self._lags.append_left(log_ret)\n", " return torch.tensor(self._lags.to_numpy(), dtype=torch.float32) if self._lags.is_full() else None\n", " else:\n", " return None\n", " \n", " def __repr__(self) -> str:\n", " cls_name = self.__class__.__name__\n", " return f\"{cls_name}(lags={self._lags}, log_return={self._log_return})\" " ] }, { "cell_type": "code", "execution_count": 37, "id": "7d62e078", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "tensor([ 0.3747, -0.3102, 0.4055])" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lags = LogReturnLags(3)\n", "lags.on_tick(90)\n", "lags.on_tick(100)\n", "lags.on_tick(150)\n", "lags.on_tick(110)\n", "features = lags.on_tick(160)\n", "features" ] }, { "cell_type": "markdown", "id": "8ab400f2", "metadata": {}, "source": [ "### Streaming features into our model" ] }, { "cell_type": "code", "execution_count": 38, "id": "43b202c4", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "tensor([-0.0088])" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X = features\n", "with torch.no_grad():\n", " y_hat = model(X)\n", "y_hat" ] }, { "cell_type": "code", "execution_count": 39, "id": "d57f4837", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "tensor(-0.0088)" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "y_hat[0]" ] }, { "cell_type": "markdown", "id": "a1f09327", "metadata": {}, "source": [ "## Build the Trading System" ] } ], "metadata": { "kernelspec": { "display_name": "study (3.14.5.final.0)", "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.14.5" } }, "nbformat": 4, "nbformat_minor": 5 }