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
41 changes: 41 additions & 0 deletions include/pineforge/engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,26 @@ struct PendingOrder {
// in the BAR-OPEN recalc (or resting at bar start) keep standard exact-level
// semantics and leave this false. Scoped to the historical path; the
// magnifier path (bar_magnifier_enabled_) ignores this bit entirely.
//
// ENTRY cascade orders use the plain "extreme-waypoint only" reach above.
// strategy.exit cascade orders follow the finer KI-67 Model S rule
// ("R-cascade-gapjump") captured by the two fields below.
bool coof_born_mid_bar = false;
// KI-67 exit cascade (Model S). Set at birth for a coof_born_mid_bar
// strategy.exit order: the historical-path LEG index (0 = O->W1, 1 = W1->W2,
// 2 = W2->C) the triggering intrabar fill (coof_cursor_price_ "ap") landed
// on — the "in-flight" leg. -1 when the order is not a mid-bar cascade exit,
// or ap is off-path (roll). The gate holds the order on that leg's remainder,
// lets it EXACT-level fill on every SUBSEQUENT leg, and (when
// coof_cascade_inflight_fires) gap-fills it at the in-flight leg-end waypoint.
int8_t coof_cascade_seg_i = -1;
// KI-67 exit cascade: true when the exit level lies inside the in-flight
// remainder (ap -> leg-end waypoint) in the trigger direction on a
// NON-terminal in-flight leg, so it gap-fills same-bar AT that waypoint price
// (Model S clause 1: limits better than level, stops worse). False otherwise
// — subsequent legs fill at the exact level and a terminal in-flight leg
// (seg_i == 2) or off-path (seg_i < 0) rolls to the next bar.
bool coof_cascade_inflight_fires = false;
PositionSide created_position_side = PositionSide::FLAT;
// True when a successful strategy.close/close_all call earlier in this
// same on_bar already targeted live quantity. This remains distinct from
Expand Down Expand Up @@ -1465,6 +1484,28 @@ class BacktestEngine {
// are held (they convert to ordinary resting orders at bar end). Set only by
// the historical dispatch; the magnifier path never sets it.
bool coof_at_extreme_waypoint_ = false;
// KI-67 exit cascade: the historical dispatch publishes its current path
// position here for the strategy.exit cascade gate. coof_hist_is_segment_
// marks a segment (vs point) evaluation; coof_hist_path_index_ is the LEG
// index (0..2) on a segment, or the path WAYPOINT index (0..3, cursor =
// path[index]) on a point. Meaningful only while coof_scheduler_active_ on
// the non-magnifier historical path; the POOC-C / margin passes publish the
// C waypoint (index 3) so cascade exits are held there.
bool coof_hist_is_segment_ = false;
int coof_hist_path_index_ = -1;
// KI-67 exit cascade: the in-flight leg index (0..2) the CURRENT fill recalc
// was triggered on — the leg the dispatch cursor traverses next after the
// triggering fill. Published by the loop right before each recalc so a
// strategy.exit placed in that recalc records its seg_i from the loop's real
// position ("a fill AT a waypoint starts the NEXT leg"), rather than
// re-deriving it from the fill price (ambiguous exactly at waypoints). -1 (or
// >=3) outside a mid-bar historical recalc / at the terminal C tick.
int coof_cascade_recalc_leg_ = -1;
// KI-67 exit cascade: set by the gate immediately before evaluate_fill_price
// so resolve_exit_path_fill runs its open-gap shortcut on the in-flight
// leg-end waypoint POINT even when is_entry_bar (entry + exit share a bar).
// Reset right after that evaluation; never set on the magnifier path.
bool coof_cascade_force_wp_gap_ = false;
bool coof_checkpoint_contains_current_bar_ = false;
double coof_cursor_price_ = std::numeric_limits<double>::quiet_NaN();
// Direct strategy.close / POOC fills can occur inside on_bar rather than
Expand Down
60 changes: 48 additions & 12 deletions src/engine_fills.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,24 +192,59 @@ BacktestEngine::CoofFillResult BacktestEngine::process_next_pending_order(
}

// KI-67 cascade eligibility (historical 4-tick path only). An order
// born in a MID-BAR fill recalc is eligible ONLY at the remaining
// extreme waypoints (W1/W2) of the bar it was born on: never
// intra-segment at an exact level, never at C. Hold it otherwise —
// it stays pending and, once bar_index_ advances past its creation
// bar, becomes an ordinary resting order (a market cascade rolls to
// the next-bar open; a priced one resumes standard semantics). The
// magnifier path (bar_magnifier_enabled_) owns its own tick model
// and is scoped out.
// born in a MID-BAR fill recalc ("cascade" order) has restricted
// same-bar reach. The magnifier path (bar_magnifier_enabled_) owns
// its own tick model and is scoped out.
coof_cascade_force_wp_gap_ = false;
if (!bar_magnifier_enabled_ && coof_scheduler_active_
&& order.coof_born_mid_bar
&& order.created_bar == bar_index_
&& !coof_at_extreme_waypoint_) {
continue;
&& order.created_bar == bar_index_) {
// Model S governs only PRICED (stop/limit, non-trail)
// strategy.exit cascade orders — the class the probe pinned.
// Opposing raw strategy.order brackets, market exits/closes and
// trailing exits keep the plain PR#95 extreme-waypoint reach
// alongside entries (a market cascade fills at the next extreme
// or rolls).
const bool priced_exit =
order.type == OrderType::EXIT
&& (!std::isnan(order.stop_price)
|| !std::isnan(order.limit_price))
&& std::isnan(order.trail_points)
&& std::isnan(order.trail_price);
if (!priced_exit) {
// ENTRY / market-close / trailing cascade order: eligible
// ONLY at the remaining extreme waypoints (W1/W2); never
// intra-segment, never at C. Held otherwise, converting to an
// ordinary resting order once bar_index_ advances past its
// creation bar.
if (!coof_at_extreme_waypoint_) continue;
} else {
// EXIT cascade order (KI-67 Model S "R-cascade-gapjump").
// seg_i is the in-flight leg the triggering fill landed on.
// Hold the order on that leg's remainder; gap-fill it at the
// leg-end waypoint POINT iff its level is in the in-flight
// remainder (coof_cascade_inflight_fires); EXACT-level fill it
// on every SUBSEQUENT leg's segment. A terminal in-flight leg
// (seg_i == 2) or an off-path fill (seg_i < 0) rolls.
const int si = order.coof_cascade_seg_i;
bool admit = false;
if (si >= 0) {
if (coof_hist_is_segment_) {
admit = coof_hist_path_index_ > si;
} else if (coof_hist_path_index_ == si + 1 && si < 2
&& order.coof_cascade_inflight_fires) {
admit = true;
coof_cascade_force_wp_gap_ = true;
}
}
if (!admit) continue;
}
}

auto fill = evaluate_fill_price(
order, i, bar, opposing_pass, trail_best_path_state,
pass0_opposing_skip_ids);
coof_cascade_force_wp_gap_ = false;
if (fill.kind != FillEvaluation::Kind::Fill) continue;

double path_position = 0.0;
Expand Down Expand Up @@ -2371,7 +2406,8 @@ BacktestEngine::FillEvaluation BacktestEngine::evaluate_fill_price(
trail_best_path_state,
is_entry_bar,
bar_magnifier_enabled_,
syminfo_mintick_);
syminfo_mintick_,
coof_cascade_force_wp_gap_);
if (exit_fill.should_fill) {
fill_price = exit_fill.fill_price;
should_fill = true;
Expand Down
16 changes: 15 additions & 1 deletion src/engine_internal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,21 @@ ExitPathFill resolve_exit_path_fill(const Bar& bar,
double trail_best_start,
bool is_entry_bar,
bool magnifier_active,
double syminfo_mintick);
double syminfo_mintick,
bool cascade_wp_gap = false);


// KI-67 exit cascade (Model S "R-cascade-gapjump"). Given the in-flight LEG
// index seg_i (0 = O->W1, 1 = W1->W2, 2 = W2->C; supplied by the dispatch loop's
// real cursor position), the full script bar, and the recalc price ap the
// triggering fill landed on, returns true when the exit level lies inside the
// in-flight remainder (ap -> leg-end waypoint W0 = path[seg_i+1]) in the trigger
// direction on a NON-terminal leg — i.e. it gap-fills same-bar at W0 (limits
// fill better than the level, stops worse). False for a terminal (seg_i>=2) or
// off-path (seg_i<0) leg, and whenever the level is not swept in the remainder.
bool cascade_exit_inflight_fires(const Bar& bar, double ap, int seg_i,
PositionSide position_side,
double stop_price, double limit_price);


// ── Lower-TF emulation helpers (defined in engine_lower_tf.cpp) ──
Expand Down
45 changes: 43 additions & 2 deletions src/engine_path_resolve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,8 @@ ExitPathFill resolve_exit_path_fill(const Bar& bar,
double trail_best_start,
bool is_entry_bar,
bool magnifier_active,
double syminfo_mintick) {
double syminfo_mintick,
bool cascade_wp_gap) {
ExitPathFill fill;
if (position_side == PositionSide::FLAT) return fill;

Expand All @@ -817,7 +818,12 @@ ExitPathFill resolve_exit_path_fill(const Bar& bar,
// shortcut on the entry bar in magnifier mode only; the wrong-side
// eligibility skip in classify_order_eligibility still gates the non-
// magnifier path against bogus na-arithmetic stops.
if (!is_entry_bar || magnifier_active) {
// cascade_wp_gap is the KI-67 exit-cascade in-flight leg-end waypoint POINT
// (a degenerate O=H=L=C=W bar). The Model S gate has already established that
// the exit level lies in the in-flight remainder in the trigger direction, so
// the order gap-fills at that waypoint price — force the open-gap shortcut on
// even though entry and exit share this bar (is_entry_bar).
if (!is_entry_bar || magnifier_active || cascade_wp_gap) {
if (try_exit_open_gap_fill(bar, is_long, has_stop, stop_price,
has_limit, limit_price, trail, &fill)) {
return fill;
Expand Down Expand Up @@ -857,5 +863,40 @@ ExitPathFill resolve_exit_path_fill(const Bar& bar,
}


bool cascade_exit_inflight_fires(const Bar& bar, double ap, int seg_i,
PositionSide position_side,
double stop_price, double limit_price) {
// A terminal (seg_i == 2 -> C) or off-path (seg_i < 0) in-flight leg never
// gap-fills same-bar; only the two extreme-ending legs (O->W1, W1->W2) do.
if (seg_i < 0 || seg_i >= 2) return false;

double path[4];
fill_bar_path_points(bar, path);

const bool is_long = (position_side == PositionSide::LONG);
// W0 is the in-flight leg-end waypoint; the in-flight remainder is ap -> W0
// (a fill AT the leg's start waypoint makes ap == path[seg_i], sweeping the
// whole leg). A limit fires on the with-cursor-direction leg (better than the
// level); a stop would need the opposite direction, so it can only trigger on
// a later (reversed) leg — the general form is kept for brackets.
const double W0 = path[seg_i + 1];
const bool goes_up = W0 >= ap;
const bool has_stop = !std::isnan(stop_price);
const bool has_limit = !std::isnan(limit_price);
bool trig = false;
if (has_stop) {
trig = trig
|| (is_long && !goes_up && W0 <= stop_price && stop_price <= ap)
|| (!is_long && goes_up && ap <= stop_price && stop_price <= W0);
}
if (has_limit) {
trig = trig
|| (is_long && goes_up && ap < limit_price && limit_price <= W0)
|| (!is_long && !goes_up && W0 <= limit_price && limit_price < ap);
}
return trig;
}


} // namespace internal
} // namespace pineforge
44 changes: 43 additions & 1 deletion src/engine_run.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() {
coof_cursor_is_bar_close_ = false;
coof_evaluating_path_segment_ = false;
coof_at_extreme_waypoint_ = false;
coof_hist_is_segment_ = false;
coof_hist_path_index_ = -1;
coof_cascade_recalc_leg_ = -1;
coof_cascade_force_wp_gap_ = false;

double path[4];
fill_bar_path_points(script_bar, path);
Expand Down Expand Up @@ -264,12 +268,20 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() {
// not admit them.
coof_at_extreme_waypoint_ =
(next_waypoint == 2 || next_waypoint == 3);
// KI-67 exit cascade: publish this POINT's path index (cursor ==
// path[next_waypoint-1]) for the strategy.exit cascade gate.
coof_hist_is_segment_ = false;
coof_hist_path_index_ = next_waypoint - 1;
const Bar point = coof_point_bar(script_bar, cursor);
current_bar_ = point;
CoofFillResult fill = process_next_pending_order(
point, /*allow_market_orders=*/true,
exit_closed_from_bar, exit_closed_was_long);
if (fill.filled) {
// A fill at this POINT (cursor == path[next_waypoint-1]) puts the
// in-flight leg at path[next_waypoint-1] -> path[next_waypoint],
// i.e. leg (next_waypoint-1) — the leg the loop traverses next.
coof_cascade_recalc_leg_ = next_waypoint - 1;
consume_fill(
fill, cursor_is_close,
/*filled_at_bar_open_point=*/next_waypoint == 1);
Expand All @@ -284,8 +296,14 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() {
const Bar segment = coof_segment_bar(script_bar, cursor, target);
current_bar_ = segment;
coof_evaluating_path_segment_ = true;
// No intra-segment exact-level fills for cascade orders.
// No intra-segment exact-level fills for ENTRY cascade orders. EXIT
// cascade orders exact-fill on SUBSEQUENT legs (leg index > seg_i); the
// gate uses the published leg index below to distinguish them.
coof_at_extreme_waypoint_ = false;
// KI-67 exit cascade: publish this SEGMENT's leg index
// (path[next_waypoint-1] -> path[next_waypoint]).
coof_hist_is_segment_ = true;
coof_hist_path_index_ = next_waypoint - 1;
CoofFillResult fill = process_next_pending_order(
segment, /*allow_market_orders=*/false,
exit_closed_from_bar, exit_closed_was_long);
Expand All @@ -295,6 +313,11 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() {
std::abs(fill.fill_price - target) <= kSegmentDenomEps;
const bool cursor_is_close = next_waypoint == 3
&& reached_target;
// A fill mid-leg leaves the in-flight leg at (next_waypoint-1); a fill
// that reaches the leg-end waypoint (path[next_waypoint]) advances to
// the NEXT leg (next_waypoint) — the loop's ++next_waypoint below.
coof_cascade_recalc_leg_ =
reached_target ? next_waypoint : (next_waypoint - 1);
consume_fill(
fill, cursor_is_close,
/*filled_at_bar_open_point=*/false);
Expand All @@ -312,7 +335,14 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() {

// Past the extreme waypoints: neither the ordinary close execution nor the
// POOC-C / margin passes admit cascade orders (they hold to the next bar).
// Publishing the C waypoint (index 3) also holds EXIT cascade orders there:
// a terminal in-flight leg never gap-fills, and no leg is "subsequent" to C.
coof_at_extreme_waypoint_ = false;
coof_hist_is_segment_ = false;
coof_hist_path_index_ = 3;
// No in-flight leg remains: any exit placed by the ordinary-close / POOC-C /
// margin recalcs is terminal and rolls.
coof_cascade_recalc_leg_ = -1;

// The regular historical close execution is still required after all
// fill-triggered executions. It starts from the prior committed checkpoint
Expand Down Expand Up @@ -373,6 +403,10 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() {
coof_cursor_is_bar_close_ = false;
coof_evaluating_path_segment_ = false;
coof_at_extreme_waypoint_ = false;
coof_hist_is_segment_ = false;
coof_hist_path_index_ = -1;
coof_cascade_recalc_leg_ = -1;
coof_cascade_force_wp_gap_ = false;
coof_direct_fill_events_remaining_ = 0;
coof_checkpoint_contains_current_bar_ = false;
history_slot_is_new_ = true;
Expand Down Expand Up @@ -444,6 +478,10 @@ void BacktestEngine::reset_run_state() {
coof_cursor_is_bar_close_ = false;
coof_evaluating_path_segment_ = false;
coof_at_extreme_waypoint_ = false;
coof_hist_is_segment_ = false;
coof_hist_path_index_ = -1;
coof_cascade_recalc_leg_ = -1;
coof_cascade_force_wp_gap_ = false;
coof_cursor_price_ = std::numeric_limits<double>::quiet_NaN();
coof_direct_fill_events_remaining_ = 0;
coof_checkpoint_contains_current_bar_ = false;
Expand Down Expand Up @@ -903,6 +941,10 @@ void BacktestEngine::run_magnified_bar_calc_on_order_fills(
coof_cursor_is_bar_close_ = false;
coof_evaluating_path_segment_ = false;
coof_at_extreme_waypoint_ = false;
coof_hist_is_segment_ = false;
coof_hist_path_index_ = -1;
coof_cascade_recalc_leg_ = -1;
coof_cascade_force_wp_gap_ = false;
coof_direct_fill_events_remaining_ = 0;
coof_checkpoint_contains_current_bar_ = false;
history_slot_is_new_ = true;
Expand Down
Loading
Loading