Skip to content

事件壓縮器

EventCompactorInstance::run() 產出的 Event 流的增量過濾器與生命週期追蹤器。專為服務端整合設計:可在即時 K 線確認(收盤)時通知呼叫方,並可選過濾 Log/Alert 雜訊,再持久化事件。

適用場景

以下場景適合使用 EventCompactor

  • 持久化事件時剔除即時中間態。每根即時 K 線在每個 tick 都會產生一套完整的事件週期(BarStart → 繪圖事件 → BarEnd)。大多數持久化場景只需要最終狀態;event_count 精確告知緩衝區末尾有多少條屬於剛剛確認的 K 線,可據此覆蓋或丟棄之前的中間態記錄。
  • 將事件持久化到資料庫或訊息佇列,需要明確的每根即時 K 線提交點。
  • 向下游消費者轉發事件,希望剔除 Log/Alert 雜訊。
  • 需要知道即時 K 線何時收盤,以便觸發批次操作。

如果只是本地簡單使用,無需 EventCompactor,直接消費 RunHandle 流即可。

核心型別

EventCompactionRules

rust
pub struct EventCompactionRules {
    /// 丟棄 `Event::Log` 事件,不轉發。預設:`false`。
    pub filter_log: bool,
    /// 丟棄 `Event::Alert` 事件,不轉發。預設:`false`。
    pub filter_alert: bool,
}

RealtimeConfirmReason

rust
pub enum RealtimeConfirmReason {
    /// 收到時間戳不同的新即時 K 線,上一根自動閉合。
    Rollover,
    /// 流結束時仍有未確認的即時 K 線,在 `finish()` 時閉合。
    StreamEnd,
}

RealtimeConfirmedInfo

即時 K 線確認時傳遞給 EventCompactionSink::on_realtime_confirmed

rust
pub struct RealtimeConfirmedInfo {
    /// 已確認 K 線的從 0 開始的索引。
    pub bar_index: usize,
    /// 已確認 K 線的 Unix 毫秒時間戳。
    pub bar_time: i64,
    /// K 線確認的原因。
    pub reason: RealtimeConfirmReason,
    /// 自本根 K 線進入 `RealtimeNew` 階段起,compactor 轉發給 `on_event`
    /// 的事件數量(不含 `ScriptInfo`、`Warning`、`HistoryEnd` 等流級別事件)。
    pub event_count: usize,
}

event_count 是管理中間狀態的核心指標,詳見使用場景

EventCompactionSink

rust
pub trait EventCompactionSink {
    /// 每個通過過濾規則的事件都會呼叫此方法。
    fn on_event(&mut self, event: Event);
    /// 即時 K 線確認時呼叫此方法。
    fn on_realtime_confirmed(&mut self, info: RealtimeConfirmedInfo);
}

建立 Compactor

rust
use navi_vm::{EventCompactor, EventCompactionRules};

let rules = EventCompactionRules {
    filter_log: true,    // 過濾 log.*() 雜訊
    filter_alert: false, // 保留 alert() 事件
};
let mut compactor = EventCompactor::new(rules);

推送事件

RunHandle 的每個事件呼叫 push,流結束後呼叫 finish

rust
use navi_vm::{Event, EventCompactor, EventCompactionRules, EventCompactionSink,
                  RealtimeConfirmedInfo};

struct MySink {
    buffer: Vec<Event>,
}

impl EventCompactionSink for MySink {
    fn on_event(&mut self, event: Event) {
        self.buffer.push(event);
    }
    fn on_realtime_confirmed(&mut self, info: RealtimeConfirmedInfo) {
        println!(
            "K 線 {} 已確認({:?}),共 {} 個事件",
            info.bar_index, info.reason, info.event_count
        );
        // 提交或歸檔 self.buffer 中最後 event_count 條記錄
    }
}

let mut sink = MySink { buffer: Vec::new() };
let mut compactor = EventCompactor::new(EventCompactionRules::default());

let mut handle = instance.run(symbol, tf);
while let Some(result) = handle.next_event().await {
    compactor.push(result?, &mut sink);
}
compactor.finish(&mut sink);

事件路由

compactor 按如下規則路由事件:

事件是否轉發是否計入 event_count
ScriptInfo始終轉發否——流級別
Warning始終轉發否——流級別
HistoryEnd始終轉發否——流級別
BarStart(History)始終轉發
BarStart(RealtimeNew/Update/Confirmed)始終轉發
BarEnd始終轉發是(即時階段時)
Draw始終轉發是(即時階段時)
Strategy始終轉發是(即時階段時)
Logfilter_log = false轉發時計入
Alertfilter_alert = false轉發時計入

Rollover 行為

當收到 bar_state = RealtimeNew 且時間戳不同於當前即時 K 線的 BarStart 時,compactor:

  1. reason = Rollover 呼叫 on_realtime_confirmed,通知前一根 K 線已確認。
  2. 開始追蹤新 K 線。

這與 VM 自身的 rollover 邏輯一致:時間戳更晚的新 tick 會隱式關閉前一根 K 線。

push BarStart(N, RealtimeNew, t=T0)    → on_event,開始追蹤 K 線 N
push BarEnd                             → on_event,event_count = 2
push BarStart(N, RealtimeUpdate, t=T0) → on_event,event_count = 3
push BarEnd                             → on_event,event_count = 4
push BarStart(N+1, RealtimeNew, t=T1)  → on_realtime_confirmed(K 線 N, Rollover, count=4)
                                          on_event,開始追蹤 K 線 N+1

使用場景

清理中間狀態

向緩衝區追加事件時,event_count 告知末尾有多少條屬於剛剛確認的 K 線,可將其刪除或覆蓋,只保留最終狀態:

rust
fn on_realtime_confirmed(&mut self, info: RealtimeConfirmedInfo) {
    let keep_from = self.buffer.len().saturating_sub(info.event_count);
    // 丟棄中間更新,只保留已確認 K 線的事件
    self.buffer.drain(..keep_from);
}

資料庫批次提交

確認後原子提交最後 event_count 行:

rust
fn on_realtime_confirmed(&mut self, info: RealtimeConfirmedInfo) {
    let start = self.pending.len().saturating_sub(info.event_count);
    let confirmed_rows = &self.pending[start..];
    db.commit_batch(confirmed_rows, info.bar_index, info.bar_time);
    self.pending.truncate(start);
}

稽核追蹤

使用 bar_indexevent_count 按 K 線分區儲存事件,便於後續回放或偵錯。

ChartAccumulator 組合

EventCompactorChartAccumulator 可自然組合——在 sink 中驅動 accumulator 的同時追蹤生命週期:

rust
use navi_vm::{
    Event, EventCompactor, EventCompactionRules, EventCompactionSink, RealtimeConfirmedInfo,
    visuals::{ChartAccumulator, ChartEvent},
};

struct ChartSink {
    acc: ChartAccumulator,
}

impl EventCompactionSink for ChartSink {
    fn on_event(&mut self, event: Event) {
        if let Ok(chart_event) = ChartEvent::try_from(event) {
            self.acc.apply(chart_event);
        }
    }
    fn on_realtime_confirmed(&mut self, info: RealtimeConfirmedInfo) {
        println!("即時 K 線 {} 已收盤({:?})", info.bar_index, info.reason);
    }
}

下一步

基於 MIT 許可證發佈。