This commit is contained in:
2026-05-13 13:19:23 +02:00
parent 24d21818cf
commit 693a456e62
19 changed files with 631 additions and 40 deletions

View File

@@ -63,3 +63,4 @@
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.
0.7.32 - Clarification de la sémantique des diagnostics locaux : séparation des gaps littéraux de paires et des gaps bloquants/actionnables, ajout des compteurs de matérialisation par paire, résumé `pairActionabilitySummaries`, profil `0.7.32_validation_report_semantics` et garde-fous sur la matrice DEX sans modification de la matérialisation trade/candle.
0.7.33 - Ajout de la classification diagnostique `pairTradingReadiness` pour les paires, avec `quoteAssetClass`, `tradingRouteRequired`, résumé `pairTradingReadinessSummaries`, profil de validation `0.7.33_pair_trading_readiness` et mise à jour de la sélection UI Demo Pipeline 2 sans modifier la matérialisation trade/candle.

View File

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

View File

@@ -4,7 +4,7 @@
`khadhroony-bobobot` est un workspace Rust destiné à la détection, au décodage, à lanalyse et, à terme, au trading semi-automatisé de tokens Solana.
Le README précédent décrivait surtout létat `0.3.1`. Ce fichier reflète létat de reprise autour de `0.7.32` : le socle transport HTTP/WS, la résolution transactionnelle, le modèle SQLite, plusieurs connecteurs DEX, les candles, les signaux analytiques, la validation locale et une matrice DEX commune existent déjà.
Le README précédent décrivait surtout létat `0.3.1`. Ce fichier reflète létat de reprise autour de `0.7.33` : le socle transport HTTP/WS, la résolution transactionnelle, le modèle SQLite, plusieurs connecteurs DEX, les candles, les signaux analytiques, la validation locale et une matrice DEX commune existent déjà.
## 1. Objectif
@@ -31,7 +31,7 @@ Le workspace contient deux crates principales.
La logique métier doit rester dans `kb_lib`. `kb_demo_app` doit rester une façade UI/Tauri et ne doit pas récupérer de logique Solana ou DEX profonde.
## 3. État actuel autour de `0.7.32`
## 3. État actuel autour de `0.7.33`
### 3.1. Socle stabilisé à ne pas refactorer maintenant
@@ -101,6 +101,8 @@ Depuis `0.7.30`, les événements décodés reçoivent aussi une classification
Depuis `0.7.32`, les diagnostics distinguent explicitement les gaps littéraux de catalogue (`literalPairWithoutTradeCount`, `literalPairWithoutCandleCount`) des gaps bloquants/actionnables (`blockingPairWithoutTradeCount`, `blockingPairWithoutCandleCount`). Les anciens champs `pairWithoutTradeCount` et `pairWithoutCandleCount` restent exposés comme alias de compatibilité pour les gaps bloquants/actionnables.
Depuis `0.7.33`, les diagnostics ajoutent une classification `pairTradingReadiness` au niveau des paires et des résumés agrégés `pairTradingReadinessSummaries`. Cette classification sépare les paires directement lisibles/tradables contre WSOL ou stable, les paires inversées avec WSOL/stable en base, les paires cross-quote nécessitant un routeur/aggregator, les paires non matérialisées en trade et les cas de quote inconnue. Elle reste purement diagnostique : elle ne modifie ni le replay, ni les `trade_events`, ni les candles.
| Code cible | Type | Statut `0.7.29` | Prochaine action |
|---|---:|---|---|
| `pump_fun` | Launch + bonding curve | partiel | verrouiller le rattachement mint initial -> pools migrés |
@@ -217,17 +219,18 @@ Les tests peuvent rester plus souples lorsque cela clarifie le test.
La reprise doit suivre cet ordre :
1. conserver la sémantique `0.7.32` : les gaps littéraux de catalogue ne doivent pas être confondus avec les gaps bloquants/actionnables utilisés par la validation ;
2. conserver la non-régression `0.7.31` : transactions failed traçables mais exclues des `trade_events`, metrics et candles ;
3. utiliser la matrice `0.7.29` comme source commune pour le catalogue, la classification et les protocol candidates ;
4. 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` ;
5. ajouter les launch surfaces manquantes comme origines de mint : LaunchLab/Launchpad, LetsBonk/Bonk.fun, Boop.fun, Moonshot/Moonit, Believe, Bags ;
6. traiter Heaven ;
7. consolider Orca/FluxBeam/DexLab ;
8. isoler Raydium AMM v4 legacy ;
9. effectuer une validation DEX v1 consolidée ;
10. reprendre ensuite lUI analytique et les vues token/pair/pool.
1. conserver la classification `0.7.33` : les paires matérialisées doivent être classées par readiness trading sans transformer les paires cross-quote ou inveres en erreurs bloquantes ;
2. conserver la sémantique `0.7.32` : les gaps littéraux de catalogue ne doivent pas être confondus avec les gaps bloquants/actionnables utilisés par la validation ;
3. conserver la non-régression `0.7.31` : transactions failed traçables mais exclues des `trade_events`, metrics et candles ;
4. utiliser la matrice `0.7.29` comme source commune pour le catalogue, la classification et les protocol candidates ;
5. relier progressivement les événements non-trade aux tables existantes : lifecycle, liquidité, fees, rewards, admin ;
6. consolider Meteora, surtout `meteora_dlmm` et le cas partiel `meteora_damm_v1` ;
7. ajouter les launch surfaces manquantes comme origines de mint : LaunchLab/Launchpad, LetsBonk/Bonk.fun, Boop.fun, Moonshot/Moonit, Believe, Bags ;
8. traiter Heaven ;
9. consolider Orca/FluxBeam/DexLab ;
10. isoler Raydium AMM v4 legacy ;
11. effectuer une validation DEX v1 consolidée ;
12. reprendre ensuite lUI analytique et les vues token/pair/pool.
## 9. Fichiers utiles pour reprendre dans une nouvelle session

View File

@@ -861,7 +861,20 @@ Réalisé :
Repoussé après cette clarification : consolider les transactions inconnues et protocol candidates sans polluer les trades/candles.
### 6.065. Version `0.7.33` — Événements non-trade v1 : liquidité et cycle de vie pool
### 6.065. Version `0.7.33` — Readiness trading des paires
Réalisé :
- ajouter une classification diagnostique `pairTradingReadiness` pour chaque paire inspectée localement ;
- distinguer `direct_wsol_quote`, `direct_stable_quote`, `inverse_wsol_base`, `inverse_stable_base`, `cross_quote_requires_router`, `unknown_quote` et `non_trade_materialized` ;
- exposer `quoteAssetClass` et `tradingRouteRequired` dans les diagnostics par paire ;
- ajouter `pairTradingReadinessSummaries` dans le résumé local du pipeline ;
- ajouter le profil `0.7.33_pair_trading_readiness` ;
- valider que les résumés de readiness couvrent toutes les paires et restent cohérents avec les compteurs `tradeMaterializedPairCount`, `tradeEventCount` et `pairCandleCount` ;
- ne pas modifier la logique de replay, `trade_events`, metrics ou candles.
Objectif : préparer la future couche dachat/vente en distinguant les paires immédiatement exploitables contre WSOL/stable des paires qui nécessitent inversion de lecture ou routeur/aggregator.
### 6.066. Version `0.7.34` — É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.
À faire :
@@ -874,7 +887,7 @@ Objectif : exploiter les événements utiles à lanalyse et au trading semi-a
- 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.
### 6.066. Version `0.7.34` — Événements non-trade v2 : fees, rewards et administration
### 6.067. Version `0.7.35` — É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.
À faire :
@@ -888,7 +901,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,
- documenter clairement que ces événements ne sont ni des trades ni des candles.
### 6.067. Version `0.7.35` — Meteora : DBC / DAMM v1 / DAMM v2 / DLMM
### 6.068. Version `0.7.36` — 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.
À faire :
@@ -902,7 +915,7 @@ Objectif : consolider Meteora comme famille multi-programmes au lieu de traiter
- vérifier lidempotence du replay local sur un corpus Meteora mixte,
- documenter les limites connues des variantes insuffisamment couvertes.
### 6.068. Version `0.7.36` — Launch surfaces : LaunchLab, LetsBonk, Bags, Moonshot/Moonit, Boop.fun, Believe
### 6.069. Version `0.7.37` — 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.
À faire :
@@ -917,7 +930,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,
- exposer les origins dans les diagnostics et lUI dinspection.
### 6.069. Version `0.7.37` — Heaven : corpus, launch et AMM
### 6.070. Version `0.7.38` — Heaven : corpus, launch et AMM
Objectif : ajouter Heaven sans le classer trop tôt comme simple DEX ou simple launchpad.
À faire :
@@ -929,7 +942,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,
- vérifier que Heaven ne crée pas de candles invalides en cas dévénement de launch non pricé.
### 6.070. Version `0.7.38` — Orca / FluxBeam / DexLab : corpus et validation ciblée
### 6.071. Version `0.7.39` — Orca / FluxBeam / DexLab : corpus et validation ciblée
Objectif : consolider les connecteurs déjà présents à partir de corpus locaux vérifiables.
À faire :
@@ -941,7 +954,7 @@ Objectif : consolider les connecteurs déjà présents à partir de corpus locau
- marquer explicitement les variantes partiellement supportées ou heuristiques,
- rejouer les corpus plusieurs fois pour vérifier lidempotence et labsence de trades/candles invalides.
### 6.071. Version `0.7.39` — Raydium AMM v4 legacy : corpus et validation ciblée
### 6.072. Version `0.7.40` — 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.
À faire :
@@ -954,7 +967,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`,
- documenter les limites connues si le corpus AMM v4 reste faible.
### 6.072. Version `0.7.40` — Validation DEX v1 consolidée
### 6.073. Version `0.7.41` — Validation DEX v1 consolidée
Objectif : rejouer tous les DEX et launch surfaces supportés et valider les invariants du pipeline complet.
À faire :
@@ -967,7 +980,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,
- verrouiller les invariants avant douvrir lanalyse `0.8.x`.
### 6.073. Version `0.7.41` — `kb_demo_app` : overlays analytiques
### 6.074. Version `0.7.42` — `kb_demo_app` : overlays analytiques
Objectif : rendre visibles les signaux analytiques directement sur les graphes et vues de marché.
À faire :
@@ -978,7 +991,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,
- 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.074. Version `0.7.42` — `kb_demo_app` : vues consolidées token / pair / pool
### 6.075. Version `0.7.43` — `kb_demo_app` : vues consolidées token / pair / pool
Objectif : fournir une lecture métier plus confortable du modèle `0.7.x`.
À faire :
@@ -990,7 +1003,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,
- rendre explicites les cas `tradeCount = null`, `lastPriceQuotePerBase = null`, tokens non enrichis et événements conservés uniquement pour analyse.
### 6.075. Version `0.7.43` — Finition UI `0.7.x`
### 6.076. Version `0.7.44` — Finition UI `0.7.x`
Objectif : stabiliser la couche desktop de validation avant louverture de `0.8.x`.
À faire :
@@ -1001,7 +1014,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`,
- vérifier que les commandes Tauri restent de simples façades vers `kb_lib`.
### 6.076. Version `0.7.x` — Couverture DEX v1
### 6.077. 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.
Protocoles et surfaces cibles :
@@ -1044,7 +1057,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 dagrégats DEX plus riches, de candles/OHLCV et dune UI dinspection du pipeline `0.7.x`.
### 6.077. Version `0.8.x` — Analyse et filtrage
### 6.078. Version `0.8.x` — Analyse et filtrage
Objectif : transformer les événements bruts en signaux exploitables.
À faire :
@@ -1059,7 +1072,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,
- séparation stricte entre signaux analytiques observés, projections hypothétiques et décisions de trading.
### 6.078. Version `1.x.y` — Wallets et swap préparatoire
### 6.079. Version `1.x.y` — Wallets et swap préparatoire
Objectif : préparer la couche daction.
À faire :
@@ -1070,7 +1083,7 @@ Objectif : préparer la couche daction.
- préparation dordres et de swaps,
- simulation et garde-fous.
### 6.079. Version `2.x.y` — Trading semi-automatisé
### 6.080. Version `2.x.y` — Trading semi-automatisé
Objectif : brancher lanalyse à laction tout en gardant des garde-fous explicites.
À faire :
@@ -1081,7 +1094,7 @@ Objectif : brancher lanalyse à laction tout en gardant des garde-fous exp
- confirmations explicites ou semi-automatiques,
- journaux dexécution.
### 6.080. Version `3.x.y` — Yellowstone gRPC
### 6.081. Version `3.x.y` — Yellowstone gRPC
Objectif : ajouter le connecteur gRPC dédié.
À faire :
@@ -1215,9 +1228,10 @@ La priorité immédiate est désormais la suivante :
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. conserver la clarification `0.7.32` entre gaps littéraux de catalogue et gaps bloquants/actionnables,
3. 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,
4. garder les clients HTTP/WS et managers réseau hors du refactor DEX tant quils ne bloquent pas le pipeline,
5. 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,
3. conserver la classification `0.7.33` des paires par readiness trading : direct WSOL/stable, inverse WSOL/stable, cross-quote avec routeur requis, inconnue ou non matérialisée,
4. 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,
5. garder les clients HTTP/WS et managers réseau hors du refactor DEX tant quils ne bloquent pas le pipeline,
6. 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,
6. rattacher les launch surfaces aux tokens et aux pools migrés : Raydium LaunchLab/Launchpad, LetsBonk/Bonk.fun, Boop.fun, Moonshot/Moonit, Believe, Bags et Heaven,
7. consolider Meteora avec corpus fiable : `meteora_dlmm`, `meteora_damm_v1`, `meteora_damm_v2`, `meteora_dbc` et `meteora_dlc` si le programme est confirmé,
8. consolider Orca, FluxBeam et DexLab sur corpus,

View File

@@ -166,7 +166,8 @@
<div class="mb-3">
<label for="demoPipeline2ValidationProfileSelect" class="form-label">Validation profile</label>
<select id="demoPipeline2ValidationProfileSelect" class="form-select">
<option value="0.7.32_validation_report_semantics" selected>0.7.32validation report semantics</option>
<option value="0.7.33_pair_trading_readiness" selected>0.7.33pair trading readiness</option>
<option value="0.7.32_validation_report_semantics">0.7.32 — validation report semantics</option>
<option value="0.7.31_trade_event_actionability_policy">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>

View File

@@ -63,4 +63,16 @@ pairCandleCount: number,
/**
* Last known price.
*/
lastPriceQuotePerBase: number | null, };
lastPriceQuotePerBase: number | null,
/**
* Pair trading-readiness class derived from base/quote orientation.
*/
pairTradingReadiness: string,
/**
* Quote asset class used by the readiness classifier.
*/
quoteAssetClass: string,
/**
* Whether the pair likely requires a router or aggregator before direct execution.
*/
tradingRouteRequired: boolean, };

View File

@@ -0,0 +1,38 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* Local pair trading-readiness diagnostics summary for the UI.
*/
export type DemoPipeline2LocalPairTradingReadinessDiagnosticSummary = {
/**
* Pair trading-readiness class.
*/
pairTradingReadiness: string,
/**
* Quote asset class.
*/
quoteAssetClass: string,
/**
* Whether a router or aggregator is required before direct execution.
*/
tradingRouteRequired: boolean,
/**
* Pair count.
*/
pairCount: number,
/**
* Decoded event count.
*/
decodedEventCount: number,
/**
* Decoded trade candidate count.
*/
decodedTradeCandidateCount: number,
/**
* Trade event count.
*/
tradeEventCount: number,
/**
* Pair candle count.
*/
pairCandleCount: number, };

View File

@@ -10,6 +10,7 @@ import type { DemoPipeline2LocalNonActionablePairDiagnosticSummary } from "./Dem
import type { DemoPipeline2LocalPairActionabilityDiagnosticSummary } from "./DemoPipeline2LocalPairActionabilityDiagnosticSummary";
import type { DemoPipeline2LocalPairDiagnosticSummary } from "./DemoPipeline2LocalPairDiagnosticSummary";
import type { DemoPipeline2LocalPairGapDiagnosticSample } from "./DemoPipeline2LocalPairGapDiagnosticSample";
import type { DemoPipeline2LocalPairTradingReadinessDiagnosticSummary } from "./DemoPipeline2LocalPairTradingReadinessDiagnosticSummary";
/**
* Local pipeline diagnostics summary for the UI.
@@ -184,6 +185,10 @@ pairSummaries: Array<DemoPipeline2LocalPairDiagnosticSummary>,
* Diagnostics grouped by pair materialization/actionability class.
*/
pairActionabilitySummaries: Array<DemoPipeline2LocalPairActionabilityDiagnosticSummary>,
/**
* Diagnostics grouped by pair trading-readiness class.
*/
pairTradingReadinessSummaries: Array<DemoPipeline2LocalPairTradingReadinessDiagnosticSummary>,
/**
* Diagnostics grouped by decoded event kind.
*/

View File

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

View File

@@ -368,6 +368,9 @@ pub(crate) struct DemoPipeline2LocalPipelineDiagnosticSummary {
/// Diagnostics grouped by pair materialization/actionability class.
pub pair_actionability_summaries:
std::vec::Vec<DemoPipeline2LocalPairActionabilityDiagnosticSummary>,
/// Diagnostics grouped by pair trading-readiness class.
pub pair_trading_readiness_summaries:
std::vec::Vec<DemoPipeline2LocalPairTradingReadinessDiagnosticSummary>,
/// Diagnostics grouped by decoded event kind.
pub decoded_event_summaries: std::vec::Vec<DemoPipeline2LocalDecodedEventDiagnosticSummary>,
/// Diagnostics grouped by decoded event classification.
@@ -476,6 +479,12 @@ pub(crate) struct DemoPipeline2LocalPairDiagnosticSummary {
/// Last known price.
#[ts(type = "number | null")]
pub last_price_quote_per_base: std::option::Option<f64>,
/// Pair trading-readiness class derived from base/quote orientation.
pub pair_trading_readiness: std::string::String,
/// Quote asset class used by the readiness classifier.
pub quote_asset_class: std::string::String,
/// Whether the pair likely requires a router or aggregator before direct execution.
pub trading_route_required: bool,
}
/// Local pair actionability diagnostics summary for the UI.
@@ -511,6 +520,37 @@ pub(crate) struct DemoPipeline2LocalPairActionabilityDiagnosticSummary {
pub pair_candle_count: i64,
}
/// Local pair trading-readiness diagnostics summary for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
export,
export_to = "../frontend/ts/bindings/DemoPipeline2LocalPairTradingReadinessDiagnosticSummary.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalPairTradingReadinessDiagnosticSummary {
/// Pair trading-readiness class.
pub pair_trading_readiness: std::string::String,
/// Quote asset class.
pub quote_asset_class: std::string::String,
/// Whether a router or aggregator is required before direct execution.
pub trading_route_required: bool,
/// Pair count.
#[ts(type = "number")]
pub pair_count: i64,
/// Decoded event count.
#[ts(type = "number")]
pub decoded_event_count: i64,
/// Decoded trade candidate count.
#[ts(type = "number")]
pub decoded_trade_candidate_count: i64,
/// Trade event count.
#[ts(type = "number")]
pub trade_event_count: i64,
/// Pair candle count.
#[ts(type = "number")]
pub pair_candle_count: i64,
}
/// Local decoded-event diagnostics summary for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
@@ -1035,7 +1075,7 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
let service = kb_lib::LocalPipelineValidationService::new(database.clone());
let profile_code = match request {
Some(request) => request.profile_code,
None => "0.7.32_validation_report_semantics".to_string(),
None => "0.7.33_pair_trading_readiness".to_string(),
};
let run_result = match profile_code.as_str() {
"0.7.27" | "0.7.27_dexes_non_regression" => {
@@ -1056,6 +1096,9 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
"0.7.32" | "0.7.32_validation_report_semantics" => {
service.validate_v0_7_32_current_database().await
},
"0.7.33" | "0.7.33_pair_trading_readiness" => {
service.validate_v0_7_33_current_database().await
},
other => Err(kb_lib::Error::InvalidState(format!(
"unsupported local pipeline validation profile: {other}"
))),
@@ -1547,6 +1590,14 @@ fn demo_pipeline2_map_local_diagnostics_summary(
),
);
}
let mut pair_trading_readiness_summaries = std::vec::Vec::new();
for pair_trading_readiness_summary in summary.pair_trading_readiness_summaries {
pair_trading_readiness_summaries.push(
demo_pipeline2_map_local_pair_trading_readiness_diagnostic_summary(
pair_trading_readiness_summary,
),
);
}
let mut decoded_event_summaries = std::vec::Vec::new();
for decoded_event_summary in summary.decoded_event_summaries {
decoded_event_summaries
@@ -1642,6 +1693,7 @@ fn demo_pipeline2_map_local_diagnostics_summary(
dex_summaries,
pair_summaries,
pair_actionability_summaries,
pair_trading_readiness_summaries,
decoded_event_summaries,
event_classification_summaries,
missing_trade_event_reason_summaries,
@@ -1689,6 +1741,9 @@ fn demo_pipeline2_map_local_pair_diagnostic_summary(
invalid_trade_event_count: summary.invalid_trade_event_count,
pair_candle_count: summary.pair_candle_count,
last_price_quote_per_base: summary.last_price_quote_per_base,
pair_trading_readiness: summary.pair_trading_readiness,
quote_asset_class: summary.quote_asset_class,
trading_route_required: summary.trading_route_required,
}
}
@@ -1707,6 +1762,21 @@ fn demo_pipeline2_map_local_pair_actionability_diagnostic_summary(
}
}
fn demo_pipeline2_map_local_pair_trading_readiness_diagnostic_summary(
summary: kb_lib::LocalPairTradingReadinessDiagnosticSummaryDto,
) -> DemoPipeline2LocalPairTradingReadinessDiagnosticSummary {
DemoPipeline2LocalPairTradingReadinessDiagnosticSummary {
pair_trading_readiness: summary.pair_trading_readiness,
quote_asset_class: summary.quote_asset_class,
trading_route_required: summary.trading_route_required,
pair_count: summary.pair_count,
decoded_event_count: summary.decoded_event_count,
decoded_trade_candidate_count: summary.decoded_trade_candidate_count,
trade_event_count: summary.trade_event_count,
pair_candle_count: summary.pair_candle_count,
}
}
fn demo_pipeline2_map_local_decoded_event_diagnostic_summary(
summary: kb_lib::LocalDecodedEventDiagnosticSummaryDto,
) -> DemoPipeline2LocalDecodedEventDiagnosticSummary {

View File

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

View File

@@ -40,6 +40,7 @@ pub use dtos::LocalNonActionablePairDiagnosticSummaryDto;
pub use dtos::LocalPairActionabilityDiagnosticSummaryDto;
pub use dtos::LocalPairDiagnosticSummaryDto;
pub use dtos::LocalPairGapDiagnosticSampleDto;
pub use dtos::LocalPairTradingReadinessDiagnosticSummaryDto;
pub use dtos::LocalPipelineDiagnosticCountersDto;
pub use dtos::LocalPipelineDiagnosticSummaryDto;
pub use dtos::ObservedTokenDto;
@@ -153,6 +154,7 @@ pub use queries::query_local_multi_trade_signature_pair_diagnostic_list_samples;
pub use queries::query_local_non_actionable_pair_diagnostic_list_summaries;
pub use queries::query_local_pair_actionability_diagnostic_list_summaries;
pub use queries::query_local_pair_diagnostic_list_summaries;
pub use queries::query_local_pair_trading_readiness_diagnostic_list_summaries;
pub use queries::query_local_pair_without_candle_diagnostic_list_samples;
pub use queries::query_local_pair_without_trade_diagnostic_list_samples;
pub use queries::query_local_pipeline_diagnostic_get_counters;

View File

@@ -52,6 +52,7 @@ pub(crate) use local_pipeline_diagnostics::LocalNonActionablePairDiagnosticSumma
pub(crate) use local_pipeline_diagnostics::LocalPairActionabilityDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalPairDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalPairGapDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalPairTradingReadinessDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalPipelineDiagnosticCountersRow;
pub use analysis_signal::AnalysisSignalDto;
@@ -79,6 +80,7 @@ pub use local_pipeline_diagnostics::LocalNonActionablePairDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPairActionabilityDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPairDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPairGapDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalPairTradingReadinessDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticCountersDto;
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticSummaryDto;
pub use observed_token::ObservedTokenDto;

View File

@@ -105,6 +105,9 @@ pub struct LocalPipelineDiagnosticSummaryDto {
/// Diagnostics grouped by pair materialization/actionability class.
pub pair_actionability_summaries:
std::vec::Vec<crate::LocalPairActionabilityDiagnosticSummaryDto>,
/// Diagnostics grouped by pair trading readiness class.
pub pair_trading_readiness_summaries:
std::vec::Vec<crate::LocalPairTradingReadinessDiagnosticSummaryDto>,
/// Diagnostics grouped by decoded event kind.
pub decoded_event_summaries: std::vec::Vec<crate::LocalDecodedEventDiagnosticSummaryDto>,
/// Diagnostics grouped by decoded event category, lifecycle kind and actionability.
@@ -187,6 +190,12 @@ pub struct LocalPairDiagnosticSummaryDto {
pub pair_candle_count: i64,
/// Last known price.
pub last_price_quote_per_base: std::option::Option<f64>,
/// Pair trading-readiness class derived from base/quote orientation.
pub pair_trading_readiness: std::string::String,
/// Quote asset class used by the readiness classifier.
pub quote_asset_class: std::string::String,
/// Whether the pair likely requires a router or aggregator before direct bot execution.
pub trading_route_required: bool,
}
/// Local pair diagnostics grouped by materialization/actionability class.
@@ -210,6 +219,27 @@ pub struct LocalPairActionabilityDiagnosticSummaryDto {
pub pair_candle_count: i64,
}
/// Local pair diagnostics grouped by trading readiness class.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalPairTradingReadinessDiagnosticSummaryDto {
/// Pair trading-readiness class.
pub pair_trading_readiness: std::string::String,
/// Quote asset class attached to this readiness group.
pub quote_asset_class: std::string::String,
/// Whether the group requires a router or aggregator before direct execution.
pub trading_route_required: bool,
/// Total pairs in this readiness group.
pub pair_count: i64,
/// Total decoded events attached to pairs in this readiness group.
pub decoded_event_count: i64,
/// Total decoded trade candidates attached to pairs in this readiness group.
pub decoded_trade_candidate_count: i64,
/// Total persisted trade events attached to pairs in this readiness group.
pub trade_event_count: i64,
/// Total persisted candle buckets attached to pairs in this readiness group.
pub pair_candle_count: i64,
}
/// Local decoded-event diagnostics summary.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalDecodedEventDiagnosticSummaryDto {
@@ -464,6 +494,9 @@ pub(crate) struct LocalPairDiagnosticSummaryRow {
pub(crate) invalid_trade_event_count: i64,
pub(crate) pair_candle_count: i64,
pub(crate) last_price_quote_per_base: std::option::Option<f64>,
pub(crate) pair_trading_readiness: std::string::String,
pub(crate) quote_asset_class: std::string::String,
pub(crate) trading_route_required: i64,
}
/// SQL row for local pair actionability diagnostics.
@@ -479,6 +512,19 @@ pub(crate) struct LocalPairActionabilityDiagnosticSummaryRow {
pub(crate) pair_candle_count: i64,
}
/// SQL row for local pair trading-readiness diagnostics.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalPairTradingReadinessDiagnosticSummaryRow {
pub(crate) pair_trading_readiness: std::string::String,
pub(crate) quote_asset_class: std::string::String,
pub(crate) trading_route_required: i64,
pub(crate) pair_count: i64,
pub(crate) decoded_event_count: i64,
pub(crate) decoded_trade_candidate_count: i64,
pub(crate) trade_event_count: i64,
pub(crate) pair_candle_count: i64,
}
/// SQL row for local decoded-event diagnostics.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalDecodedEventDiagnosticSummaryRow {

View File

@@ -90,6 +90,7 @@ pub use local_pipeline_diagnostics::query_local_multi_trade_signature_pair_diagn
pub use local_pipeline_diagnostics::query_local_non_actionable_pair_diagnostic_list_summaries;
pub use local_pipeline_diagnostics::query_local_pair_actionability_diagnostic_list_summaries;
pub use local_pipeline_diagnostics::query_local_pair_diagnostic_list_summaries;
pub use local_pipeline_diagnostics::query_local_pair_trading_readiness_diagnostic_list_summaries;
pub use local_pipeline_diagnostics::query_local_pair_without_candle_diagnostic_list_samples;
pub use local_pipeline_diagnostics::query_local_pair_without_trade_diagnostic_list_samples;
pub use local_pipeline_diagnostics::query_local_pipeline_diagnostic_get_counters;

View File

@@ -555,7 +555,56 @@ SELECT
WHERE te_last.pair_id = pair.id
ORDER BY te_last.id DESC
LIMIT 1
) AS last_price_quote_per_base
) AS last_price_quote_per_base,
CASE
WHEN COUNT(DISTINCT te.id) = 0 THEN 'non_trade_materialized'
WHEN quote_token.mint = 'So11111111111111111111111111111111111111112' THEN 'direct_wsol_quote'
WHEN quote_token.mint IN (
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
'USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB',
'JuprjznTrTSp2UFa3ZBUFgwdAmtZCq4MQCwysN55USD'
) THEN 'direct_stable_quote'
WHEN base_token.mint = 'So11111111111111111111111111111111111111112' THEN 'inverse_wsol_base'
WHEN base_token.mint IN (
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
'USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB',
'JuprjznTrTSp2UFa3ZBUFgwdAmtZCq4MQCwysN55USD'
) THEN 'inverse_stable_base'
WHEN quote_token.mint IS NULL OR quote_token.mint = '' THEN 'unknown_quote'
ELSE 'cross_quote_requires_router'
END AS pair_trading_readiness,
CASE
WHEN quote_token.mint = 'So11111111111111111111111111111111111111112' THEN 'wsol'
WHEN quote_token.mint IN (
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
'USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB',
'JuprjznTrTSp2UFa3ZBUFgwdAmtZCq4MQCwysN55USD'
) THEN 'stable'
WHEN quote_token.mint IS NULL OR quote_token.mint = '' THEN 'unknown'
ELSE 'other'
END AS quote_asset_class,
CASE
WHEN COUNT(DISTINCT te.id) = 0 THEN 0
WHEN quote_token.mint IS NULL OR quote_token.mint = '' THEN 1
WHEN quote_token.mint = 'So11111111111111111111111111111111111111112' THEN 0
WHEN quote_token.mint IN (
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
'USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB',
'JuprjznTrTSp2UFa3ZBUFgwdAmtZCq4MQCwysN55USD'
) THEN 0
WHEN base_token.mint = 'So11111111111111111111111111111111111111112' THEN 0
WHEN base_token.mint IN (
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
'USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB',
'JuprjznTrTSp2UFa3ZBUFgwdAmtZCq4MQCwysN55USD'
) THEN 0
ELSE 1
END AS trading_route_required
FROM k_sol_pairs pair
JOIN k_sol_pools p ON p.id = pair.pool_id
JOIN k_sol_dexes d ON d.id = p.dex_id
@@ -605,6 +654,9 @@ ORDER BY pair.id
invalid_trade_event_count: row.invalid_trade_event_count,
pair_candle_count: row.pair_candle_count,
last_price_quote_per_base: row.last_price_quote_per_base,
pair_trading_readiness: row.pair_trading_readiness,
quote_asset_class: row.quote_asset_class,
trading_route_required: row.trading_route_required != 0,
});
}
return Ok(summaries);
@@ -716,6 +768,146 @@ ORDER BY
return Ok(summaries);
},
}
}
/// Lists local pair trading-readiness summaries.
pub async fn query_local_pair_trading_readiness_diagnostic_list_summaries(
database: &crate::Database,
) -> Result<std::vec::Vec<crate::LocalPairTradingReadinessDiagnosticSummaryDto>, crate::Error> {
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let rows_result = sqlx::query_as::<
sqlx::Sqlite,
crate::db::dtos::LocalPairTradingReadinessDiagnosticSummaryRow,
>(
r#"
WITH pair_state AS (
SELECT
pair.id AS pair_id,
base_token.mint AS base_mint,
quote_token.mint AS quote_mint,
COUNT(DISTINCT dde.id) AS decoded_event_count,
COUNT(DISTINCT CASE WHEN json_extract(dde.payload_json, '$.tradeCandidate') = 1 THEN dde.id END) AS decoded_trade_candidate_count,
COUNT(DISTINCT te.id) AS trade_event_count,
COUNT(DISTINCT pc.bucket_start_unix || ':' || pc.timeframe_seconds) AS pair_candle_count
FROM k_sol_pairs pair
JOIN k_sol_pools p ON p.id = pair.pool_id
JOIN k_sol_tokens base_token ON base_token.id = pair.base_token_id
JOIN k_sol_tokens quote_token ON quote_token.id = pair.quote_token_id
LEFT JOIN k_sol_dex_decoded_events dde ON dde.pool_account = p.address
LEFT JOIN k_sol_trade_events te ON te.pair_id = pair.id
LEFT JOIN k_sol_pair_candles pc ON pc.pair_id = pair.id
GROUP BY pair.id, base_token.mint, quote_token.mint
), classified AS (
SELECT
CASE
WHEN trade_event_count = 0 THEN 'non_trade_materialized'
WHEN quote_mint = 'So11111111111111111111111111111111111111112' THEN 'direct_wsol_quote'
WHEN quote_mint IN (
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
'USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB',
'JuprjznTrTSp2UFa3ZBUFgwdAmtZCq4MQCwysN55USD'
) THEN 'direct_stable_quote'
WHEN base_mint = 'So11111111111111111111111111111111111111112' THEN 'inverse_wsol_base'
WHEN base_mint IN (
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
'USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB',
'JuprjznTrTSp2UFa3ZBUFgwdAmtZCq4MQCwysN55USD'
) THEN 'inverse_stable_base'
WHEN quote_mint IS NULL OR quote_mint = '' THEN 'unknown_quote'
ELSE 'cross_quote_requires_router'
END AS pair_trading_readiness,
CASE
WHEN quote_mint = 'So11111111111111111111111111111111111111112' THEN 'wsol'
WHEN quote_mint IN (
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
'USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB',
'JuprjznTrTSp2UFa3ZBUFgwdAmtZCq4MQCwysN55USD'
) THEN 'stable'
WHEN quote_mint IS NULL OR quote_mint = '' THEN 'unknown'
ELSE 'other'
END AS quote_asset_class,
CASE
WHEN trade_event_count = 0 THEN 0
WHEN quote_mint IS NULL OR quote_mint = '' THEN 1
WHEN quote_mint = 'So11111111111111111111111111111111111111112' THEN 0
WHEN quote_mint IN (
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
'USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB',
'JuprjznTrTSp2UFa3ZBUFgwdAmtZCq4MQCwysN55USD'
) THEN 0
WHEN base_mint = 'So11111111111111111111111111111111111111112' THEN 0
WHEN base_mint IN (
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
'USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB',
'JuprjznTrTSp2UFa3ZBUFgwdAmtZCq4MQCwysN55USD'
) THEN 0
ELSE 1
END AS trading_route_required,
pair_id,
decoded_event_count,
decoded_trade_candidate_count,
trade_event_count,
pair_candle_count
FROM pair_state
)
SELECT
pair_trading_readiness AS pair_trading_readiness,
quote_asset_class AS quote_asset_class,
trading_route_required AS trading_route_required,
COUNT(pair_id) AS pair_count,
SUM(decoded_event_count) AS decoded_event_count,
SUM(decoded_trade_candidate_count) AS decoded_trade_candidate_count,
SUM(trade_event_count) AS trade_event_count,
SUM(pair_candle_count) AS pair_candle_count
FROM classified
GROUP BY pair_trading_readiness, quote_asset_class, trading_route_required
ORDER BY
CASE pair_trading_readiness
WHEN 'direct_wsol_quote' THEN 1
WHEN 'direct_stable_quote' THEN 2
WHEN 'inverse_wsol_base' THEN 3
WHEN 'inverse_stable_base' THEN 4
WHEN 'cross_quote_requires_router' THEN 5
WHEN 'unknown_quote' THEN 6
ELSE 7
END,
pair_trading_readiness,
quote_asset_class
"#,
)
.fetch_all(pool)
.await;
let rows = match rows_result {
Ok(rows) => rows,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list local pair trading readiness diagnostic summaries on sqlite: {}",
error
)));
},
};
let mut summaries = std::vec::Vec::new();
for row in rows {
summaries.push(crate::LocalPairTradingReadinessDiagnosticSummaryDto {
pair_trading_readiness: row.pair_trading_readiness,
quote_asset_class: row.quote_asset_class,
trading_route_required: row.trading_route_required != 0,
pair_count: row.pair_count,
decoded_event_count: row.decoded_event_count,
decoded_trade_candidate_count: row.decoded_trade_candidate_count,
trade_event_count: row.trade_event_count,
pair_candle_count: row.pair_candle_count,
});
}
return Ok(summaries);
},
}
}
/// Lists local decoded-event diagnostic summaries.

View File

@@ -363,6 +363,8 @@ pub use db::LocalPairActionabilityDiagnosticSummaryDto;
pub use db::LocalPairDiagnosticSummaryDto;
/// Sample of a pair gap.
pub use db::LocalPairGapDiagnosticSampleDto;
/// Local pair diagnostics grouped by trading readiness class.
pub use db::LocalPairTradingReadinessDiagnosticSummaryDto;
/// Internal flat counter row for local diagnostics.
pub use db::LocalPipelineDiagnosticCountersDto;
/// Local pipeline diagnostics summary.
@@ -577,6 +579,8 @@ pub use db::query_local_non_actionable_pair_diagnostic_list_summaries;
pub use db::query_local_pair_actionability_diagnostic_list_summaries;
/// Lists local pair diagnostic summaries.
pub use db::query_local_pair_diagnostic_list_summaries;
/// Lists local pair trading-readiness summaries.
pub use db::query_local_pair_trading_readiness_diagnostic_list_summaries;
/// Lists samples of pairs without candles.
pub use db::query_local_pair_without_candle_diagnostic_list_samples;
/// Lists samples of pairs without trade events.

View File

@@ -42,6 +42,15 @@ impl LocalPipelineDiagnosticsService {
Ok(summaries) => summaries,
Err(error) => return Err(error),
};
let pair_trading_readiness_summaries_result =
crate::query_local_pair_trading_readiness_diagnostic_list_summaries(
self.database.as_ref(),
)
.await;
let pair_trading_readiness_summaries = match pair_trading_readiness_summaries_result {
Ok(summaries) => summaries,
Err(error) => return Err(error),
};
let decoded_event_summaries_result =
crate::query_local_decoded_event_diagnostic_list_summaries(self.database.as_ref())
.await;
@@ -182,6 +191,7 @@ impl LocalPipelineDiagnosticsService {
dex_summaries,
pair_summaries,
pair_actionability_summaries,
pair_trading_readiness_summaries,
decoded_event_summaries,
event_classification_summaries,
missing_trade_event_reason_summaries,

View File

@@ -42,6 +42,8 @@ pub struct LocalPipelineValidationConfig {
pub require_no_non_actionable_trade_events_materialized: bool,
/// Whether the DEX support matrix must satisfy internal semantic invariants.
pub require_dex_support_matrix_semantics: bool,
/// Whether pair trading-readiness diagnostics must satisfy internal semantic invariants.
pub require_pair_trading_readiness_semantics: bool,
}
impl Default for LocalPipelineValidationConfig {
@@ -62,6 +64,7 @@ impl Default for LocalPipelineValidationConfig {
require_candles_per_dex: true,
require_no_non_actionable_trade_events_materialized: true,
require_dex_support_matrix_semantics: false,
require_pair_trading_readiness_semantics: false,
};
}
}
@@ -90,6 +93,7 @@ impl LocalPipelineValidationConfig {
require_candles_per_dex: true,
require_no_non_actionable_trade_events_materialized: true,
require_dex_support_matrix_semantics: false,
require_pair_trading_readiness_semantics: false,
};
}
@@ -123,6 +127,7 @@ impl LocalPipelineValidationConfig {
require_candles_per_dex: false,
require_no_non_actionable_trade_events_materialized: true,
require_dex_support_matrix_semantics: false,
require_pair_trading_readiness_semantics: false,
};
}
@@ -155,6 +160,7 @@ impl LocalPipelineValidationConfig {
require_candles_per_dex: false,
require_no_non_actionable_trade_events_materialized: true,
require_dex_support_matrix_semantics: false,
require_pair_trading_readiness_semantics: false,
};
}
@@ -191,6 +197,17 @@ impl LocalPipelineValidationConfig {
config.require_dex_support_matrix_semantics = true;
return config;
}
/// Builds the `0.7.33` pair trading-readiness validation config.
///
/// This profile keeps the `0.7.32` diagnostics semantics and requires all
/// persisted pairs to be covered by the pair trading-readiness summaries.
pub fn v0_7_33_pair_trading_readiness() -> Self {
let mut config = Self::v0_7_32_validation_report_semantics();
config.profile_code = "0.7.33_pair_trading_readiness".to_string();
config.require_pair_trading_readiness_semantics = true;
return config;
}
}
/// A single local pipeline validation issue.
@@ -343,6 +360,14 @@ impl LocalPipelineValidationService {
let config = crate::LocalPipelineValidationConfig::v0_7_32_validation_report_semantics();
return self.validate_current_database(&config).await;
}
/// Diagnoses the current database with the `0.7.33` pair trading-readiness profile.
pub async fn validate_v0_7_33_current_database(
&self,
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
let config = crate::LocalPipelineValidationConfig::v0_7_33_pair_trading_readiness();
return self.validate_current_database(&config).await;
}
}
/// Validates a diagnostics summary without performing database access.
@@ -457,6 +482,9 @@ pub fn validate_local_pipeline_diagnostics_summary(
if config.require_dex_support_matrix_semantics {
validate_dex_support_matrix_semantics(&mut issues);
}
if config.require_pair_trading_readiness_semantics {
validate_pair_trading_readiness_semantics(&mut issues, summary);
}
if config.require_all_expected_dexes {
for expected_dex_code in &expected_dex_codes {
if !observed_dex_codes.contains(expected_dex_code) {
@@ -532,6 +560,109 @@ pub fn validate_local_pipeline_diagnostics_summary(
};
}
fn validate_pair_trading_readiness_semantics(
issues: &mut std::vec::Vec<crate::LocalPipelineValidationIssueDto>,
summary: &crate::LocalPipelineDiagnosticSummaryDto,
) {
if summary.pair_count > 0 && summary.pair_trading_readiness_summaries.is_empty() {
issues.push(crate::LocalPipelineValidationIssueDto {
code: "pair_trading_readiness_summary_missing".to_string(),
message: "pair trading-readiness summaries are missing".to_string(),
subject: None,
blocking: true,
});
return;
}
let mut readiness_pair_count = 0_i64;
let mut readiness_trade_materialized_pair_count = 0_i64;
let mut readiness_trade_event_count = 0_i64;
let mut readiness_pair_candle_count = 0_i64;
for readiness_summary in &summary.pair_trading_readiness_summaries {
if readiness_summary.pair_trading_readiness.trim().is_empty() {
issues.push(crate::LocalPipelineValidationIssueDto {
code: "pair_trading_readiness_empty".to_string(),
message: "pair trading-readiness class must not be empty".to_string(),
subject: None,
blocking: true,
});
}
if readiness_summary.pair_count <= 0 {
issues.push(crate::LocalPipelineValidationIssueDto {
code: "pair_trading_readiness_empty_group".to_string(),
message: format!(
"pair trading-readiness group '{}' has no pair",
readiness_summary.pair_trading_readiness
),
subject: Some(readiness_summary.pair_trading_readiness.clone()),
blocking: true,
});
}
readiness_pair_count += readiness_summary.pair_count;
readiness_trade_event_count += readiness_summary.trade_event_count;
readiness_pair_candle_count += readiness_summary.pair_candle_count;
if readiness_summary.pair_trading_readiness != "non_trade_materialized" {
readiness_trade_materialized_pair_count += readiness_summary.pair_count;
}
if readiness_summary.pair_trading_readiness == "unknown_quote"
&& readiness_summary.trade_event_count > 0
{
issues.push(crate::LocalPipelineValidationIssueDto {
code: "pair_trading_readiness_unknown_quote_materialized".to_string(),
message: format!(
"unknown quote readiness group has {} linked trade event(s)",
readiness_summary.trade_event_count
),
subject: Some(readiness_summary.pair_trading_readiness.clone()),
blocking: true,
});
}
}
if readiness_pair_count != summary.pair_count {
issues.push(crate::LocalPipelineValidationIssueDto {
code: "pair_trading_readiness_pair_count_mismatch".to_string(),
message: format!(
"pair trading-readiness summaries cover {} pair(s), expected {}",
readiness_pair_count, summary.pair_count
),
subject: None,
blocking: true,
});
}
if readiness_trade_materialized_pair_count != summary.trade_materialized_pair_count {
issues.push(crate::LocalPipelineValidationIssueDto {
code: "pair_trading_readiness_materialized_pair_count_mismatch".to_string(),
message: format!(
"pair trading-readiness materialized groups cover {} pair(s), expected {}",
readiness_trade_materialized_pair_count, summary.trade_materialized_pair_count
),
subject: None,
blocking: true,
});
}
if readiness_trade_event_count != summary.trade_event_count {
issues.push(crate::LocalPipelineValidationIssueDto {
code: "pair_trading_readiness_trade_event_count_mismatch".to_string(),
message: format!(
"pair trading-readiness summaries cover {} trade event(s), expected {}",
readiness_trade_event_count, summary.trade_event_count
),
subject: None,
blocking: true,
});
}
if readiness_pair_candle_count != summary.pair_candle_count {
issues.push(crate::LocalPipelineValidationIssueDto {
code: "pair_trading_readiness_candle_count_mismatch".to_string(),
message: format!(
"pair trading-readiness summaries cover {} candle bucket(s), expected {}",
readiness_pair_candle_count, summary.pair_candle_count
),
subject: None,
blocking: true,
});
}
}
fn validate_dex_support_matrix_semantics(
issues: &mut std::vec::Vec<crate::LocalPipelineValidationIssueDto>,
) {
@@ -728,6 +859,38 @@ mod tests {
pair_candle_count: 0,
},
],
pair_trading_readiness_summaries: vec![
crate::LocalPairTradingReadinessDiagnosticSummaryDto {
pair_trading_readiness: "direct_wsol_quote".to_string(),
quote_asset_class: "wsol".to_string(),
trading_route_required: false,
pair_count: 20,
decoded_event_count: 180,
decoded_trade_candidate_count: 180,
trade_event_count: 180,
pair_candle_count: 200,
},
crate::LocalPairTradingReadinessDiagnosticSummaryDto {
pair_trading_readiness: "direct_stable_quote".to_string(),
quote_asset_class: "stable".to_string(),
trading_route_required: false,
pair_count: 3,
decoded_event_count: 30,
decoded_trade_candidate_count: 30,
trade_event_count: 30,
pair_candle_count: 30,
},
crate::LocalPairTradingReadinessDiagnosticSummaryDto {
pair_trading_readiness: "non_trade_materialized".to_string(),
quote_asset_class: "other".to_string(),
trading_route_required: false,
pair_count: 4,
decoded_event_count: 0,
decoded_trade_candidate_count: 0,
trade_event_count: 0,
pair_candle_count: 0,
},
],
decoded_event_summaries: vec![],
event_classification_summaries: vec![],
missing_trade_event_reason_summaries: vec![],
@@ -778,6 +941,11 @@ mod tests {
});
summary.pool_count = 95;
summary.pair_count = 95;
for readiness_summary in &mut summary.pair_trading_readiness_summaries {
if readiness_summary.pair_trading_readiness == "non_trade_materialized" {
readiness_summary.pair_count = 72;
}
}
return summary;
}
@@ -883,6 +1051,28 @@ mod tests {
assert_eq!(summary.blocking_pair_without_trade_count, 0);
}
#[test]
fn validation_accepts_0_7_33_pair_trading_readiness_summary() {
let summary = make_0_7_28_summary_with_meteora();
let config = crate::LocalPipelineValidationConfig::v0_7_33_pair_trading_readiness();
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
assert!(report.validation_passed);
assert_eq!(report.validation_profile_code, "0.7.33_pair_trading_readiness");
assert_eq!(report.blocking_issue_count, 0);
}
#[test]
fn validation_rejects_0_7_33_pair_trading_readiness_mismatch() {
let mut summary = make_0_7_28_summary_with_meteora();
summary.pair_trading_readiness_summaries.retain(|readiness_summary| {
return readiness_summary.pair_trading_readiness != "non_trade_materialized";
});
let config = crate::LocalPipelineValidationConfig::v0_7_33_pair_trading_readiness();
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
assert!(!report.validation_passed);
assert_eq!(report.issues[0].code, "pair_trading_readiness_pair_count_mismatch");
}
#[test]
fn validation_rejects_0_7_31_failed_transaction_trade_events() {
let mut summary = make_0_7_28_summary_with_meteora();