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