238 OpenLotsExample host;
239 if (!run_once(host, pineforge::NativeReportPolicy::KernelRecorded)) {
240 std::cerr <<
"the KernelRecorded run did not complete with one snapshot per bar\n";
245 for (std::size_t k = 0; k < host.snaps.size(); ++k) {
246 const auto& snap = host.snaps[k];
247 check(snap.rows.size() == snap.lot_count,
248 "one row per lot of physical_position().lot_count");
250 for (std::size_t i = 0; i < snap.rows.size(); ++i) {
251 check(snap.rows[i].ordinal == i,
"rows are ordinals 0..n-1, oldest first");
252 check(snap.rows[i].favorable_excursion >= 0.0
253 && snap.rows[i].adverse_excursion >= 0.0,
254 "both excursions are magnitudes");
255 sum += snap.rows[i].unrealized_pnl;
257 check(near(snap.marked_equity, snap.balance + sum),
258 "native_marked_equity(mark) == balance + sum(unrealized_pnl)");
261 check(snap.unmarked.size() == snap.rows.size(),
"a NaN mark still lists the book");
262 for (std::size_t i = 0; i < snap.unmarked.size(); ++i) {
263 check(snap.unmarked[i].entry_label == snap.rows[i].entry_label
264 && near(snap.unmarked[i].entry_price, snap.rows[i].entry_price)
265 && near(snap.unmarked[i].signed_units, snap.rows[i].signed_units)
266 && near(snap.unmarked[i].entry_commission, snap.rows[i].entry_commission),
267 "a NaN mark keeps every booking fact");
268 check(std::isnan(snap.unmarked[i].unrealized_pnl),
269 "a NaN mark leaves unrealized_pnl NaN");
275 const auto& full = host.snaps[5];
276 std::printf(
"open lots at bar 5 (mark %.2f), marked equity %.2f = balance %.2f + rows\n",
277 full.mark, full.marked_equity, full.balance);
278 print_rows(full.rows, full.mark);
279 check(full.rows.size() == 3,
"three lots after three openings");
280 if (full.rows.size() == 3) {
281 const auto& a = full.rows[0];
282 const auto& b = full.rows[1];
283 const auto& c = full.rows[2];
284 check(a.entry_label ==
"L1" && a.entry_comment ==
"first"
285 && near(a.entry_price, 100.0) && near(a.signed_units, 1.0)
286 && near(a.entry_commission, kFee) && a.entry_bar_index == 1,
287 "lot 0 is L1: one unit at 100, its own 2.00 ticket, opened on bar 1");
288 check(b.entry_label ==
"L2" && near(b.entry_price, 102.0)
289 && near(b.signed_units, 2.0) && near(b.entry_commission, kFee),
290 "lot 1 is L2: two units at 102 on one ticket");
291 check(c.entry_label ==
"L3" && near(c.entry_price, 104.0)
292 && near(c.signed_units, 1.0) && near(c.entry_commission, kFee),
293 "lot 2 is L3: one unit at 104");
295 check(near(a.unrealized_pnl, 4.0),
"L1 marks +4.00 at 106 (6.00 gross less its 2.00 fee)");
296 check(near(b.unrealized_pnl, 6.0),
"L2 marks +6.00 at 106");
297 check(near(c.unrealized_pnl, 0.0),
"L3 marks 0.00 at 106");
298 check(a.side == no::Side::Long && a.entry_incarnation != 0
299 && a.cycle == b.cycle && b.cycle == c.cycle,
300 "one long position cycle, and every lot names the request that opened it");
304 const auto& rest = host.snaps[7];
305 std::printf(
"open lots at bar 7 (mark %.2f), after Reduce 1.5 filled at 103.00\n", rest.mark);
306 print_rows(rest.rows, rest.mark);
307 check(rest.rows.size() == 2,
"FIFO closed L1 whole and split L2");
308 if (rest.rows.size() == 2) {
309 check(rest.rows[0].entry_label ==
"L2" && near(rest.rows[0].signed_units, 1.5)
310 && near(rest.rows[0].entry_price, 102.0),
311 "the remainder of L2 is 1.5 units, still at its own entry price");
312 check(near(rest.rows[0].entry_commission, kFee * 0.75),
313 "the closed quarter took a quarter of L2's entry fee with it");
314 check(rest.rows[1].entry_label ==
"L3" && near(rest.rows[1].signed_units, 1.0)
315 && near(rest.rows[1].entry_commission, kFee),
320 const auto& stats = host.statistics;
321 std::printf(
"report at the last calculation: equity=%.4f netprofit=%.4f "
322 "grossprofit=%.4f grossloss=%.4f openprofit=%.4f avg_trade=%.4f\n",
323 stats.equity, stats.netprofit, stats.grossprofit, stats.grossloss,
324 stats.openprofit, stats.avg_trade);
325 std::printf(
" wintrades=%d losstrades=%d eventrades=%d "
326 "max_contracts_held=%.2f capital_held=%.2f position_size=%+.2f\n",
327 stats.wintrades, stats.losstrades, host.eventrades(),
328 host.max_contracts_held_all(), stats.capital_held, stats.position_size);
329 check(near(stats.netprofit, stats.grossprofit + stats.grossloss),
330 "netprofit is grossprofit plus grossloss (the loss term is signed)");
331 check(stats.wintrades + stats.losstrades + host.eventrades() == host.trade_count(),
332 "every closed row is a win, a loss or an even trade");
333 check(near(stats.contracts_held, 4.0),
334 "the book peaked at four units (1 + 2 + 1)");
335 check(near(stats.position_size, 2.5),
"2.5 units are still open at the end");
345 OpenLotsExample bare;
346 if (!run_once(bare, pineforge::NativeReportPolicy::HostRecorded)) {
347 std::cerr <<
"the HostRecorded run did not complete with one snapshot per bar\n";
350 const auto& bare_stats = bare.statistics;
351 std::printf(
"report policy: KernelRecorded folds max_drawdown=%.4f max_runup=%.4f "
352 "max_contracts_held=%.2f; HostRecorded folds %.4f / %.4f / %.2f\n",
353 stats.max_drawdown, stats.max_runup, stats.contracts_held,
354 bare_stats.max_drawdown, bare_stats.max_runup, bare_stats.contracts_held);
357 check(near(stats.max_drawdown, 16.5) && near(stats.max_runup, 0.0)
358 && near(stats.contracts_held, 4.0),
359 "KernelRecorded folds the equity extremes and the position-size peak");
360 check(bare_stats.max_drawdown == 0.0 && bare_stats.max_runup == 0.0
361 && bare_stats.contracts_held == 0.0,
362 "HostRecorded folds none of them: that series is the host's");
363 check(bare.trade_count() == host.trade_count()
364 && near(bare_stats.netprofit, stats.netprofit),
365 "recording moves no fill: the two runs close the same trades for the same money");
368 std::cerr << failures <<
" check(s) failed\n";
371 std::cout <<
"closed trades: " << host.trade_count() <<
'\n';
372 for (
int i = 0; i < host.trade_count(); ++i) {
373 const auto& trade = host.get_trade(i);
374 std::cout <<
" " << (trade.is_long ?
"long " :
"short")
375 <<
" qty=" << trade.qty
376 <<
" entry=" << trade.entry_price
377 <<
" exit=" << trade.exit_price
378 <<
" pnl=" << trade.pnl
379 <<
" commission=" << trade.commission <<
'\n';
381 return host.trade_count() > 0 ? 0 : 1;