This commit is contained in:
2026-05-13 09:39:50 +02:00
parent aa19ca9c18
commit 69385094ff
16 changed files with 293 additions and 36 deletions

View File

@@ -61,3 +61,4 @@
0.7.28 - Refactor DEX commun et verrouillage des invariants de normalisation : séparation des événements décodés, actionnables, trade candidates et candle candidates ; conservation des transactions failed comme traçables mais non actionnables ; ajout de la règle bloquante empêchant tout trade/candle candidate sans payload de montants exploitable, notamment pour le cas partiel `meteora_damm_v1.swap` sans base/quote amount. 0.7.28 - Refactor DEX commun et verrouillage des invariants de normalisation : séparation des événements décodés, actionnables, trade candidates et candle candidates ; conservation des transactions failed comme traçables mais non actionnables ; ajout de la règle bloquante empêchant tout trade/candle candidate sans payload de montants exploitable, notamment pour le cas partiel `meteora_damm_v1.swap` sans base/quote amount.
0.7.29 - Ajout dune matrice DEX commune (`dex_support_matrix`) utilisée par le catalogue DEX, la classification transactionnelle et lenregistrement des protocol candidates ; ajout du profil de validation `0.7.29_multi_dex_matrix_baseline` exposant la matrice dans le rapport de validation ; préparation explicite des surfaces planifiées sans inventer de program ids non vérifiés. 0.7.29 - Ajout dune matrice DEX commune (`dex_support_matrix`) utilisée par le catalogue DEX, la classification transactionnelle et lenregistrement des protocol candidates ; ajout du profil de validation `0.7.29_multi_dex_matrix_baseline` exposant la matrice dans le rapport de validation ; préparation explicite des surfaces planifiées sans inventer de program ids non vérifiés.
0.7.30 - Ajout dune taxonomie DEX plus fine pour les événements décodés : `eventLifecycleKind`, `eventActionability`, `nonTradeUseful`, compteurs diagnostics des événements non-trade utiles, trades non actionnables et classifications inconnues ; ajout du profil `0.7.30_non_trade_event_classification` sans modification volontaire de la matérialisation trade/candle. 0.7.30 - Ajout dune taxonomie DEX plus fine pour les événements décodés : `eventLifecycleKind`, `eventActionability`, `nonTradeUseful`, compteurs diagnostics des événements non-trade utiles, trades non actionnables et classifications inconnues ; ajout du profil `0.7.30_non_trade_event_classification` sans modification volontaire de la matérialisation trade/candle.
0.7.31 - Application de la politique Option B : les transactions failed restent traçables dans les événements décodés mais ne peuvent plus alimenter `trade_events`, metrics ou candles ; le replay local réinitialise les tables de matérialisation marché avant reconstruction pour supprimer les anciennes lignes dérivées non actionnables.

View File

@@ -8,7 +8,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.7.30" version = "0.7.31"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot" repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"

View File

@@ -215,7 +215,7 @@ Les tests peuvent rester plus souples lorsque cela clarifie le test.
La reprise doit suivre cet ordre : La reprise doit suivre cet ordre :
1. conserver la non-régression `0.7.28` : transactions failed traçables mais non actionnables, aucun trade/candle candidate sans montant exploitable ; 1. conserver la non-régression `0.7.31` : transactions failed traçables mais exclues des `trade_events`, metrics et candles ;
2. utiliser la matrice `0.7.29` comme source commune pour le catalogue, la classification et les protocol candidates ; 2. utiliser la matrice `0.7.29` comme source commune pour le catalogue, la classification et les protocol candidates ;
3. relier progressivement les événements non-trade aux tables existantes : lifecycle, liquidité, fees, rewards, admin ; 3. relier progressivement les événements non-trade aux tables existantes : lifecycle, liquidité, fees, rewards, admin ;
4. consolider Meteora, surtout `meteora_dlmm` et le cas partiel `meteora_damm_v1` ; 4. consolider Meteora, surtout `meteora_dlmm` et le cas partiel `meteora_damm_v1` ;

View File

@@ -826,9 +826,7 @@ Matrice cible initiale :
| `zora` | à vérifier | à vérifier | hors phasage actif avant preuve Solana | | `zora` | à vérifier | à vérifier | hors phasage actif avant preuve Solana |
### 6.062. Version `0.7.30` — Classification fine des événements DEX décodés ### 6.062. Version `0.7.30` — Classification fine des événements DEX décodés
Objectif : préparer les événements non-trade utiles sans modifier la matérialisation trade/candle validée. Réalisé :
Fait / à valider :
- ajouter `DexEventLifecycleKind` pour distinguer `trade_swap`, `pool_creation`, `pair_creation`, `liquidity_add`, `liquidity_remove`, `position_open`, `position_close`, `migration`, `launch`, `mint`, `burn`, `fee_collection`, `reward`, `admin_config` et `unknown`, - ajouter `DexEventLifecycleKind` pour distinguer `trade_swap`, `pool_creation`, `pair_creation`, `liquidity_add`, `liquidity_remove`, `position_open`, `position_close`, `migration`, `launch`, `mint`, `burn`, `fee_collection`, `reward`, `admin_config` et `unknown`,
- ajouter `DexEventActionability` pour distinguer `trade_candidate`, `non_actionable_trade`, `non_trade_useful`, `failed_transaction`, `informational` et `unknown`, - ajouter `DexEventActionability` pour distinguer `trade_candidate`, `non_actionable_trade`, `non_trade_useful`, `failed_transaction`, `informational` et `unknown`,
@@ -838,7 +836,17 @@ Fait / à valider :
- ajouter le profil `0.7.30_non_trade_event_classification`, - ajouter le profil `0.7.30_non_trade_event_classification`,
- ne pas matérialiser encore les événements non-trade dans leurs tables dédiées. - ne pas matérialiser encore les événements non-trade dans leurs tables dédiées.
### 6.063. Version `0.7.31` — Transactions inconnues et protocol candidates ### 6.063. Version `0.7.31` — Politique de matérialisation des failed transactions
Réalisé :
- empêcher `TradeAggregationService` de matérialiser une transaction dont `err_json` est renseigné ;
- conserver les événements DEX décodés des failed transactions pour audit et diagnostic ;
- réinitialiser les tables dérivées de marché pendant le replay local : `k_sol_trade_events`, `k_sol_pair_metrics`, `k_sol_pair_candles`, `k_sol_pair_analytic_signals` ;
- reconstruire ensuite les trades/candles uniquement à partir des événements `tradeCandidate=true` et de transactions OK ;
- exposer `resetMarketMaterializationDeletedCount` dans le résultat de replay UI ;
- conserver la validation multi-DEX et la matrice DEX comme garde-fous avant dajouter les surfaces restantes.
### 6.064. Version `0.7.32` — Transactions inconnues et protocol candidates
Objectif : ne plus perdre les transactions utiles qui ne correspondent pas encore à un DEX connu. Objectif : ne plus perdre les transactions utiles qui ne correspondent pas encore à un DEX connu.
À faire : À faire :
@@ -851,7 +859,7 @@ Objectif : ne plus perdre les transactions utiles qui ne correspondent pas encor
- permettre de promouvoir plus tard un protocol candidate vers un vrai DEX/surface sans perdre lhistorique, - permettre de promouvoir plus tard un protocol candidate vers un vrai DEX/surface sans perdre lhistorique,
- garantir que ces tables nalimentent jamais directement les trades/candles. - garantir que ces tables nalimentent jamais directement les trades/candles.
### 6.064. Version `0.7.32` — Événements non-trade v1 : liquidité et cycle de vie pool ### 6.065. Version `0.7.33` — Événements non-trade v1 : liquidité et cycle de vie pool
Objectif : exploiter les événements utiles à lanalyse et au trading semi-automatique sans les mélanger avec les swaps/candles. Objectif : exploiter les événements utiles à lanalyse et au trading semi-automatique sans les mélanger avec les swaps/candles.
À faire : À faire :
@@ -864,7 +872,7 @@ Objectif : exploiter les événements utiles à lanalyse et au trading semi-a
- alimenter les diagnostics locaux avec les compteurs liquidité/lifecycle, - alimenter les diagnostics locaux avec les compteurs liquidité/lifecycle,
- garantir quun événement de liquidité ou de cycle de vie ne produit jamais de candle directement. - garantir quun événement de liquidité ou de cycle de vie ne produit jamais de candle directement.
### 6.065. Version `0.7.33` — Événements non-trade v2 : fees, rewards et administration ### 6.066. Version `0.7.34` — Événements non-trade v2 : fees, rewards et administration
Objectif : conserver les événements utiles au risque, au scoring, à léconomie du pool et à la traçabilité opérationnelle. Objectif : conserver les événements utiles au risque, au scoring, à léconomie du pool et à la traçabilité opérationnelle.
À faire : À faire :
@@ -878,7 +886,7 @@ Objectif : conserver les événements utiles au risque, au scoring, à lécon
- rattacher ces événements aux transactions, decoded events, pools, paires et wallets observés lorsque les comptes le permettent, - rattacher ces événements aux transactions, decoded events, pools, paires et wallets observés lorsque les comptes le permettent,
- documenter clairement que ces événements ne sont ni des trades ni des candles. - documenter clairement que ces événements ne sont ni des trades ni des candles.
### 6.066. Version `0.7.34` — Meteora : DBC / DAMM v1 / DAMM v2 / DLMM ### 6.067. Version `0.7.35` — Meteora : DBC / DAMM v1 / DAMM v2 / DLMM
Objectif : consolider Meteora comme famille multi-programmes au lieu de traiter chaque variante comme un cas isolé incomplet. Objectif : consolider Meteora comme famille multi-programmes au lieu de traiter chaque variante comme un cas isolé incomplet.
À faire : À faire :
@@ -892,7 +900,7 @@ Objectif : consolider Meteora comme famille multi-programmes au lieu de traiter
- vérifier lidempotence du replay local sur un corpus Meteora mixte, - vérifier lidempotence du replay local sur un corpus Meteora mixte,
- documenter les limites connues des variantes insuffisamment couvertes. - documenter les limites connues des variantes insuffisamment couvertes.
### 6.066. Version `0.7.34` — Launch surfaces : LaunchLab, LetsBonk, Bags, Moonshot/Moonit, Boop.fun, Believe ### 6.068. Version `0.7.36` — Launch surfaces : LaunchLab, LetsBonk, Bags, Moonshot/Moonit, Boop.fun, Believe
Objectif : détecter la première source de mint/lancement des tokens même lorsque le swap final se fait ailleurs. Objectif : détecter la première source de mint/lancement des tokens même lorsque le swap final se fait ailleurs.
À faire : À faire :
@@ -907,7 +915,7 @@ Objectif : détecter la première source de mint/lancement des tokens même lors
- rattacher les launch origins aux pools et paires lorsque les comptes permettent un matching fiable, - rattacher les launch origins aux pools et paires lorsque les comptes permettent un matching fiable,
- exposer les origins dans les diagnostics et lUI dinspection. - exposer les origins dans les diagnostics et lUI dinspection.
### 6.067. Version `0.7.35` — Heaven : corpus, launch et AMM ### 6.069. Version `0.7.37` — Heaven : corpus, launch et AMM
Objectif : ajouter Heaven sans le classer trop tôt comme simple DEX ou simple launchpad. Objectif : ajouter Heaven sans le classer trop tôt comme simple DEX ou simple launchpad.
À faire : À faire :
@@ -919,7 +927,7 @@ Objectif : ajouter Heaven sans le classer trop tôt comme simple DEX ou simple l
- documenter les limites si le corpus ne permet pas encore de matérialiser tous les événements, - documenter les limites si le corpus ne permet pas encore de matérialiser tous les événements,
- vérifier que Heaven ne crée pas de candles invalides en cas dévénement de launch non pricé. - vérifier que Heaven ne crée pas de candles invalides en cas dévénement de launch non pricé.
### 6.068. Version `0.7.36` — Orca / FluxBeam / DexLab : corpus et validation ciblée ### 6.070. Version `0.7.38` — Orca / FluxBeam / DexLab : corpus et validation ciblée
Objectif : consolider les connecteurs déjà présents à partir de corpus locaux vérifiables. Objectif : consolider les connecteurs déjà présents à partir de corpus locaux vérifiables.
À faire : À faire :
@@ -931,7 +939,7 @@ Objectif : consolider les connecteurs déjà présents à partir de corpus locau
- marquer explicitement les variantes partiellement supportées ou heuristiques, - marquer explicitement les variantes partiellement supportées ou heuristiques,
- rejouer les corpus plusieurs fois pour vérifier lidempotence et labsence de trades/candles invalides. - rejouer les corpus plusieurs fois pour vérifier lidempotence et labsence de trades/candles invalides.
### 6.069. Version `0.7.37` — Raydium AMM v4 legacy : corpus et validation ciblée ### 6.071. Version `0.7.39` — Raydium AMM v4 legacy : corpus et validation ciblée
Objectif : traiter le vrai Raydium AMM v4 historique après les autres Raydium, afin de lisoler de `raydium_cpmm`, `raydium_clmm` et des labels Raydium génériques. Objectif : traiter le vrai Raydium AMM v4 historique après les autres Raydium, afin de lisoler de `raydium_cpmm`, `raydium_clmm` et des labels Raydium génériques.
À faire : À faire :
@@ -944,7 +952,7 @@ Objectif : traiter le vrai Raydium AMM v4 historique après les autres Raydium,
- renommer/stabiliser les fonctions internes autour de `raydium_amm_v4` pour éviter lambiguïté avec `raydium_cpmm` et `raydium_clmm`, - renommer/stabiliser les fonctions internes autour de `raydium_amm_v4` pour éviter lambiguïté avec `raydium_cpmm` et `raydium_clmm`,
- documenter les limites connues si le corpus AMM v4 reste faible. - documenter les limites connues si le corpus AMM v4 reste faible.
### 6.070. Version `0.7.38` — Validation DEX v1 consolidée ### 6.072. Version `0.7.40` — Validation DEX v1 consolidée
Objectif : rejouer tous les DEX et launch surfaces supportés et valider les invariants du pipeline complet. Objectif : rejouer tous les DEX et launch surfaces supportés et valider les invariants du pipeline complet.
À faire : À faire :
@@ -957,7 +965,7 @@ Objectif : rejouer tous les DEX et launch surfaces supportés et valider les inv
- conserver une matrice de support par DEX, variante, instruction et type dévénement, - conserver une matrice de support par DEX, variante, instruction et type dévénement,
- verrouiller les invariants avant douvrir lanalyse `0.8.x`. - verrouiller les invariants avant douvrir lanalyse `0.8.x`.
### 6.071. Version `0.7.39` — `kb_demo_app` : overlays analytiques ### 6.073. Version `0.7.41` — `kb_demo_app` : overlays analytiques
Objectif : rendre visibles les signaux analytiques directement sur les graphes et vues de marché. Objectif : rendre visibles les signaux analytiques directement sur les graphes et vues de marché.
À faire : À faire :
@@ -968,7 +976,7 @@ Objectif : rendre visibles les signaux analytiques directement sur les graphes e
- afficher un panneau latéral listant les signaux liés à une paire et à un timeframe, - afficher un panneau latéral listant les signaux liés à une paire et à un timeframe,
- préparer lextension future vers Ichimoku, Kumo, projections ABCD et égalités temps/prix sans les mélanger au pipeline de décodage DEX. - préparer lextension future vers Ichimoku, Kumo, projections ABCD et égalités temps/prix sans les mélanger au pipeline de décodage DEX.
### 6.072. Version `0.7.40` — `kb_demo_app` : vues consolidées token / pair / pool ### 6.074. Version `0.7.42` — `kb_demo_app` : vues consolidées token / pair / pool
Objectif : fournir une lecture métier plus confortable du modèle `0.7.x`. Objectif : fournir une lecture métier plus confortable du modèle `0.7.x`.
À faire : À faire :
@@ -980,7 +988,7 @@ Objectif : fournir une lecture métier plus confortable du modèle `0.7.x`.
- préparer une navigation transversale entre objets techniques et objets métier, - préparer une navigation transversale entre objets techniques et objets métier,
- rendre explicites les cas `tradeCount = null`, `lastPriceQuotePerBase = null`, tokens non enrichis et événements conservés uniquement pour analyse. - rendre explicites les cas `tradeCount = null`, `lastPriceQuotePerBase = null`, tokens non enrichis et événements conservés uniquement pour analyse.
### 6.073. Version `0.7.41` — Finition UI `0.7.x` ### 6.075. Version `0.7.43` — Finition UI `0.7.x`
Objectif : stabiliser la couche desktop de validation avant louverture de `0.8.x`. Objectif : stabiliser la couche desktop de validation avant louverture de `0.8.x`.
À faire : À faire :
@@ -991,7 +999,7 @@ Objectif : stabiliser la couche desktop de validation avant louverture de `0.
- préparer une base UI suffisamment stable pour la future phase danalyse et filtrage `0.8.x`, - préparer une base UI suffisamment stable pour la future phase danalyse et filtrage `0.8.x`,
- vérifier que les commandes Tauri restent de simples façades vers `kb_lib`. - vérifier que les commandes Tauri restent de simples façades vers `kb_lib`.
### 6.074. Version `0.7.x` — Couverture DEX v1 ### 6.076. Version `0.7.x` — Couverture DEX v1
Objectif : structurer les connecteurs DEX autour dun pipeline complet de résolution, décodage, normalisation métier et classification des événements non-trade. Objectif : structurer les connecteurs DEX autour dun pipeline complet de résolution, décodage, normalisation métier et classification des événements non-trade.
Protocoles et surfaces cibles : Protocoles et surfaces cibles :
@@ -1034,7 +1042,7 @@ Résultat attendu :
- préparation dune détection temps réel hybride et dun backfill ciblé compatible avec les mêmes objets métier, - préparation dune détection temps réel hybride et dun backfill ciblé compatible avec les mêmes objets métier,
- préparation dagrégats DEX plus riches, de candles/OHLCV et dune UI dinspection du pipeline `0.7.x`. - préparation dagrégats DEX plus riches, de candles/OHLCV et dune UI dinspection du pipeline `0.7.x`.
### 6.075. Version `0.8.x` — Analyse et filtrage ### 6.077. Version `0.8.x` — Analyse et filtrage
Objectif : transformer les événements bruts en signaux exploitables. Objectif : transformer les événements bruts en signaux exploitables.
À faire : À faire :
@@ -1049,7 +1057,7 @@ Objectif : transformer les événements bruts en signaux exploitables.
- outils de sélection manuelle de points ABC et projection dun point D selon des règles temps/prix explicites, - outils de sélection manuelle de points ABC et projection dun point D selon des règles temps/prix explicites,
- séparation stricte entre signaux analytiques observés, projections hypothétiques et décisions de trading. - séparation stricte entre signaux analytiques observés, projections hypothétiques et décisions de trading.
### 6.076. Version `1.x.y` — Wallets et swap préparatoire ### 6.078. Version `1.x.y` — Wallets et swap préparatoire
Objectif : préparer la couche daction. Objectif : préparer la couche daction.
À faire : À faire :
@@ -1060,7 +1068,7 @@ Objectif : préparer la couche daction.
- préparation dordres et de swaps, - préparation dordres et de swaps,
- simulation et garde-fous. - simulation et garde-fous.
### 6.077. Version `2.x.y` — Trading semi-automatisé ### 6.079. Version `2.x.y` — Trading semi-automatisé
Objectif : brancher lanalyse à laction tout en gardant des garde-fous explicites. Objectif : brancher lanalyse à laction tout en gardant des garde-fous explicites.
À faire : À faire :
@@ -1071,7 +1079,7 @@ Objectif : brancher lanalyse à laction tout en gardant des garde-fous exp
- confirmations explicites ou semi-automatiques, - confirmations explicites ou semi-automatiques,
- journaux dexécution. - journaux dexécution.
### 6.078. Version `3.x.y` — Yellowstone gRPC ### 6.080. Version `3.x.y` — Yellowstone gRPC
Objectif : ajouter le connecteur gRPC dédié. Objectif : ajouter le connecteur gRPC dédié.
À faire : À faire :
@@ -1187,7 +1195,7 @@ Réalisé / à maintenir :
Validé en `0.7.30` : classification fine des événements décodés via `eventLifecycleKind`, `eventActionability` et `nonTradeUseful`, avec diagnostics associés et sans changement volontaire sur les trades/candles. Validé en `0.7.30` : classification fine des événements décodés via `eventLifecycleKind`, `eventActionability` et `nonTradeUseful`, avec diagnostics associés et sans changement volontaire sur les trades/candles.
À poursuivre en `0.7.31` : transactions inconnues et protocol candidates ; puis en `0.7.32` : matérialisation contrôlée des événements non-trade utiles. À poursuivre après `0.7.31` : transactions inconnues/protocol candidates, puis matérialisation contrôlée des événements non-trade utiles, sans alimenter les trades/candles actionnables.
## 11. Documentation et livrables de référence ## 11. Documentation et livrables de référence
Le projet doit maintenir au minimum : Le projet doit maintenir au minimum :
@@ -1203,7 +1211,7 @@ Le projet doit maintenir au minimum :
La priorité immédiate est désormais la suivante : La priorité immédiate est désormais la suivante :
1. conserver la validation acquise `0.7.28` : transactions failed traçables mais non actionnables, aucun trade/candle candidate sans payload montant/prix exploitable, aucun diagnostic bloquant masqué, 1. conserver la validation acquise `0.7.31` : transactions failed traçables mais exclues des `trade_events`, metrics et candles, aucun trade/candle candidate sans payload montant/prix exploitable, aucun diagnostic bloquant masqué,
2. utiliser la matrice `0.7.29` (`kb_lib/src/dex_support_matrix.rs`) comme source commune pour le catalogue DEX, les mappings program id -> protocole, la classification transactionnelle et les protocol candidates, 2. utiliser la matrice `0.7.29` (`kb_lib/src/dex_support_matrix.rs`) comme source commune pour le catalogue DEX, les mappings program id -> protocole, la classification transactionnelle et les protocol candidates,
3. garder les clients HTTP/WS et managers réseau hors du refactor DEX tant quils ne bloquent pas le pipeline, 3. garder les clients HTTP/WS et managers réseau hors du refactor DEX tant quils ne bloquent pas le pipeline,
4. consolider les événements non-trade sans les confondre avec les trades/candles : lifecycle de pool, liquidité, fees, rewards, admin/config, migration et launch/mint, 4. consolider les événements non-trade sans les confondre avec les trades/candles : lifecycle de pool, liquidité, fees, rewards, admin/config, migration et launch/mint,

View File

@@ -166,10 +166,11 @@
<div class="mb-3"> <div class="mb-3">
<label for="demoPipeline2ValidationProfileSelect" class="form-label">Validation profile</label> <label for="demoPipeline2ValidationProfileSelect" class="form-label">Validation profile</label>
<select id="demoPipeline2ValidationProfileSelect" class="form-select"> <select id="demoPipeline2ValidationProfileSelect" class="form-select">
<option value="0.7.30_non_trade_event_classification" selected>0.7.30non-trade event classification</option> <option value="0.7.31_trade_event_actionability_policy" selected>0.7.31 — trade event actionability policy</option>
<option value="0.7.30_non_trade_event_classification">0.7.30 — non-trade event classification</option>
<option value="0.7.29_multi_dex_matrix_baseline">0.7.29 — DEX matrix baseline</option> <option value="0.7.29_multi_dex_matrix_baseline">0.7.29 — DEX matrix baseline</option>
<option value="0.7.28_multi_dex_non_regression">0.7.28 — multi-DEX non-regression</option> <option value="0.7.28_multi_dex_non_regression">0.7.28 — multi-DEX non-regression</option>
<option value="0.7.27_dexes_non_regression" selected>0.7.27 — first DEXes non-regression</option> <option value="0.7.27_dexes_non_regression">0.7.27 — first DEXes non-regression</option>
</select> </select>
</div> </div>

View File

@@ -61,6 +61,7 @@ interface LocalPipelineReplayResult {
analyticSignalUpsertCount: number; analyticSignalUpsertCount: number;
tokenMetadataUpdatedCount: number; tokenMetadataUpdatedCount: number;
pairSymbolUpdatedCount: number; pairSymbolUpdatedCount: number;
resetMarketMaterializationDeletedCount: number;
globalErrorCount: number; globalErrorCount: number;
} }
function appendLogLine(textarea: HTMLTextAreaElement, line: string): void { function appendLogLine(textarea: HTMLTextAreaElement, line: string): void {
@@ -617,7 +618,7 @@ document.addEventListener("DOMContentLoaded", async () => {
appendLogLine( appendLogLine(
logTextarea, logTextarea,
`[ui] local pipeline replay completed: ${result.replayedTransactionCount.toString()} replayed, ${result.tradeEventCount.toString()} trades, ${result.pairCandleUpsertCount.toString()} candle upserts`, `[ui] local pipeline replay completed: ${result.replayedTransactionCount.toString()} replayed, ${result.tradeEventCount.toString()} trades, ${result.pairCandleUpsertCount.toString()} candle upserts, resetDeleted='${result.resetMarketMaterializationDeletedCount.toString()}'`,
); );
await refreshCatalog(); await refreshCatalog();

View File

@@ -1,7 +1,7 @@
{ {
"name": "kb-demo-app", "name": "kb-demo-app",
"private": true, "private": true,
"version": "0.7.29", "version": "0.7.31",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -971,7 +971,7 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
let service = kb_lib::LocalPipelineValidationService::new(database.clone()); let service = kb_lib::LocalPipelineValidationService::new(database.clone());
let profile_code = match request { let profile_code = match request {
Some(request) => request.profile_code, Some(request) => request.profile_code,
None => "0.7.30_non_trade_event_classification".to_string(), None => "0.7.31_trade_event_actionability_policy".to_string(),
}; };
let run_result = match profile_code.as_str() { let run_result = match profile_code.as_str() {
"0.7.27" | "0.7.27_dexes_non_regression" => { "0.7.27" | "0.7.27_dexes_non_regression" => {
@@ -986,6 +986,9 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
"0.7.30" | "0.7.30_non_trade_event_classification" => { "0.7.30" | "0.7.30_non_trade_event_classification" => {
service.validate_v0_7_30_current_database().await service.validate_v0_7_30_current_database().await
}, },
"0.7.31" | "0.7.31_trade_event_actionability_policy" => {
service.validate_v0_7_31_current_database().await
},
other => Err(kb_lib::Error::InvalidState(format!( other => Err(kb_lib::Error::InvalidState(format!(
"unsupported local pipeline validation profile: {other}" "unsupported local pipeline validation profile: {other}"
))), ))),
@@ -1356,6 +1359,7 @@ pub(crate) async fn demo_pipeline2_replay_local_pipeline(
limit, limit,
refresh_missing_token_metadata, refresh_missing_token_metadata,
token_metadata_limit, token_metadata_limit,
reset_market_materialization_before_replay: true,
}; };
let database = state.database.clone(); let database = state.database.clone();
let service = if refresh_missing_token_metadata { let service = if refresh_missing_token_metadata {

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "kb-demo-app", "productName": "kb-demo-app",
"version": "0.7.29", "version": "0.7.31",
"identifier": "com.sasedev.kb-demo-app", "identifier": "com.sasedev.kb-demo-app",
"build": { "build": {
"beforeDevCommand": "npm run dev", "beforeDevCommand": "npm run dev",

View File

@@ -207,6 +207,7 @@ pub use queries::query_trade_events_get_by_decoded_event_id;
pub use queries::query_trade_events_list_by_pair_id; pub use queries::query_trade_events_list_by_pair_id;
pub use queries::query_trade_events_list_by_transaction_id; pub use queries::query_trade_events_list_by_transaction_id;
pub use queries::query_trade_events_upsert; pub use queries::query_trade_events_upsert;
pub use queries::query_trade_market_materialization_delete_all;
pub use queries::query_transaction_classifications_get_by_signature; pub use queries::query_transaction_classifications_get_by_signature;
pub use queries::query_transaction_classifications_get_by_transaction_id; pub use queries::query_transaction_classifications_get_by_transaction_id;
pub use queries::query_transaction_classifications_list_recent; pub use queries::query_transaction_classifications_list_recent;

View File

@@ -145,6 +145,7 @@ pub use trade_event::query_trade_events_get_by_decoded_event_id;
pub use trade_event::query_trade_events_list_by_pair_id; pub use trade_event::query_trade_events_list_by_pair_id;
pub use trade_event::query_trade_events_list_by_transaction_id; pub use trade_event::query_trade_events_list_by_transaction_id;
pub use trade_event::query_trade_events_upsert; pub use trade_event::query_trade_events_upsert;
pub use trade_event::query_trade_market_materialization_delete_all;
pub use transaction_classification::query_transaction_classifications_get_by_signature; pub use transaction_classification::query_transaction_classifications_get_by_signature;
pub use transaction_classification::query_transaction_classifications_get_by_transaction_id; pub use transaction_classification::query_transaction_classifications_get_by_transaction_id;
pub use transaction_classification::query_transaction_classifications_list_recent; pub use transaction_classification::query_transaction_classifications_list_recent;

View File

@@ -2,6 +2,41 @@
//! Queries for `k_sol_trade_events`. //! Queries for `k_sol_trade_events`.
/// Deletes all local market materialization rows that are rebuilt from trade events.
///
/// The local replay pipeline is deterministic over persisted raw transactions. Clearing
/// these derived tables before replay prevents stale failed-transaction trades, metrics
/// or candles from surviving after actionability rules change.
pub async fn query_trade_market_materialization_delete_all(
database: &crate::Database,
) -> Result<u64, crate::Error> {
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let statements = [
("k_sol_pair_analytic_signals", "DELETE FROM k_sol_pair_analytic_signals"),
("k_sol_pair_candles", "DELETE FROM k_sol_pair_candles"),
("k_sol_pair_metrics", "DELETE FROM k_sol_pair_metrics"),
("k_sol_trade_events", "DELETE FROM k_sol_trade_events"),
];
let mut deleted_count = 0_u64;
for (table_name, statement) in statements {
let query_result = sqlx::query(statement).execute(pool).await;
let result = match query_result {
Ok(result) => result,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot clear {} during local market materialization reset on sqlite: {}",
table_name, error
)));
},
};
deleted_count += result.rows_affected();
}
return Ok(deleted_count);
},
}
}
/// Inserts or updates one trade-event row and returns its stable internal id. /// Inserts or updates one trade-event row and returns its stable internal id.
pub async fn query_trade_events_upsert( pub async fn query_trade_events_upsert(
database: &crate::Database, database: &crate::Database,

View File

@@ -687,6 +687,8 @@ pub use db::query_trade_events_list_by_pair_id;
pub use db::query_trade_events_list_by_transaction_id; pub use db::query_trade_events_list_by_transaction_id;
/// Inserts or updates one trade-event row and returns its stable internal id. /// Inserts or updates one trade-event row and returns its stable internal id.
pub use db::query_trade_events_upsert; pub use db::query_trade_events_upsert;
/// Deletes all local market materialization rows rebuilt from trade events.
pub use db::query_trade_market_materialization_delete_all;
/// Reads one transaction classification by signature. /// Reads one transaction classification by signature.
pub use db::query_transaction_classifications_get_by_signature; pub use db::query_transaction_classifications_get_by_signature;
/// Reads one transaction classification by transaction id. /// Reads one transaction classification by transaction id.

View File

@@ -16,6 +16,8 @@ pub struct LocalPipelineReplayConfig {
pub refresh_missing_token_metadata: bool, pub refresh_missing_token_metadata: bool,
/// Maximum number of missing token metadata rows to resolve. /// Maximum number of missing token metadata rows to resolve.
pub token_metadata_limit: std::option::Option<i64>, pub token_metadata_limit: std::option::Option<i64>,
/// Whether locally replayed market materialization tables are reset before replay.
pub reset_market_materialization_before_replay: bool,
} }
impl Default for LocalPipelineReplayConfig { impl Default for LocalPipelineReplayConfig {
@@ -24,6 +26,7 @@ impl Default for LocalPipelineReplayConfig {
limit: Some(10_000), limit: Some(10_000),
refresh_missing_token_metadata: false, refresh_missing_token_metadata: false,
token_metadata_limit: Some(250), token_metadata_limit: Some(250),
reset_market_materialization_before_replay: true,
}; };
} }
} }
@@ -71,6 +74,8 @@ pub struct LocalPipelineReplayResult {
pub token_metadata_updated_count: usize, pub token_metadata_updated_count: usize,
/// Number of pair symbols updated after replay. /// Number of pair symbols updated after replay.
pub pair_symbol_updated_count: usize, pub pair_symbol_updated_count: usize,
/// Number of derived market materialization rows deleted before replay.
pub reset_market_materialization_deleted_count: u64,
/// Number of errors outside per-signature replay. /// Number of errors outside per-signature replay.
pub global_error_count: usize, pub global_error_count: usize,
} }
@@ -120,6 +125,19 @@ impl LocalPipelineReplayService {
Ok(signatures) => signatures, Ok(signatures) => signatures,
Err(error) => return Err(error), Err(error) => return Err(error),
}; };
let mut reset_market_materialization_deleted_count = 0_u64;
if config.reset_market_materialization_before_replay {
let reset_result =
crate::query_trade_market_materialization_delete_all(self.database.as_ref()).await;
reset_market_materialization_deleted_count = match reset_result {
Ok(deleted_count) => deleted_count,
Err(error) => return Err(error),
};
tracing::debug!(
deleted_count = reset_market_materialization_deleted_count,
"local pipeline replay reset market materialization tables"
);
}
let dex_decode = crate::DexDecodeService::new(self.database.clone()); let dex_decode = crate::DexDecodeService::new(self.database.clone());
let dex_detect = crate::DexDetectService::new(self.database.clone()); let dex_detect = crate::DexDetectService::new(self.database.clone());
let trade_aggregation = crate::TradeAggregationService::new(self.database.clone()); let trade_aggregation = crate::TradeAggregationService::new(self.database.clone());
@@ -130,6 +148,7 @@ impl LocalPipelineReplayService {
crate::TransactionClassificationService::new(self.database.clone()); crate::TransactionClassificationService::new(self.database.clone());
let mut result = LocalPipelineReplayResult { let mut result = LocalPipelineReplayResult {
selected_transaction_count: signatures.len(), selected_transaction_count: signatures.len(),
reset_market_materialization_deleted_count,
..Default::default() ..Default::default()
}; };
for signature in signatures { for signature in signatures {

View File

@@ -38,6 +38,8 @@ pub struct LocalPipelineValidationConfig {
pub require_trade_events_per_dex: bool, pub require_trade_events_per_dex: bool,
/// Whether each DEX summary must contain at least one candle. /// Whether each DEX summary must contain at least one candle.
pub require_candles_per_dex: bool, pub require_candles_per_dex: bool,
/// Whether non-actionable classification groups must have no linked trade events.
pub require_no_non_actionable_trade_events_materialized: bool,
} }
impl Default for LocalPipelineValidationConfig { impl Default for LocalPipelineValidationConfig {
@@ -56,6 +58,7 @@ impl Default for LocalPipelineValidationConfig {
require_decoded_events_per_dex: true, require_decoded_events_per_dex: true,
require_trade_events_per_dex: true, require_trade_events_per_dex: true,
require_candles_per_dex: true, require_candles_per_dex: true,
require_no_non_actionable_trade_events_materialized: true,
}; };
} }
} }
@@ -82,6 +85,7 @@ impl LocalPipelineValidationConfig {
require_decoded_events_per_dex: true, require_decoded_events_per_dex: true,
require_trade_events_per_dex: true, require_trade_events_per_dex: true,
require_candles_per_dex: true, require_candles_per_dex: true,
require_no_non_actionable_trade_events_materialized: true,
}; };
} }
@@ -113,10 +117,10 @@ impl LocalPipelineValidationConfig {
require_decoded_events_per_dex: true, require_decoded_events_per_dex: true,
require_trade_events_per_dex: false, require_trade_events_per_dex: false,
require_candles_per_dex: false, require_candles_per_dex: false,
require_no_non_actionable_trade_events_materialized: true,
}; };
} }
/// Builds the `0.7.29` DEX support matrix baseline validation config. /// Builds the `0.7.29` DEX support matrix baseline validation config.
/// ///
/// This profile preserves the `0.7.28` trade/candle non-regression checks /// This profile preserves the `0.7.28` trade/candle non-regression checks
@@ -144,6 +148,7 @@ impl LocalPipelineValidationConfig {
require_decoded_events_per_dex: true, require_decoded_events_per_dex: true,
require_trade_events_per_dex: false, require_trade_events_per_dex: false,
require_candles_per_dex: false, require_candles_per_dex: false,
require_no_non_actionable_trade_events_materialized: true,
}; };
} }
@@ -157,6 +162,18 @@ impl LocalPipelineValidationConfig {
config.profile_code = "0.7.30_non_trade_event_classification".to_string(); config.profile_code = "0.7.30_non_trade_event_classification".to_string();
return config; return config;
} }
/// Builds the `0.7.31` trade-event actionability validation config.
///
/// This profile keeps the `0.7.30` classification checks and additionally
/// requires failed/non-actionable classification groups to have no linked
/// persisted trade events.
pub fn v0_7_31_trade_event_actionability_policy() -> Self {
let mut config = Self::v0_7_30_non_trade_event_classification();
config.profile_code = "0.7.31_trade_event_actionability_policy".to_string();
config.require_no_non_actionable_trade_events_materialized = true;
return config;
}
} }
/// A single local pipeline validation issue. /// A single local pipeline validation issue.
@@ -277,7 +294,6 @@ impl LocalPipelineValidationService {
return self.validate_current_database(&config).await; return self.validate_current_database(&config).await;
} }
/// Diagnoses the current database with the `0.7.29` DEX matrix baseline profile. /// Diagnoses the current database with the `0.7.29` DEX matrix baseline profile.
pub async fn validate_v0_7_29_current_database( pub async fn validate_v0_7_29_current_database(
&self, &self,
@@ -293,6 +309,15 @@ impl LocalPipelineValidationService {
let config = crate::LocalPipelineValidationConfig::v0_7_30_non_trade_event_classification(); let config = crate::LocalPipelineValidationConfig::v0_7_30_non_trade_event_classification();
return self.validate_current_database(&config).await; return self.validate_current_database(&config).await;
} }
/// Diagnoses the current database with the `0.7.31` trade actionability profile.
pub async fn validate_v0_7_31_current_database(
&self,
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
let config =
crate::LocalPipelineValidationConfig::v0_7_31_trade_event_actionability_policy();
return self.validate_current_database(&config).await;
}
} }
/// Validates a diagnostics summary without performing database access. /// Validates a diagnostics summary without performing database access.
@@ -386,6 +411,24 @@ pub fn validate_local_pipeline_diagnostics_summary(
blocking: true, blocking: true,
}); });
} }
if config.require_no_non_actionable_trade_events_materialized {
for classification_summary in &summary.event_classification_summaries {
if classification_summary.event_actionability != "trade_candidate"
&& classification_summary.trade_event_count > 0
{
issues.push(LocalPipelineValidationIssueDto {
code: "non_actionable_trade_events_materialized".to_string(),
message: format!(
"classification '{}' has {} linked trade event(s)",
classification_summary.event_actionability,
classification_summary.trade_event_count
),
subject: Some(classification_summary.event_actionability.clone()),
blocking: true,
});
}
}
}
if config.require_all_expected_dexes { if config.require_all_expected_dexes {
for expected_dex_code in &expected_dex_codes { for expected_dex_code in &expected_dex_codes {
if !observed_dex_codes.contains(expected_dex_code) { if !observed_dex_codes.contains(expected_dex_code) {
@@ -453,8 +496,7 @@ pub fn validate_local_pipeline_diagnostics_summary(
expected_dex_codes, expected_dex_codes,
observed_dex_codes, observed_dex_codes,
decoded_non_trade_useful_event_count: summary.decoded_non_trade_useful_event_count, decoded_non_trade_useful_event_count: summary.decoded_non_trade_useful_event_count,
decoded_non_actionable_trade_event_count: summary decoded_non_actionable_trade_event_count: summary.decoded_non_actionable_trade_event_count,
.decoded_non_actionable_trade_event_count,
decoded_unknown_event_count: summary.decoded_unknown_event_count, decoded_unknown_event_count: summary.decoded_unknown_event_count,
dex_support_matrix_entry_count: crate::dex_support_matrix_entries().len() as i64, dex_support_matrix_entry_count: crate::dex_support_matrix_entries().len() as i64,
dex_support_matrix: crate::dex_support_matrix_entry_dtos(), dex_support_matrix: crate::dex_support_matrix_entry_dtos(),
@@ -670,6 +712,63 @@ mod tests {
assert_eq!(report.decoded_unknown_event_count, 0); assert_eq!(report.decoded_unknown_event_count, 0);
} }
#[test]
fn validation_accepts_0_7_31_trade_actionability_policy_summary() {
let mut summary = make_0_7_28_summary_with_meteora();
summary.event_classification_summaries.push(
crate::LocalEventClassificationDiagnosticSummaryDto {
event_category: "trade".to_string(),
event_lifecycle_kind: "trade_swap".to_string(),
event_actionability: "failed_transaction".to_string(),
non_trade_useful: false,
event_count: 10,
decoded_trade_candidate_count: 0,
decoded_candle_candidate_count: 0,
trade_event_count: 0,
},
);
summary.event_classification_summaries.push(
crate::LocalEventClassificationDiagnosticSummaryDto {
event_category: "trade".to_string(),
event_lifecycle_kind: "trade_swap".to_string(),
event_actionability: "trade_candidate".to_string(),
non_trade_useful: false,
event_count: 12,
decoded_trade_candidate_count: 12,
decoded_candle_candidate_count: 12,
trade_event_count: 12,
},
);
let config =
crate::LocalPipelineValidationConfig::v0_7_31_trade_event_actionability_policy();
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
assert!(report.validation_passed);
assert_eq!(report.validation_profile_code, "0.7.31_trade_event_actionability_policy");
}
#[test]
fn validation_rejects_0_7_31_failed_transaction_trade_events() {
let mut summary = make_0_7_28_summary_with_meteora();
summary.event_classification_summaries.push(
crate::LocalEventClassificationDiagnosticSummaryDto {
event_category: "trade".to_string(),
event_lifecycle_kind: "trade_swap".to_string(),
event_actionability: "failed_transaction".to_string(),
non_trade_useful: false,
event_count: 10,
decoded_trade_candidate_count: 0,
decoded_candle_candidate_count: 0,
trade_event_count: 2,
},
);
let config =
crate::LocalPipelineValidationConfig::v0_7_31_trade_event_actionability_policy();
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
assert!(!report.validation_passed);
assert_eq!(report.blocking_issue_count, 1);
assert_eq!(report.issues[0].code, "non_actionable_trade_events_materialized");
}
#[test] #[test]
fn validation_report_exposes_dex_support_matrix() { fn validation_report_exposes_dex_support_matrix() {
let summary = make_0_7_28_summary_with_meteora(); let summary = make_0_7_28_summary_with_meteora();

View File

@@ -47,6 +47,13 @@ impl TradeAggregationService {
Err(error) => return Err(error), Err(error) => return Err(error),
}; };
let transaction = transaction_context.transaction; let transaction = transaction_context.transaction;
if transaction.err_json.is_some() {
tracing::debug!(
signature = %transaction.signature,
"skipping trade aggregation for failed transaction"
);
return Ok(std::vec::Vec::new());
}
let transaction_id = transaction_context.transaction_id; let transaction_id = transaction_context.transaction_id;
let decoded_events = transaction_context.decoded_events; let decoded_events = transaction_context.decoded_events;
let mut results = std::vec::Vec::new(); let mut results = std::vec::Vec::new();
@@ -221,11 +228,12 @@ mod tests {
return std::sync::Arc::new(database); return std::sync::Arc::new(database);
} }
async fn seed_fluxbeam_swap_transaction( async fn seed_fluxbeam_swap_transaction_with_err(
database: std::sync::Arc<crate::Database>, database: std::sync::Arc<crate::Database>,
signature: &str, signature: &str,
base_amount_raw: &str, base_amount_raw: &str,
quote_amount_raw: &str, quote_amount_raw: &str,
meta_err: serde_json::Value,
) { ) {
let transaction_model = crate::TransactionModelService::new(database.clone()); let transaction_model = crate::TransactionModelService::new(database.clone());
let dex_decode = crate::DexDecodeService::new(database.clone()); let dex_decode = crate::DexDecodeService::new(database.clone());
@@ -263,7 +271,7 @@ mod tests {
} }
}, },
"meta": { "meta": {
"err": null, "err": meta_err,
"logMessages": [ "logMessages": [
"Program log: Instruction: Swap", "Program log: Instruction: Swap",
"Program log: buy" "Program log: buy"
@@ -290,6 +298,38 @@ mod tests {
} }
} }
async fn seed_fluxbeam_swap_transaction(
database: std::sync::Arc<crate::Database>,
signature: &str,
base_amount_raw: &str,
quote_amount_raw: &str,
) {
seed_fluxbeam_swap_transaction_with_err(
database,
signature,
base_amount_raw,
quote_amount_raw,
serde_json::Value::Null,
)
.await;
}
async fn seed_failed_fluxbeam_swap_transaction(
database: std::sync::Arc<crate::Database>,
signature: &str,
base_amount_raw: &str,
quote_amount_raw: &str,
) {
seed_fluxbeam_swap_transaction_with_err(
database,
signature,
base_amount_raw,
quote_amount_raw,
serde_json::json!({ "InstructionError": [0, { "Custom": 1 }] }),
)
.await;
}
#[tokio::test] #[tokio::test]
async fn record_transaction_by_signature_creates_trade_event_and_pair_metric() { async fn record_transaction_by_signature_creates_trade_event_and_pair_metric() {
let database = make_database().await; let database = make_database().await;
@@ -375,4 +415,49 @@ mod tests {
}; };
assert_eq!(pair_metric.trade_count, 1); assert_eq!(pair_metric.trade_count, 1);
} }
#[tokio::test]
async fn record_transaction_by_signature_skips_failed_transaction() {
let database = make_database().await;
seed_failed_fluxbeam_swap_transaction(
database.clone(),
"sig-trade-aggregation-failed-1",
"1000",
"2500",
)
.await;
let transaction_result = crate::query_chain_transactions_get_by_signature(
database.as_ref(),
"sig-trade-aggregation-failed-1",
)
.await;
let transaction_option = match transaction_result {
Ok(transaction_option) => transaction_option,
Err(error) => panic!("transaction fetch must succeed: {}", error),
};
let transaction = match transaction_option {
Some(transaction) => transaction,
None => panic!("transaction must exist"),
};
let transaction_id = match transaction.id {
Some(transaction_id) => transaction_id,
None => panic!("transaction id must exist"),
};
let service = crate::TradeAggregationService::new(database.clone());
let record_result =
service.record_transaction_by_signature("sig-trade-aggregation-failed-1").await;
let results = match record_result {
Ok(results) => results,
Err(error) => panic!("failed transaction aggregation must not fail: {}", error),
};
assert_eq!(results.len(), 0);
let trade_events_result =
crate::query_trade_events_list_by_transaction_id(database.as_ref(), transaction_id)
.await;
let trade_events = match trade_events_result {
Ok(trade_events) => trade_events,
Err(error) => panic!("trade event list must succeed: {}", error),
};
assert_eq!(trade_events.len(), 0);
}
} }