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
11 changes: 10 additions & 1 deletion include/pineforge/engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1843,6 +1843,9 @@ class BacktestEngine {
// adverse leg before one script recalculation. Broker fills remain
// individually counted by fill_events and broker_fill_event_seq_.
bool grouped_stop_recalc = false;
// Physical lot opened by this actual fresh COOF MARKET fill. Only
// its first callback may use this provenance; direct later fills do not.
uint64_t market_entry_incarnation = 0;
};
CoofFillResult process_next_pending_order(const Bar& bar,
bool allow_market_orders,
Expand Down Expand Up @@ -2893,6 +2896,11 @@ class BacktestEngine {
// lets strategy.exit apply the one pinned exception: a marketable LIMIT may
// resume at W1, while marketable STOP suppression remains whole-entry-bar.
bool coof_recalc_after_first_open_fill_ = false;
// Round15: identify the MARKET opening whose first callback is active.
// A direct close/partial/reentry in that body changes the serial and must
// not inherit the original fill's permission to arm a recrossing limit.
uint64_t coof_market_entry_recalc_incarnation_ = 0;
uint64_t coof_market_entry_recalc_fill_seq_ = 0;
// KI-67: true only during a point-bar evaluation that sits AT an extreme
// waypoint (W1 or W2) of the historical 4-tick path. Cascade orders born
// this bar may fill only while this holds; on segments, at O, at C, and on
Expand Down Expand Up @@ -4064,7 +4072,8 @@ class BacktestEngine {
uint64_t triggering_events,
uint64_t max_events,
uint64_t events_already,
bool grouped_stop_recalc = false);
bool grouped_stop_recalc = false,
uint64_t market_entry_incarnation = 0);
void run_simple_bar_loop(const Bar* input_bars, int n_input);
void run_aggregation_bar_loop(const Bar* input_bars, int n_input,
bool bar_magnifier, int expected_script_bars);
Expand Down
13 changes: 13 additions & 0 deletions src/engine_fills.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,14 @@ BacktestEngine::CoofFillResult BacktestEngine::process_next_pending_order(
const PositionSide side_before_fill = position_side_;
const uint64_t events_before = broker_fill_event_seq_;
const double realized_before_fill = net_profit_sum_;
// Capture before the fill kernel: other order kinds may erase OCA
// siblings, invalidating references into the pending-order vector.
const bool fresh_coof_market_entry =
order.type == OrderType::MARKET
&& order.created_during_coof_recalc
&& order.created_bar == bar_index_
&& side_before_fill == PositionSide::FLAT;
const uint64_t opening_incarnation = order.incarnation;
apply_filled_order_to_state(
order, candidate.order_index, candidate.fill.fill_price,
candidate.fill.is_limit_fill, bar,
Expand Down Expand Up @@ -649,6 +657,11 @@ BacktestEngine::CoofFillResult BacktestEngine::process_next_pending_order(
result.fill_events = grouped_fill_events;
result.chart_waypoint_price = candidate.chart_waypoint_price;
result.grouped_stop_recalc = group_resting_stops && grouped_fills > 1;
if (fresh_coof_market_entry && produced == 1
&& position_side_ == PositionSide::LONG
&& pyramid_entries_.size() == 1
&& pyramid_entries_.front().entry_incarnation == opening_incarnation)
result.market_entry_incarnation = opening_incarnation;
// No callbacks, new orders or OCA erasures can occur in the
// proven group. Keep indices stable until its last existing
// candidate has passed through the ordinary fill kernel.
Expand Down
20 changes: 18 additions & 2 deletions src/engine_run.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,8 @@ uint64_t BacktestEngine::execute_coof_script_body(
coof_fill_recalc_active_ = false;
coof_recalc_at_bar_open_ = false;
coof_recalc_after_first_open_fill_ = false;
coof_market_entry_recalc_incarnation_ = 0;
coof_market_entry_recalc_fill_seq_ = 0;
coof_direct_fill_events_remaining_ = 0;
return broker_fill_event_seq_ - before;
}
Expand All @@ -329,7 +331,8 @@ uint64_t BacktestEngine::run_coof_recalc_chain(
uint64_t triggering_events,
uint64_t max_events,
uint64_t events_already,
bool grouped_stop_recalc) {
bool grouped_stop_recalc,
uint64_t market_entry_incarnation) {
uint64_t total_events = triggering_events;
uint64_t pending_recalcs = grouped_stop_recalc ? 1 : triggering_events;
uint64_t handled = 0;
Expand All @@ -346,6 +349,9 @@ uint64_t BacktestEngine::run_coof_recalc_chain(
recalc_at_bar_open && events_already == 0 && handled == 1;
coof_recalc_after_first_open_fill_ =
recalc_at_bar_open && !first_open_fill_recalc;
coof_market_entry_recalc_incarnation_ =
handled == 1 ? market_entry_incarnation : 0;
coof_market_entry_recalc_fill_seq_ = broker_fill_event_seq_;
const uint64_t direct = execute_coof_script_body(
script_bar, broker_cursor_price, cursor_is_bar_point,
/*is_fill_recalc=*/true,
Expand Down Expand Up @@ -409,6 +415,8 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() {
coof_cascade_recalc_leg_ = -1;
coof_cascade_force_wp_gap_ = false;
coof_recalc_after_first_open_fill_ = false;
coof_market_entry_recalc_incarnation_ = 0;
coof_market_entry_recalc_fill_seq_ = 0;

double path[4];
fill_bar_path_points(script_bar, path);
Expand Down Expand Up @@ -439,7 +447,7 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() {
script_bar, fill.fill_price, /*cursor_is_bar_point=*/false, cursor_is_close,
filled_at_bar_open_point,
fill.fill_events, kNoFillEventBudget, fill_events,
fill.grouped_stop_recalc);
fill.grouped_stop_recalc, fill.market_entry_incarnation);
// The carried order's open fill triggers one execution at O, and the
// order born in that first execution may also fill at O. Every later
// fill—including the first fill when it occurs inside a path segment—
Expand Down Expand Up @@ -608,6 +616,8 @@ void BacktestEngine::dispatch_bar_calc_on_order_fills() {
coof_fill_recalc_active_ = false;
coof_recalc_at_bar_open_ = false;
coof_recalc_after_first_open_fill_ = false;
coof_market_entry_recalc_incarnation_ = 0;
coof_market_entry_recalc_fill_seq_ = 0;
coof_cursor_is_bar_close_ = false;
coof_evaluating_path_segment_ = false;
coof_at_extreme_waypoint_ = false;
Expand Down Expand Up @@ -712,6 +722,8 @@ void BacktestEngine::reset_run_state() {
coof_fill_recalc_active_ = false;
coof_recalc_at_bar_open_ = false;
coof_recalc_after_first_open_fill_ = false;
coof_market_entry_recalc_incarnation_ = 0;
coof_market_entry_recalc_fill_seq_ = 0;
coof_cursor_is_bar_close_ = false;
coof_evaluating_path_segment_ = false;
coof_at_extreme_waypoint_ = false;
Expand Down Expand Up @@ -1120,6 +1132,8 @@ void BacktestEngine::run_magnified_bar_calc_on_order_fills(
coof_cursor_is_bar_close_ = false;
coof_evaluating_path_segment_ = false;
coof_recalc_after_first_open_fill_ = false;
coof_market_entry_recalc_incarnation_ = 0;
coof_market_entry_recalc_fill_seq_ = 0;

double cursor = ticks.front().price;
bool cursor_is_bar_point = true; // finding-446, see the simple loop
Expand Down Expand Up @@ -1257,6 +1271,8 @@ void BacktestEngine::run_magnified_bar_calc_on_order_fills(
coof_fill_recalc_active_ = false;
coof_recalc_at_bar_open_ = false;
coof_recalc_after_first_open_fill_ = false;
coof_market_entry_recalc_incarnation_ = 0;
coof_market_entry_recalc_fill_seq_ = 0;
coof_cursor_is_bar_close_ = false;
coof_evaluating_path_segment_ = false;
coof_at_extreme_waypoint_ = false;
Expand Down
37 changes: 36 additions & 1 deletion src/engine_strategy_commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2234,11 +2234,46 @@ void BacktestEngine::strategy_exit(const std::string& id, const std::string& fro
later_same_open_marketable_limit =
later_same_open_priced_exit_on_entry_bar
&& limit_marketable_at_coof_cursor;
// Round15 F/EUR JOAT pins: a fresh MARKET long at W1=H arms a
// marketable full limit, waits through H->L, and takes its later L->C
// recross at the exact level. It neither fills at placement nor gets
// a new waypoint-gap permission. Keep KI-67's existing leg gate.
// This first patch covers only a limit strictly inside H/L with no
// reachable stop competitor and the actual opening's first callback.
const bool first_high_market_limit_recross =
!bar_magnifier_enabled_ && !process_orders_on_close_
&& !stream_warmup_mode_ && stream_phase_ == StreamPhase::IDLE
&& order.coof_born_mid_bar && !coof_hist_is_segment_
&& coof_at_extreme_waypoint_ && coof_hist_path_index_ == 1
&& coof_cascade_recalc_leg_ == 1
&& coof_market_entry_recalc_incarnation_ != 0
&& coof_market_entry_recalc_fill_seq_ == broker_fill_event_seq_
&& position_side_ == PositionSide::LONG
&& position_entry_count_ == 1 && pyramiding_ == 0
&& pyramid_entries_.size() == 1
&& pyramid_entries_.front().entry_incarnation
== coof_market_entry_recalc_incarnation_
&& !from_entry.empty()
&& from_entry == pyramid_entries_.front().entry_id
&& !is_partial && std::isfinite(reserved_qty)
&& std::abs(reserved_qty - position_qty_) <= kQtyEpsilon
&& pending_orders_.empty() && oca_name.empty()
&& !has_trail_request && slippage_ == 0
&& syminfo_.pointvalue == 1 && account_currency_fx_ == 1
&& account_currency_fx_timestamps_.empty()
&& limit_marketable_at_coof_cursor
&& internal::bar_path_uses_high_first(current_bar_)
&& coof_cursor_price_ == bar_fill_price(current_bar_.high)
&& current_bar_.low < order.limit_price
&& order.limit_price < current_bar_.high
&& (std::isnan(order.stop_price)
|| order.stop_price < current_bar_.low);
order.coof_suppress_stop_on_entry_bar =
stop_marketable_at_coof_cursor;
order.coof_suppress_limit_on_entry_bar =
limit_marketable_at_coof_cursor
&& !later_same_open_marketable_limit;
&& !later_same_open_marketable_limit
&& !first_high_market_limit_recross;
}
// KI-67 exit cascade (Model S). Record this mid-bar cascade exit's in-flight
// leg so the historical dispatch gate can hold it on that leg's remainder,
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ set(TEST_SOURCES
test_coof_cascade_eligibility
test_coof_open_recalc_context
test_cascade_exit_gapjump
test_coof_market_limit_recross
test_pooc_position_visibility
test_prearmed_exit_path_cursor
test_prearmed_market_parent_gap_exit
Expand Down
174 changes: 174 additions & 0 deletions tests/test_coof_market_limit_recross.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Round15 JOAT: five covered TradingView panels from r14-joat-audit.
// F carry/fresh CSV 5eff7824, high control 2308af1a; EUR carry f0cce2d5,
// high control 69197a4b. Six synthetic bars retain the two relevant OHLC
// legs and explicit quantities, without loading or replaying a strategy/feed.
// A MARKET reentry at W1=H may place a marketable full long limit. It waits
// through H->L, then fills at the limit on the L->C recross. An earlier
// terminal-W2 entry (F) or a final leg that cannot recross (EUR) still rolls.
#include <cmath>
#include <cstdio>
#include <limits>
#include <vector>
#include <pineforge/bar.hpp>
#include <pineforge/engine.hpp>

using namespace pineforge;
static int passed = 0, failed = 0;
#define CHECK(x) do { if (x) ++passed; else { \
++failed; std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #x); \
} } while (0)

namespace {
constexpr double N = std::numeric_limits<double>::quiet_NaN();
bool near(double a, double b) { return std::abs(a-b) < 1e-7; }
enum class Guard { None, RawParent, PricedParent, CompetingOrder,
DirectPartial, ReachableStop, LimitBelowLow };

class RecrossProbe : public BacktestEngine {
public:
RecrossProbe(bool eur, bool high = false, bool fresh = false,
Guard guard = Guard::None)
: eur_(eur), high_(high), fresh_(fresh), guard_(guard) {
initial_capital_ = 100000;
pyramiding_ = 0;
margin_long_ = margin_short_ = 100;
qty_step_ = eur ? .01 : 1;
syminfo_.pointvalue = 1;
set_syminfo_mintick(eur ? .00001 : .01);
commission_type_ = CommissionType::PERCENT;
commission_value_ = .01;
slippage_ = 0;
calc_on_order_fills_ = true;
process_orders_on_close_ = false;
}
void on_bar(const Bar&) override {
if (bar_index_ == 0 && position_side_ == PositionSide::FLAT
&& trade_count() == 0)
strategy_entry("L", true, N, N, eur_ ? 8389.91 : 840, "OLD");
if (bar_index_ == 2 && position_side_ == PositionSide::FLAT
&& trade_count() == 1)
strategy_entry("L", true, N, N, eur_ ? 8371.05 : 833, "REENTRY1");
if (bar_index_ == 3 && position_side_ == PositionSide::FLAT
&& trade_count() == 2) {
if (guard_ == Guard::RawParent)
strategy_order("L", true, 832);
else
strategy_entry("L", true, N,
guard_ == Guard::PricedParent ? 12.04 : N,
eur_ ? 8369.44 : 832, "REENTRY2");
}
if (position_side_ == PositionSide::LONG) {
const bool last = trade_count() >= 2;
if (last && bar_index_ == 3 && guard_ == Guard::CompetingOrder)
strategy_order("Far", false, 1, 20.0);
if (last && trade_count() == 2 && bar_index_ == 3
&& guard_ == Guard::DirectPartial)
strategy_close("L", "PARTIAL", 1, N, true);
const double target = last && guard_ == Guard::LimitBelowLow ? 11.95
: last && high_ ? (eur_ ? 1.175 : 12.20)
: (eur_ ? 1.173199565095035 : 12.011758862989522);
strategy_exit(last && fresh_ ? "FreshRisk" : "Risk", "L",
target, last && guard_ == Guard::ReachableStop ? 12.00
: (eur_ ? 1.168520271815603 : 11.854525710631547),
N, N, N, 100, "TP");
}
if (bar_index_ == 4) strategy_close("", "END");
}
uint64_t fills() const { return broker_fill_event_seq_; }
private:
bool eur_, high_, fresh_;
Guard guard_;
};

std::vector<Bar> bars(bool eur) {
if (eur) return {
{1.1703,1.1703,1.1703,1.1703,1,1000},
{1.17033,1.171,1.1702,1.1705,1,2000},
{1.17284,1.17322,1.17242,1.17318,1,3000},
{1.17316,1.17342,1.1728,1.1734,1,4000},
{1.1734,1.17461,1.17334,1.17454,1,5000},
{1.17456,1.17508,1.17418,1.17418,1,6000},
};
return {
{11.9,11.9,11.9,11.9,1,1000},
{11.92,11.93,11.91,11.92,1,2000},
{11.99,12.03,11.975,12.01,1,3000},
{12.005,12.04,11.965,12.035,1,4000},
{12.035,12.05,12.02,12.045,1,5000},
{12.045,12.06,12.01,12.015,1,6000},
};
}

void check_panel(bool eur, bool high = false, bool fresh = false) {
RecrossProbe p(eur, high, fresh);
const auto b = bars(eur);
for (int repeat = 0; repeat < 2; ++repeat) {
p.run(b.data(), b.size());
CHECK(p.last_error().empty());
CHECK(p.trade_count() == 3);
CHECK(p.fills() == 6);
if (p.trade_count() != 3) continue;
const auto& first = p.get_trade(0);
const auto& second = p.get_trade(1);
const auto& last = p.get_trade(2);
CHECK(first.entry_bar_index == 1 && first.exit_bar_index == 2);
CHECK(second.entry_bar_index == 2 && second.exit_bar_index == 3);
CHECK(near(first.entry_price, eur ? 1.17033 : 11.92));
CHECK(near(first.exit_price, eur ? 1.1732 : 12.02));
CHECK(near(second.entry_price, eur ? 1.17322 : 12.03));
CHECK(near(second.exit_price, eur ? 1.1732 : 12.02));
CHECK(near(last.entry_price, eur ? 1.17342 : 12.04));
CHECK(last.entry_bar_index == 3);
CHECK(near(last.qty, eur ? 8369.44 : 832));
const double exit = high ? (eur ? 1.17456 : 12.05)
: (eur ? 1.1732 : 12.02);
CHECK(last.exit_bar_index == (high ? 5 : 3));
CHECK(near(last.exit_price, exit));
CHECK(last.exit_comment == (high ? "END" : "TP"));
if (!high) CHECK(last.exit_id == (fresh ? "FreshRisk" : "Risk"));
CHECK(near(last.commission, (last.entry_price + exit) * last.qty * .0001));
CHECK(near(last.pnl, (exit - last.entry_price) * last.qty
- (last.entry_price + exit) * last.qty * .0001));
}
}

// These are scope guards checked against both the unchanged parent runtime
// and this candidate, not claims that the related unpinned TV shapes are fixed.
void check_guards() {
for (Guard g : {Guard::RawParent, Guard::PricedParent, Guard::CompetingOrder,
Guard::DirectPartial, Guard::ReachableStop, Guard::LimitBelowLow}) {
RecrossProbe p(false, false, false, g);
const auto b = bars(false);
p.run(b.data(), b.size());
CHECK(p.last_error().empty());
const bool partial = g == Guard::DirectPartial;
CHECK(p.trade_count() == (partial ? 4 : 3));
CHECK(p.fills() == (partial ? 7 : 6));
if (p.trade_count() != (partial ? 4 : 3)) continue;
const auto& last = p.get_trade(partial ? 3 : 2);
CHECK(last.entry_bar_index == 3);
CHECK(near(last.entry_price, 12.04));
CHECK(near(last.qty, partial ? 831 : 832));
CHECK(last.exit_bar_index == (g == Guard::ReachableStop ? 3 : 4));
CHECK(near(last.exit_price, g == Guard::ReachableStop ? 11.97 : 12.04));
if (partial) {
CHECK(near(p.get_trade(2).qty, 1));
CHECK(p.get_trade(2).exit_bar_index == 3);
CHECK(near(p.get_trade(2).exit_price, 12.04));
}
}
}
} // namespace

int main(int argc, char**) {
if (argc == 1) {
check_panel(false);
check_panel(false, false, true);
check_panel(false, true);
check_panel(true);
check_panel(true, true);
}
check_guards();
std::printf("%d passed, %d failed\n", passed, failed);
return failed ? 1 : 0;
}
Loading