44 case pineforge::NativeCompletionKind::Confirmed:
return "Confirmed";
45 case pineforge::NativeCompletionKind::LazyComplete:
return "LazyComplete";
46 case pineforge::NativeCompletionKind::SessionShortened:
return "SessionShortened";
47 case pineforge::NativeCompletionKind::PartialFinalized:
return "PartialFinalized";
55 std::size_t subscription;
56 pineforge::Bar bucket;
58 std::int64_t delivered_at_ms;
60 std::vector<Delivery> deliveries;
61 bool declared =
false;
63 int series0_present = 0;
64 int series1_present = 0;
67 std::optional<std::int64_t> acted_on_bucket_;
70 void on_native_run_begin()
override {
73 acted_on_bucket_.reset();
74 pineforge::NativeTimeframeSubscription hourly;
76 pineforge::NativeTimeframeSubscription hourly_gaps = hourly;
77 hourly_gaps.
gaps =
true;
78 declared = declare_timeframe_subscriptions({hourly, hourly_gaps});
82 void on_native_timeframe_bar(
const pineforge::Bar& bucket,
83 const pineforge::NativeTimeframeBarContext& context)
override {
89 void on_native_bar(
const pineforge::Bar&,
90 const pineforge::NativeDecisionContext&)
override {
92 if (native_series_bar(0)) ++series0_present;
93 if (native_series_bar(1)) ++series1_present;
94 const auto hour = native_series_bar(0);
95 if (!hour || (acted_on_bucket_ && *acted_on_bucket_ == hour->timestamp))
return;
96 acted_on_bucket_ = hour->timestamp;
97 const bool flat = physical_position().signed_units == 0.0;
98 if (hour->close > hour->open && flat) {
99 submit({no::Transact{1.0},
"hour-up",
"htf"});
100 }
else if (hour->close < hour->open && !flat) {
101 submit({no::Flatten{},
"hour-down",
"htf"});
114 spec.
type =
"crypto";
123 spec.
fee_kind = pineforge::NativeFeeKind::Percent;
129constexpr std::int64_t kQuarter = 15LL * 60LL * 1000LL;
135 {100.00, 100.50, 99.75, 100.25, 4.0, 0 * kQuarter},
136 {100.25, 100.75, 100.00, 100.50, 4.0, 1 * kQuarter},
137 {100.50, 101.00, 100.25, 100.75, 4.0, 2 * kQuarter},
138 {100.75, 101.25, 100.50, 101.00, 4.0, 3 * kQuarter},
139 {101.00, 101.25, 100.25, 100.50, 4.0, 4 * kQuarter},
140 {100.50, 100.75, 99.75, 100.00, 4.0, 5 * kQuarter},
141 {100.00, 100.25, 99.25, 99.50, 4.0, 6 * kQuarter},
142 { 99.50, 100.00, 99.00, 99.75, 4.0, 8 * kQuarter},
143 { 99.75, 100.25, 99.50, 100.00, 4.0, 9 * kQuarter},
144 {100.00, 100.50, 99.75, 100.25, 4.0, 10 * kQuarter},
145 {100.25, 100.75, 100.00, 100.50, 4.0, 11 * kQuarter},
146 {100.50, 101.00, 100.25, 100.75, 4.0, 12 * kQuarter},
154 if (host.configure_native(
make_spec()).status
155 != pineforge::NativeSetupStatus::Applied) {
156 std::cerr <<
"configure: " << host.last_error() <<
'\n';
161 if (host.native_state().kind != pineforge::NativeLifecycleKind::Completed) {
162 std::cerr <<
"run: " << host.last_error() <<
'\n';
165 if (!host.declared) {
166 std::cerr <<
"declare_timeframe_subscriptions refused the list at begin\n";
171 std::printf(
"deliveries: %zu\n", host.deliveries.size());
172 for (
const auto& d : host.deliveries) {
173 std::printf(
" series %zu: hour opened %02lld:%02lld o=%.2f h=%.2f l=%.2f c=%.2f %s, delivered on the %02lld:%02lld bar\n",
175 static_cast<long long>(d.bucket.timestamp / 3600000),
176 static_cast<long long>(d.bucket.timestamp / 60000 % 60),
177 d.bucket.open, d.bucket.high, d.bucket.low, d.bucket.close,
178 completion_name(d.completion),
179 static_cast<long long>(d.delivered_at_ms / 3600000),
180 static_cast<long long>(d.delivered_at_ms / 60000 % 60));
182 std::vector<HtfExample::Delivery> series0;
183 for (
const auto& d : host.deliveries)
if (d.subscription == 0) series0.push_back(d);
184 if (host.deliveries.size() != 6 || series0.size() != 3) {
185 std::cerr <<
"expected three hourly buckets on each of the two series\n";
188 const bool shapes_ok =
189 series0[0].completion == pineforge::NativeCompletionKind::Confirmed
190 && series0[0].bucket.open == 100.00 && series0[0].bucket.close == 101.00
191 && series0[0].delivered_at_ms == 3 * kQuarter
192 && series0[1].completion == pineforge::NativeCompletionKind::LazyComplete
193 && series0[1].bucket.open == 101.00 && series0[1].bucket.close == 99.50
194 && series0[1].delivered_at_ms == 8 * kQuarter
195 && series0[2].completion == pineforge::NativeCompletionKind::Confirmed
196 && series0[2].bucket.open == 99.50 && series0[2].bucket.close == 100.50
197 && series0[2].delivered_at_ms == 11 * kQuarter;
199 std::cerr <<
"unexpected bucket shapes, completions or delivery bars\n";
204 std::printf(
"native_series_bar inside on_native_bar: series 0 (gaps off) present on %d of %d bars, "
205 "series 1 (gaps on) present on %d of %d bars\n",
206 host.series0_present, host.bars, host.series1_present, host.bars);
207 if (host.bars !=
kBarCount || host.series0_present != 9 || host.series1_present != 3) {
208 std::cerr <<
"expected the gaps-off series from its first delivery on, the gaps-on series on delivery bars only\n";
212 std::cout <<
"closed trades: " << host.trade_count() <<
'\n';
213 for (
int i = 0; i < host.trade_count(); ++i) {
214 const auto& trade = host.get_trade(i);
215 std::cout <<
" " << (trade.is_long ?
"long " :
"short")
216 <<
" qty=" << trade.qty
217 <<
" entry=" << trade.entry_price
218 <<
" exit=" << trade.exit_price
219 <<
" pnl=" << trade.pnl <<
'\n';
221 return host.trade_count() > 0 ? 0 : 1;
The public native host: an abstract subclass of BacktestEngine with no PineScript on it.
static const pf_bar_t kBars[]
static pf_native_run_spec_v1 make_spec(void)
NativeCompletionKind
How a script interval or a higher-timeframe bucket was closed.
static void submit(struct host_state *state, uint32_t intent, double value, uint32_t trigger, double price, const char *label)
NativeCompletionKind completion
std::int64_t delivered_at_ms
One complete setup value, staged/copied by NativeStrategyHost before it is applied at begin.
native_order::RunIdentity identity