chore: initial import
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
*.tmp
|
||||||
|
*.log
|
||||||
6
README.md
Normal file
6
README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# trade-doc
|
||||||
|
|
||||||
|
Dokumentacja całego projektu `trade` (migracja k3s/GitOps/CI, log działań).
|
||||||
|
|
||||||
|
- Plan migracji: `migration.md`
|
||||||
|
- Log działań: `steps.md`
|
||||||
338
bot-plan.html
Normal file
338
bot-plan.html
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="pl">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Bot: analiza wielu par na Solanie + sygnał wejścia (top 10)</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: light dark;
|
||||||
|
--bg: #0b1020;
|
||||||
|
--panel: rgba(255, 255, 255, 0.06);
|
||||||
|
--text: rgba(255, 255, 255, 0.92);
|
||||||
|
--muted: rgba(255, 255, 255, 0.68);
|
||||||
|
--accent: #7c3aed;
|
||||||
|
--border: rgba(255, 255, 255, 0.12);
|
||||||
|
--shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
--bg: #f6f7fb;
|
||||||
|
--panel: rgba(0, 0, 0, 0.04);
|
||||||
|
--text: rgba(0, 0, 0, 0.88);
|
||||||
|
--muted: rgba(0, 0, 0, 0.62);
|
||||||
|
--accent: #6d28d9;
|
||||||
|
--border: rgba(0, 0, 0, 0.10);
|
||||||
|
--shadow: 0 12px 40px rgba(15, 23, 42, 0.14);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial,
|
||||||
|
"Apple Color Emoji", "Segoe UI Emoji";
|
||||||
|
line-height: 1.55;
|
||||||
|
background: radial-gradient(1200px 600px at 20% -10%, rgba(124, 58, 237, 0.28), transparent),
|
||||||
|
radial-gradient(900px 500px at 95% 10%, rgba(59, 130, 246, 0.18), transparent),
|
||||||
|
var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 48px auto;
|
||||||
|
padding: 0 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
padding: 22px 22px 18px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.03));
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 26px;
|
||||||
|
margin: 0 0 6px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
margin-top: 18px;
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--panel);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 18px 18px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 14px 0 8px;
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
margin: 8px 0 0 18px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||||
|
"Courier New", monospace;
|
||||||
|
font-size: 0.95em;
|
||||||
|
padding: 0.05em 0.35em;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.12em 0.55em;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: rgba(124, 58, 237, 0.12);
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>Bot: analiza wielu par na Solanie + sygnał wejścia (top 10)</h1>
|
||||||
|
<p class="subtitle">
|
||||||
|
Specyfikacja funkcjonalna: skanowanie wielu rynków, ranking i generowanie sygnałów (z opcją
|
||||||
|
egzekucji).
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
<h2>Cel</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Monitoruje i analizuje dane z kilkudziesięciu par/rynków na Solanie.</li>
|
||||||
|
<li>
|
||||||
|
Wylicza sygnał wejścia (<em>long/short</em> lub <em>buy/sell</em>) i wybiera ~10
|
||||||
|
najlepszych okazji.
|
||||||
|
</li>
|
||||||
|
<li>Opcjonalnie automatycznie składa zlecenia (po dopracowaniu ryzyka).</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Założenia / pytania decyzyjne <span class="pill">ustalić na start</span></h2>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
Czy handlujemy <strong>SPOT</strong> czy <strong>PERPS</strong>?
|
||||||
|
<ul>
|
||||||
|
<li>SPOT: swap + ewentualnie limit (zależnie od venue).</li>
|
||||||
|
<li>PERPS: leverage, funding, osobne ryzyka i mechanika zleceń.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Interwał i horyzont: skaner 5s / 30s / 1m? Sygnał intraday czy swing?</li>
|
||||||
|
<li>Typy sygnałów: momentum, mean reversion, breakout, funding-arb, basis, orderflow.</li>
|
||||||
|
<li>
|
||||||
|
Ograniczenia: max dźwignia, max ekspozycja per rynek, max łączna ekspozycja, SL/TP.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Źródła danych (Solana)</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
RPC: stan kont, transakcje, price feeds, state orderbook/AMM (Helius/Alchemy/QuickNode).
|
||||||
|
</li>
|
||||||
|
<li>Oracle: Pyth (ceny), Switchboard (alternatywnie).</li>
|
||||||
|
<li>
|
||||||
|
Dane DEX:
|
||||||
|
<ul>
|
||||||
|
<li>AMM (Orca/Raydium): pool state, price, liquidity, tick.</li>
|
||||||
|
<li>Orderbook (Phoenix/OpenBook): book depth, spread, imbalance.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Dane PERPS:
|
||||||
|
<ul>
|
||||||
|
<li>Drift: mark price, oracle, funding, OI, pozycje, margin, perp state (SDK).</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Architektura (proponowana)</h2>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<strong>Universe (lista rynków)</strong>
|
||||||
|
<ul>
|
||||||
|
<li>Start: 30–80 rynków (top wolumen / top OI / ręcznie wybrane).</li>
|
||||||
|
<li>Aktualizacja listy raz dziennie lub co godzinę.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Collector (pobieranie danych)</strong>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Polling (bez batch) + cache: ceny (Pyth/oracle), wolumen/OI/funding (perps),
|
||||||
|
spread/depth (orderbook), liquidity/TVL (AMM).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Normalizacja do jednego formatu:
|
||||||
|
<code>timestamp, symbol/marketId, price, returns, vol, spread, depth, funding, OI, liquidity</code>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Feature engineering (cechy)</strong>
|
||||||
|
<ul>
|
||||||
|
<li>returns (1m/5m/15m), RSI, EMA cross, ATR/volatility,</li>
|
||||||
|
<li>orderbook imbalance, spread %, slippage estymowana dla rozmiaru pozycji,</li>
|
||||||
|
<li>perps: funding (aktualny + trend), basis, utilization, OI change.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Scoring (ranking i selekcja top N)</strong>
|
||||||
|
<ul>
|
||||||
|
<li>wynik = f(signal_strength, liquidity_score, cost_score, risk_score)</li>
|
||||||
|
<li>filtr: minimalna płynność / maksymalny spread / maks. slippage</li>
|
||||||
|
<li>wybór: top 10 rynków do wejścia lub do obserwacji</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Execution (składanie zleceń)</strong> – dopiero po stabilizacji
|
||||||
|
<ul>
|
||||||
|
<li>tryb “paper” (log + symulacja) → tryb “live” (małe size)</li>
|
||||||
|
<li>
|
||||||
|
risk manager przed wysłaniem:
|
||||||
|
<ul>
|
||||||
|
<li>max size per rynek</li>
|
||||||
|
<li>max łączna ekspozycja</li>
|
||||||
|
<li>cooldown, max liczba otwartych pozycji</li>
|
||||||
|
<li>warunki anulowania (np. slippage > X)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Observability</strong>
|
||||||
|
<ul>
|
||||||
|
<li>logi sygnałów i decyzji</li>
|
||||||
|
<li>metryki: winrate, MDD, slippage, koszt fee, latency</li>
|
||||||
|
<li>alerty: RPC errors, divergence oracle/mark, brak danych</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Jaki dApp jest najlepszy do bota?</h2>
|
||||||
|
<p>
|
||||||
|
To zależy, czy chcesz SPOT czy PERPS. Kryteria “najlepszy” dla bota:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>stabilne API/SDK (TS/Python), dobre typy i przykłady,</li>
|
||||||
|
<li>zlecenia limit/market i sensowna płynność,</li>
|
||||||
|
<li>bezpieczny model kluczy (delegate / subaccount / ograniczenia),</li>
|
||||||
|
<li>koszty (fees, slippage) i ryzyka (leverage/funding).</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Rekomendacja (PERPS, sygnały na 10 rynkach)</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Drift</strong> jest najpraktyczniejszy, bo ma dojrzałe SDK
|
||||||
|
(<code>@drift-labs/sdk</code>), workflow delegate i spójny system ryzyk (margin/health).
|
||||||
|
</li>
|
||||||
|
<li>Jest to spójne z obecnym repozytorium (workflow Drift + delegate).</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Rekomendacja (SPOT, egzekucja swapów)</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Jupiter</strong> zwykle najlepiej nadaje się do wykonania swapów (routing,
|
||||||
|
egzekucja, slippage).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Jeśli potrzebujesz limit orders na SPOT: Phoenix/OpenBook (większa złożoność:
|
||||||
|
orderbook + cancel/replace).
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Proponowany “hybrid”</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Sygnał niezależny od venue (feature store + ranking).</li>
|
||||||
|
<li>SPOT: egzekucja przez Jupiter.</li>
|
||||||
|
<li>PERPS: egzekucja na Drift.</li>
|
||||||
|
<li>Dane: oracle + metryki DEX + metryki perps.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Minimalny MVP (1–2 tygodnie, bez live trading)</h2>
|
||||||
|
<ol>
|
||||||
|
<li>Universe 30–50 rynków + konfiguracja w pliku.</li>
|
||||||
|
<li>Collector: ceny + podstawowe metryki per rynek (wspólny format).</li>
|
||||||
|
<li>Ranking: prosty score (momentum + liquidity − cost).</li>
|
||||||
|
<li>
|
||||||
|
Output co X sekund:
|
||||||
|
<ul>
|
||||||
|
<li>top 10 rynków</li>
|
||||||
|
<li>strona (long/short)</li>
|
||||||
|
<li>siła sygnału</li>
|
||||||
|
<li>est. slippage</li>
|
||||||
|
<li>uzasadnienie (cechy)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Backtest “na sucho”: zapis sygnałów do CSV/JSONL, analiza w Pythonie.</li>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Następne kroki</h2>
|
||||||
|
<ol>
|
||||||
|
<li>Potwierdź: SPOT czy PERPS (czy oba)?</li>
|
||||||
|
<li>Podaj listę par (albo kryterium wyboru).</li>
|
||||||
|
<li>Potwierdź częstotliwość skanowania (np. 10s/30s/60s) i horyzont.</li>
|
||||||
|
<li>Powiedz, czy bot ma tylko sygnalizować, czy też automatycznie tradować.</li>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
99
bot-plan.txt
Normal file
99
bot-plan.txt
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
BOT: analiza wielu par na Solanie + sygnał wejścia (top 10)
|
||||||
|
|
||||||
|
Cel
|
||||||
|
- Zbudować bota, który:
|
||||||
|
- monitoruje i analizuje dane z kilkudziesięciu par/rynków na Solanie,
|
||||||
|
- wylicza sygnał wejścia (long/short lub buy/sell) i wybiera ~10 najlepszych okazji,
|
||||||
|
- opcjonalnie automatycznie składa zlecenia (po wcześniejszym dopracowaniu ryzyka).
|
||||||
|
|
||||||
|
Założenia / pytania decyzyjne (ustalić na start)
|
||||||
|
1) Czy handlujemy SPOT czy PERPS?
|
||||||
|
- SPOT: swap + ewentualnie limit (zależnie od venue).
|
||||||
|
- PERPS: leverage, funding, osobne ryzyka i mechanika zleceń.
|
||||||
|
2) Interwał i horyzont: skaner 5s / 30s / 1m? Sygnał intraday czy swing?
|
||||||
|
3) Jakie typy sygnałów: momentum, mean reversion, breakout, funding-arb, basis, orderflow?
|
||||||
|
4) Jakie ograniczenia: max dźwignia, max ekspozycja per rynek, max łączna ekspozycja, SL/TP?
|
||||||
|
|
||||||
|
Źródła danych (Solana)
|
||||||
|
- RPC (stan kont, transakcje, price feeds, orderbook/AMM state): Helius/Alchemy/QuickNode (zależnie od budżetu).
|
||||||
|
- Oracle: Pyth (ceny), Switchboard (alternatywnie).
|
||||||
|
- Dane DEX:
|
||||||
|
- AMM (Orca/Raydium): pool state, price, liquidity, tick.
|
||||||
|
- Orderbook (Phoenix/OpenBook): book depth, spread, imbalance.
|
||||||
|
- Dane PERPS:
|
||||||
|
- Drift: mark price, oracle, funding, OI, pozycje, margin, orderbook/perp state (przez Drift SDK).
|
||||||
|
|
||||||
|
Architektura (proponowana)
|
||||||
|
1) Universe (lista rynków)
|
||||||
|
- Start: 30–80 rynków (np. top wolumen / top open interest / ręcznie wybrane).
|
||||||
|
- Aktualizacja listy raz dziennie lub co godzinę.
|
||||||
|
2) Collector (pobieranie danych)
|
||||||
|
- Polling (bez batch) + cache:
|
||||||
|
- ceny (Pyth/oracle),
|
||||||
|
- wolumen / OI / funding (perps),
|
||||||
|
- spread/depth (orderbook),
|
||||||
|
- liquidity/TVL (AMM).
|
||||||
|
- Normalizacja do jednego formatu:
|
||||||
|
- timestamp, symbol/marketId, price, returns, vol, spread, depth, funding, OI, liquidity.
|
||||||
|
3) Feature engineering (cechy)
|
||||||
|
- returns (1m/5m/15m), RSI, EMA cross, ATR/volatility,
|
||||||
|
- orderbook imbalance, spread %, slippage estymowana dla rozmiaru pozycji,
|
||||||
|
- perps: funding (aktualny + trend), basis, utilization, OI change.
|
||||||
|
4) Scoring (ranking i selekcja top N)
|
||||||
|
- wynik = f(signal_strength, liquidity_score, cost_score, risk_score)
|
||||||
|
- filtr: minimalna płynność / maksymalny spread / maks. slippage
|
||||||
|
- wybór: top 10 rynków do wejścia lub do obserwacji.
|
||||||
|
5) Execution (składanie zleceń) – dopiero po stabilizacji
|
||||||
|
- tryb “paper” (log + symulacja) -> tryb “live” (małe size).
|
||||||
|
- risk manager przed wysłaniem zlecenia:
|
||||||
|
- max size per rynek,
|
||||||
|
- max łączna ekspozycja,
|
||||||
|
- cooldown, max liczba otwartych pozycji,
|
||||||
|
- warunki anulowania (np. slippage > X).
|
||||||
|
6) Observability
|
||||||
|
- logi sygnałów i decyzji,
|
||||||
|
- metryki (winrate, MDD, slippage, koszt fee, latency),
|
||||||
|
- alerty (RPC errors, divergence oracle/mark, brak danych).
|
||||||
|
|
||||||
|
Jaki dApp jest najlepszy do bota?
|
||||||
|
To zależy od tego, czy chcesz SPOT czy PERPS. Praktyczne kryteria “najlepszy” dla bota:
|
||||||
|
- Czy ma stabilne API/SDK (TS/Python), dobre typy i przykłady?
|
||||||
|
- Czy obsługuje zlecenia limit/market i ma sensowną płynność?
|
||||||
|
- Czy pozwala na bezpieczny model kluczy (delegate / subaccount / ograniczenia)?
|
||||||
|
- Jakie są koszty (fees, slippage) i ryzyka (leverage/funding)?
|
||||||
|
|
||||||
|
Rekomendacja (jeśli celem jest PERPS i sygnały na 10 rynkach)
|
||||||
|
- Drift (PERPS) jest najpraktyczniejszy dla bota na Solanie, bo:
|
||||||
|
- ma dojrzałe SDK (@drift-labs/sdk) i workflow delegate (hot key do tradingu),
|
||||||
|
- umożliwia margin/perps, typowe order types i subaccounty,
|
||||||
|
- ma jeden spójny system ryzyk (margin, health), co ułatwia kontrolę ekspozycji.
|
||||||
|
- To jest też spójne z obecnym repozytorium (workflow Drift + delegate + SOL-PERP).
|
||||||
|
|
||||||
|
Rekomendacja (jeśli celem jest SPOT i egzekucja swapów)
|
||||||
|
- Jupiter (agregator) jest zwykle najlepszy do wykonania swapów na SPOT:
|
||||||
|
- optymalizuje routing, upraszcza egzekucję,
|
||||||
|
- łatwo wycenić i policzyć slippage dla różnych par.
|
||||||
|
- Jeśli potrzebujesz limit orders na SPOT:
|
||||||
|
- rozważ venue orderbook (Phoenix/OpenBook) albo dApp, które natywnie wspiera limity,
|
||||||
|
- kosztem większej złożoności (orderbook + cancel/replace).
|
||||||
|
|
||||||
|
Proponowany “hybrid” (często najlepszy w praktyce)
|
||||||
|
- Sygnał generujesz niezależnie od venue (feature store + ranking),
|
||||||
|
- SPOT wykonujesz przez Jupiter (najlepsza egzekucja),
|
||||||
|
- PERPS wykonujesz na Drift (leverage + perps),
|
||||||
|
- Dane rynkowe mieszasz: oracle + DEX metrics + perps metrics.
|
||||||
|
|
||||||
|
Minimalny MVP (1–2 tygodnie, bez live trading)
|
||||||
|
1) Universe 30–50 rynków + konfiguracja w pliku.
|
||||||
|
2) Collector: ceny + podstawowe metryki per rynek (wspólny format).
|
||||||
|
3) Ranking: prosty score (momentum + liquidity - cost).
|
||||||
|
4) Output: co X sekund zapis:
|
||||||
|
- top 10 rynków, strona (long/short), siła sygnału, est. slippage, uzasadnienie (cechy).
|
||||||
|
5) Backtest “na sucho”:
|
||||||
|
- zapis sygnałów do CSV/JSONL, później analiza w Pythonie.
|
||||||
|
|
||||||
|
Następne kroki (żeby doprecyzować i dobrać dApp ostatecznie)
|
||||||
|
1) Potwierdź: SPOT czy PERPS (czy oba)?
|
||||||
|
2) Podaj listę par (albo kryterium wyboru).
|
||||||
|
3) Potwierdź częstotliwość skanowania (np. 10s/30s/60s) i horyzont.
|
||||||
|
4) Powiedz, czy bot ma tylko sygnalizować, czy też automatycznie tradować.
|
||||||
234
dapp-choice.html
Normal file
234
dapp-choice.html
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="pl">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Jaki dApp najlepszy pod bota (Solana)?</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: light dark;
|
||||||
|
--bg: #0b1020;
|
||||||
|
--panel: rgba(255, 255, 255, 0.06);
|
||||||
|
--text: rgba(255, 255, 255, 0.92);
|
||||||
|
--muted: rgba(255, 255, 255, 0.68);
|
||||||
|
--accent: #7c3aed;
|
||||||
|
--border: rgba(255, 255, 255, 0.12);
|
||||||
|
--shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
--bg: #f6f7fb;
|
||||||
|
--panel: rgba(0, 0, 0, 0.04);
|
||||||
|
--text: rgba(0, 0, 0, 0.88);
|
||||||
|
--muted: rgba(0, 0, 0, 0.62);
|
||||||
|
--accent: #6d28d9;
|
||||||
|
--border: rgba(0, 0, 0, 0.10);
|
||||||
|
--shadow: 0 12px 40px rgba(15, 23, 42, 0.14);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial,
|
||||||
|
"Apple Color Emoji", "Segoe UI Emoji";
|
||||||
|
line-height: 1.55;
|
||||||
|
background: radial-gradient(1200px 600px at 20% -10%, rgba(124, 58, 237, 0.28), transparent),
|
||||||
|
radial-gradient(900px 500px at 95% 10%, rgba(59, 130, 246, 0.18), transparent),
|
||||||
|
var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 48px auto;
|
||||||
|
padding: 0 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
padding: 22px 22px 18px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.03));
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 26px;
|
||||||
|
margin: 0 0 6px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
margin-top: 18px;
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--panel);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 18px 18px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
margin: 8px 0 0 18px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||||
|
"Courier New", monospace;
|
||||||
|
font-size: 0.95em;
|
||||||
|
padding: 0.05em 0.35em;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.12em 0.55em;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: rgba(124, 58, 237, 0.12);
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 650;
|
||||||
|
font-size: 12px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer strong {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>Jaki dApp jest najlepszy pod bota (Solana)?</h1>
|
||||||
|
<p class="subtitle">
|
||||||
|
Jeśli bot ma analizować dziesiątki rynków i generować sygnały wejścia na top 10, to
|
||||||
|
wybór “dAppa” zależy od tego, czy chcesz <strong>PERPS</strong> czy <strong>SPOT</strong>.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
<h2>Najkrótsza odpowiedź <span class="pill">top choice</span></h2>
|
||||||
|
<p class="answer">
|
||||||
|
<strong>PERPS:</strong> Drift • <strong>SPOT (egzekucja swapów):</strong>
|
||||||
|
Jupiter (a sygnały liczysz w swoim bocie)
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Dlaczego “dApp do swapów” to za mało?</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
“Sygnał” to nie przycisk w UI, tylko proces: dane → cechy → ranking → filtr kosztów →
|
||||||
|
decyzja.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Potrzebujesz stabilnego <strong>SDK/API</strong> do automatyzacji, a nie tylko
|
||||||
|
interfejsu dla człowieka.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Przy “top 10 z 50 rynków” kluczowe są koszty (spread/slippage/fees) i kontrola ryzyka
|
||||||
|
(limity ekspozycji), których UI zwykle nie gwarantuje.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>PERPS → Drift (najbardziej praktyczne pod bota)</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Jeden spójny system perps: margin/health, funding, OI, mark vs oracle.</li>
|
||||||
|
<li>
|
||||||
|
Naturalny model bezpieczeństwa: authority (cold/Ledger) + delegate/hot key do tradingu.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Łatwiej zbudować ranking “okazji” między wieloma rynkami, bo metryki są porównywalne
|
||||||
|
(np. funding, basis, zmiana OI, momentum).
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>SPOT → Jupiter (egzekucja), sygnał po Twojej stronie</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Jupiter jest świetny do wykonania swapów na wielu parach (routing + wycena slippage).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Ale Jupiter nie jest “silnikiem sygnałów” — sygnał i ranking robisz w swoim
|
||||||
|
<strong>collector/scorer</strong>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Jeśli potrzebujesz limit orders na SPOT, zwykle dochodzi orderbook (Phoenix/OpenBook)
|
||||||
|
i robi się to bardziej złożone (cancel/replace, kolejka, częściowe fill).
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Jak wygląda sensowny podział systemu</h2>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<strong>Dane</strong>: oracle (np. Pyth) + metryki DEX/perps + płynność/spread/depth.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Ranking</strong>: score = siła sygnału − koszt (slippage/spread) − ryzyko
|
||||||
|
(płynność, limity, zmienność).
|
||||||
|
</li>
|
||||||
|
<li><strong>Selekcja</strong>: wybór top 10 (z filtrami minimalnej jakości).</li>
|
||||||
|
<li>
|
||||||
|
<strong>Egzekucja</strong>: Drift (PERPS) albo Jupiter (SPOT) + risk manager przed
|
||||||
|
transakcją.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Co musisz doprecyzować (żeby “najlepszy dApp” był jednoznaczny)</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Chcesz sygnały pod <strong>PERPS</strong> czy <strong>SPOT</strong> (czy oba)?</li>
|
||||||
|
<li>Bot ma tylko sygnalizować, czy automatycznie wchodzić?</li>
|
||||||
|
<li>Interwał skanowania (np. 10s/30s/60s) i horyzont (intraday vs swing).</li>
|
||||||
|
<li>Docelowy rozmiar pozycji (bo koszt i slippage zależą od size).</li>
|
||||||
|
</ul>
|
||||||
|
<p class="subtitle" style="margin-top: 10px">
|
||||||
|
Tip: jeśli celem jest perps + top 10 sygnałów, zwykle wygrywa Drift; jeśli celem jest
|
||||||
|
czysty spot-execution, wygrywa Jupiter (ale sygnał nadal jest “Twój”).
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
150
gitea-k3s-rv32i.md
Normal file
150
gitea-k3s-rv32i.md
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
# Gitea na k3s (Traefik + cert-manager + Let’s Encrypt) dla `rv32i.pl`
|
||||||
|
|
||||||
|
Ten dokument instaluje Gitea w k3s:
|
||||||
|
- Ingress: Traefik
|
||||||
|
- TLS: cert-manager + Let’s Encrypt (`ClusterIssuer` = `letsencrypt-prod`)
|
||||||
|
- Host: `rv32i.pl`
|
||||||
|
- Secret TLS: `rv32i-pl-tls`
|
||||||
|
|
||||||
|
## Ważne (hasła/sekrety)
|
||||||
|
|
||||||
|
Nie wstawiam i nie zalecam trzymania haseł w plikach w repo (`doc/`), bo to zwykle kończy się wyciekiem (git history, backupy, screeny).
|
||||||
|
|
||||||
|
Zamiast tego:
|
||||||
|
- trzymamy hasła w **Kubernetes Secret** (w klastrze),
|
||||||
|
- a w dokumentacji zostawiamy **placeholderey** i komendy, które proszą o hasło interaktywnie.
|
||||||
|
|
||||||
|
Jeśli mimo wszystko chcesz „na sztywno” wpisać hasła do pliku, podaj je jawnie w wiadomości — ale to jest zła praktyka.
|
||||||
|
|
||||||
|
## 0) Wymagania
|
||||||
|
|
||||||
|
1) `cert-manager` działa, a `ClusterIssuer` jest gotowy:
|
||||||
|
```bash
|
||||||
|
sudo k3s kubectl get clusterissuer letsencrypt-prod
|
||||||
|
```
|
||||||
|
|
||||||
|
2) DNS wskazuje na VPS:
|
||||||
|
```bash
|
||||||
|
dig +short rv32i.pl A
|
||||||
|
```
|
||||||
|
Oczekiwane: `77.90.8.171`
|
||||||
|
|
||||||
|
3) Porty 80/443 są otwarte z internetu (firewall/ACL u providera).
|
||||||
|
|
||||||
|
## 1) Instalacja Helm (jeśli nie masz)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm version || true
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | sudo bash
|
||||||
|
helm version
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2) Namespace
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo k3s kubectl create namespace gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3) Secret z kontem admina (bez wpisywania hasła do historii)
|
||||||
|
|
||||||
|
Ten krok tworzy w klastrze sekret `gitea-admin` z loginem/hasłem/email admina.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
read -rp "Gitea admin username: " GITEA_ADMIN_USER
|
||||||
|
read -rsp "Gitea admin password: " GITEA_ADMIN_PASS; echo
|
||||||
|
read -rp "Gitea admin email: " GITEA_ADMIN_EMAIL
|
||||||
|
|
||||||
|
sudo k3s kubectl -n gitea create secret generic gitea-admin \
|
||||||
|
--from-literal=username="$GITEA_ADMIN_USER" \
|
||||||
|
--from-literal=password="$GITEA_ADMIN_PASS" \
|
||||||
|
--from-literal=email="$GITEA_ADMIN_EMAIL"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4) Helm values (Ingress + TLS + Postgres w klastrze)
|
||||||
|
|
||||||
|
Utwórz plik `gitea-values.yaml` na VPS (nie musi być w repo):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat <<'YAML' > gitea-values.yaml
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: traefik
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
hosts:
|
||||||
|
- host: rv32i.pl
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls:
|
||||||
|
- secretName: rv32i-pl-tls
|
||||||
|
hosts:
|
||||||
|
- rv32i.pl
|
||||||
|
|
||||||
|
gitea:
|
||||||
|
admin:
|
||||||
|
existingSecret: gitea-admin
|
||||||
|
config:
|
||||||
|
server:
|
||||||
|
DOMAIN: rv32i.pl
|
||||||
|
ROOT_URL: https://rv32i.pl/
|
||||||
|
PROTOCOL: http
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
storageClass: local-path
|
||||||
|
size: 10Gi
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
enabled: true
|
||||||
|
primary:
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
storageClass: local-path
|
||||||
|
size: 10Gi
|
||||||
|
YAML
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5) Instalacja Gitea
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm repo add gitea-charts https://dl.gitea.com/charts/
|
||||||
|
helm repo update
|
||||||
|
|
||||||
|
helm upgrade --install gitea gitea-charts/gitea \
|
||||||
|
-n gitea \
|
||||||
|
-f gitea-values.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6) Sprawdzenie
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo k3s kubectl -n gitea get pods -o wide
|
||||||
|
sudo k3s kubectl -n gitea get ingress -o wide
|
||||||
|
sudo k3s kubectl -n gitea get certificate -o wide
|
||||||
|
sudo k3s kubectl -n gitea get order,challenge
|
||||||
|
```
|
||||||
|
|
||||||
|
Wejdź na:
|
||||||
|
- `https://rv32i.pl`
|
||||||
|
|
||||||
|
Jeśli `Certificate` nie robi się `Ready=True`, to najczęściej:
|
||||||
|
- port 80 nie dochodzi z internetu (Let’s Encrypt HTTP-01),
|
||||||
|
- Ingress nie ma `className: traefik`,
|
||||||
|
- DNS nie wskazuje na VPS.
|
||||||
|
|
||||||
|
## 7) (Opcjonalnie) dostęp do haseł po czasie
|
||||||
|
|
||||||
|
Jeśli chcesz podejrzeć login/email z sekretu (hasło też da się odczytać, ale rób to ostrożnie):
|
||||||
|
```bash
|
||||||
|
sudo k3s kubectl -n gitea get secret gitea-admin -o jsonpath='{.data.username}' | base64 -d; echo
|
||||||
|
sudo k3s kubectl -n gitea get secret gitea-admin -o jsonpath='{.data.email}' | base64 -d; echo
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8) Uwaga o Git przez SSH
|
||||||
|
|
||||||
|
Na VPS port 22 zajmuje systemowy OpenSSH. Na start korzystaj z klonowania przez HTTPS.
|
||||||
|
Jeśli chcesz klonować przez SSH z Gitei:
|
||||||
|
- najprościej wystawić Gitea SSH na innym porcie (np. 2222) i zrobić NodePort/LoadBalancer,
|
||||||
|
- albo zmapować port przez Traefik TCP (wymaga dodatkowej konfiguracji).
|
||||||
|
|
||||||
167
k8s-migracja.md
Normal file
167
k8s-migracja.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# Plan migracji na Kubernetes (mikroserwisy + CI/CD)
|
||||||
|
|
||||||
|
## 1) Co jest dziś w repo (stan wejściowy)
|
||||||
|
|
||||||
|
Ten projekt już ma podział “mikroserwisowy” w Docker Compose:
|
||||||
|
|
||||||
|
- **DB stack** (`devops/db/docker-compose.yml`)
|
||||||
|
- `postgres` (TimescaleDB, port 5432)
|
||||||
|
- `hasura` (GraphQL Engine, port 8080)
|
||||||
|
- `pgadmin` (narzędzie dev, port 5050)
|
||||||
|
- **App stack** (`devops/app/docker-compose.yml`)
|
||||||
|
- `api` = **trade-api** (Node, port 8787, `devops/api/Dockerfile`)
|
||||||
|
- `frontend` = **trade-frontend** (Node + statyczny build visualizera, port 8081, `devops/app/frontend/Dockerfile`)
|
||||||
|
- `ingestor` = worker (Node, `devops/ingestor/Dockerfile`) – opcjonalnie, profil `ingest`
|
||||||
|
- **One-shot bootstrap** (`devops/tools/bootstrap/docker-compose.yml`)
|
||||||
|
- `db-init` (aplikuje SQL `devops/db/initdb/001_init.sql`)
|
||||||
|
- `db-version` / `db-backfill` (migracje “wersji” ticków)
|
||||||
|
- `hasura-bootstrap` (Node script `devops/db/hasura-bootstrap.mjs`: track tabel + permissions + funkcje)
|
||||||
|
|
||||||
|
Konfiguracja i sekrety dziś są w `tokens/*.json` (gitignored) i są montowane jako pliki do kontenerów.
|
||||||
|
|
||||||
|
## 2) Docelowa architektura na K8s
|
||||||
|
|
||||||
|
Minimalny sensowny podział na K8s (1 namespace lub 2):
|
||||||
|
|
||||||
|
- **timescaledb** (stanowe) → `StatefulSet` + `PVC` + `Service`
|
||||||
|
- **hasura** → `Deployment` + `Service`
|
||||||
|
- **trade-api** → `Deployment` + `Service`
|
||||||
|
- **trade-frontend** → `Deployment` + `Service` + `Ingress`
|
||||||
|
- **trade-ingestor** → `Deployment` (1 replika) albo `CronJob` (jeśli chcesz uruchamiać okresowo)
|
||||||
|
- **bootstrap / migracje** → `Job` (odpalane ręcznie albo jako część release’u)
|
||||||
|
|
||||||
|
Opcjonalnie:
|
||||||
|
- `pgadmin` tylko na dev/staging (nieprodukcyjnie).
|
||||||
|
|
||||||
|
## 3) Proponowane zasoby Kubernetes (mapowanie 1:1 z Compose)
|
||||||
|
|
||||||
|
### 3.1 timescaledb (Postgres)
|
||||||
|
- `StatefulSet` z `volumeClaimTemplates` (dane w PVC)
|
||||||
|
- `Service` typu `ClusterIP` (np. `timescaledb:5432`)
|
||||||
|
- Init schematu:
|
||||||
|
- *na pierwszy start* można nadal użyć mechanizmu `/docker-entrypoint-initdb.d` (ConfigMap z SQL),
|
||||||
|
- *dla istniejących wolumenów* potrzebny jest osobny `Job` (odpowiednik `db-init` z compose).
|
||||||
|
|
||||||
|
### 3.2 Hasura
|
||||||
|
- `Deployment` + `Service` (`hasura:8080`)
|
||||||
|
- `Secret` na:
|
||||||
|
- `HASURA_GRAPHQL_ADMIN_SECRET`
|
||||||
|
- klucz JWT (`HASURA_GRAPHQL_JWT_SECRET` / `HASURA_JWT_KEY`)
|
||||||
|
- connection string do Postgresa (albo osobno hasło)
|
||||||
|
- Bootstrap metadanych:
|
||||||
|
- `Job` uruchamiający `devops/db/hasura-bootstrap.mjs` (odpowiednik `hasura-bootstrap`).
|
||||||
|
|
||||||
|
### 3.3 trade-api
|
||||||
|
- `Deployment` + `Service` (`trade-api:8787`)
|
||||||
|
- `readinessProbe`/`livenessProbe`: `GET /healthz` (już istnieje)
|
||||||
|
- Konfiguracja:
|
||||||
|
- env: `HASURA_GRAPHQL_URL`, `TICKS_TABLE`, `CANDLES_FUNCTION`, `APP_VERSION`, `BUILD_TIMESTAMP`
|
||||||
|
- sekret plikowy (jak dziś): `tokens/hasura.json` + `tokens/api.json` → `Secret` montowany do `/app/tokens/*`
|
||||||
|
|
||||||
|
### 3.4 trade-frontend
|
||||||
|
- `Deployment` + `Service` (`trade-frontend:8081`)
|
||||||
|
- `Ingress` wystawiający UI na zewnątrz (TLS opcjonalnie)
|
||||||
|
- `readinessProbe`/`livenessProbe`: `GET /healthz` (już istnieje)
|
||||||
|
- Sekrety plikowe:
|
||||||
|
- `tokens/frontend.json` (basic auth do UI)
|
||||||
|
- `tokens/read.json` (read token do proxy `/api/*`)
|
||||||
|
- oba jako `Secret` montowany do `/tokens/*`
|
||||||
|
- `API_UPSTREAM`: `http://trade-api:8787` (Service DNS)
|
||||||
|
|
||||||
|
### 3.5 trade-ingestor
|
||||||
|
- `Deployment` (zwykle `replicas: 1`)
|
||||||
|
- Sekrety:
|
||||||
|
- RPC do Drift (np. `tokens/heliusN.json` / `rpcUrl` / `heliusApiKey`)
|
||||||
|
- write token do API (`tokens/alg.json`)
|
||||||
|
- Konfiguracja:
|
||||||
|
- `MARKET_NAME`, `INTERVAL_MS`, `SOURCE`, `INGEST_API_URL=http://trade-api:8787`
|
||||||
|
- Uwaga: worker wymaga egress do internetu (Helius RPC + Drift).
|
||||||
|
|
||||||
|
### 3.6 Jobs: db-init / db-version / db-backfill / hasura-bootstrap
|
||||||
|
Bezpieczny pattern:
|
||||||
|
- uruchamiane manualnie (kubectl) albo jako “hook” w Helm (pre/post-install)
|
||||||
|
- odpalane w tym samym namespace co DB/Hasura (żeby DNS działał prosto)
|
||||||
|
|
||||||
|
## 4) Sekrety i konfiguracja (z `tokens/` → K8s)
|
||||||
|
|
||||||
|
Rekomendacja: rozdziel na 2 typy:
|
||||||
|
|
||||||
|
- **Secret** (nie commitować w git):
|
||||||
|
- `tokens/hasura.json` (adminSecret, jwtKey)
|
||||||
|
- `tokens/api.json` (api adminSecret)
|
||||||
|
- `tokens/frontend.json` (basic auth)
|
||||||
|
- `tokens/read.json` (read token)
|
||||||
|
- `tokens/alg.json` (write token)
|
||||||
|
- `tokens/heliusN.json` (RPC token / url)
|
||||||
|
- **ConfigMap**:
|
||||||
|
- wersja i parametry nie-wrażliwe: `APP_VERSION`, `BUILD_TIMESTAMP`, `TICKS_TABLE`, `CANDLES_FUNCTION`, `MARKET_NAME`, `INTERVAL_MS`
|
||||||
|
|
||||||
|
Jeśli chcesz GitOps bez trzymania sekretów “na piechotę”, wybierz jedno:
|
||||||
|
- **Sealed Secrets** (zaszyfrowane sekrety w repo),
|
||||||
|
- **External Secrets Operator** (Vault / AWS / GCP / Azure),
|
||||||
|
- “na start” manualne `kubectl create secret ...` per środowisko.
|
||||||
|
|
||||||
|
## 5) Wersjonowanie (v1, v2…) i cutover bez wyłączania DB
|
||||||
|
|
||||||
|
W `scripts/ops/` jest workflow wersjonowania Compose:
|
||||||
|
- nowa wersja `api+frontend(+ingestor)` działa równolegle
|
||||||
|
- pisze do **nowej tabeli** (`drift_ticks_vN`) i używa **nowej funkcji** (`get_drift_candles_vN`)
|
||||||
|
|
||||||
|
Na K8s najprościej odwzorować to tak:
|
||||||
|
- Helm release name albo suffix w nazwach zasobów: `trade-v1`, `trade-v2`, …
|
||||||
|
- wspólne DB/Hasura pozostają bez zmian
|
||||||
|
- `Job` “version-init”:
|
||||||
|
- odpala SQL migracji (odpowiednik `db-version`)
|
||||||
|
- odpala `hasura-bootstrap` z `TICKS_TABLE` i `CANDLES_FUNCTION` ustawionymi na vN
|
||||||
|
- “cutover”:
|
||||||
|
- startujesz `trade-ingestor` vN
|
||||||
|
- stopujesz `trade-ingestor` v1
|
||||||
|
- po czasie robisz `db-backfill` (opcjonalnie) i sprzątasz stare zasoby
|
||||||
|
|
||||||
|
## 6) CI/CD “na gita” (build → deploy po pushu)
|
||||||
|
|
||||||
|
### Opcja A (polecana): GitOps (Argo CD / Flux)
|
||||||
|
1) CI (GitHub Actions / GitLab CI) buduje obrazy:
|
||||||
|
- `trade-api`
|
||||||
|
- `trade-frontend`
|
||||||
|
- `trade-ingestor`
|
||||||
|
2) CI publikuje je do registry (np. GHCR)
|
||||||
|
3) CD (ArgoCD/Flux) automatycznie synchronizuje manifesty/Helm z repo i robi rollout
|
||||||
|
|
||||||
|
Tagowanie obrazów:
|
||||||
|
- `:sha-<shortsha>` dla każdego commita
|
||||||
|
- opcjonalnie `:vN` dla release’ów
|
||||||
|
|
||||||
|
Aktualizacja tagów:
|
||||||
|
- ArgoCD Image Updater / Flux Image Automation **albo**
|
||||||
|
- CI robi commit do `k8s/` (np. podmienia tag w `values.yaml`)
|
||||||
|
|
||||||
|
### Opcja B (prostsza na start): “kubectl apply” z CI
|
||||||
|
1) CI buduje i pushuje obrazy
|
||||||
|
2) CI wykonuje `helm upgrade --install` albo `kubectl apply -k ...`
|
||||||
|
3) Dostęp do klastra przez sekret w repo (kubeconfig / token)
|
||||||
|
|
||||||
|
## 7) Proponowana sekwencja migracji (checklista)
|
||||||
|
|
||||||
|
1) **Decyzje**: gdzie stoi klaster (EKS/GKE/AKS/k3s), jakie registry, jaki Ingress, jak trzymamy sekrety.
|
||||||
|
2) **K8s “base”**: namespace, storage class, ingress controller, certy (jeśli TLS).
|
||||||
|
3) **DB**: wdroż Timescale (StatefulSet + PVC), odpal `db-init` job.
|
||||||
|
4) **Hasura**: wdroż Hasurę, odpal `hasura-bootstrap` job.
|
||||||
|
5) **API**: wdroż `trade-api`, sprawdź `/healthz`.
|
||||||
|
6) **Tokeny**: wygeneruj read/write tokeny (obecnym mechanizmem API) i wgraj je jako Secrets.
|
||||||
|
7) **Frontend**: wdroż `trade-frontend` + Ingress, sprawdź `/healthz` i UI.
|
||||||
|
8) **Ingestor**: wdroż `trade-ingestor` (1 replika), potwierdź że ticki wpadają.
|
||||||
|
9) **CI/CD**: dodaj workflow build+push i deploy (GitOps albo kubectl).
|
||||||
|
10) **Staging → Prod**: rollout na staging, potem prod.
|
||||||
|
|
||||||
|
## 8) Pytania, które domykają plan
|
||||||
|
|
||||||
|
1) Jaki “git”: **GitHub czy GitLab**?
|
||||||
|
2) Gdzie ma stać K8s: cloud (EKS/GKE/AKS) czy on-prem/k3s?
|
||||||
|
3) DB w klastrze (StatefulSet) czy zewnętrzny managed Postgres/Timescale?
|
||||||
|
4) Czy “po pushu” ma:
|
||||||
|
- tylko robić rollout na `main`,
|
||||||
|
- czy tworzyć **preview env per branch/PR**,
|
||||||
|
- czy startować **nową wersję vN równolegle** (jak `scripts/ops/`)?
|
||||||
|
5) Jaki dostęp z zewnątrz: domena + TLS, czy wystarczy port-forward / internal?
|
||||||
|
|
||||||
339
migration.md
Normal file
339
migration.md
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
# Migracja `trade` do k3s + GitOps (pull) na Gitea + CI/CD
|
||||||
|
|
||||||
|
Ten dokument opisuje plan migracji obecnego stacka (Docker Compose) do k3s z CD w modelu **pull-based** (GitOps): klaster sam synchronizuje „desired state” z repo na Gitei, a CI jedynie buduje/publikuje obrazy i aktualizuje repo deploymentu.
|
||||||
|
|
||||||
|
## Status (VPS / k3s)
|
||||||
|
|
||||||
|
Na VPS jest już uruchomione (k3s single-node):
|
||||||
|
|
||||||
|
- Ingress: Traefik (80/443)
|
||||||
|
- TLS: cert-manager + `ClusterIssuer/letsencrypt-prod`
|
||||||
|
- Gitea (Ingress `https://rv32i.pl`) + registry (`https://rv32i.pl/v2/`)
|
||||||
|
- Argo CD w namespace `argocd`
|
||||||
|
- Gitea Actions runner w namespace `gitea-actions` (act_runner + DinD)
|
||||||
|
- GitOps repo `trade/trade-deploy` podpięte do Argo:
|
||||||
|
- `Application/argocd/trade-staging` (auto-sync)
|
||||||
|
- `namespace/trade-staging`: Postgres/Timescale + Hasura + pgAdmin + Job `hasura-bootstrap`
|
||||||
|
- `namespace/trade-staging`: `trade-api` + `trade-ingestor` (obrazy z registry `rv32i.pl`)
|
||||||
|
- `namespace/trade-staging`: `trade-frontend` + Ingress `trade.rv32i.pl` (basic auth, TLS OK)
|
||||||
|
|
||||||
|
Szybka weryfikacja (VPS):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging get deploy trade-api trade-ingestor -o wide
|
||||||
|
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging logs deploy/trade-ingestor --tail=40
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tokeny `trade-api` (read/write)
|
||||||
|
|
||||||
|
Endpointy w `trade-api` są chronione tokenami z tabeli `api_tokens`:
|
||||||
|
- `GET /v1/ticks`, `GET /v1/chart` → scope `read`
|
||||||
|
- `POST /v1/ingest/tick` → scope `write`
|
||||||
|
|
||||||
|
W `staging` tokeny są przechowywane jako K8s Secrets (bez commitowania do gita):
|
||||||
|
- `trade-staging/Secret/trade-ingestor-tokens`: `alg.json` (write) + `heliusN.json` (RPC)
|
||||||
|
- `trade-staging/Secret/trade-read-token`: `read.json` (read)
|
||||||
|
- `trade-staging/Secret/trade-frontend-tokens`: `frontend.json` (basic auth) + `read.json` (proxy do API)
|
||||||
|
|
||||||
|
### Dostęp: Argo CD UI (bez konfliktu z lokalnym Hasurą na 8080)
|
||||||
|
|
||||||
|
Port-forward uruchamiasz „na żądanie” (to nie jest stały serwis). Jeśli lokalnie masz Hasurę na `8080`, użyj `8090`.
|
||||||
|
|
||||||
|
Na VPS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n argocd port-forward --address 127.0.0.1 svc/argocd-server 8090:443
|
||||||
|
```
|
||||||
|
|
||||||
|
Na swoim komputerze:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -L 8090:127.0.0.1:8090 user@rv32i.pl
|
||||||
|
```
|
||||||
|
|
||||||
|
UI: `https://localhost:8090`
|
||||||
|
|
||||||
|
### Argo CD: username / hasło
|
||||||
|
|
||||||
|
- Username: `admin`
|
||||||
|
- Hasło startowe (pobierz na VPS; nie commituj / nie wklejaj do gita):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d; echo
|
||||||
|
```
|
||||||
|
|
||||||
|
Po pierwszym logowaniu ustaw własne hasło i (opcjonalnie) usuń `argocd-initial-admin-secret`.
|
||||||
|
|
||||||
|
### Runner: log (czy CI działa)
|
||||||
|
|
||||||
|
Na VPS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n gitea-actions logs deploy/gitea-act-runner -c runner --tail=80
|
||||||
|
```
|
||||||
|
|
||||||
|
### Portainer (Kubernetes UI): `portainer.rv32i.pl`
|
||||||
|
|
||||||
|
Portainer jest wdrażany przez Argo CD jako osobna aplikacja `portainer` (namespace `portainer`) z Ingressem i cert-managerem.
|
||||||
|
|
||||||
|
Wymagania:
|
||||||
|
- DNS: rekord A `portainer.rv32i.pl` → `77.90.8.171`
|
||||||
|
|
||||||
|
Weryfikacja na VPS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n argocd get application portainer -o wide
|
||||||
|
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n portainer get pods,svc,ingress,certificate -o wide
|
||||||
|
```
|
||||||
|
|
||||||
|
Jeśli cert-manager „utknie” na HTTP-01 z powodu cache NXDOMAIN w klastrze, pomocne jest zrestartowanie CoreDNS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n kube-system rollout restart deploy/coredns
|
||||||
|
```
|
||||||
|
|
||||||
|
Jeśli Portainer pokaże ekran `timeout.html` (setup/login timed out), zrestartuj deployment i od razu dokończ inicjalizację:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n portainer rollout restart deploy/portainer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend UI: `trade.rv32i.pl`
|
||||||
|
|
||||||
|
Frontend jest wdrożony w `trade-staging` i ma Ingress na `trade.rv32i.pl` (Traefik + cert-manager).
|
||||||
|
|
||||||
|
Wymagania:
|
||||||
|
- DNS: rekord A `trade.rv32i.pl` → `77.90.8.171`
|
||||||
|
|
||||||
|
Weryfikacja na VPS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging get deploy trade-frontend -o wide
|
||||||
|
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging get ingress trade-frontend -o wide
|
||||||
|
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n trade-staging get certificate trade-rv32i-pl-tls -o wide
|
||||||
|
```
|
||||||
|
|
||||||
|
Status na dziś:
|
||||||
|
- `Certificate/trade-rv32i-pl-tls` = `Ready=True` (Let’s Encrypt)
|
||||||
|
|
||||||
|
## 0) Stan wejściowy (co dziś jest w repo)
|
||||||
|
|
||||||
|
Projekt ma już logiczny podział „mikroserwisowy”:
|
||||||
|
|
||||||
|
- **DB stack**: `devops/db/docker-compose.yml`
|
||||||
|
- TimescaleDB/Postgres (`postgres`, port 5432)
|
||||||
|
- Hasura (`hasura`, port 8080)
|
||||||
|
- pgAdmin (`pgadmin`, port 5050) – raczej tylko dev/staging
|
||||||
|
- **App stack**: `devops/app/docker-compose.yml`
|
||||||
|
- `trade-api` (`api`, port 8787; health: `GET /healthz`)
|
||||||
|
- `trade-frontend` (`frontend`, port 8081; health: `GET /healthz`)
|
||||||
|
- `trade-ingestor` (`ingestor`, profil `ingest`) – worker ingestujący ticki
|
||||||
|
- **One-shot bootstrap**: `devops/tools/bootstrap/docker-compose.yml`
|
||||||
|
- `db-init`, `db-version`, `db-backfill` (SQL)
|
||||||
|
- `hasura-bootstrap` (track tabel + permissions)
|
||||||
|
|
||||||
|
Sekrety/konfiguracja lokalnie są dziś w `tokens/*.json` (gitignored) i są montowane jako pliki do kontenerów.
|
||||||
|
|
||||||
|
## 1) Cel migracji
|
||||||
|
|
||||||
|
- Przeniesienie usług do k3s (Kubernetes) bez zmiany logiki aplikacji.
|
||||||
|
- Wdrożenia przez GitOps (**pull**, bez „kubectl apply” z CI).
|
||||||
|
- Powtarzalność: jedna ścieżka build → deploy, jednoznaczne tagi/digests.
|
||||||
|
- Minimalny downtime: możliwość uruchamiania wersji równolegle (v1/v2…), jak w `scripts/ops/*`.
|
||||||
|
|
||||||
|
## 2) Założenia i decyzje do podjęcia (checklista)
|
||||||
|
|
||||||
|
Ustal na start (zaznacz jedną opcję, resztę możesz doprecyzować później):
|
||||||
|
|
||||||
|
1) **CD (pull)**: Argo CD czy Flux
|
||||||
|
- Argo CD + (opcjonalnie) Image Updater – popularny i „klikany”
|
||||||
|
- Flux + Image Automation – bardzo „git-first”
|
||||||
|
2) **Registry obrazów**: Gitea Registry czy osobne (Harbor / registry:2)
|
||||||
|
3) **Sekrety w GitOps**: manualne seedy (na start) czy docelowo SOPS/SealedSecrets/ExternalSecrets
|
||||||
|
4) **DB**: w klastrze (StatefulSet + PVC) czy zewnętrzny Postgres/Timescale
|
||||||
|
5) **Środowiska**: `staging` + `prod`? (opcjonalnie preview per branch/PR)
|
||||||
|
6) **Ingress/TLS**: Traefik + cert-manager + Let’s Encrypt (lub inny wariant)
|
||||||
|
|
||||||
|
## 3) Docelowa architektura na k3s (mapowanie 1:1)
|
||||||
|
|
||||||
|
Minimalny, praktyczny podział zasobów:
|
||||||
|
|
||||||
|
- `timescaledb` (Postgres/Timescale): `StatefulSet` + `PVC` + `Service`
|
||||||
|
- `hasura`: `Deployment` + `Service`
|
||||||
|
- `trade-api`: `Deployment` + `Service`
|
||||||
|
- `trade-frontend`: `Deployment` + `Service` + `Ingress`
|
||||||
|
- `trade-ingestor`: `Deployment` (zwykle `replicas: 1`) albo `CronJob` (jeśli ingest ma być okresowy)
|
||||||
|
- migracje/bootstrap:
|
||||||
|
- `Job` dla `db-init`/`db-version`/`db-backfill`
|
||||||
|
- `Job` dla `hasura-bootstrap`
|
||||||
|
|
||||||
|
Ważne:
|
||||||
|
- `trade-api` i `trade-frontend` mają gotowe endpointy `/healthz` do `readinessProbe`/`livenessProbe`.
|
||||||
|
- `trade-ingestor` potrzebuje egress do internetu (RPC do Helius/Drift).
|
||||||
|
|
||||||
|
## 4) Podział repozytoriów (rekomendowane)
|
||||||
|
|
||||||
|
Żeby GitOps było czyste i proste:
|
||||||
|
|
||||||
|
1) Repo aplikacji: **`trade`** (to repo)
|
||||||
|
- kod i Dockerfile: `devops/api/Dockerfile`, `devops/ingestor/Dockerfile`, `devops/app/frontend/Dockerfile`
|
||||||
|
2) Repo deploymentu: **`trade-deploy`** (nowe repo na Gitei)
|
||||||
|
- manifesty K8s (Helm chart albo Kustomize)
|
||||||
|
- overlays per środowisko: `staging/`, `prod/`
|
||||||
|
|
||||||
|
Zasada: CI nie wykonuje deploya do klastra; CI tylko aktualizuje `trade-deploy`, a CD w klastrze to „pulluje”.
|
||||||
|
|
||||||
|
## 5) CI na Gitei (build → push)
|
||||||
|
|
||||||
|
Do wyboru:
|
||||||
|
- **Gitea Actions** + `act_runner` (najbliżej GitHub Actions)
|
||||||
|
- alternatywnie Woodpecker/Drone, jeśli już masz w infra
|
||||||
|
|
||||||
|
Zakres CI:
|
||||||
|
- zbuduj i wypchnij obrazy:
|
||||||
|
- `trade-api`
|
||||||
|
- `trade-frontend`
|
||||||
|
- `trade-ingestor`
|
||||||
|
- taguj deterministycznie, np.:
|
||||||
|
- `sha-<shortsha>` (zawsze)
|
||||||
|
- opcjonalnie `vN` na release
|
||||||
|
- po push obrazu: zaktualizuj `trade-deploy` (tag/digest) lub zostaw to automatyzacji obrazów (patrz niżej).
|
||||||
|
|
||||||
|
## 6) CD (pull) z klastra: GitOps
|
||||||
|
|
||||||
|
Wariant A (najprostszy operacyjnie): **CI robi commit do `trade-deploy`**
|
||||||
|
- CI po zbudowaniu obrazu aktualizuje `values.yaml`/`kustomization.yaml` w `trade-deploy` (tag lub digest) i robi commit/push.
|
||||||
|
- Argo CD / Flux wykrywa zmianę i robi rollout.
|
||||||
|
|
||||||
|
Wariant B (bardziej „autopilot”): **automatyczna aktualizacja obrazów**
|
||||||
|
- Argo CD Image Updater albo Flux Image Automation obserwuje registry i sam aktualizuje repo `trade-deploy`.
|
||||||
|
- CI tylko buduje/pushuje obrazy.
|
||||||
|
|
||||||
|
## 7) Sekrety: `tokens/*.json` → Kubernetes Secret (plikowy mount)
|
||||||
|
|
||||||
|
Docelowo w K8s montujesz te same pliki co w Compose:
|
||||||
|
|
||||||
|
- `tokens/hasura.json` → dla `trade-api` (admin do Hasury)
|
||||||
|
- `tokens/api.json` → dla `trade-api` (admin secret do tokenów API)
|
||||||
|
- `tokens/read.json` → dla `trade-frontend` (proxy do API)
|
||||||
|
- `tokens/frontend.json` → dla `trade-frontend` (basic auth)
|
||||||
|
- `tokens/alg.json` → dla `trade-ingestor` (write token)
|
||||||
|
- `tokens/heliusN.json` (lub `rpcUrl`) → dla `trade-ingestor`
|
||||||
|
|
||||||
|
Nie commituj sekretów do gita. W GitOps wybierz jedną drogę:
|
||||||
|
- start: ręczne `kubectl create secret ...` na środowisko
|
||||||
|
- docelowo: SOPS (age) / SealedSecrets / ExternalSecrets (Vault itp.)
|
||||||
|
|
||||||
|
## 8) Plan wdrożenia (kolejność kroków)
|
||||||
|
|
||||||
|
### Etap 1: Klaster i narzędzia bazowe
|
||||||
|
1) Namespace’y (np. `trade`, `gitea`, `argocd`/`flux-system`).
|
||||||
|
2) StorageClass (na start `local-path`, docelowo rozważ Longhorn).
|
||||||
|
3) Ingress controller (Traefik w k3s) + cert-manager + ClusterIssuer.
|
||||||
|
4) Registry obrazów (Gitea Registry / Harbor / registry:2).
|
||||||
|
5) GitOps controller (Argo CD albo Flux) podpięty do `trade-deploy`.
|
||||||
|
|
||||||
|
### Etap 2: DB + Hasura
|
||||||
|
6) Deploy TimescaleDB (StatefulSet + PVC + Service).
|
||||||
|
7) Uruchom `Job` `db-init` (idempotent) – odpowiednik `devops/tools/bootstrap:db-init`.
|
||||||
|
8) Deploy Hasura (Deployment + Service + Secret na admin/jwt/db-url).
|
||||||
|
9) Uruchom `Job` `hasura-bootstrap` – odpowiednik `devops/tools/bootstrap:hasura-bootstrap`.
|
||||||
|
|
||||||
|
### Etap 3: App stack
|
||||||
|
10) Deploy `trade-api` (Deployment + Service, env `HASURA_GRAPHQL_URL`, `TICKS_TABLE`, `CANDLES_FUNCTION`).
|
||||||
|
11) Sprawdź `GET /healthz` po Service DNS i (opcjonalnie) z zewnątrz.
|
||||||
|
12) Wygeneruj i wgraj tokeny:
|
||||||
|
- read token dla frontendu (`tokens/read.json`)
|
||||||
|
- write token dla ingestora (`tokens/alg.json`)
|
||||||
|
13) Deploy `trade-frontend` + Ingress (TLS opcjonalnie); ustaw `API_UPSTREAM=http://trade-api:8787`.
|
||||||
|
14) Deploy `trade-ingestor` (`replicas: 1`); potwierdź, że ticki wpadają.
|
||||||
|
|
||||||
|
### Etap 4: CI/CD end-to-end
|
||||||
|
15) Skonfiguruj runnera CI (Gitea Actions / Woodpecker).
|
||||||
|
16) Workflow CI: build+push obrazów (i ewentualny commit do `trade-deploy`).
|
||||||
|
17) Zasady promocji: staging → prod (np. tylko tag/release).
|
||||||
|
|
||||||
|
## 9) Wersjonowanie v1/v2… (równoległe wdrożenia + cutover)
|
||||||
|
|
||||||
|
Repo ma już pattern wersjonowania w Compose (`scripts/ops/*` + `devops/versions/vN.env`).
|
||||||
|
|
||||||
|
Odwzorowanie na K8s:
|
||||||
|
- osobne release’y Helm (np. `trade-v1`, `trade-v2`) albo osobne namespace’y
|
||||||
|
- wspólny DB/Hasura bez zmian
|
||||||
|
- `Job` „version-init”:
|
||||||
|
- uruchamia SQL `db-version` (tworzy `drift_ticks_vN` i funkcje)
|
||||||
|
- uruchamia `hasura-bootstrap` z `TICKS_TABLE`/`CANDLES_FUNCTION` ustawionymi na vN
|
||||||
|
- cutover:
|
||||||
|
- start `trade-ingestor` vN
|
||||||
|
- stop `trade-ingestor` v1
|
||||||
|
- (opcjonalnie) `db-backfill` jako osobny job
|
||||||
|
|
||||||
|
## 10) Definition of Done (DoD)
|
||||||
|
|
||||||
|
- `trade-api` i `trade-frontend` działają na k3s (proby `Ready=True`, brak crashloopów).
|
||||||
|
- `trade-ingestor` zapisuje ticki do właściwej tabeli, a UI pokazuje wykres.
|
||||||
|
- Deploy jest sterowany przez GitOps (zmiana w `trade-deploy` → rollout bez ręcznego `kubectl apply`).
|
||||||
|
- Sekrety są poza gitem (manualnie lub przez wybraną metodę GitOps).
|
||||||
|
|
||||||
|
## 11) Referencje w repo
|
||||||
|
|
||||||
|
- Compose: `devops/db/docker-compose.yml`, `devops/app/docker-compose.yml`, `devops/tools/bootstrap/docker-compose.yml`
|
||||||
|
- Workflow end-to-end: `doc/workflow-api-ingest.md`
|
||||||
|
- Szkic migracji K8s: `doc/k8s-migracja.md`
|
||||||
|
- Gitea na k3s (Traefik/TLS): `doc/gitea-k3s-rv32i.md`
|
||||||
|
|
||||||
|
## 12) Metoda „superproject” (subrepo) – plan refaktoru repozytoriów
|
||||||
|
|
||||||
|
Cel: utrzymać osobne repo dla usług (API/ingestor/frontend) i osobne repo GitOps (`trade-deploy`), a do tego mieć jedno repo „główne” (superproject) spinające wszystko (np. jako **git submodules**).
|
||||||
|
|
||||||
|
### Wybór repo głównego
|
||||||
|
|
||||||
|
Rekomendacja: **`trade/trade-infra` jako repo główne (superproject)**:
|
||||||
|
- jest neutralne (infra/ops), nie miesza kodu aplikacji z manifestami GitOps,
|
||||||
|
- pozwala trzymać jedną „zafiksowaną” wersję całego systemu (SHAs submodułów),
|
||||||
|
- nie wymaga zmiany Argo CD (który nadal może śledzić `trade/trade-deploy`).
|
||||||
|
|
||||||
|
### Docelowy podział ról repo
|
||||||
|
|
||||||
|
- `trade/trade-api` – kod + Dockerfile dla `trade-api`
|
||||||
|
- `trade/trade-ingestor` – kod + Dockerfile dla `trade-ingestor`
|
||||||
|
- `trade/trade-frontend` – kod + Dockerfile dla `trade-frontend` (apps/visualizer + serwer)
|
||||||
|
- `trade/trade-deploy` – GitOps: Kustomize/Helm dla k3s (Argo CD pull)
|
||||||
|
- `trade/trade-doc` – dokumentacja całego projektu (`doc/`), w tym log działań
|
||||||
|
- `trade/trade-infra` – superproject + narzędzia/ops (np. makefile, skrypty, checklisty)
|
||||||
|
|
||||||
|
### Proponowany układ w superprojekcie
|
||||||
|
|
||||||
|
Przykład (ścieżki w `trade/trade-infra`):
|
||||||
|
|
||||||
|
```text
|
||||||
|
trade-infra/
|
||||||
|
api/ -> submodule: trade-api
|
||||||
|
ingestor/ -> submodule: trade-ingestor
|
||||||
|
frontend/ -> submodule: trade-frontend
|
||||||
|
deploy/ -> submodule: trade-deploy
|
||||||
|
doc/ -> submodule: trade-doc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plan wdrożenia refaktoru (po akceptacji)
|
||||||
|
|
||||||
|
1) Zainicjalizować `trade/trade-infra` jako superproject i dodać submodule do wszystkich subrepo.
|
||||||
|
2) Wyciąć obecny kod do subrepo:
|
||||||
|
- API: `services/api/*` + `devops/api/Dockerfile` → `trade-api/`
|
||||||
|
- Frontend: `apps/visualizer/*` + `services/frontend/*` + `devops/app/frontend/*` → `trade-frontend/`
|
||||||
|
- Ingestor: `scripts/ingest-drift-oracle.ts` (+ minimalny zestaw zależności i Dockerfile) → `trade-ingestor/`
|
||||||
|
3) Przenieść dokumentację do `trade-doc` (w tym log: `doc/steps.md`) i zostawić w pozostałych repo linki do docs.
|
||||||
|
4) Ujednolicić nazwy obrazów i tagowanie:
|
||||||
|
- `rv32i.pl/trade/trade-api:<tag>`
|
||||||
|
- `rv32i.pl/trade/trade-ingestor:<tag>`
|
||||||
|
- `rv32i.pl/trade/trade-frontend:<tag>`
|
||||||
|
5) CI/CD (Gitea Actions) per repo usługi:
|
||||||
|
- build → push do registry,
|
||||||
|
- aktualizacja `trade-deploy` (commit/PR z bumpem tagu/digesta),
|
||||||
|
- staging auto-sync w Argo (prod manual).
|
||||||
|
6) Walidacja end-to-end na k3s:
|
||||||
|
- rollout `trade-staging`,
|
||||||
|
- UI `https://trade.rv32i.pl`,
|
||||||
|
- ingest ticków (`trade-ingestor` → `trade-api`).
|
||||||
|
|
||||||
|
Uwaga: lokalnie repo nie ma historii commitów, więc refaktor będzie startem „od zera” w nowych repo (initial commit per subrepo).
|
||||||
211
spot-mvp.html
Normal file
211
spot-mvp.html
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="pl">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Bot SPOT na Solanie: skan 10/50 par (MVP)</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: light dark;
|
||||||
|
--bg: #0b1020;
|
||||||
|
--panel: rgba(255, 255, 255, 0.06);
|
||||||
|
--text: rgba(255, 255, 255, 0.92);
|
||||||
|
--muted: rgba(255, 255, 255, 0.68);
|
||||||
|
--accent: #7c3aed;
|
||||||
|
--border: rgba(255, 255, 255, 0.12);
|
||||||
|
--shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
--bg: #f6f7fb;
|
||||||
|
--panel: rgba(0, 0, 0, 0.04);
|
||||||
|
--text: rgba(0, 0, 0, 0.88);
|
||||||
|
--muted: rgba(0, 0, 0, 0.62);
|
||||||
|
--accent: #6d28d9;
|
||||||
|
--border: rgba(0, 0, 0, 0.10);
|
||||||
|
--shadow: 0 12px 40px rgba(15, 23, 42, 0.14);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial,
|
||||||
|
"Apple Color Emoji", "Segoe UI Emoji";
|
||||||
|
line-height: 1.55;
|
||||||
|
background: radial-gradient(1200px 600px at 20% -10%, rgba(124, 58, 237, 0.28), transparent),
|
||||||
|
radial-gradient(900px 500px at 95% 10%, rgba(59, 130, 246, 0.18), transparent),
|
||||||
|
var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 48px auto;
|
||||||
|
padding: 0 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
padding: 22px 22px 18px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.03));
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 26px;
|
||||||
|
margin: 0 0 6px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
margin-top: 18px;
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--panel);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 18px 18px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
margin: 8px 0 0 18px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
code,
|
||||||
|
pre {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||||
|
"Courier New", monospace;
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 0.05em 0.35em;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
padding: 12px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: rgba(0, 0, 0, 0.18);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.12em 0.55em;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: rgba(124, 58, 237, 0.12);
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 650;
|
||||||
|
font-size: 12px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>Bot SPOT na Solanie (MVP)</h1>
|
||||||
|
<p class="subtitle">
|
||||||
|
Analiza dziesiątek par → ranking → wybór TOP 10 sygnałów wejścia. Start na SPOT zwykle
|
||||||
|
jest bezpieczniejszy i prostszy niż PERPS.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
<h2>Dlaczego SPOT jest lepszy na start <span class="pill">praktycznie</span></h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Brak likwidacji</strong> (największy “killer” na perpsach).</li>
|
||||||
|
<li>Prostsze koszty: <strong>swap fee + slippage + fee Solany</strong>.</li>
|
||||||
|
<li>Łatwiej zrobić stabilny ranking (płynność / volatility / volume) i testować strategię.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Jak to zrobić praktycznie na Solanie (stack)</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Jupiter</strong> jako egzekucja swapów (routing po DEX-ach, najlepsza cena).</li>
|
||||||
|
<li><strong>Helius</strong> jako RPC do wysyłki transakcji + monitoring (potwierdzenia, logi).</li>
|
||||||
|
<li>
|
||||||
|
Dane do sygnałów:
|
||||||
|
<ul>
|
||||||
|
<li>najprościej: OHLCV + volume z zewnętrznych market data (szybko),</li>
|
||||||
|
<li>albo: on-chain (trudniej, ale najbardziej “raw”).</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Zasady bezpieczeństwa na SPOT (żeby bot nie palił kapitału)</h2>
|
||||||
|
<ol>
|
||||||
|
<li><strong>Filtr płynności</strong>: nie wchodź w pary z małą płynnością (slippage zabije edge).</li>
|
||||||
|
<li><strong>Limit slippage</strong>: ustaw twardy max (np. 0.3–1% zależnie od tokenów).</li>
|
||||||
|
<li><strong>Cooldown po transakcji</strong>: unikniesz “mielenia” (overtrading).</li>
|
||||||
|
<li><strong>Blacklist</strong>: wyklucz tokeny z anomaliami (freeze, podatki transferowe, honeypoty itd.).</li>
|
||||||
|
<li><strong>Rozmiar pozycji ∝ płynność</strong>: im mniejsza płynność, tym mniejszy notional.</li>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Prosty wzór rankingu “TOP 10” na SPOT (na start)</h2>
|
||||||
|
<pre><code>Score_i = μ_i - (fee_i + slip_i + net_i) - λ · risk_i</code></pre>
|
||||||
|
<ul>
|
||||||
|
<li><code>μ_i</code> = przewidywany zwrot z Twojego sygnału (np. momentum / mean reversion)</li>
|
||||||
|
<li><code>fee_i</code> = koszt swapów (wejście + wyjście)</li>
|
||||||
|
<li><code>slip_i</code> = oczekiwany slippage / price impact (wejście + wyjście)</li>
|
||||||
|
<li><code>net_i</code> = koszt sieci / priority (mały, ale rośnie przy częstych trade’ach)</li>
|
||||||
|
<li><code>risk_i</code> = kara za ryzyko (np. ATR/volatility)</li>
|
||||||
|
<li><code>λ</code> = jak mocno karzesz ryzyko</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Kiedy przechodzić na PERPS (Drift)</h2>
|
||||||
|
<p class="subtitle">
|
||||||
|
Dopiero gdy na SPOT masz stabilny pipeline danych, umiesz liczyć realny koszt wejścia/wyjścia i
|
||||||
|
masz sensowny EV/winrate — wtedy warto dodać shorty + dźwignię na perpsach.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>stabilny pipeline danych</li>
|
||||||
|
<li>realne koszty wejścia/wyjścia (slippage + fees)</li>
|
||||||
|
<li>sensowny winrate/EV</li>
|
||||||
|
<li>chęć dodania shortów + dźwigni</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
216
steps.md
Normal file
216
steps.md
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
# steps.md
|
||||||
|
|
||||||
|
Log działań wykonywanych w ramach migracji `trade` → k3s + Gitea + GitOps (pull).
|
||||||
|
Uwaga: **nie zapisuję sekretów** (hasła, tokeny, prywatne klucze) – jeśli pojawiają się w outputach narzędzi, są pomijane/redagowane.
|
||||||
|
|
||||||
|
## 2026-01-05
|
||||||
|
|
||||||
|
### Repo: inwentaryzacja i plan
|
||||||
|
- Przejrzano strukturę repo (`apps/`, `devops/`, `services/`, `doc/`) i istniejące Compose stacki:
|
||||||
|
- DB: `devops/db/docker-compose.yml`
|
||||||
|
- App: `devops/app/docker-compose.yml`
|
||||||
|
- Bootstrap (one-shot): `devops/tools/bootstrap/docker-compose.yml`
|
||||||
|
- Sprawdzono Dockerfile:
|
||||||
|
- API: `devops/api/Dockerfile`
|
||||||
|
- Frontend: `devops/app/frontend/Dockerfile`
|
||||||
|
- Ingestor: `devops/ingestor/Dockerfile`
|
||||||
|
- Sprawdzono skrypty wersjonowania Compose (v1/v2…): `scripts/ops/*` + env: `devops/versions/v1.env`.
|
||||||
|
- Dodano dokument planu migracji: `doc/migration.md`.
|
||||||
|
|
||||||
|
### Gitea: repo GitOps (MCP Gitea)
|
||||||
|
- Utworzono repo `trade/trade-deploy` (public, `main`, auto-init).
|
||||||
|
- Dodano szkielet Kustomize:
|
||||||
|
- `kustomize/base/` (placeholder ConfigMap)
|
||||||
|
- `kustomize/overlays/staging/` (`namespace: trade-staging`)
|
||||||
|
- `kustomize/overlays/prod/` (`namespace: trade-prod`)
|
||||||
|
- Dodano bootstrap manifesty Argo CD Application:
|
||||||
|
- `bootstrap/argocd/application-trade-staging.yaml` (auto-sync + CreateNamespace)
|
||||||
|
- `bootstrap/argocd/application-trade-prod.yaml` (CreateNamespace, bez auto-sync)
|
||||||
|
- Dodano DB stack do `trade-deploy` (manifests + bootstrap):
|
||||||
|
- Postgres/Timescale: `kustomize/base/postgres/*` + init SQL: `kustomize/base/initdb/001_init.sql`
|
||||||
|
- Hasura: `kustomize/base/hasura/*` + job `hasura-bootstrap` (track tables/functions)
|
||||||
|
- Staging: `kustomize/overlays/staging/pgadmin.yaml` + patch Hasury (console/dev mode)
|
||||||
|
- Zaktualizowano `README.md` w `trade-deploy` o wymagane sekrety i port-forward.
|
||||||
|
|
||||||
|
### VPS: audyt stanu (MCP SSH)
|
||||||
|
- Host: `qstack` (Debian), k3s działa; dostęp do klastra uzyskany przez `KUBECONFIG=/etc/rancher/k3s/k3s.yaml` (bez sudo).
|
||||||
|
- Ingress: Traefik działa (LB na `77.90.8.171`, porty 80/443).
|
||||||
|
- TLS: cert-manager działa; `ClusterIssuer/letsencrypt-prod` jest `Ready=True`.
|
||||||
|
- Gitea jest zainstalowana w k3s (Helm release `gitea` w namespace `gitea`), Ingress na `rv32i.pl`, cert `rv32i-pl-tls` jest `Ready=True`.
|
||||||
|
- GitOps controller (Argo CD / Flux): **nie znaleziono** (brak namespace `argocd` i `flux-system`).
|
||||||
|
- Wdrożenia aplikacji `trade-*` w k3s: **nie znaleziono** (brak deploy/podów z nazwą `trade`).
|
||||||
|
- Registry: endpoint `https://rv32i.pl/v2/` odpowiada (401 bez autoryzacji).
|
||||||
|
|
||||||
|
### Gitea: audyt stanu (MCP Gitea)
|
||||||
|
- Zalogowany użytkownik: `u1` (admin).
|
||||||
|
- Organizacja `trade` istnieje; są utworzone repozytoria (puste, bez commitów/branchy):
|
||||||
|
- `trade/trade-api`
|
||||||
|
- `trade/trade-frontend`
|
||||||
|
- `trade/trade-ingestor`
|
||||||
|
- `trade/trade-infra`
|
||||||
|
- Repo `trade-deploy` (manifesty GitOps): **nie znaleziono**.
|
||||||
|
|
||||||
|
### VPS: Argo CD (MCP SSH)
|
||||||
|
- Zainstalowano Argo CD przez Helm: release `argocd` w namespace `argocd` (`argo/argo-cd`).
|
||||||
|
- Utworzono `Application` dla staging z `trade-deploy`:
|
||||||
|
- zastosowano `bootstrap/argocd/application-trade-staging.yaml`
|
||||||
|
- Argo utworzyło namespace `trade-staging` i zsynchronizowało placeholder ConfigMap `trade-deploy-info`.
|
||||||
|
- Uwaga: jeśli port `8080` jest zajęty lokalnie (np. przez Hasurę), do port-forward użyj innego portu (np. `8090:443`).
|
||||||
|
|
||||||
|
### VPS: Gitea Actions runner (MCP SSH)
|
||||||
|
- Wykryto wymaganie: `sudo` wymaga hasła, więc runner postawiony w k3s (bez instalacji binarki na hoście).
|
||||||
|
- Utworzono namespace `gitea-actions` oraz zasoby dla `act_runner` (DinD sidecar + runner):
|
||||||
|
- manifest w `trade-deploy`: `bootstrap/gitea-actions/act-runner.yaml`
|
||||||
|
- sekret `act-runner-registration-token` utworzony w klastrze z tokena rejestracyjnego pobranego z Gitei (API admin; token nie trafia do gita)
|
||||||
|
- Naprawiono start Dockera w sidecar:
|
||||||
|
- początkowo próba po unix-sockecie powodowała konflikt (`docker.sock` jako katalog)
|
||||||
|
- finalnie ustawiono `DOCKER_HOST=tcp://127.0.0.1:2375` (runner ↔ dind po localhost), co uruchomiło runnera poprawnie.
|
||||||
|
|
||||||
|
### Uwaga: seed sekretów (blokada narzędzia)
|
||||||
|
- Przy próbie seedowania sekretów do `trade-staging` przez MCP SSH pojawił się trwały timeout na każde polecenie (`ssh-mcp/exec`), więc ten krok został opisany w `trade-deploy/README.md` do wykonania ręcznie na VPS.
|
||||||
|
|
||||||
|
### VPS: dostęp SSH kluczem (wymagane hasło)
|
||||||
|
- Użytkownik poprosił o używanie naszego klucza prywatnego dla VPS: `k/qstack/qstack`.
|
||||||
|
- Klucz jest zaszyfrowany hasłem (passphrase). Bez podania passphrase nie da się go użyć w trybie nieinteraktywnym (`ssh`/`ssh-keygen` zwraca błąd o niepoprawnym passphrase / brak `ssh-askpass`).
|
||||||
|
- Żeby kontynuować automatyzację z tej sesji są 2 opcje:
|
||||||
|
1) dostarczyć passphrase poza czatem (np. lokalnie w pliku w `tokens/`, gitignored) i używać `SSH_ASKPASS`, albo
|
||||||
|
2) wygenerować osobny klucz bez passphrase jako “deploy key” tylko do automatyzacji i dodać jego publiczną część do `~user/.ssh/authorized_keys` na VPS.
|
||||||
|
|
||||||
|
### Następne kroki (do zrobienia)
|
||||||
|
- Wybrać GitOps controller (Argo CD vs Flux) i zainstalować w k3s.
|
||||||
|
- Utworzyć repo `trade-deploy` i dodać strukturę Helm/Kustomize (staging/prod).
|
||||||
|
- Postawić runner CI (Gitea Actions `act_runner` lub alternatywa) i dodać pipeline build+push obrazów.
|
||||||
|
- Dopiero potem: manifesty K8s dla `trade-api`/`trade-frontend`/`trade-ingestor` + DB/Hasura + joby migracji.
|
||||||
|
|
||||||
|
## 2026-01-06
|
||||||
|
|
||||||
|
### VPS: SSH kluczem projektu (passphrase)
|
||||||
|
- Użyto klucza prywatnego `k/qstack/qstack` do połączenia z VPS przez `ssh` (z `SSH_ASKPASS` czytającym passphrase z `tokens/vps-ssh.json`, plik gitignored).
|
||||||
|
- Skrypt `tokens/ssh-askpass.sh` był tworzony tymczasowo i został usunięty po użyciu (żeby nie ryzykować przypadkowego commita).
|
||||||
|
|
||||||
|
### Doprecyzowanie dostępu i bezpieczeństwo repo
|
||||||
|
- Dopisano w `doc/migration.md` bieżący status VPS/k3s oraz komendy do Argo CD (port-forward na `8090`) i logów runnera.
|
||||||
|
- Zaktualizowano `.gitignore`, żeby ignorować sekrety/klucze lokalne: `tokens/*`, `k/`, `argo/pass`, `gitea/token`.
|
||||||
|
|
||||||
|
### k3s: seed sekretów (staging)
|
||||||
|
- Utworzono sekrety w namespace `trade-staging` (wartości wygenerowane losowo, nie logowane):
|
||||||
|
- `trade-postgres` (`POSTGRES_*`)
|
||||||
|
- `trade-hasura` (`HASURA_GRAPHQL_ADMIN_SECRET`, `HASURA_JWT_KEY`)
|
||||||
|
- `trade-pgadmin` (`PGADMIN_DEFAULT_*`)
|
||||||
|
|
||||||
|
### Argo CD: deploy DB stack (staging)
|
||||||
|
- Wymuszono refresh aplikacji Argo `trade-staging` (annotation `argocd.argoproj.io/refresh=hard`).
|
||||||
|
- Status: `trade-staging` = `Synced` / `Healthy`.
|
||||||
|
- Workloady w `trade-staging` działają:
|
||||||
|
- `StatefulSet/postgres` = `postgres-0 Running`
|
||||||
|
- `Deployment/hasura` = `Running`
|
||||||
|
- `Deployment/pgadmin` = `Running`
|
||||||
|
- `Job/hasura-bootstrap` = `Completed` (log: `[hasura-bootstrap] ok`)
|
||||||
|
|
||||||
|
### Portainer: dlaczego nie widać k3s (wyjaśnienie)
|
||||||
|
- Portainer uruchomiony jako kontener Docker domyślnie widzi tylko środowisko Docker; k3s używa `containerd`, więc nie zobaczysz „podów” w `docker ps` ani w Portainerze jako kontenerów Dockera.
|
||||||
|
- Żeby Portainer widział „wnętrze” k3s trzeba dodać środowisko typu `Kubernetes` w Portainer UI albo postawić Portainera bezpośrednio w klastrze (rekomendowane).
|
||||||
|
|
||||||
|
### Portainer (opcja A): wdrożenie w k3s + Ingress `portainer.rv32i.pl`
|
||||||
|
- Dodano do GitOps repo `trade/trade-deploy` paczkę Kustomize: `kustomize/infra/portainer` (Namespace+RBAC+PVC+Deployment+Service+Ingress).
|
||||||
|
- Dodano Argo CD `Application` dla Portainera: `bootstrap/argocd/application-portainer.yaml`.
|
||||||
|
- Zastosowano `Application` na VPS i potwierdzono status: `portainer` = `Synced` / `Healthy`.
|
||||||
|
- Ingress utworzony na host `portainer.rv32i.pl` (Traefik).
|
||||||
|
- cert-manager utworzył `Certificate/portainer-rv32i-pl-tls`, ale ACME jest `pending` dopóki DNS `portainer.rv32i.pl` nie wskazuje na `77.90.8.171` (brak rekordu A w momencie sprawdzania).
|
||||||
|
|
||||||
|
### Portainer: DNS + TLS + odblokowanie „New Portainer installation timed out”
|
||||||
|
- Potwierdzono, że rekord A `portainer.rv32i.pl` istnieje na autorytatywnych DNS (`dns*.home.pl`) i rozwiązuje się na publicznych resolverach.
|
||||||
|
- Zrestartowano `Deployment/portainer` w namespace `portainer`, żeby odblokować ekran inicjalizacji (komunikat “timed out for security purposes”).
|
||||||
|
- cert-manager miał „zawieszone” HTTP-01 self-check przez cache NXDOMAIN w klastrze; zrestartowano `Deployment/coredns` w `kube-system`, po czym certyfikat stał się `Ready=True` (`Certificate/portainer-rv32i-pl-tls`).
|
||||||
|
|
||||||
|
### Registry: token do Gitea Packages + docker login
|
||||||
|
- Na VPS (z poziomu poda Gitei) wygenerowano token `read:package,write:package` dla użytkownika `u1`:
|
||||||
|
- polecenie: `gitea admin user generate-access-token --username u1 --scopes "read:package,write:package" --raw`
|
||||||
|
- token zapisany lokalnie w `tokens/gitea-registry.token` (gitignored; wartość nie jest logowana).
|
||||||
|
- Wykonano `docker login rv32i.pl` (bez logowania tokena).
|
||||||
|
- Zaktualizowano `.dockerignore`, żeby nie wysyłać do build context katalogów z sekretami/kluczami (`tokens/*`, `k/`, `gitea/`, `argo/`).
|
||||||
|
|
||||||
|
### Obrazy: build + push (trade-api, trade-ingestor)
|
||||||
|
- Zbudowano i wypchnięto obrazy do Gitea registry:
|
||||||
|
- `rv32i.pl/trade/trade-api:k3s-20260106013603`
|
||||||
|
- `rv32i.pl/trade/trade-ingestor:k3s-20260106013603`
|
||||||
|
- Tag zapisany lokalnie w `tokens/last-image-tag.txt` (gitignored).
|
||||||
|
|
||||||
|
### GitOps: manifesty k3s dla API + ingestor (trade-deploy)
|
||||||
|
- Dodano do repo `trade/trade-deploy`:
|
||||||
|
- `kustomize/base/api/deployment.yaml` + `kustomize/base/api/service.yaml`
|
||||||
|
- `kustomize/base/ingestor/deployment.yaml`
|
||||||
|
- aktualizacja `kustomize/base/kustomization.yaml` (dopięcie nowych zasobów).
|
||||||
|
- Konfiguracja:
|
||||||
|
- `trade-api` używa `HASURA_GRAPHQL_URL=http://hasura:8080/v1/graphql` i sekretów `trade-hasura` + `trade-api` (admin).
|
||||||
|
- `trade-ingestor` startuje w trybie `INGEST_VIA=hasura` (bez tokenów API) i używa `trade-hasura` + `trade-ingestor-tokens` (RPC).
|
||||||
|
|
||||||
|
### k3s: seedy sekretów + weryfikacja (staging)
|
||||||
|
- Utworzono w `trade-staging`:
|
||||||
|
- `Secret/gitea-registry` (imagePullSecret do `rv32i.pl`)
|
||||||
|
- `Secret/trade-api` (`API_ADMIN_SECRET`, wygenerowany losowo; nie logowany)
|
||||||
|
- `Secret/trade-ingestor-tokens` (plik `heliusN.json`; wartość nie logowana)
|
||||||
|
- Argo CD `Application/trade-staging` po refresh: `Synced` / `Healthy`.
|
||||||
|
- Pody w `trade-staging` uruchomione: `trade-api` i `trade-ingestor` (`Running`).
|
||||||
|
- Logi:
|
||||||
|
- `trade-api` startuje i maskuje sekrety (`***`).
|
||||||
|
- `trade-ingestor` pobiera ceny i ingestuje ticki; URL RPC jest redagowany (`api-key=***`).
|
||||||
|
|
||||||
|
### Ingest przez API + tokeny read/write
|
||||||
|
- Zmieniono `trade-ingestor` na `INGEST_VIA=api` (pisze do `trade-api` zamiast bezpośrednio do Hasury); manifest w `trade/trade-deploy`: `kustomize/base/ingestor/deployment.yaml`.
|
||||||
|
- Utworzono tokeny w `trade-api`:
|
||||||
|
- `write` (dla ingestora) oraz `read` (dla klientów UI/odczytu)
|
||||||
|
- tokeny zapisane jako K8s Secrets (wartości nie logowane):
|
||||||
|
- `trade-staging/Secret/trade-ingestor-tokens`: `alg.json` (write) + `heliusN.json` (RPC)
|
||||||
|
- `trade-staging/Secret/trade-read-token`: `read.json` (read)
|
||||||
|
- Wymuszono rollout `Deployment/trade-ingestor` i potwierdzono w logach:
|
||||||
|
- `ingestVia: "api"`, `writeUrl: "http://trade-api:8787/v1/ingest/tick"`, `auth: "bearer"`.
|
||||||
|
- Potwierdzono, że `trade-api /v1/ticks` działa z tokenem `read` (bez ujawniania tokena).
|
||||||
|
|
||||||
|
### Frontend: deploy na k3s (staging) + Ingress `trade.rv32i.pl`
|
||||||
|
- Zbudowano i wypchnięto obraz do Gitea registry:
|
||||||
|
- `rv32i.pl/trade/trade-frontend:k3s-20260106013603`
|
||||||
|
- Dodano manifesty do `trade/trade-deploy`:
|
||||||
|
- `kustomize/base/frontend/deployment.yaml` + `kustomize/base/frontend/service.yaml`
|
||||||
|
- staging: `kustomize/overlays/staging/frontend-ingress.yaml` (host `trade.rv32i.pl`)
|
||||||
|
- aktualizacje `kustomize/base/kustomization.yaml` i `kustomize/overlays/staging/kustomization.yaml`.
|
||||||
|
- Utworzono sekret `trade-staging/Secret/trade-frontend-tokens` (pliki `frontend.json` + `read.json`; wartości nie logowane).
|
||||||
|
- Argo CD `Application/trade-staging`: `Synced` / `Healthy`; `Deployment/trade-frontend` = `Running`, `/healthz` działa.
|
||||||
|
- TLS dla `trade.rv32i.pl` jest `pending` dopóki DNS `trade.rv32i.pl` nie wskazuje na `77.90.8.171` (w razie cache NXDOMAIN: restart `kube-system/coredns` jak wcześniej).
|
||||||
|
|
||||||
|
### DNS: `trade.rv32i.pl` (weryfikacja)
|
||||||
|
- Sprawdzono rekord A `trade.rv32i.pl` na autorytatywnych DNS (`dns*.home.pl`) oraz na publicznych resolverach: **brak odpowiedzi** (rekord nie był jeszcze widoczny na NS), więc cert-manager trzyma `trade-rv32i-pl-tls` w stanie `pending` (NXDOMAIN).
|
||||||
|
- Uwaga: jeśli w panelu DNS dodasz rekord i nadal masz `NXDOMAIN`, sprawdź czy host nie ma literówki (np. `rade` zamiast `trade`) i czy rekord jest zapisany jako A/CNAME dla właściwej nazwy `trade.rv32i.pl`.
|
||||||
|
|
||||||
|
### DNS/TLS: `trade.rv32i.pl` (finalizacja)
|
||||||
|
- Użytkownik potwierdził rekord A `trade.rv32i.pl` → `77.90.8.171` na autorytatywnych DNS (`dns.home.pl`).
|
||||||
|
- Na VPS potwierdzono:
|
||||||
|
- `Ingress/trade-frontend` ma host `trade.rv32i.pl`.
|
||||||
|
- `Certificate/trade-rv32i-pl-tls` = `Ready=True`.
|
||||||
|
- `https://trade.rv32i.pl` odpowiada `HTTP 401` (basic auth) – czyli Ingress + TLS działają.
|
||||||
|
|
||||||
|
### Weryfikacja wdrożenia (MCP SSH)
|
||||||
|
- `argocd/Application`: `trade-staging` i `portainer` = `Synced` / `Healthy`.
|
||||||
|
- `trade-staging`: wszystkie workloady `Running` (Postgres/Hasura/pgAdmin/trade-api/trade-ingestor/trade-frontend).
|
||||||
|
- `trade-ingestor` ma `INGEST_VIA=api` oraz `INGEST_API_URL=http://trade-api:8787` (ingest przez API z tokenem write).
|
||||||
|
|
||||||
|
### Portainer: odblokowanie ekranu `timeout.html`
|
||||||
|
- Zrestartowano `Deployment/portainer` w namespace `portainer`, żeby ponownie pojawił się kreator inicjalizacji (admin user/password).
|
||||||
|
|
||||||
|
### Gitea: lista repo w organizacji `trade`
|
||||||
|
- `trade/trade-api`
|
||||||
|
- `trade/trade-deploy`
|
||||||
|
- `trade/trade-doc`
|
||||||
|
- `trade/trade-frontend`
|
||||||
|
- `trade/trade-infra`
|
||||||
|
- `trade/trade-ingestor`
|
||||||
|
|
||||||
|
## 2026-01-06
|
||||||
|
|
||||||
|
### Zmiana: log działań w `doc/`
|
||||||
|
- Przeniesiono log działań z `steps.md` → `doc/steps.md` (zgodnie z nową zasadą: wszystko projektowe ląduje w `doc/`).
|
||||||
|
|
||||||
|
### Superproject: decyzja + plan (do akceptacji)
|
||||||
|
- Zaproponowano `trade/trade-infra` jako repo główne (superproject) spinające subrepo (API/ingestor/frontend/deploy/doc).
|
||||||
|
- Plan refaktoru opisany w `doc/migration.md` (sekcja „Metoda superproject”).
|
||||||
|
- Użytkownik zaakceptował `trade/trade-infra` jako superproject; do decyzji pozostało: `submodules` vs `subtree` (rekomendacja: submodules, jeśli chcemy zachować niezależne repo + pinowanie wersji).
|
||||||
37
t2
Normal file
37
t2
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
cat > gitea-values.yaml <<'YAML'
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: traefik
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
hosts:
|
||||||
|
- host: rv32i.pl
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls:
|
||||||
|
- secretName: rv32i-pl-tls
|
||||||
|
hosts:
|
||||||
|
- rv32i.pl
|
||||||
|
gitea:
|
||||||
|
admin:
|
||||||
|
existingSecret: gitea-admin
|
||||||
|
config:
|
||||||
|
server:
|
||||||
|
DOMAIN: rv32i.pl
|
||||||
|
ROOT_URL: https://rv32i.pl/
|
||||||
|
PROTOCOL: http
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
storageClass: local-path
|
||||||
|
size: 10Gi
|
||||||
|
postgresql:
|
||||||
|
enabled: true
|
||||||
|
primary:
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
storageClass: local-path
|
||||||
|
size: 10Gi
|
||||||
|
postgresql-ha:
|
||||||
|
enabled: false
|
||||||
|
YAML
|
||||||
171
workflow-api-ingest.md
Normal file
171
workflow-api-ingest.md
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
# Workflow: init bazy + app stack + ingest przez API (tokeny + basic auth)
|
||||||
|
|
||||||
|
Docelowy podział na 3 compose’y:
|
||||||
|
1) `devops/db/docker-compose.yml` → **Postgres/TimescaleDB + Hasura + pgAdmin** (DB stack)
|
||||||
|
2) `devops/tools/bootstrap/docker-compose.yml` → **one-shot tools** (init SQL + Hasura metadata)
|
||||||
|
3) `devops/app/docker-compose.yml` → **API + frontend (+ opcjonalny ingestor)** (app stack)
|
||||||
|
|
||||||
|
## TL;DR (local, end-to-end)
|
||||||
|
1) `npm install`
|
||||||
|
2) DB: `docker compose -f devops/db/docker-compose.yml up -d`
|
||||||
|
3) Schema/metadata: `docker compose -f devops/tools/bootstrap/docker-compose.yml run --rm db-init` + `docker compose -f devops/tools/bootstrap/docker-compose.yml run --rm hasura-bootstrap`
|
||||||
|
4) Tokeny: skopiuj `tokens/*.example.json` → `tokens/*.json` + ustaw RPC w `tokens/heliusN.json` (albo po prostu `cp tokens/helius.json tokens/heliusN.json`)
|
||||||
|
5) API: `docker compose -f devops/app/docker-compose.yml up -d api` + `curl -sS http://localhost:8787/healthz`
|
||||||
|
6) Tokeny (revocable): `npm run token:api -- --scopes write --out tokens/alg.json` + `npm run token:api -- --scopes read --out tokens/read.json`
|
||||||
|
7) Frontend: `docker compose -f devops/app/docker-compose.yml up -d frontend`
|
||||||
|
8) Ingestor: `docker compose -f devops/app/docker-compose.yml --profile ingest up -d`
|
||||||
|
|
||||||
|
Uwaga: ceny w `drift_ticks` są `NUMERIC` (Hasura zwykle zwraca/oczekuje stringów dla `numeric`); `trade-api` normalizuje je do `number` w `/v1/chart`.
|
||||||
|
|
||||||
|
## Migracja (bez kasowania danych)
|
||||||
|
Szybka sekwencja do migracji schematu + odpalenia stacka (bez `down -v`) jest w `r1.txt`.
|
||||||
|
|
||||||
|
## Wersjonowanie API/UI (v2, v3...) bez ruszania DB/Hasury
|
||||||
|
Cel: uruchamiasz nową wersję `api+frontend(+ingestor)` równolegle, na **nowych tabelach**, na **nowych portach**, a potem robisz cutover ingestora.
|
||||||
|
|
||||||
|
Konwencja:
|
||||||
|
- v1: tabela `drift_ticks`, funkcja `get_drift_candles`, porty `8787/8081`
|
||||||
|
- vN: tabela `drift_ticks_vN`, funkcja `get_drift_candles_vN`, porty `8787+(N-1)` i `8081+(N-1)`
|
||||||
|
- wspólne: Postgres/Hasura/pgAdmin + `api_tokens` (tokeny) zostają bez wersjonowania
|
||||||
|
|
||||||
|
Skrypty: `scripts/ops/` (tworzą też env w `devops/versions/vN.env`)
|
||||||
|
- `bash scripts/ops/version-init.sh 2` migracja DB + create `drift_ticks_v2` + track w Hasurze
|
||||||
|
- `bash scripts/ops/version-up.sh 2` start `api+frontend` v2 (host: `8788/8082`)
|
||||||
|
- `bash scripts/ops/version-ingestor-up.sh 2` start ingestor v2 (pisze do v2 API → `drift_ticks_v2`)
|
||||||
|
- `bash scripts/ops/version-cutover.sh 1 2` start v2 ingestor, potem stop v1 ingestor
|
||||||
|
- `bash scripts/ops/version-backfill.sh 1 2` backfill ALL: `drift_ticks` → `drift_ticks_v2`
|
||||||
|
- `bash scripts/ops/version-down.sh 1` usuń stare kontenery v1 (DB bez zmian)
|
||||||
|
- `bash scripts/ops/version-check.sh 2` healthz/ticks/chart dla v2
|
||||||
|
|
||||||
|
## Reset (UWAGA: kasuje dane z bazy)
|
||||||
|
Jeśli uruchomisz `docker compose -f devops/db/docker-compose.yml down -v`, Postgres/Timescale traci cały wolumen (czyli rekordy z `drift_ticks`).
|
||||||
|
|
||||||
|
## Dane logowania (dev)
|
||||||
|
- Postgres: `postgres://admin:pass@localhost:5432/crypto`
|
||||||
|
- Hasura: `http://localhost:8080` (admin secret: `tokens/hasura.json` → `adminSecret`)
|
||||||
|
- pgAdmin: `http://localhost:5050` (login: `admin@example.com`, hasło: `admin`)
|
||||||
|
- Frontend UI: `http://localhost:8081` (basic auth: `tokens/frontend.json` → `username`/`password`)
|
||||||
|
|
||||||
|
## 0) Start DB stack
|
||||||
|
Z root repo:
|
||||||
|
```bash
|
||||||
|
docker compose -f devops/db/docker-compose.yml up -d
|
||||||
|
```
|
||||||
|
Ten krok tworzy wspólną sieć Dockera `trade-net`, przez którą app stack łączy się do Hasury.
|
||||||
|
|
||||||
|
## 1) Init / upgrade schematu bazy (idempotent)
|
||||||
|
Użyj zwłaszcza, jeśli masz istniejący wolumen Postgresa:
|
||||||
|
```bash
|
||||||
|
docker compose -f devops/tools/bootstrap/docker-compose.yml run --rm db-init
|
||||||
|
```
|
||||||
|
To jest też krok, który trzeba odpalić po zmianach w `devops/db/initdb/001_init.sql` (np. migracja `DOUBLE PRECISION` → `NUMERIC`).
|
||||||
|
|
||||||
|
## 2) Hasura metadata bootstrap (track tabel + permissions)
|
||||||
|
```bash
|
||||||
|
docker compose -f devops/tools/bootstrap/docker-compose.yml run --rm hasura-bootstrap
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3) Skonfiguruj API + frontend
|
||||||
|
Utwórz lokalne pliki (gitignored) w `tokens/`:
|
||||||
|
- `tokens/hasura.json` z `tokens/hasura.example.json` (URL + admin secret dla Hasury, używane przez `trade-api`)
|
||||||
|
- `tokens/api.json` z `tokens/api.example.json` (sekret admina do tworzenia/revoke tokenów API)
|
||||||
|
- `tokens/frontend.json` z `tokens/frontend.example.json` (basic auth do UI)
|
||||||
|
- `tokens/heliusN.json` (RPC dla Drift; albo `heliusApiKey`, albo pełny `rpcUrl`)
|
||||||
|
|
||||||
|
Jeśli odpalasz lokalne skrypty (`npm run token:*`, `npm run ingest:*`) wykonaj też:
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4) Start API (trade-api)
|
||||||
|
```bash
|
||||||
|
docker compose -f devops/app/docker-compose.yml up -d api
|
||||||
|
```
|
||||||
|
|
||||||
|
- API: `http://localhost:8787`
|
||||||
|
|
||||||
|
Healthcheck:
|
||||||
|
```bash
|
||||||
|
curl -sS http://localhost:8787/healthz
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5) Wygeneruj tokeny (revocable)
|
||||||
|
**Write token** dla ingestora (domyślnie zapisuje do `tokens/alg.json`):
|
||||||
|
```bash
|
||||||
|
npm run token:api -- --name algo1 --scopes write --out tokens/alg.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Jeśli wcześniej uruchomiłeś `frontend` bez pliku `tokens/read.json`, Docker mógł utworzyć katalog `tokens/read.json`.
|
||||||
|
Usuń go przed generacją tokena:
|
||||||
|
```bash
|
||||||
|
rm -rf tokens/read.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Read token** dla frontendu (proxy wstrzykuje go serwerowo; nie trafia do JS):
|
||||||
|
```bash
|
||||||
|
npm run token:api -- --name reader-ui --scopes read --out tokens/read.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6) Start frontend (UI)
|
||||||
|
```bash
|
||||||
|
docker compose -f devops/app/docker-compose.yml up -d frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
- Frontend: `http://localhost:8081` (basic auth)
|
||||||
|
|
||||||
|
Dev mode (Vite + proxy `/api` z tokenem z `tokens/read.json`):
|
||||||
|
```bash
|
||||||
|
npm run visualizer:dev
|
||||||
|
```
|
||||||
|
- Dev UI: `http://localhost:5173`
|
||||||
|
|
||||||
|
## 7) Uruchom kontener ingestora, który wysyła ticki do API
|
||||||
|
W ramach app stack (profil `ingest`):
|
||||||
|
```bash
|
||||||
|
docker compose -f devops/app/docker-compose.yml --profile ingest up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatywnie (bez Dockera) możesz odpalić ingest lokalnie:
|
||||||
|
```bash
|
||||||
|
npm run ingest:oracle -- --ingest-via api --market-name PUMP-PERP --interval-ms 1000 --source drift_oracle
|
||||||
|
```
|
||||||
|
Wymaga: `tokens/heliusN.json` (RPC) + `tokens/alg.json` (write token do API) + działającego `trade-api`.
|
||||||
|
|
||||||
|
Override (przykład):
|
||||||
|
```bash
|
||||||
|
MARKET_NAME=PUMP-PERP INTERVAL_MS=250 SOURCE=drift_oracle \
|
||||||
|
docker compose -f devops/app/docker-compose.yml --profile ingest up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8) Sprawdź, czy rekordy wpadają
|
||||||
|
Przez API (wymaga read tokena):
|
||||||
|
```bash
|
||||||
|
curl -sS "http://localhost:8787/v1/ticks?symbol=PUMP-PERP&limit=5" \
|
||||||
|
-H "Authorization: Bearer $(node -p 'require("./tokens/read.json").token')"
|
||||||
|
```
|
||||||
|
|
||||||
|
W UI: `http://localhost:8081` → wykres powinien się aktualizować.
|
||||||
|
|
||||||
|
Sprawdź agregacje + wskaźniki (candles + indicators z backendu):
|
||||||
|
```bash
|
||||||
|
curl -sS "http://localhost:8787/v1/chart?symbol=PUMP-PERP&tf=1m&limit=120" \
|
||||||
|
-H "Authorization: Bearer $(node -p 'require("./tokens/read.json").token')"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checklist (copy/paste)
|
||||||
|
- `npm install` zainstaluj zależności do `npm run token:*` / `npm run ingest:*`
|
||||||
|
- `cp tokens/hasura.example.json tokens/hasura.json` skopiuj konfigurację Hasury
|
||||||
|
- `cp tokens/api.example.json tokens/api.json` skopiuj konfigurację API (adminSecret)
|
||||||
|
- `cp tokens/frontend.example.json tokens/frontend.json` skopiuj basic auth do UI
|
||||||
|
- `cp tokens/helius.json tokens/heliusN.json` ustaw RPC dla Drift (fallback jest też `tokens/helius.json`)
|
||||||
|
- `docker compose -f devops/db/docker-compose.yml up -d` uruchom Postgres/Timescale + Hasura + pgAdmin
|
||||||
|
- `docker compose -f devops/tools/bootstrap/docker-compose.yml run --rm db-init` zastosuj schemat PG (tabele/indeksy/hypertable)
|
||||||
|
- `docker compose -f devops/tools/bootstrap/docker-compose.yml run --rm hasura-bootstrap` track tabel + permissions w Hasurze
|
||||||
|
- `docker compose -f devops/app/docker-compose.yml up -d api` uruchom API
|
||||||
|
- `curl -sS http://localhost:8787/healthz` sprawdź czy API działa
|
||||||
|
- `npm run token:api -- --name algo1 --scopes write --out tokens/alg.json` wygeneruj write token dla ingestora
|
||||||
|
- `npm run token:api -- --name reader-ui --scopes read --out tokens/read.json` wygeneruj read token dla UI/curl
|
||||||
|
- `docker compose -f devops/app/docker-compose.yml up -d frontend` uruchom frontend
|
||||||
|
- `docker compose -f devops/app/docker-compose.yml --profile ingest up -d` uruchom ingestora (Drift → API → DB)
|
||||||
|
- `curl -sS "http://localhost:8787/v1/ticks?symbol=PUMP-PERP&limit=5" -H "Authorization: Bearer $(node -p 'require("./tokens/read.json").token')"` sprawdź czy ticki wpadają
|
||||||
|
- `curl -sS "http://localhost:8787/v1/chart?symbol=PUMP-PERP&tf=1m&limit=120" -H "Authorization: Bearer $(node -p 'require("./tokens/read.json").token')"` sprawdź candles + wskaźniki z backendu
|
||||||
Reference in New Issue
Block a user