158 RiskLimitsExample host;
159 if (host.configure_native(
make_spec()).status != pineforge::NativeSetupStatus::Applied) {
160 std::cerr <<
"configure: " << host.last_error() <<
'\n';
163 const auto tape = make_tape();
164 host.run(tape.data(),
static_cast<int>(tape.size()));
165 if (host.native_state().kind != pineforge::NativeLifecycleKind::Completed) {
166 std::cerr <<
"run: " << host.last_error() <<
'\n';
171 std::vector<no::NativeRiskEvent> breaches;
172 int kernel_flattens = 0;
173 int re_entry_refusals = 0;
174 int day_three_refusals = 0;
175 for (
const auto& event : host.native_events(0)) {
176 if (!event.command)
continue;
177 if (
const auto* risk = std::get_if<no::NativeRiskEvent>(&*event.command)) {
178 breaches.push_back(*risk);
180 if (
const auto* accepted = std::get_if<no::AcceptedEvent>(&*event.command)) {
181 if (accepted->definition
182 && accepted->definition->origin == no::RequestOrigin::KernelRisk) {
186 if (
const auto* rejected = std::get_if<no::MatchRejectedEvent>(&*event.command)) {
187 if (rejected->reason != no::MatchRejectReason::RiskLimit)
continue;
188 if (host.same_day_re_entry && rejected->handle() == *host.same_day_re_entry) {
191 if (host.day_three_entry && rejected->handle() == *host.day_three_entry) {
192 ++day_three_refusals;
196 for (
const auto& breach : breaches) {
197 std::printf(
"risk event: %-22s limit=%8.2f observed=%8.2f at script bar %lld\n",
198 limit_name(breach.kind), breach.limit, breach.observed,
199 static_cast<long long>(breach.cursor.point.interval_index));
203 if (breaches.size() != 3
204 || breaches[0].kind != no::RiskLimitKind::MaxIntradayLoss
205 || !near(breaches[0].limit, 1000.0) || !near(breaches[0].observed, 1200.0)
206 || breaches[0].cursor.point.interval_index != 2
207 || breaches[1].kind != no::RiskLimitKind::MaxIntradayLoss
208 || !near(breaches[1].limit, 988.0) || !near(breaches[1].observed, 1000.0)
209 || breaches[1].cursor.point.interval_index != 6
210 || breaches[2].kind != no::RiskLimitKind::MaxConsecutiveLossDays
211 || !near(breaches[2].limit, 2.0) || !near(breaches[2].observed, 2.0)
212 || breaches[2].cursor.point.interval_index != 8) {
213 std::cerr <<
"expected two intraday-loss breaches and the loss-days breach\n";
216 if (breaches[1].day_ordinal != breaches[0].day_ordinal + 1
217 || breaches[2].day_ordinal != breaches[1].day_ordinal + 1) {
218 std::cerr <<
"expected the three breaches on three consecutive risk days\n";
222 std::printf(
"kernel-originated flattens: %d; same-day re-entry refused %d time(s); "
223 "day-3 entry refused %d time(s)\n",
224 kernel_flattens, re_entry_refusals, day_three_refusals);
226 if (kernel_flattens != 2 || re_entry_refusals == 0 || day_three_refusals == 0) {
227 std::cerr <<
"expected one kernel flatten per intraday breach and both openings refused\n";
232 for (std::size_t day = 0; day < host.ledger_at_day_end.size(); ++day) {
233 const auto& row = host.ledger_at_day_end[day];
234 std::printf(
"day %zu close: blocked=%d reason=%-22s fills_today=%llu loss_days=%u "
235 "day_open_equity=%.2f peak=%.2f\n",
236 day + 1, row.blocked ? 1 : 0, row.reason ? limit_name(*row.reason) :
"-",
237 static_cast<unsigned long long>(row.fills_today), row.consecutive_loss_days,
238 row.day_open_equity, row.peak_equity);
240 if (host.ledger_at_day_end.size() != 3)
return 1;
241 const auto& day1 = host.ledger_at_day_end[0];
242 const auto& day2 = host.ledger_at_day_end[1];
243 const auto& day3 = host.ledger_at_day_end[2];
244 if (!day1.blocked || !day1.reason || *day1.reason != no::RiskLimitKind::MaxIntradayLoss
245 || day1.fills_today != 2 || day1.consecutive_loss_days != 0
246 || !near(day1.day_open_equity, 100000.0)
247 || !day2.blocked || !day2.reason || *day2.reason != no::RiskLimitKind::MaxIntradayLoss
248 || day2.fills_today != 2 || day2.consecutive_loss_days != 1
249 || !near(day2.day_open_equity, 98800.0)
250 || !day3.blocked || !day3.reason
251 || *day3.reason != no::RiskLimitKind::MaxConsecutiveLossDays
252 || day3.fills_today != 0 || day3.consecutive_loss_days != 2
253 || !near(day3.day_open_equity, 97800.0) || !near(day3.peak_equity, 100000.0)) {
254 std::cerr <<
"expected the ledger to carry each day's opening equity, fills and streak\n";
257 if (host.physical_position().signed_units != 0.0) {
258 std::cerr <<
"expected a flat book at the end\n";
264 for (
int i = 0; i < host.trade_count(); ++i) net += host.get_trade(i).pnl;
265 if (host.trade_count() != 2 || !near(net, -2200.0)
266 || std::string(host.get_trade(0).exit_id) !=
"__kernel_risk__"
267 || std::string(host.get_trade(1).exit_id) !=
"__kernel_risk__") {
268 std::cerr <<
"expected two kernel-closed rows netting -2200\n";
272 std::cout <<
"closed trades: " << host.trade_count() <<
'\n';
273 for (
int i = 0; i < host.trade_count(); ++i) {
274 const auto& trade = host.get_trade(i);
275 std::cout <<
" " << (trade.is_long ?
"long " :
"short")
276 <<
" qty=" << trade.qty
277 <<
" entry=" << trade.entry_price
278 <<
" exit=" << trade.exit_price
279 <<
" pnl=" << trade.pnl
280 <<
" exit_id=" << trade.exit_id <<
'\n';