C/C++ API
OpenPine provides C ABI bindings with a C++ wrapper, allowing you to embed the Pine Script engine in C and C++ applications.
Requirements
- C++17 compiler
- CMake 3.11+
- Rust toolchain (for building the native library)
Setup with CMake
The C++ API uses Corrosion to build the Rust library from CMake:
cmake_minimum_required(VERSION 3.11)
project(my_project)
add_subdirectory(path/to/openpine)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE openpine_capi)Basic Usage
#include <openpine.hpp>
#include <iostream>
int main() {
const char* script = R"(
//@version=6
indicator("SMA")
plot(ta.sma(close, 5))
)";
// Create instance
openpine::CreateInstanceOptions opts(script, "1D", "NASDAQ:AAPL");
try {
openpine::MyProvider provider(candlesticks);
opts.setDataProvider(&provider);
auto instance = openpine::Instance::create(opts);
// Run asynchronously; callback fires on the execution thread
std::promise<void> done;
instance.run("NASDAQ:AAPL", "1D", 0, [&done](const openpine::Result& r) {
if (!r.isSuccess())
std::cerr << "Error: " << r.errorMessage() << std::endl;
done.set_value();
});
done.get_future().wait();
// Read chart outputs
auto chart = instance.chart();
auto iter = chart.seriesGraphIterator(
openpine::SeriesGraphType::Plot
);
while (iter.next()) {
const auto plot = iter.seriesGraph().asPlot().value();
std::cout << "Plot: " << plot.title() << std::endl;
for (size_t i = 0; i < chart.seriesLength(); i++) {
auto val = plot.value(i);
if (val.has_value()) {
std::cout << " bar " << i << ": " << val.value()
<< std::endl;
}
}
}
} catch (const openpine::Error& e) {
std::cerr << "Error: " << e.errorMessage() << std::endl;
return 1;
}
return 0;
}Key C++ Types
| Type | Description |
|---|---|
CreateInstanceOptions | Configuration: source, timeframe, symbol, optional DataProvider and OutputMode |
Instance | Compiled, executable script |
DataProvider | Abstract base: implement symbolInfo + candlesticksOpen |
CandlestickStream | RAII push stream; create in candlesticksOpen, push from any thread |
CandlestickItem | A bar plus confirmed flag (true = closed, false = forming) |
Chart | Visual outputs collection |
SeriesGraphIterator | Iterates series graphs with optional type filter |
GraphIterator | Iterates point-in-time graphs |
SeriesGraph | Tagged union (Plot, PlotCandle, PlotShape, etc.) |
Graph | Tagged union (Label, Line, Box, Table, etc.) |
Error | Exception with error message and optional span |
Output Mode
Set the output mode on CreateInstanceOptions to control how plot() data is produced:
openpine::CreateInstanceOptions opts(script, "1D", "NASDAQ:AAPL");
opts.setOutputMode(ffi::OutputMode::Stream);| Mode | Behavior |
|---|---|
OutputMode::Chart (default) | Plot data is written to instance.chart() |
OutputMode::Stream | Plot data is emitted as events via the event callback |
Event Callback
Pass an optional event callback to Instance::run() to receive events as the script executes:
instance.run("NASDAQ:AAPL", "1D", 0,
// Completion callback
[&done](const openpine::Result& r) {
done.set_value();
},
// Event callback (optional)
[](const ffi::Event& event) {
switch (event.kind) {
case ffi::EventKind::BarStart:
std::cout << "bar " << event.bar_start.bar_index << std::endl;
break;
case ffi::EventKind::HistoryEnd:
std::cout << "history complete" << std::endl;
break;
case ffi::EventKind::DrawUpdatePlot:
std::cout << "plot " << event.draw_update_plot.id
<< " = " << event.draw_update_plot.value << std::endl;
break;
default:
break;
}
}
);Without an event callback (or passing nullptr), run() executes to completion without per-event notifications — equivalent to the previous behavior.
Feeding Data
Implement DataProvider and pass it to CreateInstanceOptions::setDataProvider(). In candlesticksOpen, create a CandlestickStream, push bars (synchronously or from another thread), and return the raw handle:
class MyProvider : public openpine::DataProvider {
public:
explicit MyProvider(const std::vector<openpine::Candlestick>& bars)
: bars_(bars) {}
openpine::PartialSymbolInfo symbolInfo(std::string_view) override {
return {}; // use defaults
}
ffi::CandlestickStream* candlesticksOpen(
std::string_view symbol, std::string_view timeframe,
int64_t from_time) override {
auto stream = std::make_unique<openpine::CandlestickStream>(
symbol, timeframe, from_time);
for (const auto& bar : bars_)
stream->push(openpine::CandlestickItem{bar, true});
stream->finish();
return stream->release();
}
private:
std::vector<openpine::Candlestick> bars_;
};
// Usage
MyProvider provider(candlesticks);
opts.setDataProvider(&provider);
auto instance = openpine::Instance::create(opts);
std::promise<void> done;
instance.run("NASDAQ:AAPL", "1D", 0, [&done](const openpine::Result& r) {
done.set_value();
});
done.get_future().wait();For live streaming, push bars from a separate thread asynchronously:
ffi::CandlestickStream* candlesticksOpen(...) override {
auto* stream = new openpine::CandlestickStream(symbol, timeframe, from_time);
std::thread([stream, this]() {
for (auto& bar : fetch_bars())
stream->push(openpine::CandlestickItem{bar, true});
stream->finish();
delete stream;
}).detach();
return stream->handle(); // hand raw handle to VM, thread owns the wrapper
}Accessing Graphs
After executing bars, call instance.chart() to access all visual outputs. The chart provides two iterator types:
| Method | Filter | Contents |
|---|---|---|
chart.seriesGraphIterator(type?) | SeriesGraphType | Per-bar data: plots, backgrounds, fills, candles, shapes, arrows |
chart.graphIterator(type?) | GraphType | Point-in-time objects: labels, lines, boxes, tables, hlines, polylines |
Both iterators accept an optional type filter. Pass std::nullopt to iterate all types.
auto chart = instance.chart();
auto length = chart.seriesLength();
// Series graphs: one value per bar
auto iter = chart.seriesGraphIterator(std::nullopt);
while (iter.next()) {
auto sg = iter.seriesGraph();
if (sg.type() == openpine::SeriesGraphType::Plot) {
auto plot = sg.asPlot().value();
// plot.value(i), plot.color(i), plot.title(), ...
}
if (sg.type() == openpine::SeriesGraphType::PlotCandle) {
auto candle = sg.asPlotCandle().value();
// candle.value(i) -> std::optional<Bar>
}
// PlotShape, PlotArrow, PlotBar, PlotChar, BackgroundColors, Fill
}
// Point-in-time graphs
auto giter = chart.graphIterator(std::nullopt);
while (giter.next()) {
auto g = giter.graph();
if (g.type() == openpine::GraphType::Label) {
auto label = g.asLabel().value();
// label.x(), label.y(), label.text(), ...
}
if (g.type() == openpine::GraphType::Line) {
auto line = g.asLine().value();
// line.x1(), line.y1(), line.x2(), line.y2(), ...
}
// Box, Table, Hline, LineFill, Polyline
}For the complete data model — every field, enum, and rendering algorithm — see Chart Rendering.
Error Handling
All errors are thrown as openpine::Error exceptions:
try {
opts.setDataProvider(&provider);
auto instance = openpine::Instance::create(opts);
std::promise<void> done;
instance.run("NASDAQ:AAPL", "1D", 0, [&](const openpine::Result& r) {
done.set_value();
});
done.get_future().wait();
} catch (const openpine::Error& e) {
std::cerr << "Error: " << e.errorMessage() << std::endl;
// Optional: get error source location
auto span = e.errorSpan();
if (span.has_value()) {
std::cerr << " at line " << span.value().startLine << std::endl;
}
}Next Steps
- LSP & Editor Support — language server features
- Integration — Rust API reference