PineForge v0.13.1-379-g9b50973
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
native_selected_strategy.cpp
Go to the documentation of this file.
1// Pine-free native example: the ownership and execution seams.
2//
3// Where native_market_strategy.cpp is the minimum host, this one exercises the
4// public request vocabulary a Pine strategy would reach through
5// `strategy.entry` / `strategy.exit` / `strategy.close`:
6//
7// * a host-sized opening behind a limit trigger (`HostSized` +
8// `resolve_execution_terms`);
9// * a child stop bound to that opening's cycle (`BindOpening`), the native
10// spelling of a bracket leg;
11// * a close selected against exactly one opening (`BindOpenings`), executed
12// at the current point after a readiness preview;
13// * an exact reversal to a signed target (`ReverseTo`).
14//
15// Like its sibling it is both a loadable module and a standalone program, and
16// contains no Pine command call, formula, or protected engine write.
17
19
20#include <cstdint>
21#include <iostream>
22#include <optional>
23#include <variant>
24
25namespace {
26
27namespace no = pineforge::native_order;
28
29// Not `final`: PINEFORGE_EXPORT_NATIVE_STRATEGY derives the module class from
30// this one.
31class NativeSelectedExample : public pineforge::NativeStrategyHost {
32 std::optional<no::RequestHandle> opening_;
33 std::optional<no::RequestHandle> reversal_;
34 std::optional<no::RequestHandle> selected_;
35 std::optional<std::int64_t> opening_cycle_;
36 int bars_ = 0;
37 bool child_submitted_ = false;
38
39 struct AppliedMetrics {
40 int host_sized_applied = 0;
41 int reversal_applied = 0;
42 int selected_applied = 0;
43 double host_sized_opened_units = 0.0;
44 double reversal_opened_units = 0.0;
45 double selected_ticket = 0.0;
46 };
47
48 // The sizing seam. An unresolved HostSized request asks the host for its
49 // units; returning none leaves it unresolved and fails the run.
50 no::ExecutionTerms resolve_execution_terms(
51 const pineforge::NativeExecutionTermsFacts& facts) const override {
52 if (std::holds_alternative<no::RemainingDeferred>(facts.remaining)) {
53 return {facts.default_resolved_price, 2.0, no::OpeningShape::Transact};
54 }
55 return {facts.default_resolved_price, std::nullopt, no::OpeningShape::Transact};
56 }
57
58 pineforge::NativePrecommitVerdict validate_execution_precommit(
59 const pineforge::NativePrecommitView&) const override {
60 return pineforge::NativePrecommitVerdict::Proceed;
61 }
62
63 void on_native_run_begin() override {
64 opening_.reset();
65 reversal_.reset();
66 selected_.reset();
67 opening_cycle_.reset();
68 bars_ = 0;
69 child_submitted_ = false;
70 }
71
72 void on_native_bar(const pineforge::Bar&,
73 const pineforge::NativeDecisionContext&) override {
74 ++bars_;
75 if (bars_ == 1) {
76 submit(no::Request{no::Transact{1.0}, "baseline-open", ""});
77 no::Request open;
78 open.intent = no::HostSized{no::HostSizedKind::Open, no::Side::Long};
79 open.label = "sized-limit";
80 open.trigger = no::Limit{101.0};
81 opening_ = submit(open).handle;
82 return;
83 }
84
85 if (!child_submitted_ && opening_ && opening_cycle_) {
86 no::Request child;
87 child.intent = no::Reduce{no::OwnerOpenedUnits{}};
88 child.label = "bound-stop";
89 child.trigger = no::Stop{99.0};
90 child.owner = no::BindOpening{*opening_, *opening_cycle_};
91 submit(child);
92 child_submitted_ = true;
93 }
94
95 if (bars_ == 3 && opening_ && opening_cycle_ && !selected_) {
96 no::Request selected{no::Flatten{}, "selected-flatten", ""};
97 selected.owner = no::BindOpenings{{*opening_}, *opening_cycle_};
98 const auto accepted = submit(selected);
99 selected_ = accepted.handle;
100 if (selected_) {
101 const pineforge::NativeCurrentExecution current{
102 *selected_, pineforge::NativeCurrentPriceRule::NearestTick};
103 const auto preview = inspect_current_execution(current);
104 if (!preview.refusal
105 && preview.settlement_readiness == pineforge::execution::Status::Applied) {
106 execute_current(current);
107 }
108 }
109 }
110
111 if (bars_ == 4 && !reversal_) {
112 no::Request reverse;
113 reverse.intent = no::ReverseTo{-1.0};
114 reverse.label = "exact-reverse";
115 reversal_ = submit(reverse).handle;
116 }
117 }
118
119 void on_native_applied(const no::ExecutionAppliedEvent& event,
120 const pineforge::NativeDecisionContext&) override {
121 if (opening_ && event.handle() == *opening_) {
122 opening_cycle_ = event.cycle_after;
123 }
124 }
125
126 AppliedMetrics applied_metrics() const {
127 AppliedMetrics result;
128 for (const auto& event : native_events(0)) {
129 if (!event.command) continue;
130 const auto* applied = std::get_if<no::ExecutionAppliedEvent>(&*event.command);
131 if (!applied) continue;
132 if (opening_ && applied->handle() == *opening_) {
133 ++result.host_sized_applied;
134 result.host_sized_opened_units = applied->opened_units;
135 }
136 if (reversal_ && applied->handle() == *reversal_) {
137 ++result.reversal_applied;
138 result.reversal_opened_units = applied->opened_units;
139 }
140 if (selected_ && applied->handle() == *selected_) {
141 ++result.selected_applied;
142 result.selected_ticket = applied->current_ticket;
143 }
144 }
145 return result;
146 }
147
148public:
149 int host_sized_applied() const { return applied_metrics().host_sized_applied; }
150 int reversal_applied() const { return applied_metrics().reversal_applied; }
151 int selected_applied() const { return applied_metrics().selected_applied; }
152 double host_sized_opened_units() const { return applied_metrics().host_sized_opened_units; }
153 double reversal_opened_units() const { return applied_metrics().reversal_opened_units; }
154 double selected_ticket() const { return applied_metrics().selected_ticket; }
155
156 int reversal_terminal_kind() const {
157 if (!reversal_) return -1;
158 int observed = 0;
159 for (const auto& event : native_events(0)) {
160 if (!event.command) continue;
161 const auto* command = &*event.command;
162 if (const auto* applied = std::get_if<no::ExecutionAppliedEvent>(command)) {
163 if (applied->handle() == *reversal_) return 1;
164 }
165 if (const auto* rejected = std::get_if<no::MatchRejectedEvent>(command)) {
166 if (rejected->handle() == *reversal_) {
167 return 100 + static_cast<int>(rejected->reason);
168 }
169 }
170 if (const auto* cancelled = std::get_if<no::CancelledEvent>(command)) {
171 if (cancelled->handle() == *reversal_) return 200 + static_cast<int>(cancelled->reason);
172 }
173 if (const auto* no_effect = std::get_if<no::NoEffectEvent>(command)) {
174 if (no_effect->handle() == *reversal_) return 300;
175 }
176 if (const auto* accepted = std::get_if<no::AcceptedEvent>(command)) {
177 if (accepted->handle() == *reversal_) observed = 400;
178 }
179 }
180 return observed;
181 }
182};
183
184NativeSelectedExample* as_example(pf_strategy_t strategy) {
185 return strategy
186 ? dynamic_cast<NativeSelectedExample*>(
188 : nullptr;
189}
190
191int lifecycle_kind(pf_strategy_t strategy) {
192 const auto* example = as_example(strategy);
193 return example ? static_cast<int>(example->native_state().kind) : -1;
194}
195
196int failure_code(pf_strategy_t strategy) {
197 const auto* example = as_example(strategy);
198 return example ? static_cast<int>(example->native_state().failure.code) : -1;
199}
200
201std::uint32_t failure_discriminator(pf_strategy_t strategy) {
202 const auto* example = as_example(strategy);
203 return example ? example->native_state().failure.discriminator : 0;
204}
205
208 spec.identity.session_key = "native-selected-example";
209 spec.identity.run_number = 1;
210 spec.input_tf = "5";
211 spec.script_tf = "5";
212 spec.ticker = "SELECTED";
213 spec.tickerid = "EXCHANGE:SELECTED";
214 spec.type = "crypto";
215 spec.currency = "USD";
216 spec.basecurrency = "USD";
217 spec.description = "native-selected-example";
218 spec.volumetype = "base";
219 spec.timezone = "UTC";
220 spec.session = "24x7";
221 spec.chart_timezone = "UTC";
222 spec.initial_capital = 10000.0;
223 spec.point_value = 1.0;
224 spec.account_fx = 1.0;
225 spec.price_tick = 0.01;
226 spec.slippage_ticks = 0;
227 spec.fee_kind = pineforge::NativeFeeKind::CashPerExecution;
228 spec.fee_value = 1.0;
229 spec.close_execution = pineforge::NativeCloseExecution::NextEligiblePoint;
230 spec.allowed_open_directions = pineforge::NativeOpenDirections::Both;
231 return spec;
232}
233
234const pineforge::Bar kBars[] = {
235 {100.0, 102.0, 100.0, 101.0, 1.0, 0},
236 {102.0, 103.0, 101.0, 102.5, 1.0, 300000},
237 {103.0, 104.0, 102.0, 103.5, 1.0, 600000},
238 {104.0, 105.0, 103.0, 104.5, 1.0, 900000},
239 {105.0, 106.0, 104.0, 105.5, 1.0, 1200000},
240};
241constexpr int kBarCount = 5;
242
243bool completed(const NativeSelectedExample& host, const char* where) {
244 if (host.native_state().kind == pineforge::NativeLifecycleKind::Completed) return true;
245 std::cerr << where << ": lifecycle="
246 << static_cast<int>(host.native_state().kind)
247 << " code=" << static_cast<int>(host.native_state().failure.code)
248 << " " << host.last_error() << '\n';
249 return false;
250}
251
252} // namespace
253
254PINEFORGE_EXPORT_NATIVE_STRATEGY(NativeSelectedExample);
255
256extern "C" {
257
258// Example-specific observation symbols kept outside the macro: the C ABI test
259// reads the host's own counters through them.
261 (void)pf_abi_version();
262 return 15;
263}
264
266 return lifecycle_kind(strategy);
267}
268
270 return failure_code(strategy);
271}
272
274 return failure_discriminator(strategy);
275}
276
278 const auto* example = as_example(strategy);
279 return example ? example->host_sized_applied() : -1;
280}
281
283 const auto* example = as_example(strategy);
284 return example ? example->host_sized_opened_units() : 0.0;
285}
286
288 const auto* example = as_example(strategy);
289 return example ? example->reversal_applied() : -1;
290}
291
293 const auto* example = as_example(strategy);
294 return example ? example->reversal_opened_units() : 0.0;
295}
296
298 const auto* example = as_example(strategy);
299 return example ? example->reversal_terminal_kind() : -1;
300}
301
303 const auto* example = as_example(strategy);
304 return example ? example->selected_applied() : -1;
305}
306
308 const auto* example = as_example(strategy);
309 return example ? example->selected_ticket() : 0.0;
310}
311
312} // extern "C"
313
314int main() {
315 const auto spec = make_spec();
316
317 NativeSelectedExample batch;
318 if (batch.configure_native(spec).status != pineforge::NativeSetupStatus::Applied) {
319 std::cerr << "configure: " << batch.last_error() << '\n';
320 return 1;
321 }
322 batch.run(kBars, kBarCount, "5", "5");
323 if (!completed(batch, "run(tf)")) return 1;
324
325 NativeSelectedExample stream;
326 if (stream.configure_native(spec).status != pineforge::NativeSetupStatus::Applied) {
327 std::cerr << "configure(stream): " << stream.last_error() << '\n';
328 return 1;
329 }
330 if (!stream.stream_begin(kBars, 1, "5", "5")) {
331 std::cerr << "stream_begin: " << stream.last_error() << '\n';
332 return 1;
333 }
334 for (int i = 1; i < kBarCount; ++i) {
335 if (!stream.stream_push_bar(kBars[i])) {
336 std::cerr << "stream_push_bar: " << stream.last_error() << '\n';
337 return 1;
338 }
339 }
340 if (!stream.stream_end()) {
341 std::cerr << "stream_end: " << stream.last_error() << '\n';
342 return 1;
343 }
344 if (!completed(stream, "stream")) return 1;
345
346 std::cout << "host-sized applied: " << batch.host_sized_applied()
347 << " units=" << batch.host_sized_opened_units()
348 << " selected-close applied: " << batch.selected_applied()
349 << " closed trades: " << batch.trade_count() << '\n';
350 return 0;
351}
The public native host: an abstract subclass of BacktestEngine with no PineScript on it.
int pf_abi_version(void)
static const pf_bar_t kBars[]
static pf_native_run_spec_v1 make_spec(void)
@ kBarCount
NativePrecommitVerdict
The host is consulted before generic opening-margin admission.
BacktestEngine * as_engine(pf_strategy_t strategy)
One-line C ABI export for a hand-written NativeStrategyHost.
#define PINEFORGE_EXPORT_NATIVE_STRATEGY(Class)
Define the loadable-module C ABI for one NativeStrategyHost subclass.
static void submit(struct host_state *state, uint32_t intent, double value, uint32_t trigger, double price, const char *label)
int native_selected_example_host_epoch()
int native_selected_example_lifecycle(pf_strategy_t strategy)
int native_selected_example_host_sized_applied(pf_strategy_t strategy)
int native_selected_example_selected_applied(pf_strategy_t strategy)
int native_selected_example_failure_code(pf_strategy_t strategy)
double native_selected_example_reversal_opened_units(pf_strategy_t strategy)
double native_selected_example_selected_ticket(pf_strategy_t strategy)
int native_selected_example_reversal_applied(pf_strategy_t strategy)
double native_selected_example_host_sized_opened_units(pf_strategy_t strategy)
std::uint32_t native_selected_example_failure_discriminator(pf_strategy_t strategy)
int native_selected_example_reversal_terminal_kind(pf_strategy_t strategy)
void * pf_strategy_t
Opaque handle to a compiled strategy instance.
Definition pineforge.h:433
#define PF_API
Definition pineforge.h:70
One complete setup value, staged/copied by NativeStrategyHost before it is applied at begin.