执行脚本
Candlestick
Candlestick 结构体表示一根 OHLCV K 线:
use openpine_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 openpine_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);
// 从字符串解析(TradingView 格式)
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(),然后调用 instance.run_to_end():
use openpine_vm::TimeFrame;
let mut instance = Instance::builder(historical_data, source, TimeFrame::days(1), "NASDAQ:AAPL")
.build().await?;
instance.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:CandlestickItem::Confirmed(candle) 表示已收盘的 K 线,CandlestickItem::Realtime(candle) 表示正在形成的实时 K 线。在所有历史 K 线之后、实时 K 线之前 yield CandlestickItem::HistoryEnd,标记历史数据已结束。VM 会自动处理回滚和确认逻辑。
事件流
Instance::run() 返回 Stream<Item = Result<Event, Error>>,在脚本逐 K 线执行时逐个产出事件。
事件类型
| 事件 | 何时发出 |
|---|---|
BarStart(BarStartEvent) | 每根 K 线执行前。包含 bar_index、timestamp(epoch ms)、bar_state(见下方)。 |
BarEnd | 每根 K 线执行完成后(策略处理、脚本体等)。始终与前一个 BarStart 配对。 |
HistoryEnd | 仅一次,当 DataProvider yield CandlestickItem::HistoryEnd 时。标记从历史回放到实时数据的转换。不跟随 BarStart。 |
Log(LogEvent) | K 线执行期间,脚本调用 log.info()、log.warning() 或 log.error() 时。在 BarStart 和 BarEnd 之间发出。 |
Alert(AlertEvent) | K 线执行期间,脚本调用 alert() 或 alertcondition() 触发时。在 BarStart 和 BarEnd 之间发出。 |
Draw(DrawEvent) | 仅 OutputMode::Stream 模式下,脚本调用 plot() 时。AddPlot 在首根 K 线发出一次;UpdatePlot 每根 K 线都发出。 |
Bar 状态
BarStartEvent.bar_state 说明当前 K 线为何被执行:
| 状态 | 含义 |
|---|---|
History | 来自 CandlestickItem::Confirmed 的已收盘历史 K 线。每根仅执行一次。执行后 bar_index 递增。这是初始回测阶段的状态。 |
RealtimeNew | 实时 K 线的第一个 tick。出现在:HistoryEnd 后的第一个 Realtime,或时间戳晚于前一个 Realtime 的 tick(此时 VM 会先自动确认前一根 K 线)。bar_index 在确认前不会递增。 |
RealtimeUpdate | 同一根实时 K 线的后续 tick(与前一个 Realtime 时间戳相同)。VM 回滚变量状态到该 K 线的初始快照,然后用更新后的 OHLCV 数据重新执行脚本。bar_index 与前面的 RealtimeNew 相同。 |
RealtimeConfirmed | 当前实时 K 线被确认(收盘)。两种情况:(1) 收到时间戳更晚的新 Realtime tick — VM 在打开新 K 线前自动确认前一根;(2) 数据流结束时仍有未确认的实时 K 线 — VM 将其作为最后一步确认。确认后 bar_index 递增。 |
事件顺序
典型的历史 + 实时会话中,流的产出顺序:
BarStart(0, History) → Log/Alert/Draw... → BarEnd
BarStart(1, History) → Log/Alert/Draw... → BarEnd
...
HistoryEnd
BarStart(N, RealtimeNew) → ... → BarEnd ← K 线 N 的第一个 tick
BarStart(N, RealtimeUpdate) → ... → BarEnd ← 同时间戳 tick 更新
BarStart(N, RealtimeConfirmed) → ... → BarEnd ← K 线 N 确认(新时间戳到达)
BarStart(N+1, RealtimeNew) → ... → BarEnd ← K 线 N+1 的第一个 tick
BarStart(N+1, RealtimeConfirmed) → ... → BarEnd ← 流结束,最终确认用法
use std::pin::pin;
use futures_util::StreamExt;
use openpine_vm::{Event, Instance, TimeFrame};
let mut stream = pin!(instance.run("NASDAQ:AAPL", TimeFrame::days(1)));
while let Some(result) = stream.next().await {
match result? {
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 模式 */ }
}
}如果不需要事件流,使用便捷方法 run_to_end(),它会在内部消费完整个流:
instance.run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;
// 执行完毕后通过 instance.chart()、instance.strategy_report() 等读取结果输出模式
OutputMode 控制 plot() 数据的产出方式。通过构建器设置:
use openpine_vm::{Instance, OutputMode, TimeFrame};
let mut instance = Instance::builder(provider, source, TimeFrame::days(1), "NASDAQ:AAPL")
.with_output_mode(OutputMode::Stream)
.build().await?;| 模式 | plot() 行为 | 适用场景 |
|---|---|---|
OutputMode::Chart(默认) | 写入 instance.chart() | 执行完毕后读取完整图表 |
OutputMode::Stream | 在事件流中发出 DrawEvent | 流式消费绘图数据,无需构建图表 |
Stream 模式示例
在 OutputMode::Stream 下,每次 plot() 调用会发出事件而非写入图表:
use std::pin::pin;
use futures_util::StreamExt;
use openpine_vm::{DrawEvent, Event, Instance, OutputMode, TimeFrame};
let mut instance = Instance::builder(bars, source, TimeFrame::days(1), "NASDAQ:AAPL")
.with_output_mode(OutputMode::Stream)
.build().await?;
let mut stream = pin!(instance.run("NASDAQ:AAPL", TimeFrame::days(1)));
while let Some(result) = stream.next().await {
match result? {
Event::Draw(DrawEvent::AddPlot { id, title }) => {
println!("新绘图: id={}, title={:?}", id, title);
}
Event::Draw(DrawEvent::UpdatePlot { id, bar_index, timestamp, value }) => {
println!("绘图 {} K线 {}: {:?}", id, bar_index, value);
}
_ => {}
}
}BarStart、BarEnd、HistoryEnd、Log、Alert 在两种模式下都会发出。只有 Draw 事件受输出模式影响。