Files
2026-07-09 02:25:38 -07:00

477 lines
12 KiB
Plaintext

{
"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
}