53constexpr std::int64_t kFiveMinutes = 5LL * 60LL * 1000LL;
55constexpr int kEarlyWarmup = 6;
56constexpr int kLateWarmup = 12;
57constexpr int kPrefix = 12;
59constexpr std::uint64_t kFixedExecutionHash = 0;
64 100.0, 101.0, 102.0, 103.0, 102.0, 101.0, 102.0, 103.0,
65 104.0, 105.0, 103.0, 104.0, 105.0, 106.0, 104.0, 103.0,
66 102.0, 103.0, 104.0, 105.0, 104.0, 103.0, 104.0, 103.5,
69std::vector<pineforge::Bar> make_bars() {
70 std::vector<pineforge::Bar> bars;
71 double previous = kCloses[0];
73 const double close = kCloses[i];
74 const double open = previous;
76 std::min(open, close) - 0.5, close, 10.0,
85 explicit BrokerHashExample(
bool fold) : fold_(fold) {}
88 std::vector<std::uint64_t> broker_half;
93 std::int64_t streak_ = 0;
94 double previous_close_ = 0.0;
95 std::int64_t entries_ = 0;
97 void on_native_run_begin()
override {
99 previous_close_ = 0.0;
104 void on_native_bar(
const pineforge::Bar& bar,
105 const pineforge::NativeDecisionContext&)
override {
106 streak_ = (previous_close_ > 0.0 && bar.
close > previous_close_) ? streak_ + 1 : 0;
107 previous_close_ = bar.
close;
108 const double held = physical_position().signed_units;
109 if (held == 0.0 && streak_ >= 2) {
110 submit({no::Transact{1.0},
"long",
"streak"});
112 }
else if (held > 0.0 && streak_ == 0) {
113 submit({no::Flatten{},
"flat",
"streak"});
115 broker_half.push_back(broker_state_hash_from_execution_hash(kFixedExecutionHash));
121 void hash_host_extension(pineforge::BrokerStateHashSink& sink)
const override {
126 sink.
s(
"streak-host/v1");
128 sink.
d(previous_close_);
141 spec.
type =
"crypto";
150 spec.
fee_kind = pineforge::NativeFeeKind::Percent;
154 spec.
report_policy = pineforge::NativeReportPolicy::KernelRecorded;
160 std::vector<std::uint64_t> rows;
161 std::vector<std::uint64_t> broker_half;
162 std::vector<pineforge::Trade> trades;
163 std::int64_t script_bars = 0;
164 std::int64_t curve_points = 0;
165 std::uint64_t scalar = 0;
166 std::uint64_t continuation = 0;
169Drive harvest(BrokerHashExample& host) {
170 assert(host.native_state().kind == pineforge::NativeLifecycleKind::Completed);
172 host.fill_report(&report);
174 drive.rows.assign(report.broker_state_hash,
175 report.broker_state_hash + report.broker_state_hash_len);
176 drive.script_bars = report.script_bars_processed;
177 drive.curve_points = report.equity_curve_len;
179 drive.broker_half = host.broker_half;
180 for (
int i = 0; i < host.trade_count(); ++i) drive.trades.push_back(host.get_trade(i));
181 drive.scalar = host.broker_state_hash();
182 drive.continuation = host.native_continuation_hash();
184 assert(
static_cast<std::int64_t
>(drive.rows.size()) == drive.script_bars);
185 assert(drive.curve_points == drive.script_bars);
190Drive batch(
bool fold,
int bars) {
191 const auto feed = make_bars();
192 BrokerHashExample host(fold);
193 host.set_broker_state_hash_recording(
true);
194 const auto setup = host.configure_native(
make_spec());
195 assert(setup.status == pineforge::NativeSetupStatus::Applied);
196 host.run(feed.data(), bars);
197 return harvest(host);
201Drive stream(
bool fold,
int warmup) {
202 const auto feed = make_bars();
203 BrokerHashExample host(fold);
206 host.set_broker_state_hash_recording(
true);
207 const auto setup = host.configure_native(
make_spec());
208 assert(setup.status == pineforge::NativeSetupStatus::Applied);
209 const bool begun = host.stream_begin(feed.data(), warmup,
"5",
"5");
211 for (
int i = warmup; i <
kBarCount; ++i) {
212 const bool pushed = host.stream_push_bar(feed[
static_cast<std::size_t
>(i)]);
215 const bool ended = host.stream_end(
false);
217 return harvest(host);
220std::size_t shared_rows(
const std::vector<std::uint64_t>& a,
const std::vector<std::uint64_t>& b) {
221 assert(a.size() ==
b.size());
222 std::size_t same = 0;
223 for (std::size_t i = 0; i < a.size(); ++i) same += a[i] == b[i];
227bool same_trades(
const Drive& a,
const Drive& b) {
228 if (a.trades.size() !=
b.trades.size())
return false;
229 for (std::size_t i = 0; i < a.trades.size(); ++i) {
230 const auto& x = a.trades[i];
231 const auto& y =
b.trades[i];
232 if (x.entry_time != y.entry_time || x.exit_time != y.exit_time
233 || x.entry_price != y.entry_price || x.exit_price != y.exit_price
234 || x.qty != y.qty || x.pnl != y.pnl || x.commission != y.commission
235 || x.entry_id != y.entry_id || x.exit_id != y.exit_id) {
245 const Drive folded = batch(
true,
kBarCount);
246 const Drive plain = batch(
false,
kBarCount);
247 std::printf(
"batch: %zu rows for %lld script bars, %lld equity points, %zu closed trades\n",
248 folded.rows.size(),
static_cast<long long>(folded.script_bars),
249 static_cast<long long>(folded.curve_points), folded.trades.size());
250 assert(folded.script_bars ==
kBarCount && folded.trades.size() == 4);
253 std::printf(
"extension on vs off: %zu of %d rows shared; scalar %016llx vs %016llx; "
255 shared_rows(folded.rows, plain.rows),
kBarCount,
256 static_cast<unsigned long long>(folded.scalar),
257 static_cast<unsigned long long>(plain.scalar),
258 folded.continuation == plain.continuation ?
"identical" :
"DIFFERS");
259 assert(shared_rows(folded.rows, plain.rows) == 0);
260 assert(folded.scalar != plain.scalar);
261 assert(folded.continuation == plain.continuation);
262 assert(same_trades(folded, plain));
265 assert(batch(
true,
kBarCount).rows == folded.rows);
266 const Drive prefix = batch(
true, kPrefix);
267 std::printf(
"prefix: a batch of %d bars ends on row %016llx; the full batch's row %d is "
269 kPrefix,
static_cast<unsigned long long>(prefix.rows.back()), kPrefix - 1,
270 static_cast<unsigned long long>(folded.rows[kPrefix - 1]));
271 assert(prefix.rows.size() ==
static_cast<std::size_t
>(kPrefix));
272 assert(std::equal(prefix.rows.begin(), prefix.rows.end(), folded.rows.begin()));
275 const Drive early = stream(
true, kEarlyWarmup);
276 const Drive late = stream(
true, kLateWarmup);
277 const Drive early_plain = stream(
false, kEarlyWarmup);
278 for (
const Drive* drive : {&early, &late, &early_plain}) {
279 assert(drive->script_bars ==
kBarCount && drive->rows.size() == folded.rows.size());
282 assert(same_trades(early, folded) && same_trades(late, folded));
284 assert(shared_rows(early.broker_half, folded.broker_half) ==
kBarCount);
285 assert(shared_rows(late.broker_half, folded.broker_half) ==
kBarCount);
287 std::printf(
"batch vs stream (warmup %d): %zu of %d rows shared; broker half %zu of %d\n",
288 kEarlyWarmup, shared_rows(folded.rows, early.rows),
kBarCount,
289 shared_rows(folded.broker_half, early.broker_half),
kBarCount);
290 assert(shared_rows(folded.rows, early.rows) == 0);
291 assert(shared_rows(folded.rows, late.rows) == 0);
295 const bool same = early.rows[
static_cast<std::size_t
>(i)]
296 == late.rows[
static_cast<std::size_t
>(i)];
297 assert(same == (i < kEarlyWarmup || i > kLateWarmup));
299 std::printf(
"stream warmup %d vs %d: rows equal on [0,%d) and [%d,%d), different on [%d,%d]\n",
300 kEarlyWarmup, kLateWarmup, kEarlyWarmup, kLateWarmup + 1,
kBarCount,
301 kEarlyWarmup, kLateWarmup);
303 assert(shared_rows(early.rows, early_plain.rows) == 0);
304 assert(same_trades(early, early_plain));
307 std::printf(
"per-bar rows %zu/%lld batch, %zu/%lld stream extension moved %zu/%zu "
308 "batch vs stream shared %zu/%zu rows, %zu/%zu broker half closed trades: %zu\n",
309 folded.rows.size(),
static_cast<long long>(folded.script_bars),
310 early.rows.size(),
static_cast<long long>(early.script_bars),
311 folded.rows.size() - shared_rows(folded.rows, plain.rows), folded.rows.size(),
312 shared_rows(folded.rows, early.rows), folded.rows.size(),
313 shared_rows(folded.broker_half, early.broker_half), folded.broker_half.size(),
314 folded.trades.size());
static void free_report(ReportC *report)
virtual void hash_host_extension(BrokerStateHashSink &) const
void s(const std::string &v)
The public native host: an abstract subclass of BacktestEngine with no PineScript on it.
static pf_native_run_spec_v1 make_spec(void)
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.
native_order::RunIdentity identity
NativeReportPolicy report_policy