執行腳本
Candlestick
Candlestick 結構體表示一根 OHLCV K 線:
use navi_vm::{Candlestick, market::TradeSession};
let candle = Candlestick::new(
1700000000000, // time: 毫秒級時間戳
150.0, // open
155.0, // high
149.0, // low
153.0, // close
1_000_000.0, // volume
0.0, // turnover
TradeSession::Regular, // 交易時段
);Candlestick 還提供衍生價格計算:
candle.hl2(); // (high + low) / 2
candle.hlc3(); // (high + low + close) / 3
candle.ohlc4(); // (open + high + low + close) / 4
candle.hlcc4(); // (high + low + close + close) / 4TimeFrame
TimeFrame 指定圖表週期。可以使用便捷方法建立或從字串解析:
use navi_vm::TimeFrame;
// 便捷建構函式
let tf = TimeFrame::days(1);
let tf = TimeFrame::minutes(5);
let tf = TimeFrame::weeks(1);
let tf = TimeFrame::months(1);
let tf = TimeFrame::seconds(30);
// 從字串簡寫解析
let tf: TimeFrame = "D".parse().unwrap(); // 1 天
let tf: TimeFrame = "5".parse().unwrap(); // 5 分鐘
let tf: TimeFrame = "60".parse().unwrap(); // 60 分鐘(1 小時)
let tf: TimeFrame = "W".parse().unwrap(); // 1 週
let tf: TimeFrame = "3M".parse().unwrap(); // 3 個月
// 檢查
let seconds = tf.in_seconds(); // Option<u64>
let display = tf.to_string(); // "D", "5", "W" 等SymbolInfo
SymbolInfo 攜帶交易標的的元資料。你不再需要手動構造它——只需將標的字串傳給 Instance::builder,建構器會自動解析(若配置了 provider 則呼叫 DataProvider::symbol_info,否則使用內建預設值)。
支援的市場前綴:NYSE、NASDAQ、SHSE、SZSE、HKEX、SGX。
如需提供自訂標的元資料(描述、貨幣、最小變動單位等),實現 DataProvider::symbol_info 並回傳 PartialSymbolInfo。詳情參見實例建構器。
提供資料
歷史回測
直接將 Vec<Candlestick> 傳給 Instance::builder(),呼叫 run_to_end() 時 VM 會主動從中拉取資料:
use navi_vm::TimeFrame;
let instance = Instance::builder(historical_data, source, TimeFrame::days(1), "NASDAQ:AAPL")
.build().await?
.run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;對於惰性載入/串流資料,先收集為 Vec<Candlestick>,或實作自訂 DataProvider:
// 從迭代器收集
let bars: Vec<Candlestick> = load_bars_from_db().collect();
let mut instance = Instance::builder(bars, source, timeframe, "NASDAQ:AAPL")
.build().await?;即時串流
實作 DataProvider 並 yield CandlestickItem::Bar(candle)。HistoryEnd 之前的所有 bar 必須是已完全收盤的歷史 K 線——HistoryEnd 是一條硬邊界,意味著「所有歷史資料均已確認」。在所有已確認的歷史 bar 發送完畢後 yield CandlestickItem::HistoryEnd。HistoryEnd 之後的 Bar 為即時 K 線——時間戳可能與最後一根歷史 bar 相同(若查詢時該週期仍在形成中),也可能是更晚的新時間戳(新 bar)。VM 會自動處理回滾和確認邏輯。
事件流
Instance::run() 回傳 RunHandle,在腳本逐 K 線執行時逐個產出事件。
RunHandle
RunHandle 在執行期間持有 Instance。你可以透過 handle.next_event().await 逐個迭代事件,並藉由 DerefMut 直接在 handle 上呼叫實例方法;執行結束後再透過 handle.into_instance() 取回實例。
當實時 K 線發生 rollover(先收到 Bar(tA),再收到時間戳更晚的 Bar(tB),其中 tB > tA),流保持與之前相同的事件順序——先確認 tA,再開始 tB——但這兩步分開暴露,以便呼叫方在確認邊界處執行 checkpoint。
事件類型
| 事件 | 何時發出 |
|---|---|
ScriptInfo(Arc<ScriptInfo>) | 流開始:腳本元資料(標題、overlay 旗標、輸入、宣告的序列、告警)。在任何 K 線之前發出一次(僅 Stream 模式)。 |
Warning(Warnings) | 編譯警告,在 ScriptInfo 之後、任何 K 線之前發出一次(有警告時)。 |
BarStart(BarStartEvent) | 每根 K 線執行前。包含 bar_index、candlestick(完整 OHLCV)以及 bar_state(History、RealtimeNew、RealtimeUpdate 或 RealtimeConfirmed)。 |
BarEnd | 每根 K 線執行完成後(策略處理、腳本體等)。始終與前一個 BarStart 配對。 |
HistoryEnd | 僅一次,當 DataProvider yield CandlestickItem::HistoryEnd 時。標記從歷史回放到即時資料的轉換。不跟隨 BarStart。 |
Log(LogEvent) | K 線執行期間,腳本呼叫 log.info()、log.warning() 或 log.error() 時。在 BarStart 和 BarEnd 之間發出。 |
Alert(AlertEvent) | K 線執行期間,腳本呼叫 alert() 或 alert_condition() 觸發時。在 BarStart 和 BarEnd 之間發出。 |
Draw(DrawEvent) | 僅 OutputMode::Stream 模式下。包裝 SeriesEvent、GraphEvent 或 FilledOrder。詳見讀取輸出。 |
Strategy(StrategyEvent) | 僅 OutputMode::Stream 模式下。訂單、成交、權益快照等。詳見讀取輸出。 |
事件順序
典型的歷史 + 即時會話中,流的產出順序:
BarStart(0) → Log/Alert/Draw... → BarEnd ← 歷史 K 線 0
BarStart(1) → Log/Alert/Draw... → BarEnd ← 歷史 K 線 1
...
HistoryEnd
BarStart(N) → ... → BarEnd ← K 線 N 的第一個 tick(RealtimeNew)
BarStart(N) → ... → BarEnd ← 同時間戳 tick 更新(RealtimeUpdate)
BarStart(N) → ... → BarEnd ← K 線 N 確認(RealtimeConfirmed)
BarStart(N+1) → ... → BarEnd ← K 線 N+1 的第一個 tick(RealtimeNew)
BarStart(N+1) → ... → BarEnd ← 流結束,最終確認(RealtimeConfirmed)用法
use navi_vm::{Event, Instance, TimeFrame};
let mut handle = instance.run("NASDAQ:AAPL", TimeFrame::days(1));
while let Some(result) = handle.next_event().await {
match result? {
Event::ScriptInfo(info) => {
println!("腳本類型:{:?}", info.script_type);
}
Event::BarStart(bs) => {
println!("K 線 {} 開始,階段為 {:?}", bs.bar_index, bs.bar_state);
}
Event::BarEnd => { /* K 線結束 */ }
Event::HistoryEnd => {
println!("歷史回放完成,後續為即時 K 線");
}
Event::Log(log) => println!("[{:?}] {}", log.level, log.message),
Event::Alert(alert) => println!("ALERT: {}", alert.message),
Event::Draw(draw) => { /* 繪圖資料,僅 Stream 模式 */ }
Event::Strategy(strat) => { /* 策略事件,僅 Stream 模式 */ }
_ => {}
}
}如不需要事件流,使用便捷方法 run_to_end(),它會在內部消費完整個流並回傳執行完成後的實例:
let instance = instance.run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;
// 執行完畢後透過 instance.chart()、instance.strategy_report() 等讀取結果重置狀態
instance.reset() 丟棄所有執行時狀態,將實例恢復到建構完成後的初始狀態——就好像從未執行過任何 K 線——而無需重新編譯腳本或重新建立物件。
let mut instance = instance.run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;
// 重置:清除所有 K 線、圖表、策略狀態等
instance.reset();
// 用相同的腳本和資料源從第 0 根 K 線重新執行
let instance = instance.run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;何時用 reset() vs. 新建 Instance
reset()— 適合在同一編譯程式和資料源上反覆執行(例如參數掃描、調整輸入後重跑)。省去重新編譯和 Arena 分配的開銷。- 新建
Instance— 腳本原始碼、標的或週期變化時必須新建,或需要在建構時傳入不同的輸入值覆蓋時使用。 - 快照 — 希望後續啟動時跳過歷史回放、保留已累積狀態時使用。參見快照。
注意事項
透過建構器 .with_input_value() 設定的輸入值覆蓋在 build() 時寫入 GC Arena,之後不再保存在實例上。reset() 後,輸入會恢復為腳本中宣告的預設值(例如 input.int(14, "Length") 恢復為 14)。