41 int close_calculations = 0;
42 int fill_recalculations = 0;
44 int masked_bar_opens = 0;
45 std::optional<pineforge::Bar> partial_at_entry_fill;
46 std::optional<double> scale_in_fill_price;
50 bool scaled_in_ =
false;
51 std::optional<no::RequestHandle> entry_;
52 std::optional<no::RequestHandle> scale_in_;
54 void on_native_run_begin()
override {
62 void on_native_bar_open(
const pineforge::Bar& bar,
63 const pineforge::NativeDecisionContext&)
override {
73 void on_native_bar(
const pineforge::Bar&,
74 const pineforge::NativeDecisionContext&)
override {
80 no::Request entry{no::Transact{1.0},
"entry",
"calc-on-fills"};
81 entry.trigger = no::Limit{99.50};
82 entry_ =
submit(entry).handle;
83 }
else if (bars_ == 4) {
84 submit({no::Flatten{},
"flat",
"calc-on-fills"});
90 void on_native_recalculate(
const pineforge::Bar& bar,
91 const pineforge::NativeDecisionContext& ctx,
93 const no::ExecutionAppliedEvent* cause)
override {
94 if (reason != pineforge::NativeCalculationReason::OrderFill) {
98 ++fill_recalculations;
99 const auto partial = current_partial_bar();
100 if (cause && entry_ && cause->handle() == *entry_ && !scaled_in_) {
101 partial_at_entry_fill = partial;
106 scale_in_ =
submit({no::Transact{1.0},
"scale-in",
"calc-on-fills"}).handle;
108 if (cause && scale_in_ && cause->handle() == *scale_in_) {
109 scale_in_fill_price = cause->resolved_price;
112 std::printf(
" fill recalculation #%d for %s (filled at %.2f, bar t=%lld): "
113 "bar so far o=%.2f h=%.2f l=%.2f c=%.2f v=%.0f\n",
114 fill_recalculations, cause ? cause->request().label.c_str() :
"?",
115 cause ? cause->resolved_price : 0.0,
116 cause ?
static_cast<long long>(cause->effective_time_ms()) : -1LL,
117 partial->open, partial->high, partial->low, partial->close, partial->volume);
130 spec.
type =
"crypto";
139 spec.
fee_kind = pineforge::NativeFeeKind::Percent;
143 spec.
calculation = pineforge::NativeCalculationTrigger::BarCloseAndFills;
154 { 99.50, 100.50, 99.00, 100.00, 4.0, 0},
155 {100.00, 102.00, 99.50, 101.50, 4.0, 300000},
156 {101.50, 103.00, 101.00, 102.50, 4.0, 600000},
157 {102.50, 104.00, 102.00, 103.50, 4.0, 900000},
158 {103.50, 105.00, 103.00, 104.50, 4.0, 1200000},
165 CalcOnFillsExample host;
166 if (host.configure_native(
make_spec()).status
167 != pineforge::NativeSetupStatus::Applied) {
168 std::cerr <<
"configure: " << host.last_error() <<
'\n';
173 if (host.native_state().kind != pineforge::NativeLifecycleKind::Completed) {
174 std::cerr <<
"run: " << host.last_error() <<
'\n';
178 std::printf(
"bar-open view: %d of %d bars arrived masked (H = L = C = open, volume 0)\n",
179 host.masked_bar_opens, host.bar_opens);
181 std::cerr <<
"expected OpenOnly to mask every bar-open view\n";
185 std::printf(
"close calculations: %d; fill recalculations: %d "
186 "(kernel count %llu, skipped %llu)\n",
187 host.close_calculations, host.fill_recalculations,
188 static_cast<unsigned long long>(host.native_recalculation_count()),
189 static_cast<unsigned long long>(host.native_recalculations_skipped()));
190 if (host.close_calculations !=
kBarCount || host.fill_recalculations < 2) {
191 std::cerr <<
"expected one close calculation per bar and a recalculation per fill\n";
194 if (!host.partial_at_entry_fill) {
195 std::cerr <<
"expected a bar so far at the entry's fill recalculation\n";
198 const auto& partial = *host.partial_at_entry_fill;
199 std::printf(
"bar so far at the entry fill: o=%.2f h=%.2f l=%.2f c=%.2f v=%.0f "
200 "(the complete bar is o=100.00 h=102.00 l=99.50 c=101.50)\n",
201 partial.open, partial.high, partial.low, partial.close, partial.volume);
204 if (partial.open != 100.0 || partial.high != 100.0 || partial.low < 99.50
205 || partial.close > 100.0 || partial.volume != 0.0) {
206 std::cerr <<
"expected the bar so far to hold the open leg only, without the later high\n";
209 if (!host.scale_in_fill_price) {
210 std::cerr <<
"expected the scale-in born at the fill recalculation to fill\n";
213 std::printf(
"scale-in born at that recalculation filled at %.2f, the next bar's open\n",
214 *host.scale_in_fill_price);
215 if (*host.scale_in_fill_price != 101.50) {
216 std::cerr <<
"expected the scale-in at the next eligible point, the next bar's open\n";
220 std::cout <<
"closed trades: " << host.trade_count() <<
'\n';
221 for (
int i = 0; i < host.trade_count(); ++i) {
222 const auto& trade = host.get_trade(i);
223 std::cout <<
" " << (trade.is_long ?
"long " :
"short")
224 <<
" qty=" << trade.qty
225 <<
" entry=" << trade.entry_price
226 <<
" exit=" << trade.exit_price
227 <<
" pnl=" << trade.pnl <<
'\n';
229 return host.trade_count() > 0 ? 0 : 1;
The public native host: an abstract subclass of BacktestEngine with no PineScript on it.
virtual void on_native_recalculate(const Bar &bar, const NativeDecisionContext &ctx, NativeCalculationReason reason, const native_order::ExecutionAppliedEvent *cause)
EVERY calculation of the run arrives here first, including the script bar's own close calculation (re...
static const pf_bar_t kBars[]
static pf_native_run_spec_v1 make_spec(void)
NativeCalculationReason
Why the kernel is asking the host to calculate.
static void submit(struct host_state *state, uint32_t intent, double value, uint32_t trigger, double price, const char *label)
One complete setup value, staged/copied by NativeStrategyHost before it is applied at begin.
std::uint32_t max_recalculations_per_point
NativeCalculationTrigger calculation
Calculation timing.
native_order::RunIdentity identity
NativeOpenBarView open_bar_view