Files
trade-doc/bot/bot1.tex

501 lines
14 KiB
TeX

\documentclass[11pt,a4paper]{article}
\usepackage[T1]{fontenc}
\usepackage[margin=2.5cm]{geometry}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{booktabs}
\usepackage{fancyhdr}
\usepackage{longtable}
\usepackage[hidelinks]{hyperref}
\newcommand{\docauthor}{M. Pabiszczak}
\newcommand{\docdate}{2026-03-26}
\newcommand{\docrevision}{6825e0ad-a4be-427b-9559-0c3b6f744248}
\pagestyle{fancy}
\fancyhf{}
\fancyhead[L]{\docauthor}
\fancyhead[C]{rewizja \docrevision}
\fancyhead[R]{\docdate}
\fancyfoot[C]{\thepage}
\setlength{\headheight}{14pt}
\title{Model matematyczny observera SOL-PERP}
\author{}
\date{}
\begin{document}
\maketitle
\thispagestyle{fancy}
\section{Cel dokumentu}
Ten dokument formalizuje aktualny baseline observera dla \texttt{SOL-PERP}.
Observer realizuje petle obserwacji rynku, ktora:
\begin{itemize}
\item co okolo \(1\) sekunde pobiera snapshot rynku,
\item liczy zestaw cech (features),
\item przepuszcza je przez bramki jakosci danych (gates),
\item wyznacza sygnal \texttt{long}, \texttt{short} albo \texttt{flat},
\item zapisuje wynik do \texttt{bot\_state} oraz \texttt{bot\_events}.
\end{itemize}
Model jest deterministycznym liniowym modelem scoringowym z komponentami
momentum i mikrostruktury rynku.
\section{Architektura logiczna}
Wersja biezaca ma nastepujacy przeplyw:
\[
\text{Hasura / derived DLOB / candles}
\rightarrow
\text{feature extraction}
\rightarrow
\text{gates}
\rightarrow
\text{score}
\rightarrow
\text{decision event}.
\]
Wersja docelowa doda jeszcze:
\[
\text{decision}
\rightarrow
\text{desired state}
\rightarrow
\text{risk engine}
\rightarrow
\text{order manager}
\rightarrow
\text{execution}.
\]
\section{Dane wejsciowe}
Observer korzysta z dwoch klas danych:
\begin{itemize}
\item swiece z funkcji \texttt{get\_drift\_candles(...)} dla rynku
\texttt{SOL-PERP},
\item znormalizowany snapshot orderbooka z
\texttt{dlob\_hot\_derived\_latest} z fallbackiem do
\texttt{dlob\_all\_derived\_latest}.
\end{itemize}
Z derived read-modelu pobierane sa w szczegolnosci:
\begin{equation}
\texttt{mark\_price},\;
\texttt{oracle\_price},\;
\texttt{mid\_price},\;
\texttt{spread\_bps},\;
\texttt{bids\_norm},\;
\texttt{asks\_norm}.
\label{eq:read-model-fields}
\end{equation}
Kazdy poziom orderbooka po normalizacji ma forme:
\begin{equation}
\ell = (p, q, n),
\label{eq:normalized-level}
\end{equation}
gdzie \(p\) oznacza cene, \(q\) rozmiar w bazie, a \(n\) notional w USD.
\section{Definicje cech}
\subsection{Momentum}
Niech \(c_t\) oznacza cene zamkniecia ostatniej swiecy \(1s\). Dla horyzontu
\(k\) sekund momentum liczymy jako zwrot w basis points zgodnie ze wzorem
\eqref{eq:momentum}:
\begin{equation}
\mathrm{mom}_k(t) = 10^4 \left(\frac{c_t}{c_{t-k}} - 1\right).
\label{eq:momentum}
\end{equation}
W implementacji uzywane sa trzy horyzonty opisane w~\eqref{eq:momentum-horizons}:
\begin{equation}
\mathrm{mom}_{3s},\quad \mathrm{mom}_{10s},\quad \mathrm{mom}_{30s}.
\label{eq:momentum-horizons}
\end{equation}
Na poziomie runtime ten blok modelu powinien byc realizowany przez osobny
\texttt{momentum-service}. Kontrakt serwisu jest nastepujacy:
\begin{itemize}
\item wejscie: swiece \(1s\) z Hasury przez \texttt{get\_drift\_candles(...)} oraz
parametry horyzontow i okna zmiennosci,
\item processing: obliczenie \(\mathrm{mom}_{3s}\), \(\mathrm{mom}_{10s}\),
\(\mathrm{mom}_{30s}\) i \(\mathrm{vol}_{30s}\),
\item wyjscie: wynik wystawiony przez endpoint HTTP mikroserwisu, bez tworzenia
nowego source of truth poza Hasura.
\end{itemize}
\subsection{Zmiennosc}
Dla okna \(W\) sekund budujemy ciag zwrotow:
Zwroty elementarne liczymy wzorem \eqref{eq:returns}:
\begin{equation}
r_i = 10^4 \left(\frac{c_i}{c_{i-1}} - 1\right).
\label{eq:returns}
\end{equation}
Nastepnie liczona jest odchylenie standardowe tych zwrotow wedlug
\eqref{eq:volatility}:
\begin{equation}
\mathrm{vol}_W(t) =
\sqrt{
\frac{1}{N}
\sum_{i=1}^{N}
\left(r_i - \bar r\right)^2
}.
\label{eq:volatility}
\end{equation}
W biezacej wersji observer uzywa:
\begin{equation}
\mathrm{vol}_{30s}.
\label{eq:volatility-window}
\end{equation}
\subsection{Odchylenie mark od oracle}
Niech \(m_t\) oznacza \texttt{mark\_price}, a \(o_t\) oznacza
\texttt{oracle\_price}. Odchylenie mark od oracle liczymy wzorem
\eqref{eq:mvo}:
\begin{equation}
\mathrm{mvo}(t) = 10^4 \left(\frac{m_t}{o_t} - 1\right).
\label{eq:mvo}
\end{equation}
Ta zmienna karze wejscie w trade, gdy mark znacaco odbiega od oracle.
\subsection{Depth w pasmie plus-minus b bps}
Niech \(p_t^{mid}\) oznacza mid-price oraz \(b\) szerokosc pasma w bps.
Do depth po stronie bid bierzemy poziomy spelniajace nierownosc
\eqref{eq:depth-band-bid}:
\begin{equation}
p \ge p_t^{mid}\left(1 - \frac{b}{10^4}\right).
\label{eq:depth-band-bid}
\end{equation}
Do depth po stronie ask bierzemy poziomy spelniajace nierownosc
\eqref{eq:depth-band-ask}:
\begin{equation}
p \le p_t^{mid}\left(1 + \frac{b}{10^4}\right).
\label{eq:depth-band-ask}
\end{equation}
Nastepnie liczymy zagregowany depth po obu stronach zgodnie z
\eqref{eq:depth-bid} oraz \eqref{eq:depth-ask}:
\begin{align}
D^{bid}_b(t) = \sum_{\ell \in \mathcal{B}_b(t)} n_{\ell},
\label{eq:depth-bid}\\
D^{ask}_b(t) = \sum_{\ell \in \mathcal{A}_b(t)} n_{\ell}.
\label{eq:depth-ask}
\end{align}
Imbalance w pasmie definiuje wzor \eqref{eq:imbalance}:
\begin{equation}
I_b(t) =
\frac{D^{bid}_b(t) - D^{ask}_b(t)}
{D^{bid}_b(t) + D^{ask}_b(t)}.
\label{eq:imbalance}
\end{equation}
W praktyce model uzywa:
\begin{equation}
\texttt{depth\_bid\_usd},\quad
\texttt{depth\_ask\_usd},\quad
\texttt{depth\_imbalance}.
\label{eq:depth-feature-set}
\end{equation}
\subsection{Slippage dla zadanego notionalu}
Niech \(Q\) oznacza planowany notional wejscia w USD. Dla strony \emph{buy}
symulujemy konsumowanie kolejnych poziomow z \texttt{asks\_norm}, a dla strony
\emph{sell} z \texttt{bids\_norm}.
VWAP z symulowanego wykonania liczymy wzorem \eqref{eq:vwap}:
\begin{equation}
\mathrm{VWAP}_s(Q, t) =
\frac{\text{filled\_usd}}{\text{filled\_base}}.
\label{eq:vwap}
\end{equation}
Impact w basis points dla strony \emph{buy} i \emph{sell} definiuja odpowiednio
wzory \eqref{eq:slippage-buy} i \eqref{eq:slippage-sell}:
\begin{align}
\mathrm{slip}_{buy}(Q, t) =
10^4 \left(\frac{\mathrm{VWAP}_{buy}(Q, t)}{p_t^{mid}} - 1\right),
\label{eq:slippage-buy}\\
\mathrm{slip}_{sell}(Q, t) =
10^4 \left(1 - \frac{\mathrm{VWAP}_{sell}(Q, t)}{p_t^{mid}}\right).
\label{eq:slippage-sell}
\end{align}
W modelu logowane sa:
\begin{equation}
\texttt{buy\_slippage\_bps},\quad
\texttt{sell\_slippage\_bps}.
\label{eq:slippage-feature-set}
\end{equation}
\subsection{Freshness}
Kazdy snapshot ma znacznik czasu \texttt{updated\_at}. Dla statystyk, depth i
obu slippage liczony jest wiek danych w milisekundach. Ostatecznie maksymalny
wiek danych definiuje wzor \eqref{eq:data-age}:
\begin{equation}
\mathrm{dataAge}(t) = \max(
\mathrm{age}_{stats},
\mathrm{age}_{depth},
\mathrm{age}_{buySlip},
\mathrm{age}_{sellSlip}
).
\label{eq:data-age}
\end{equation}
\section{Bramki decyzyjne}
Decyzja kierunkowa jest dozwolona tylko wtedy, gdy wszystkie bramki sa spelnione.
\subsection{Freshness gate}
Freshness gate korzysta bezposrednio z~\eqref{eq:data-age} i ma postac:
\begin{equation}
\mathrm{dataAge}(t) \le F_{\max}.
\label{eq:gate-freshness}
\end{equation}
\subsection{Spread gate}
Spread gate ma postac:
\begin{equation}
\mathrm{spread}_{bps}(t) \le S_{\max}.
\label{eq:gate-spread}
\end{equation}
\subsection{Slippage gate}
Slippage gate odwoluje sie do \eqref{eq:slippage-buy} i
\eqref{eq:slippage-sell}:
\begin{equation}
\max\left(
\mathrm{slip}_{buy}(Q, t),
\mathrm{slip}_{sell}(Q, t)
\right)
\le L_{\max}.
\label{eq:gate-slippage}
\end{equation}
\subsection{Depth gate}
Depth gate opiera sie na wielkosciach z \eqref{eq:depth-bid} i
\eqref{eq:depth-ask}:
\begin{equation}
\min\left(
D^{bid}_b(t),
D^{ask}_b(t)
\right)
\ge D_{\min}.
\label{eq:gate-depth}
\end{equation}
\subsection{History gate}
Musi istniec wystarczajaca liczba swiec, zeby policzyc najdluzsze momentum:
\begin{equation}
N_{candles} \ge k_{slow} + 1.
\label{eq:gate-history}
\end{equation}
\section{Model scoringowy}
Niech:
\begin{equation}
w_f,\; w_m,\; w_s,\; w_i,\; w_o,\; w_{sp},\; w_{sl}
\label{eq:weights}
\end{equation}
oznaczaja kolejno wagi dla:
\begin{itemize}
\item szybkiego momentum,
\item sredniego momentum,
\item skladnika reversal z dlugiego momentum,
\item imbalance,
\item odchylenia mark-vs-oracle,
\item spread,
\item slippage.
\end{itemize}
Score dla long definiuje rownanie \eqref{eq:score-long}:
\begin{equation}
\mathrm{Score}_{long}(t) =
w_f \, \mathrm{mom}_{3s}(t)
{}+ w_m \, \mathrm{mom}_{10s}(t)
{}- w_s \, \mathrm{mom}_{30s}(t)
{}+ w_i \, I_b(t)
{}- w_o \, \mathrm{mvo}(t)
{}- w_{sp} \, \mathrm{spread}_{bps}(t)
{}- w_{sl} \, \mathrm{slip}_{buy}(Q, t).
\label{eq:score-long}
\end{equation}
Score dla short definiuje rownanie \eqref{eq:score-short}:
\begin{equation}
\mathrm{Score}_{short}(t) =
- w_f \, \mathrm{mom}_{3s}(t)
- w_m \, \mathrm{mom}_{10s}(t)
{}+ w_s \, \mathrm{mom}_{30s}(t)
{}- w_i \, I_b(t)
{}+ w_o \, \mathrm{mvo}(t)
{}- w_{sp} \, \mathrm{spread}_{bps}(t)
{}- w_{sl} \, \mathrm{slip}_{sell}(Q, t).
\label{eq:score-short}
\end{equation}
Interpretacja:
\begin{itemize}
\item dodatnie krotkie momentum wzmacnia long i oslabia short,
\item dodatnie dlugie momentum jest traktowane kontrariansko przez skladnik
reversal,
\item dodatni imbalance po stronie bid wspiera long,
\item zbyt duzy spread i zbyt duzy slippage karza obie strony,
\item wysokie mark-vs-oracle dziala anty-long i pro-short.
\end{itemize}
\section{Regula decyzji}
Jesli dowolna bramka nie przechodzi, wynik to:
\begin{equation}
\texttt{side} = \texttt{flat}, \qquad \texttt{skipReason} = \texttt{gate\_failed}.
\label{eq:decision-gate-failed}
\end{equation}
W przeciwnym razie kierunek wybieramy wedlug \eqref{eq:decision-side}:
\begin{equation}
\texttt{side} =
\arg\max\left(
\mathrm{Score}_{long}(t),
\mathrm{Score}_{short}(t)
\right).
\label{eq:decision-side}
\end{equation}
Niech
\begin{equation}
\mathrm{Score}_{best}(t) =
\max\left(
\mathrm{Score}_{long}(t),
\mathrm{Score}_{short}(t)
\right).
\label{eq:score-best}
\end{equation}
Jesli zachodzi warunek progowy \eqref{eq:decision-threshold-test}:
\begin{equation}
\left|\mathrm{Score}_{best}(t)\right| < \Theta,
\label{eq:decision-threshold-test}
\end{equation}
to wynik rowniez jest:
\begin{equation}
\texttt{side} = \texttt{flat}, \qquad \texttt{skipReason} = \texttt{below\_threshold}.
\label{eq:decision-below-threshold}
\end{equation}
Jesli prog jest przekroczony, bot produkuje sygnal:
\begin{equation}
\texttt{targetNotionalUsd} = Q,
\qquad
\texttt{horizonSeconds} = H.
\label{eq:decision-target}
\end{equation}
Confidence jest normalizowane liniowo zgodnie ze wzorem
\eqref{eq:decision-confidence}:
\begin{equation}
\texttt{confidence} =
\min\left(
0.99,
\max\left(0, \frac{|\mathrm{Score}_{best}(t)|}{3 \Theta}\right)
\right).
\label{eq:decision-confidence}
\end{equation}
\section{Parametry modelu i wartosci domyslne}
\begin{longtable}{lll}
\toprule
Grupa & Parametr & Domyslna wartosc \\
\midrule
\endhead
loop & \texttt{decision\_interval\_ms} & \(1000\) ms \\
loop & \texttt{candle\_bucket\_seconds} & \(1\) s \\
loop & \texttt{candles\_limit} & \(64\) \\
features & \texttt{mom\_fast\_s} & \(3\) \\
features & \texttt{mom\_mid\_s} & \(10\) \\
features & \texttt{mom\_slow\_s} & \(30\) \\
features & \texttt{vol\_window\_s} & \(30\) \\
features & \texttt{depth\_band\_bps} & \(10\) \\
features & \texttt{slippage\_size\_usd} & \(500\) \\
gates & \texttt{freshness\_max\_ms} & \(800\) ms \\
gates & \texttt{spread\_max\_bps} & \(8\) bps \\
gates & \texttt{slippage\_max\_bps} & \(12\) bps \\
gates & \texttt{depth\_band\_min\_usd} & \(3000\) USD \\
sizing & \texttt{target\_notional\_usd} & \(500\) USD \\
decision & \texttt{threshold} & \(1.2\) \\
decision & \texttt{horizon\_s} & \(60\) s \\
weights & \texttt{mom\_fast} & \(0.9\) \\
weights & \texttt{mom\_mid} & \(0.35\) \\
weights & \texttt{mom\_slow\_reversal} & \(0.55\) \\
weights & \texttt{imbalance} & \(5.0\) \\
weights & \texttt{mark\_vs\_oracle} & \(0.08\) \\
weights & \texttt{spread} & \(0.15\) \\
weights & \texttt{slippage} & \(0.12\) \\
\bottomrule
\end{longtable}
\section{Co bot zapisuje na kazdym ticku}
Na kazdym przebiegu petli zapisywane sa:
\begin{itemize}
\item czasy i wiek danych:
\texttt{query\_latency\_ms}, \texttt{data\_age\_ms},
\texttt{stats\_updated\_at}, \texttt{depth\_updated\_at},
\texttt{buy\_slippage\_updated\_at}, \texttt{sell\_slippage\_updated\_at},
\item cechy:
\texttt{mark\_price}, \texttt{oracle\_price}, \texttt{mid\_price},
\texttt{spread\_bps}, \texttt{depth\_bid\_usd}, \texttt{depth\_ask\_usd},
\texttt{depth\_imbalance}, \texttt{buy\_slippage\_bps},
\texttt{sell\_slippage\_bps}, \texttt{mark\_vs\_oracle\_bps},
\texttt{mom\_3s}, \texttt{mom\_10s}, \texttt{mom\_30s}, \texttt{vol\_30s},
\item status gates:
\texttt{fresh}, \texttt{spread\_ok}, \texttt{slippage\_ok},
\texttt{depth\_ok}, \texttt{has\_candles},
\item wynik:
\texttt{side}, \texttt{confidence}, \texttt{long\_score},
\texttt{short\_score}, \texttt{target\_notional\_usd},
\texttt{skip\_reason}.
\end{itemize}
To daje pelny material do strojenia progow, wag i pozniejszego przejscia z
observera do executora.
\section{Wnioski}
Aktualny bot jest formalnie:
\begin{itemize}
\item deterministycznym baseline'em,
\item modelem liniowym z recznie dobranymi wagami,
\item filtrem wejsc opartym o jakosc mikrostruktury,
\item systemem zbierania danych do pozniejszego modelu tradingowego.
\end{itemize}
Najwazniejszy kolejny krok to rozdzielenie:
\begin{equation}
\text{signal} \neq \text{trade}.
\label{eq:signal-not-trade}
\end{equation}
Observer powinien dalej generowac sygnal i dane treningowe, a przyszly executor
powinien osobno realizowac risk management, order management i kill switch.
\end{document}