29 "selected example requires the v19 native host");
31 "C bar mirror must match the native host bar");
33 "C report mirror must match the native host report");
34static_assert(
static_cast<int>(pineforge::NativeLifecycleKind::Completed)
36 "C lifecycle mirror must match the native host lifecycle");
43 std::optional<no::RequestHandle> opening_;
44 std::optional<no::RequestHandle> reversal_;
45 std::optional<no::RequestHandle> selected_;
46 std::optional<std::int64_t> opening_cycle_;
48 bool child_submitted_ =
false;
50 struct AppliedMetrics {
51 int host_sized_applied = 0;
52 int reversal_applied = 0;
53 int selected_applied = 0;
54 double host_sized_opened_units = 0.0;
55 double reversal_opened_units = 0.0;
56 double selected_ticket = 0.0;
61 no::ExecutionTerms resolve_execution_terms(
62 const pineforge::NativeExecutionTermsFacts& facts)
const override {
63 if (std::holds_alternative<no::RemainingDeferred>(
facts.remaining)) {
64 return {
facts.default_resolved_price, 2.0, no::OpeningShape::Transact};
66 return {
facts.default_resolved_price, std::nullopt, no::OpeningShape::Transact};
70 const pineforge::NativePrecommitView&)
const override {
71 return pineforge::NativePrecommitVerdict::Proceed;
74 void on_native_run_begin()
override {
78 opening_cycle_.reset();
80 child_submitted_ =
false;
83 void on_native_bar(
const pineforge::Bar&,
84 const pineforge::NativeDecisionContext&)
override {
87 submit(no::Request{no::Transact{1.0},
"baseline-open",
""});
89 open.intent = no::HostSized{no::HostSizedKind::Open, no::Side::Long};
90 open.label =
"sized-limit";
91 open.trigger = no::Limit{101.0};
92 opening_ =
submit(open).handle;
96 if (!child_submitted_ && opening_ && opening_cycle_) {
98 child.intent = no::Reduce{no::OwnerOpenedUnits{}};
99 child.label =
"bound-stop";
100 child.trigger = no::Stop{99.0};
101 child.owner = no::BindOpening{*opening_, *opening_cycle_};
103 child_submitted_ =
true;
106 if (bars_ == 3 && opening_ && opening_cycle_ && !selected_) {
107 no::Request selected{no::Flatten{},
"selected-flatten",
""};
108 selected.owner = no::BindOpenings{{*opening_}, *opening_cycle_};
109 const auto accepted =
submit(selected);
110 selected_ = accepted.handle;
112 const pineforge::NativeCurrentExecution current{
113 *selected_, pineforge::NativeCurrentPriceRule::NearestTick};
114 const auto preview = inspect_current_execution(current);
117 execute_current(current);
122 if (bars_ == 4 && !reversal_) {
124 reverse.intent = no::ReverseTo{-1.0};
125 reverse.label =
"exact-reverse";
126 reversal_ =
submit(reverse).handle;
130 void on_native_applied(
const no::ExecutionAppliedEvent& event,
131 const pineforge::NativeDecisionContext&)
override {
132 if (opening_ && event.handle() == *opening_) {
133 opening_cycle_ =
event.cycle_after;
137 AppliedMetrics applied_metrics()
const {
138 AppliedMetrics result;
139 for (
const auto& event : native_events(0)) {
140 if (!event.command)
continue;
141 const auto* applied = std::get_if<no::ExecutionAppliedEvent>(&*event.command);
142 if (!applied)
continue;
143 if (opening_ && applied->handle() == *opening_) {
144 ++result.host_sized_applied;
145 result.host_sized_opened_units = applied->opened_units;
147 if (reversal_ && applied->handle() == *reversal_) {
148 ++result.reversal_applied;
149 result.reversal_opened_units = applied->opened_units;
151 if (selected_ && applied->handle() == *selected_) {
152 ++result.selected_applied;
153 result.selected_ticket = applied->current_ticket;
160 int host_sized_applied()
const {
return applied_metrics().host_sized_applied; }
161 int reversal_applied()
const {
return applied_metrics().reversal_applied; }
162 int selected_applied()
const {
return applied_metrics().selected_applied; }
163 double host_sized_opened_units()
const {
return applied_metrics().host_sized_opened_units; }
164 double reversal_opened_units()
const {
return applied_metrics().reversal_opened_units; }
165 double selected_ticket()
const {
return applied_metrics().selected_ticket; }
167 int reversal_terminal_kind()
const {
168 if (!reversal_)
return -1;
170 for (
const auto& event : native_events(0)) {
171 if (!event.command)
continue;
172 const auto*
command = &*
event.command;
173 if (
const auto* applied = std::get_if<no::ExecutionAppliedEvent>(command)) {
174 if (applied->handle() == *reversal_)
return 1;
176 if (
const auto* rejected = std::get_if<no::MatchRejectedEvent>(command)) {
177 if (rejected->handle() == *reversal_) {
178 return 100 +
static_cast<int>(rejected->reason);
181 if (
const auto* cancelled = std::get_if<no::CancelledEvent>(command)) {
182 if (
cancelled->handle() == *reversal_)
return 200 +
static_cast<int>(
cancelled->reason);
184 if (
const auto* no_effect = std::get_if<no::NoEffectEvent>(command)) {
185 if (no_effect->handle() == *reversal_)
return 300;
187 if (
const auto* accepted = std::get_if<no::AcceptedEvent>(command)) {
188 if (accepted->handle() == *reversal_) observed = 400;
197 ?
dynamic_cast<NativeSelectedExample*
>(
203 const auto* example = as_example(strategy);
204 return example ?
static_cast<int>(example->native_state().kind) : -1;
208 const auto* example = as_example(strategy);
209 return example ?
static_cast<int>(example->native_state().failure.code) : -1;
213 const auto* example = as_example(strategy);
214 return example ? example->native_state().failure.discriminator : 0;
228 spec.
tickerid =
"EXCHANGE:SELECTED";
229 spec.
type =
"crypto";
242 spec.
fee_kind = pineforge::NativeFeeKind::CashPerExecution;
244 spec.
close_execution = pineforge::NativeCloseExecution::NextEligiblePoint;
250 {100.0, 102.0, 100.0, 101.0, 1.0, 0},
251 {102.0, 103.0, 101.0, 102.5, 1.0, 300000},
252 {103.0, 104.0, 102.0, 103.5, 1.0, 600000},
253 {104.0, 105.0, 103.0, 104.5, 1.0, 900000},
254 {105.0, 106.0, 104.0, 105.5, 1.0, 1200000},
258bool completed(
const NativeSelectedExample& host,
const char* where) {
259 if (
host.native_state().kind == pineforge::NativeLifecycleKind::Completed)
return true;
260 std::cerr << where <<
": lifecycle="
261 <<
static_cast<int>(
host.native_state().kind)
262 <<
" code=" <<
static_cast<int>(
host.native_state().failure.code)
263 <<
" " <<
host.last_error() <<
'\n';
281 return lifecycle_kind(strategy);
285 return failure_code(strategy);
289 return failure_discriminator(strategy);
293 const auto* example = as_example(strategy);
294 return example ? example->host_sized_applied() : -1;
298 const auto* example = as_example(strategy);
299 return example ? example->host_sized_opened_units() : 0.0;
303 const auto* example = as_example(strategy);
304 return example ? example->reversal_applied() : -1;
308 const auto* example = as_example(strategy);
309 return example ? example->reversal_opened_units() : 0.0;
313 const auto* example = as_example(strategy);
314 return example ? example->reversal_terminal_kind() : -1;
318 const auto* example = as_example(strategy);
319 return example ? example->selected_applied() : -1;
323 const auto* example = as_example(strategy);
324 return example ? example->selected_ticket() : 0.0;
332 NativeSelectedExample batch;
333 if (batch.configure_native(spec).status != pineforge::NativeSetupStatus::Applied) {
334 std::cerr <<
"configure: " << batch.last_error() <<
'\n';
338 if (!completed(batch,
"run(tf)"))
return 1;
340 NativeSelectedExample stream;
341 if (stream.configure_native(spec).status != pineforge::NativeSetupStatus::Applied) {
342 std::cerr <<
"configure(stream): " << stream.last_error() <<
'\n';
345 if (!stream.stream_begin(
kBars, 1,
"5",
"5")) {
346 std::cerr <<
"stream_begin: " << stream.last_error() <<
'\n';
350 if (!stream.stream_push_bar(
kBars[i])) {
351 std::cerr <<
"stream_push_bar: " << stream.last_error() <<
'\n';
355 if (!stream.stream_end()) {
356 std::cerr <<
"stream_end: " << stream.last_error() <<
'\n';
359 if (!completed(stream,
"stream"))
return 1;
361 std::cout <<
"host-sized applied: " << batch.host_sized_applied()
362 <<
" units=" << batch.host_sized_opened_units()
363 <<
" selected-close applied: " << batch.selected_applied()
364 <<
" closed trades: " << batch.trade_count() <<
'\n';
The public native host: an abstract subclass of BacktestEngine with no PineScript on it.
@ PF_NATIVE_LIFECYCLE_COMPLETED
static const pf_bar_t kBars[]
static pf_native_run_spec_v1 make_spec(void)
NativePrecommitVerdict
The host is consulted before generic opening-margin admission.
BacktestEngine * as_engine(pf_strategy_t strategy)
#define PINEFORGE_HAS_NATIVE_STRATEGY_HOST_V19
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.
Single OHLCV bar pushed into the engine.
Backtest report filled by run_backtest / run_backtest_full.
One complete setup value, staged/copied by NativeStrategyHost before it is applied at begin.
NativeCloseExecution close_execution
NativeOpenDirections allowed_open_directions
std::string chart_timezone
native_order::RunIdentity identity
std::uint32_t slippage_ticks
NativeEventRetention event_retention
What native_events() can still return; see NativeEventRetention.