Skip to content

fix(oca): REDUCE reduces sibling qty by filled amount - #5

Merged
luisleo526 merged 2 commits into
mainfrom
fix/oca-reduce-proportional
May 11, 2026
Merged

luisleo526 merged 2 commits into
mainfrom
fix/oca-reduce-proportional

Conversation

@luisleo526

Copy link
Copy Markdown
Collaborator

Summary

  • Fix Pine v6 strategy.oca.reduce semantics — when one sibling fills qty Q, other siblings now have their qty REDUCED by Q (matching TV) instead of being cancelled outright (which is strategy.oca.cancel behaviour).
  • New reduce_oca_group helper in engine_orders.cpp; apply_filled_order_to_state computes filled_qty from signed position delta and dispatches to either cancel_oca_group (type 1) or reduce_oca_group (type 2). Default-sized (qty == NaN) siblings still cancel since per-order qty is unknown at place time.
  • Add tests/test_strategy_oca.cpp (5 cases / 14 assertions) covering REDUCE partial 2/3-sibling, full-fill cascade, plus CANCEL/NONE no-regression sanity.

Parity

  • Corpus excellent: 168 -> 176 (+8). 9 promotions to excellent (validation probes 52/63/72/93/95/96, mtf-probe-08/09, parity-probe-04, typed-matrix-probe-01).
  • 2 demotions (community/IES, validation_lower_tf/lower-tf-probe-02-bool-array) — neither uses OCA, likely unrelated noise.
  • 1 OCA strategy (oca-three-way-probe-02-multi-group-partial) windowed-out (no-compare-trades-interior); engine emits 5 trades vs 0 in window — REDUCE shifted trade timing as expected.

Test plan

  • ctest 23/23 pass (was 22, +1 new)
  • test_strategy_oca 14/14 assertions pass
  • Full corpus parity sweep (197 strategies, 15m OHLCV)

Pine v6 strategy.oca.reduce semantics: when one sibling fills qty Q, the
remaining siblings have their qty reduced by Q rather than being
cancelled outright (which is strategy.oca.cancel behaviour). The prior
implementation routed both oca_type==1 (CANCEL) and oca_type==2 (REDUCE)
through cancel_oca_group, wiping every other sibling on first fill.

Add reduce_oca_group helper that decrements each sibling's qty by the
just-filled qty (computed from |signed_pos_after - signed_pos_before|),
removing siblings whose qty falls to <= 0. Default-sized siblings
(qty == NaN) cannot have a meaningful per-order qty applied at place
time, so we conservatively cancel them — matching the prior behaviour
for that subset.

Corpus parity: 168 -> 176 excellent (+8), driven by validation probes
52/63/72/93/95/96 and several MTF/typed-matrix probes flipping from
strong/moderate to excellent.
Add tests/test_strategy_oca.cpp covering:
- 2-sibling REDUCE partial: A fills qty 3 -> B's qty 5 -> 2
- 3-sibling REDUCE: A fills qty 2 -> B,C qty 4 -> 2
- REDUCE full-fill cascade: filled qty >= sibling qty drops sibling to 0
- CANCEL unchanged: full sibling wipe on first fill
- NONE unchanged: siblings independent, no auto action

Uses a BacktestEngine subclass that snapshots pending_orders_ each bar
to read remaining qty without needing a public accessor.
@luisleo526
luisleo526 merged commit 4327da5 into main May 11, 2026
luisleo526 added a commit that referenced this pull request Jun 10, 2026
…it-fix sweep

- pine_v6_audit_master: mark #5/#6/#31/#32/#35 and ~14 minors [RESOLVED
  2026-06-10] against codegen 974cda7 (UNSUPPORTED_CONST_NAMESPACES,
  chart.bg/fg_color, bare ta.<name> reads, export, derived syminfo warning
  set, str.replace/timestamp//=/%=/string()/int()/openprofit_percent/
  country-GB/TZ/round_to_mintick/array fixes); headline 1 critical + ~40
  minor; next steps now point at first_index, size() int typing, O4/O5/O7,
  and corpus probe gaps
- pine_v6_coverage_detail: surgical row refresh (varip/footprint/
  volume_row/color()/color.from_gradient/max_bars_back/
  timeframe.from_seconds/indicator()/array.new_* drawing types ->
  hard-reject; syminfo metadata -> get_syminfo_metadata; prefix derived;
  tz-aware bare time vars; input.source/color/time backings); bucket
  totals re-counted by delta (181/228/216/175/141) with counting-basis
  footnote; staleness banner replaced by 2026-06-10 reconciliation note
- corpus: bump submodule for README category-table reconciliation
  (ta=61, mtf=16, input=2; per-row report sums)

Co-authored-by: Cursor <[email protected]>
@luisleo526
luisleo526 deleted the fix/oca-reduce-proportional branch July 2, 2026 04:28
luisleo526 added a commit that referenced this pull request Sep 24, 2026
… new/delete form through INT17b's one allocator

Four slice-B rows defined their own partial set of the replaceable global
allocation functions to count heap allocations -- the pattern INT17b found
aborting under the Linux sanitizers job (alloc-dealloc-mismatch: libstdc++'s
temporary buffers take the NOTHROW operator new, left as ASan's, and give it
back through a SIZED operator delete the test had routed to free()):

  test_native_intrabar_scratch      PERF-L1  plain/array/nothrow new, plain/sized delete
  test_adapter_quiet_bar (+ its     PERF-L4  plain/array new, plain/sized delete
    _probe and _ungated builds)
  test_adapter_command_allocations  PERF-L5  plain/array new, plain/sized delete
  test_native_match_allocations     PERF-L5  plain/array new, plain/sized delete

Each now includes tests/global_allocation_replacement.hpp (all twenty forms,
one malloc/free allocator) instead:

  * intrabar_scratch, command_allocations, match_allocations take the
    difference of global_allocation::allocations around the span they
    counted before (the run; the one re-issued strategy.exit call; the
    window between script bars kFirstCounted and kLastCounted), the
    style of INT17b's counted conversions; their on/off flag had no other
    reader and is gone.
  * quiet_bar keeps its body: `allocations` is now a reference to the
    shared count (the _l4d twin's precedent), reset where it was reset,
    and `count_allocations` still says whether a bar records its count.
    Every recorded span lies inside the run it was counted over, so each
    per-bar entry is the count it was.

Nothing under src/ or include/ changes (engine code has no over-aligned
allocation, so the aligned forms add nothing to a count). The four rows
print exactly what they printed before the change, line for line, on the
Mac release build: intrabar 0.003-0.045 allocations/bar per path, quiet
bars 4 per stretch (nothing live: 4) magnifier off and on, the re-issued
exit 301 allocations over 42 calls, the matcher 0 per window for every
book. No assertion, bound or pinned value changes.

Fail-before, the sanitizers profile on spark (Linux aarch64, GCC 13.3,
ci_verify's own configure argv, the job's ASAN_OPTIONS/UBSAN_OPTIONS plus
detect_stack_use_after_return=0): the four TUs as the lanes wrote them,
compiled with the sanitizer build's own compile and link commands --

  test_adapter_quiet_bar            aborts: AddressSanitizer:
  test_adapter_command_allocations    alloc-dealloc-mismatch (operator new vs free)
      #1 operator delete(void*, unsigned long)  <the test's own sized delete>
      #2 std::__detail::__return_temporary_buffer<PendingSameBarCommand>
      #5 std::stable_sort  in PineExecutionAdapter::flush_pending_same_bar_commands
      allocated by: operator new(unsigned long, std::nothrow_t const&) (asan)
  test_native_intrabar_scratch      passes (8 passed)
  test_native_match_allocations     passes (ok)

-- the mechanism INT17b fixed, so both adapter rows would have failed CI's
sanitizers job; the two kernel rows are converted so no counting test is
left with a partial set. After: all four pass there. A grep of tests/ for
global operator new/delete definitions now finds only the helper and the
uncompiled legacy base tests/test_pending_order_mirror_no_alloc.cpp that
INT17b left as it is.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant