45double cents(
double value) {
return std::round(value * 100.0) / 100.0; }
49 std::optional<double> liquidation_price_after_fill;
50 std::optional<no::MarginCallEvent> margin_call;
51 int checks_bar_open = 0;
52 int checks_after_applied = 0;
53 int requirement_views = 0;
54 double last_required = 0.0;
55 double last_equity = 0.0;
56 std::optional<no::RequestHandle> entry;
57 std::optional<no::RequestHandle> over_leveraged;
62 void on_native_run_begin()
override { bars_ = 0; }
64 void on_native_bar(
const pineforge::Bar&,
65 const pineforge::NativeDecisionContext&)
override {
69 entry =
submit({no::Transact{45.0},
"entry",
"margin"}).handle;
70 }
else if (bars_ == 2) {
73 over_leveraged =
submit({no::Transact{100.0},
"over-leveraged",
"margin"}).handle;
78 bool margin_check_allowed(
const pineforge::NativeMarginCheckPoint& point)
const override {
79 auto* self =
const_cast<MarginExample*
>(
this);
80 if (point.
kind == pineforge::NativeMarginCheckKind::BarOpen) ++self->checks_bar_open;
81 if (point.
kind == pineforge::NativeMarginCheckKind::AfterApplied) ++self->checks_after_applied;
87 std::optional<pineforge::NativeMarginDecision> resolve_margin_requirement(
88 const pineforge::NativeMarginRequirementView& view)
const override {
89 auto* self =
const_cast<MarginExample*
>(
this);
90 ++self->requirement_views;
92 self->last_equity = view.
equity;
93 pineforge::NativeMarginDecision decision;
99 void on_native_applied(
const no::ExecutionAppliedEvent& event,
100 const pineforge::NativeDecisionContext&)
override {
101 if (entry && event.handle() == *entry) {
103 liquidation_price_after_fill = native_liquidation_price();
107 void on_native_margin_call(
const no::MarginCallEvent& event)
override {
120 spec.
type =
"crypto";
129 spec.
fee_kind = pineforge::NativeFeeKind::Percent;
140 margin.
sizing = pineforge::NativeLiquidationSizing::Flatten;
141 margin.
check = pineforge::NativeLiquidationCheck::PathAdverseExtreme;
148constexpr std::int64_t kQuarter = 15LL * 60LL * 1000LL;
154 { 99.75, 100.25, 99.50, 100.00, 10.0, 0 * kQuarter},
155 {100.00, 101.00, 99.75, 100.75, 10.0, 1 * kQuarter},
156 {100.75, 102.00, 100.50, 101.50, 10.0, 2 * kQuarter},
157 {101.50, 102.25, 101.00, 101.25, 10.0, 3 * kQuarter},
158 {101.25, 101.50, 96.00, 96.50, 10.0, 4 * kQuarter},
159 { 96.50, 96.75, 88.00, 89.00, 10.0, 5 * kQuarter},
160 { 89.00, 90.00, 88.50, 89.50, 10.0, 6 * kQuarter},
161 { 89.50, 91.00, 89.25, 90.75, 10.0, 7 * kQuarter},
169 if (host.configure_native(
make_spec()).status
170 != pineforge::NativeSetupStatus::Applied) {
171 std::cerr <<
"configure: " << host.last_error() <<
'\n';
176 if (host.native_state().kind != pineforge::NativeLifecycleKind::Completed) {
177 std::cerr <<
"run: " << host.last_error() <<
'\n';
182 int refused_on_margin = 0;
183 for (
const auto& event : host.native_events(0)) {
184 if (!event.command)
continue;
185 const auto* rejected = std::get_if<no::MatchRejectedEvent>(&*event.command);
186 if (rejected && host.over_leveraged && rejected->handle() == *host.over_leveraged
187 && rejected->reason == no::MatchRejectReason::InitialMargin) {
191 std::printf(
"over-leveraged opening refused on initial margin at %d candidate(s)\n",
193 if (refused_on_margin == 0) {
194 std::cerr <<
"expected the kernel's opening gate to refuse the 100-unit request\n";
199 if (!host.liquidation_price_after_fill) {
200 std::cerr <<
"native_liquidation_price() had no answer after the fill\n";
203 std::printf(
"liquidation price after the fill: %.4f (hand: 3500 / 38.25 = %.4f)\n",
204 *host.liquidation_price_after_fill, 3500.0 / 38.25);
205 if (std::fabs(*host.liquidation_price_after_fill - 3500.0 / 38.25) > 1e-6) {
206 std::cerr <<
"solved level differs from the hand computation\n";
211 std::printf(
"check points offered: BarOpen=%d AfterApplied=%d; requirement views=%d "
212 "(last: required %.4f vs equity %.4f)\n",
213 host.checks_bar_open, host.checks_after_applied, host.requirement_views,
214 host.last_required, host.last_equity);
215 if (host.checks_bar_open == 0 || host.checks_after_applied == 0 || host.requirement_views == 0) {
216 std::cerr <<
"expected the margin hooks to be consulted\n";
221 if (!host.margin_call) {
222 std::cerr <<
"expected a kernel-issued liquidation on the adverse path\n";
229 const auto& call = *host.margin_call;
230 std::printf(
"margin call: %.4f units booked at %.4f, position %.4f -> %.4f, ticket %s; "
231 "surviving book: equity %.4f, required %.4f, level %.4f\n",
232 call.units, call.mark, call.position_before, call.position_after,
233 call.request().label.c_str(), call.equity, call.required, call.liquidation_price);
234 if (std::fabs(call.mark - 3500.0 / 38.25) > 1e-6) {
235 std::cerr <<
"expected the liquidation to book at the solved level\n";
238 if (call.position_after != 0.0 || call.request().label !=
"liquidation") {
239 std::cerr <<
"expected Flatten to close the whole position under the model's ticket\n";
242 if (host.trade_count() < 1 || host.get_trade(0).exit_id !=
"liquidation") {
243 std::cerr <<
"expected the closed row to carry the liquidation ticket\n";
247 std::cout <<
"closed trades: " << host.trade_count() <<
'\n';
248 for (
int i = 0; i < host.trade_count(); ++i) {
249 const auto& trade = host.get_trade(i);
250 std::cout <<
" " << (trade.is_long ?
"long " :
"short")
251 <<
" qty=" << trade.qty
252 <<
" entry=" << trade.entry_price
253 <<
" exit=" << trade.exit_price
254 <<
" pnl=" << trade.pnl
255 <<
" exit_id=" << trade.exit_id <<
'\n';
257 return host.trade_count() > 0 ? 0 : 1;
The public native host: an abstract subclass of BacktestEngine with no PineScript on it.
static const pf_bar_t kBars[]
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)
NativeMarginCheckKind kind
A generic per-side broker margin model (L4).
NativeLiquidationCheck check
NativeLiquidationSizing sizing
std::optional< double > maintenance_long
std::string liquidation_comment
std::string liquidation_label
std::optional< double > maintenance_short
One complete setup value, staged/copied by NativeStrategyHost before it is applied at begin.
std::optional< NativeMarginModel > margin
Opt-in generic margin model.
native_order::RunIdentity identity