501 lines
14 KiB
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}
|