Repository navigation
Cap a percent default quantity at 100 percent of equity with margins off - #816
Conversation
With margin_long and margin_short both 0, a strategy that declares default_qty_type = strategy.percent_of_equity with a value above 100 sized its default-quantity entries from the full declared percentage: 150 percent bought 150 percent of equity. For an entry from flat, without commission, TradingView sizes such an order at no more than 100 percent of equity when margins are off, for long and for short entries. With margin 1 it keeps the declared percentage and with margin 100 it refuses the order; both already behaved that way here and do not change. default_percent_of_equity() returns the declared percentage capped at 100 when the default quantity is a percent of equity and both margins are 0, and the declared value otherwise. default_sizing_cash() (both branches) and pooc_fee_units() take their money from it, so the money is capped before any commission is taken out of it. CASH and FIXED defaults, explicit quantities, typed per-order quantities and configurations with a non-zero margin are unchanged. tests/test_percent_equity_cap_margins_off.cpp runs twelve entries (long and short; margin 0 at 80, 100, 150 and 1000 percent; margin 1 and margin 100 at 150 percent) against a reference function; the four runs at 150 and 1000 percent with margins off fail without the change. Rows for a cash commission per order, a cash commission per contract, and a percentage commission under process_orders_on_close with a fractional quantity grid run each of long and short at 100 and at 150 percent: the two runs must give the same trades and the quantity must equal a reference function (these rows have no TradingView tape; the expectation follows the sizing rule stated in the code). Reverting either commission line fails those rows. Three pairs of runs (a same-side add followed by a close, a same-side add and a close in one bar, a reversal and a close on a fine quantity grid) at 100 and at 150 percent must give identical trades. tests/test_value_invalid_sizing_margin_source.cpp: the row that overflows a declared percentage of the largest double now runs at margin 100, because with both margins at 0 that declared percentage is capped at 100 and its product stays finite. Not covered: a configuration with only one margin at 0; entries that are not from flat (adds and reversals above 100 percent); commission paths against a TradingView tape; the branches that key on a declared value of exactly 100 (see the pairs above) beyond those three scripts. Refs #805. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
There was a problem hiding this comment.
Graphify reviewed this change.
Code graph out of date: Health delta baseline: last indexed commit f5e929d, 10 commit(s) behind this PR's base. Impact and hotspot numbers below may be wrong.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.
Not checked: tests were not run; formal verification proved 0 of 2 changed function(s) (2 not verified).
Formal verification. PR-changed functions: 0/2 verified (0 proven, 0 may-equivalent, 0 distinguished) · 2 not verified (2 unsupported).
Not verified on this run: default\_sizing\_cash (unsupported), pooc\_fee\_units (unsupported).
Graphify review — findings
Caps a percent-of-equity default quantity at 100% of equity when margin_long and margin_short are both 0, matching TradingView's sizing from flat. The new default_percent_of_equity helper applies the cap to the money before any commission is reserved out of it, and the default-sizing and pooc_fee_units paths all read it; with a margin enabled, the declared percentage passes through unchanged. A non-finite declared value goes through std::min and could slip past the cap as a 100% quantity, and the new test_percent_equity_cap_margins_off test is registered in CMake, so its coverage of the cap depends on that file compiling.
Worth a look
- New test file ends with an incomplete expression and cannot compile —
tests/test_percent_equity_cap_margins_off.cpp:412· Escalate · high- unverified hypothesis — survived the adversarial refute pass; needs human review
- Positive infinity is silently accepted as a 100% quantity —
src/source/pine_adapter.cpp:3253· Escalate · medium- unverified hypothesis — survived the adversarial refute pass; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1910 functions depend on the 1779 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
find()— 10 callers, 4 callees - new:
main()— 0 callers, 6 callees
Verification — 1910 functions in the blast radius were not formally verified this run (proofs are advisory here).
Health delta baseline: last indexed commit f5e929d, 10 commit(s) behind this PR's base.
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 1861 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
1401 of 1401 test file(s) selected (100%) via static blast radius.
Escalated to a full run for safety (
noncode-change,uncovered-code) — the selection is not trustworthy on its own (see below). CI should run the whole suite.
benchmarks/tests/test_aggregate.py— full-run-safetybenchmarks/tests/test_check_provenance.py— full-run-safetybenchmarks/tests/test_compare.py— full-run-safetybenchmarks/tests/test_public_names.py— full-run-safetybenchmarks/tests/test_run_all.py— full-run-safetybenchmarks/tests/test_run_strategy_feeds.py— full-run-safetydocker/pineforge_sink_finalize_test.py— full-run-safetydocker/pineforge_sink_replay_rq_test.py— full-run-safetydocker/pineforge_sink_replay_test.py— full-run-safetydocker/pineforge_sink_to_json_test.py— full-run-safetydocker/quota_columns_test.py— full-run-safetydocker/quota_convert_test.py— full-run-safetydocker/quota_finalize_test.py— full-run-safetydocker/replay_rq_engine_test.py— full-run-safetydocker/run_json_artifact_window_test.py— full-run-safetydocker/run_json_bin_test.py— full-run-safetydocker/run_json_bounded_transport_test.py— full-run-safetydocker/run_json_bulk_equity_fuzz_test.py— full-run-safetydocker/run_json_bulk_equity_test.py— full-run-safetydocker/run_json_codes_test.py— full-run-safetydocker/run_json_converter_metadata_test.py— full-run-safetydocker/run_json_converter_session_test.py— full-run-safetydocker/run_json_diagnostics_test.py— full-run-safetydocker/run_json_equity_scan_test.py— full-run-safetydocker/run_json_fused_test.py— full-run-safetydocker/run_json_harnesses_test.py— full-run-safetydocker/run_json_metrics_only_test.py— full-run-safetydocker/run_json_misc2_test.py— full-run-safetydocker/run_json_misc_test.py— full-run-safetydocker/run_json_pfbar_test.py— full-run-safetydocker/run_json_provenance_test.py— full-run-safetydocker/run_json_quota_supervisor_test.py— full-run-safetydocker/run_json_replay_test.py— full-run-safetydocker/run_json_selected_sink_test.py— full-run-safetydocker/run_json_sink_lexical_test.py— full-run-safetydocker/run_json_sink_summary_test.py— full-run-safetydocker/run_json_sink_test.py— full-run-safetydocker/run_json_sink_vectors_test.py— full-run-safetydocker/run_json_spool_work_test.py— full-run-safetydocker/run_json_stringio_test.py— full-run-safetydocker/run_json_symbol_feeds_test.py— full-run-safetydocker/run_json_syminfo_test.py— full-run-safetydocker/run_json_trade_scan_test.py— full-run-safetydocker/run_json_window_budget_test.py— full-run-safetydocker/run_json_wrapper_work_test.py— full-run-safetyinclude/pineforge/native_run_spec.hpp— full-run-safetyscripts/derive_corpus_feeds_self_test.py— full-run-safetyscripts/fingerprint_self_test.py— full-run-safetyscripts/run_corpus_self_test.py— full-run-safetyscripts/test_abi_receipt_skips.py— full-run-safety- … and 1351 more
non-code file(s) changed (
CHANGELOG.md,tests/CMakeLists.txt) → running the full suite for safety (a code graph can't see config/fixture/data deps)
changed code file(s) with no mapped test (
CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify default\_sizing\_cash.
The verifier did not have enough to check default\_sizing\_cash, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: unsupported: member function — receiver/self (`this`) not synthesizable from the simple-type literal pools — abstaining rather than guessing a receiver
Could not verify: Could not verify pooc\_fee\_units.
The verifier did not have enough to check pooc\_fee\_units, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: unsupported: member function — receiver/self (`this`) not synthesizable from the simple-type literal pools — abstaining rather than guessing a receiver
· 1 grounded finding(s) anchored inline below; 1 more finding(s) on lines outside this diff (see the check run).
|
|
||
| } // namespace | ||
|
|
||
| int main() { |
There was a problem hiding this comment.
main()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
The train now holds the changes merged into it so far, among them the fill-time drawdown rule (#804). The only textual conflict was the unreleased section of the changelog, where both lines are kept; the adapter, the host, the state hash and the test registrations merged without edits. Refs #805. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…gain The train now also holds the reversal exit sizing change (#803) and the changes merged with it. The only textual conflict was the unreleased section of the changelog, where every line is kept; the adapter and the test registrations merged without edits. Refs #805. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…#825) Include #797, #804, #817, #801 and #816: closed-trade cache placement, fill-time drawdown halt and flatten, reversal exit sizing, selected-capacity admission, and the percent default quantity cap. Document the intentional replacement of these earlier descriptions: - docs/design/native-feature-parity.md, introduced by 91d65ad: the old `update_risk_state(bar.close)` once per script bar at the close mark description is obsolete after #804. The cell now describes the fill-time peak, closing-fill threshold, halt latch and immediate flatten. Preserve MG10-a and MG10-b as HISTORICAL, measured on engine f5e929d, before the #804 change, with their values, claim status and grade unchanged. - docs/pages/memory-management.md, introduced by ba14721: "Storage is private: a global ordinal chooses one four-way set" no longer describes placement below the cap after #797 (653be41). Row n owns slot n while retained rows fit the cap; above it the existing four-way sets apply. Crossing the cap invalidates hot rows while retaining allocated pages. - docs/pages/memory-management.md, introduced by 69cddc9: "Page growth preserves entries through the 64/128/256 boundaries." is replaced by the current 256-slot page-boundary rule, which preserves entries through every page boundary within the cap. Re-anchor the remaining documentation citations and move the private-fixture registration evidence anchor from line 3470 to the actual token at line 3473. Preserve its row, predicate and reason; no new parity result or closed surface is claimed. Closes #804, closes #805. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Refs #805
With margin_long and margin_short both 0, a strategy that declares
default_qty_type = strategy.percent_of_equity with a value above 100 sized
its default-quantity entries from the full declared percentage: 150 percent
bought 150 percent of equity. For an entry from flat, without commission,
TradingView sizes such an order at no more than 100 percent of equity when
margins are off, for long and for short entries. With margin 1 it keeps the
declared percentage and with margin 100 it refuses the order; both already
behaved that way here and do not change.
default_percent_of_equity() returns the declared percentage capped at 100 when
the default quantity is a percent of equity and both margins are 0, and the
declared value otherwise. default_sizing_cash() (both branches) and
pooc_fee_units() take their money from it, so the money is capped before any
commission is taken out of it. CASH and FIXED defaults, explicit quantities,
typed per-order quantities and configurations with a non-zero margin are
unchanged.
tests/test_percent_equity_cap_margins_off.cpp runs twelve entries (long and
short; margin 0 at 80, 100, 150 and 1000 percent; margin 1 and margin 100 at
150 percent) against a reference function; the four runs at 150 and 1000
percent with margins off fail without the change. Rows for a cash commission
per order, a cash commission per contract, and a percentage commission under
process_orders_on_close with a fractional quantity grid run each of long and
short at 100 and at 150 percent: the two runs must give the same trades and
the quantity must equal a reference function (these rows have no TradingView
tape; the expectation follows the sizing rule stated in the code). Reverting
either commission line fails those rows. Three pairs of runs (a same-side add
followed by a close, a same-side add and a close in one bar, a reversal and a
close on a fine quantity grid) at 100 and at 150 percent must give identical
trades.
tests/test_value_invalid_sizing_margin_source.cpp: the row that overflows a
declared percentage of the largest double now runs at margin 100, because with
both margins at 0 that declared percentage is capped at 100 and its product
stays finite.
Not covered: a configuration with only one margin at 0; entries that are not
from flat (adds and reversals above 100 percent); commission paths against a
TradingView tape; the branches that key on a declared value of exactly 100
(see the pairs above) beyond those three scripts.
Refs #805.
🤖 Generated with Claude Code