162 RiskLimitsExample host;
163 if (host.configure_native(
make_spec()).status != pineforge::NativeSetupStatus::Applied) {
164 std::cerr <<
"configure: " << host.last_error() <<
'\n';
167 const auto tape = make_tape();
168 host.run(tape.data(),
static_cast<int>(tape.size()));
169 if (host.native_state().kind != pineforge::NativeLifecycleKind::Completed) {
170 std::cerr <<
"run: " << host.last_error() <<
'\n';
175 std::vector<no::NativeRiskEvent> breaches;
176 int kernel_flattens = 0;
177 int re_entry_refusals = 0;
178 int day_three_refusals = 0;
179 for (
const auto& event : host.native_events(0)) {
180 if (!event.command)
continue;
181 if (
const auto* risk = std::get_if<no::NativeRiskEvent>(&*event.command)) {
182 breaches.push_back(*risk);
184 if (
const auto* accepted = std::get_if<no::AcceptedEvent>(&*event.command)) {
185 if (accepted->definition
186 && accepted->definition->origin == no::RequestOrigin::KernelRisk) {
190 if (
const auto* rejected = std::get_if<no::MatchRejectedEvent>(&*event.command)) {
191 if (rejected->reason != no::MatchRejectReason::RiskLimit)
continue;
192 if (host.same_day_re_entry && rejected->handle() == *host.same_day_re_entry) {
195 if (host.day_three_entry && rejected->handle() == *host.day_three_entry) {
196 ++day_three_refusals;
200 for (
const auto& breach : breaches) {
201 std::printf(
"risk event: %-22s limit=%8.2f observed=%8.2f at script bar %lld\n",
202 limit_name(breach.kind), breach.limit, breach.observed,
203 static_cast<long long>(breach.cursor.point.interval_index));
207 if (breaches.size() != 3
208 || breaches[0].kind != no::RiskLimitKind::MaxIntradayLoss
209 || !near(breaches[0].limit, 1000.0) || !near(breaches[0].observed, 1200.0)
210 || breaches[0].cursor.point.interval_index != 2
211 || breaches[1].kind != no::RiskLimitKind::MaxIntradayLoss
212 || !near(breaches[1].limit, 988.0) || !near(breaches[1].observed, 1000.0)
213 || breaches[1].cursor.point.interval_index != 6
214 || breaches[2].kind != no::RiskLimitKind::MaxConsecutiveLossDays
215 || !near(breaches[2].limit, 2.0) || !near(breaches[2].observed, 2.0)
216 || breaches[2].cursor.point.interval_index != 8) {
217 std::cerr <<
"expected two intraday-loss breaches and the loss-days breach\n";
220 if (breaches[1].day_ordinal != breaches[0].day_ordinal + 1
221 || breaches[2].day_ordinal != breaches[1].day_ordinal + 1) {
222 std::cerr <<
"expected the three breaches on three consecutive risk days\n";
226 std::printf(
"kernel-originated flattens: %d; same-day re-entry refused %d time(s); "
227 "day-3 entry refused %d time(s)\n",
228 kernel_flattens, re_entry_refusals, day_three_refusals);
230 if (kernel_flattens != 2 || re_entry_refusals == 0 || day_three_refusals == 0) {
231 std::cerr <<
"expected one kernel flatten per intraday breach and both openings refused\n";
236 for (std::size_t day = 0; day < host.ledger_at_day_end.size(); ++day) {
237 const auto& row = host.ledger_at_day_end[day];
238 std::printf(
"day %zu close: blocked=%d reason=%-22s fills_today=%llu loss_days=%u "
239 "day_open_equity=%.2f peak=%.2f\n",
240 day + 1, row.blocked ? 1 : 0, row.reason ? limit_name(*row.reason) :
"-",
241 static_cast<unsigned long long>(row.fills_today), row.consecutive_loss_days,
242 row.day_open_equity, row.peak_equity);
244 if (host.ledger_at_day_end.size() != 3)
return 1;
245 const auto& day1 = host.ledger_at_day_end[0];
246 const auto& day2 = host.ledger_at_day_end[1];
247 const auto& day3 = host.ledger_at_day_end[2];
248 if (!day1.blocked || !day1.reason || *day1.reason != no::RiskLimitKind::MaxIntradayLoss
249 || day1.fills_today != 2 || day1.consecutive_loss_days != 0
250 || !near(day1.day_open_equity, 100000.0)
251 || !day2.blocked || !day2.reason || *day2.reason != no::RiskLimitKind::MaxIntradayLoss
252 || day2.fills_today != 2 || day2.consecutive_loss_days != 1
253 || !near(day2.day_open_equity, 98800.0)
254 || !day3.blocked || !day3.reason
255 || *day3.reason != no::RiskLimitKind::MaxConsecutiveLossDays
256 || day3.fills_today != 0 || day3.consecutive_loss_days != 2
257 || !near(day3.day_open_equity, 97800.0) || !near(day3.peak_equity, 100000.0)) {
258 std::cerr <<
"expected the ledger to carry each day's opening equity, fills and streak\n";
261 if (host.physical_position().signed_units != 0.0) {
262 std::cerr <<
"expected a flat book at the end\n";
268 for (
int i = 0; i < host.trade_count(); ++i) net += host.get_trade(i).pnl;
269 if (host.trade_count() != 2 || !near(net, -2200.0)
270 || std::string(host.get_trade(0).exit_id) !=
"__kernel_risk__"
271 || std::string(host.get_trade(1).exit_id) !=
"__kernel_risk__") {
272 std::cerr <<
"expected two kernel-closed rows netting -2200\n";
276 std::cout <<
"closed trades: " << host.trade_count() <<
'\n';
277 for (
int i = 0; i < host.trade_count(); ++i) {
278 const auto& trade = host.get_trade(i);
279 std::cout <<
" " << (trade.is_long ?
"long " :
"short")
280 <<
" qty=" << trade.qty
281 <<
" entry=" << trade.entry_price
282 <<
" exit=" << trade.exit_price
283 <<
" pnl=" << trade.pnl
284 <<
" exit_id=" << trade.exit_id <<
'\n';