\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}