244 OpenLotsExample host;
245 if (!run_once(host, pineforge::NativeReportPolicy::KernelRecorded)) {
246 std::cerr <<
"the KernelRecorded run did not complete with one snapshot per bar\n";
251 for (std::size_t k = 0; k < host.snaps.size(); ++k) {
252 const auto& snap = host.snaps[k];
253 check(snap.rows.size() == snap.lot_count,
254 "one row per lot of physical_position().lot_count");
256 for (std::size_t i = 0; i < snap.rows.size(); ++i) {
257 check(snap.rows[i].ordinal == i,
"rows are ordinals 0..n-1, oldest first");
258 check(snap.rows[i].favorable_excursion >= 0.0
259 && snap.rows[i].adverse_excursion >= 0.0,
260 "both excursions are magnitudes");
261 sum += snap.rows[i].unrealized_pnl;
263 check(near(snap.marked_equity, snap.balance + sum),
264 "native_marked_equity(mark) == balance + sum(unrealized_pnl)");
267 check(snap.unmarked.size() == snap.rows.size(),
"a NaN mark still lists the book");
268 for (std::size_t i = 0; i < snap.unmarked.size(); ++i) {
269 check(snap.unmarked[i].entry_label == snap.rows[i].entry_label
270 && near(snap.unmarked[i].entry_price, snap.rows[i].entry_price)
271 && near(snap.unmarked[i].signed_units, snap.rows[i].signed_units)
272 && near(snap.unmarked[i].entry_commission, snap.rows[i].entry_commission),
273 "a NaN mark keeps every booking fact");
274 check(std::isnan(snap.unmarked[i].unrealized_pnl),
275 "a NaN mark leaves unrealized_pnl NaN");
281 const auto& full = host.snaps[5];
282 std::printf(
"open lots at bar 5 (mark %.2f), marked equity %.2f = balance %.2f + rows\n",
283 full.mark, full.marked_equity, full.balance);
284 print_rows(full.rows, full.mark);
285 check(full.rows.size() == 3,
"three lots after three openings");
286 if (full.rows.size() == 3) {
287 const auto& a = full.rows[0];
288 const auto& b = full.rows[1];
289 const auto& c = full.rows[2];
290 check(a.entry_label ==
"L1" && a.entry_comment ==
"first"
291 && near(a.entry_price, 100.0) && near(a.signed_units, 1.0)
292 && near(a.entry_commission, kFee) && a.entry_bar_index == 1,
293 "lot 0 is L1: one unit at 100, its own 2.00 ticket, opened on bar 1");
294 check(b.entry_label ==
"L2" && near(b.entry_price, 102.0)
295 && near(b.signed_units, 2.0) && near(b.entry_commission, kFee),
296 "lot 1 is L2: two units at 102 on one ticket");
297 check(c.entry_label ==
"L3" && near(c.entry_price, 104.0)
298 && near(c.signed_units, 1.0) && near(c.entry_commission, kFee),
299 "lot 2 is L3: one unit at 104");
301 check(near(a.unrealized_pnl, 4.0),
"L1 marks +4.00 at 106 (6.00 gross less its 2.00 fee)");
302 check(near(b.unrealized_pnl, 6.0),
"L2 marks +6.00 at 106");
303 check(near(c.unrealized_pnl, 0.0),
"L3 marks 0.00 at 106");
304 check(a.side == no::Side::Long && a.entry_incarnation != 0
305 && a.cycle == b.cycle && b.cycle == c.cycle,
306 "one long position cycle, and every lot names the request that opened it");
310 const auto& rest = host.snaps[7];
311 std::printf(
"open lots at bar 7 (mark %.2f), after Reduce 1.5 filled at 103.00\n", rest.mark);
312 print_rows(rest.rows, rest.mark);
313 check(rest.rows.size() == 2,
"FIFO closed L1 whole and split L2");
314 if (rest.rows.size() == 2) {
315 check(rest.rows[0].entry_label ==
"L2" && near(rest.rows[0].signed_units, 1.5)
316 && near(rest.rows[0].entry_price, 102.0),
317 "the remainder of L2 is 1.5 units, still at its own entry price");
318 check(near(rest.rows[0].entry_commission, kFee * 0.75),
319 "the closed quarter took a quarter of L2's entry fee with it");
320 check(rest.rows[1].entry_label ==
"L3" && near(rest.rows[1].signed_units, 1.0)
321 && near(rest.rows[1].entry_commission, kFee),
326 const auto& stats = host.statistics;
327 std::printf(
"report at the last calculation: equity=%.4f netprofit=%.4f "
328 "grossprofit=%.4f grossloss=%.4f openprofit=%.4f avg_trade=%.4f\n",
329 stats.equity, stats.netprofit, stats.grossprofit, stats.grossloss,
330 stats.openprofit, stats.avg_trade);
331 std::printf(
" wintrades=%d losstrades=%d eventrades=%d "
332 "max_contracts_held=%.2f capital_held=%.2f position_size=%+.2f\n",
333 stats.wintrades, stats.losstrades, host.eventrades(),
334 host.max_contracts_held_all(), stats.capital_held, stats.position_size);
335 check(near(stats.netprofit, stats.grossprofit + stats.grossloss),
336 "netprofit is grossprofit plus grossloss (the loss term is signed)");
337 check(stats.wintrades + stats.losstrades + host.eventrades() == host.trade_count(),
338 "every closed row is a win, a loss or an even trade");
339 check(near(stats.contracts_held, 4.0),
340 "the book peaked at four units (1 + 2 + 1)");
341 check(near(stats.position_size, 2.5),
"2.5 units are still open at the end");
354 OpenLotsExample bare;
355 if (!run_once(bare, pineforge::NativeReportPolicy::HostRecorded)) {
356 std::cerr <<
"the HostRecorded run did not complete with one snapshot per bar\n";
359 const auto& bare_stats = bare.statistics;
360 std::printf(
"report policy: KernelRecorded folds max_drawdown=%.4f max_runup=%.4f "
361 "max_contracts_held=%.2f over %zu recorded points; HostRecorded folds "
362 "%.4f / %.4f / %.2f over %zu\n",
363 stats.max_drawdown, stats.max_runup, stats.contracts_held,
365 bare_stats.max_drawdown, bare_stats.max_runup, bare_stats.contracts_held,
366 bare_stats.curve_points);
369 check(near(stats.max_drawdown, 16.5) && near(stats.max_runup, 0.0)
370 && near(stats.contracts_held, 4.0),
371 "KernelRecorded folds the equity extremes and the position-size peak");
372 check(near(bare_stats.max_drawdown, stats.max_drawdown)
373 && near(bare_stats.max_runup, stats.max_runup)
374 && near(bare_stats.contracts_held, stats.contracts_held),
375 "HostRecorded folds the same three: they are the run's, not the curve owner's");
376 check(stats.curve_points > 0 && bare_stats.curve_points == 0,
377 "only the recorded CURVE is the policy's: HostRecorded records no point");
378 check(bare.trade_count() == host.trade_count()
379 && near(bare_stats.netprofit, stats.netprofit),
380 "recording moves no fill: the two runs close the same trades for the same money");
383 std::cerr << failures <<
" check(s) failed\n";
386 std::cout <<
"closed trades: " << host.trade_count() <<
'\n';
387 for (
int i = 0; i < host.trade_count(); ++i) {
388 const auto& trade = host.get_trade(i);
389 std::cout <<
" " << (trade.is_long ?
"long " :
"short")
390 <<
" qty=" << trade.qty
391 <<
" entry=" << trade.entry_price
392 <<
" exit=" << trade.exit_price
393 <<
" pnl=" << trade.pnl
394 <<
" commission=" << trade.commission <<
'\n';
396 return host.trade_count() > 0 ? 0 : 1;