Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions include/pineforge/engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,36 @@ struct PendingOrder {
// frozen-qty notional at the slipped fill price exceeds sizing_equity
// by more than one lot, given zero actual opening commission.
bool opening_affordability_exemption_candidate = false;
// design-explicit-qty-fill-admission: fill-time TV admission re-check for an
// EXPLICIT-qty (the caller passed a finite qty) true-flat MARKET entry — the
// explicit-qty sibling of the frozen gap-reject above, which the shipped
// frozen fix deliberately left alone. Set at PLACEMENT in strategy_entry's
// explicit-qty MARKET branch (the branch that does NOT freeze default
// sizing). True only for a high-level MARKET strategy.entry with a finite
// explicit qty, created TRUE-FLAT (created_position_side==FLAT &&
// !created_after_position_close_in_bar), direction-appropriate margin_pct>0,
// and finite snapshots. Fill-time consumer: the disjoint explicit-qty
// decline branch in apply_filled_order_to_state silently drops the entry (no
// trade row) when, at a still-FLAT fill, its notional at the SLIPPED FILL
// price overshoots the placement equity snapshot with zero structural slack.
// Commission is EXCLUDED from that predicate. Priced (limit/stop) entries
// carry no snapshot (type==ENTRY, not MARKET); RAW strategy.order never sets
// the flag. Evidence: probe-68 (data/probes/pf-probe-allin-floor-comm0,
// 4,740 from-flat attempts, decline iff fill notional > equity, zero slack,
// 99.94%); mdfe3757 306/306.
bool explicit_flat_admission_candidate = false;
// Placement-time equity snapshot (account ccy) for the explicit-qty gate:
// current_equity() + open_profit(close(S)) == realized equity when flat
// Captured at the explicit-qty MARKET placement point. NaN = no snapshot.
double explicit_placement_equity = std::numeric_limits<double>::quiet_NaN();
// Slipped signal close at placement (frozen_sizing_price convention:
// close(S) + slippage*mintick*(+1 buy / -1 sell)). Its |qty|-scaled notional
// floors the fill-time decline threshold, so a fill AT/BELOW the slipped
// signal close — POOC (fill == close(S)+slip both sides), a no-gap open, or a
// favorable gap — is a structural no-op even with slippage != 0; only an
// ADVERSE gap beyond the slip can decline. NaN = no snapshot.
double explicit_slipped_signal_close =
std::numeric_limits<double>::quiet_NaN();
std::string comment; // order comment for trade reporting
bool requested_partial = false; // true iff caller passed qty_percent < 100
bool created_while_in_position = false; // true if position was open when order was placed
Expand Down
59 changes: 59 additions & 0 deletions src/engine_fills.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,65 @@ void BacktestEngine::apply_filled_order_to_state(
}
}

// design-explicit-qty-fill-admission: TV declines an EXPLICIT-qty (caller
// passed a finite qty) true-flat MARKET entry at fill when its notional at
// the SLIPPED FILL price overshoots the placement equity snapshot. This is
// the explicit-qty sibling of the frozen gap-reject family above (those all
// require isnan(qty) via the frozen snapshot / candidate flag); the shipped
// frozen fix deliberately left the explicit path alone.
//
// |qty| * slipped_fill * pv * fx * (margin_pct/100)
// > max(placement_equity,
// |qty| * slipped_signal_close * pv * fx * margin/100)
// + max(1e-9, |placement_equity| * 1e-12) (float guard only)
//
// The slipped-signal-close notional floors the threshold: a fill AT/BELOW
// the slipped signal close (POOC — fill == close(S)+slip on both sides — a
// no-gap open, or a favorable gap) can never decline, so the fill-time gate
// never re-declines what the signal-time gate already admitted; only an
// ADVERSE gap beyond the slip declines. With slippage 0 the floor collapses
// to the pure "fill notional > equity" rule the census pins (probe-68 comm=0
// decline-iff-notional>equity, zero slack, 99.94%; mdfe3757 306/306).
//
// Scope: created TRUE-FLAT (candidate flag) AND still FLAT at THIS fill,
// MARKET only, margin_pct>0. Commission is EXCLUDED — a fee-only overage
// ADMITS here, then the KI-61-family entry-bar trim may fire downstream.
// Reversals/adds are out of scope (flat-at-fill required — the flag is only
// set from a true-flat placement, and position_side_==FLAT is re-proven
// here). Runs BEFORE the intraday-cap accounting below: a declined order
// never filled, so it must not consume a max_intraday_filled_orders slot.
// order.qty is NOT mutated (isnan(order.qty) stays a live default-sizing
// discriminator for OCA / reversal-binding / partial-exit classification).
if (order.explicit_flat_admission_candidate
&& order.type == OrderType::MARKET
&& position_side_ == PositionSide::FLAT
&& !std::isnan(order.qty)
&& !std::isnan(order.explicit_placement_equity)
&& !std::isnan(order.explicit_slipped_signal_close)) {
const double margin_pct = order.is_long ? margin_long_ : margin_short_;
if (margin_pct > 0.0) {
const double slipped_fill =
apply_fill_slippage(fill_price, order.is_long);
const double notional_k = syminfo_.pointvalue * account_currency_fx_
* (margin_pct / 100.0);
const double fill_notional =
std::abs(order.qty) * slipped_fill * notional_k;
// POOC / no-gap admission floor: a fill at the slipped signal close
// was already admitted at signal time.
const double signal_notional =
std::abs(order.qty) * order.explicit_slipped_signal_close
* notional_k;
const double threshold =
std::max(order.explicit_placement_equity, signal_notional);
const double float_guard =
std::max(1e-9, std::abs(order.explicit_placement_equity) * 1e-12);
if (fill_notional > threshold + float_guard) {
filled_indices.push_back(order_index);
return;
}
}
}

// Check max_intraday_filled_orders limit.
//
// TV's broker emulator (LATCH-TILL-DAY-ROLLOVER semantics):
Expand Down
32 changes: 32 additions & 0 deletions src/engine_strategy_commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,38 @@ void BacktestEngine::strategy_entry(const std::string& id, bool is_long,
&& std::isfinite(order.sizing_price)
&& std::isfinite(order.sizing_mark);
}
// design-explicit-qty-fill-admission: capture the fill-time admission
// snapshot for an EXPLICIT-qty (the caller passed a finite qty) MARKET
// entry created TRUE-FLAT. The signal-time gate above (~:139-158)
// already rejected orders whose notional at THIS bar's close overshoots
// equity; this snapshot lets the fill-time re-check drop the entry when
// the next-bar fill gaps adversely enough that |qty|*slipped_fill
// exceeds the placement equity — TV's pinned behavior for the all-in
// floor idiom (probe-68 / mdfe3757). Disjoint from the frozen default-
// sizing snapshot above (that path requires isnan(qty)); priced
// (limit/stop) entries never reach here (else-branch) and RAW
// strategy.order builds its order elsewhere, so neither carries the
// candidate flag. Commission is EXCLUDED from the fill predicate.
if (!std::isnan(qty) && !std::isnan(current_bar_.close)) {
const double explicit_margin = is_long ? margin_long_ : margin_short_;
// Equity basis matches the frozen path (KI-54): realized equity plus
// open profit marked at the signal close. == current_equity() when
// flat, which every candidate is (created_position_side == FLAT).
const double placement_equity =
current_equity() + open_profit(current_bar_.close);
const double slipped_signal_close =
frozen_sizing_price(/*is_buy=*/is_long);
order.explicit_flat_admission_candidate =
order.created_position_side == PositionSide::FLAT
&& !order.created_after_position_close_in_bar
&& std::isfinite(explicit_margin) && explicit_margin > 0.0
&& std::isfinite(placement_equity)
&& std::isfinite(slipped_signal_close);
if (order.explicit_flat_admission_candidate) {
order.explicit_placement_equity = placement_equity;
order.explicit_slipped_signal_close = slipped_signal_close;
}
}
} else {
order.type = OrderType::ENTRY;
order.limit_price = limit_price;
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ set(TEST_SOURCES
test_default_qty_signal_freeze
test_margin_admission_gate
test_frozen_flat_gap_reject
test_explicit_qty_fill_admission
test_limit_fill_slippage
test_strategy_commands_extra
test_multi_tier_exit_precedence
Expand Down
Loading
Loading