Skip to content

Lower strategy.default_entry_qty to the engine host's query and refuse the spellings TradingView refuses - #339

Merged
luisleo526 merged 3 commits into
mainfrom
fix/default-entry-qty-lowering
Nov 6, 2026
Merged

luisleo526 merged 3 commits into
mainfrom
fix/default-entry-qty-lowering

Conversation

@luisleo526

Copy link
Copy Markdown
Contributor

Summary

strategy.default_entry_qty(fill_price) is valid in TradingView: it returns the order size that the strategy's default quantity settings give at a price. Here it lowered to calc_qty(...), a function no engine host declares, so every script that read it failed to build; the integration of the pending compiler fixes then refused every call with PF-E5084 until the host had a query.

It now lowers to the engine host's default_entry_qty(price) (added by pineforge-engine #751). The spellings TradingView refuses are refused here too, at the offending argument or call, also inside the arm of a switch:

  • no price, a second price, a price given both by position and by name, an unknown argument name (PF-E1088, the existing argument binding of nz/fixnan/request.currency_rate, applied to this one strategy function);
  • a string, bool, color or collection price (PF-E2083 to PF-E2086), only where the syntax proves the type.

The generated host-member registry gains default_entry_qty, so a script variable with that name is renamed instead of hiding the host's member. PF-E5084 is retired: its catalog entry and its pin stay (a code is never removed) and nothing emits it. The public contract row and the CHANGELOG describe the supported form.

Needs

An engine whose strategy host provides default_entry_qty (pineforge-engine #751, merged). Without an engine the native rows skip, as for every native test.

Tests

tests/test_strategy_default_entry_qty.py (new, 41 tests) and tests/test_strategy_default_quantity_query.py (rewritten), added first and red on main (41 failed, 1 passed: the refusal of an unknown function was already right), green with the change (42 passed). The new file: eleven accepted price spellings lower to one call of the host query; eleven refused spellings are refused at the offending line (two inside a switch arm); native rows link the emitted code against the engine host: fixed sizing returns the configured units at a valid price and at a zero, negative or na price, a fractional size is returned unrounded, a zero size is zero, the result is a series, the price argument is evaluated once and not at all when a conditional price does not select it, a script variable named like the query does not hide it, and the script as reported (an EMA crossover sizing its entry with the query) builds and sizes its entries. The rewritten file used to pin the blanket refusal and its contract row; it now pins the contract row of the supported form.

The expected values are TradingView's own answers (exported trades of probe scripts that encode the query's answer in the entry quantity, and TradingView's failure records for the refused spellings), not outputs of this code.

Also adapted: tests/test_diagnostic_notes.py (declares the four new codes).

Evidence

Every run is on Linux aarch64 against engine d9e787e3 (it includes #751).

  • The 21 test files that touch the catalog, the code pin, the host-member registry, the argument binding, nz, fixnan, request.currency_rate and the strategy signatures: the same ids pass on main and on this change (4,070 passed, 49 skipped on both). The 49 skipped rows are rows whose private fixtures are not in the checkout (tests/test_host_member_names.py 25, tests/test_diagnostic_codes.py 22, tests/test_e2e_lane_compositions.py 2). Among them are the checks that compare the generated host-member registry with the engine's header; the name default_entry_qty is shown to exist by the native rows instead, which compile and link against the real host header.
  • Translation of the 856 tracked and 312 corpus Pine sources: identical before and after. Of 60 probe sources (the probe family, edge cases in functions, loops, blocks, switch arms, series expressions), 34 go from refused to emitted and 13 stay refused with the specific message instead of the blanket one; none that TradingView accepts is refused.
  • The 39 TradingView trade lists of the probes, joined by entry time: 36 match (the nine probes with an invalid price included: fixed sizing answers the configured size); 2 differ, the reported script and its control, by the same delta with and without the query (the engine opens earlier entries for lack of warm-up bars); 1 does not build, a probe of strategy.position_entry_name, which this change does not touch.
  • The reported script and its control on BINANCE:BTCUSDT 1h (2024-01-01 to 2024-03-01), whose two TradingView trade lists are identical to each other: here both build and give the same result, and all 35 TradingView entry times are reproduced. The engine opens two more entries at the start of the range, for lack of warm-up bars, and its quantities differ from TradingView's by at most 0.00045 (about 0.2%), the same with the query as without it. On the base, the script with the query is refused (PF-E5084) and its control gives the same result.

Not in this change

  • A color held in a variable is still accepted (TradingView refuses it): only syntactically proven kinds are refused.
  • A float assigned to an int declaration is accepted (TradingView refuses it): there is no assignment-type check in the transpiler yet.

Part of #295 (item 7).

🤖 Generated with Claude Code

luisleo526 and others added 3 commits November 6, 2026 21:47
… refused spellings are refused

strategy.default_entry_qty(fill_price) is valid in TradingView and returns the
order size the default quantity settings give at that price. Here it is refused
whatever its arguments, with a message saying that PineForge does not support
it yet, because the strategy host had no query for it; the engine host now has
one (default_entry_qty(price)). Every spelling TradingView refuses (no price,
two prices, a price given twice, an unknown argument name, a string, bool,
color or array price) is refused by the same blanket error instead of at the
offending argument.

tests/test_strategy_default_entry_qty.py (new):
- eleven accepted price spellings each lower to one call of the host query, and
  the plain series price is the call's argument;
- eleven spellings TradingView refuses (its CE10115, CE10120, CE10123, CE10165
  and CE10271 failure records) are refused at the offending line, two of them
  inside the arm of a switch;
- native rows link the emitted code against the engine host: fixed sizing
  returns the configured units at a valid price and at a zero, negative or na
  price, a fractional size is returned unrounded, a zero size is zero, the
  result is a series, the price argument is evaluated once and not at all when
  a conditional price does not select it, a script variable named like the
  query does not hide it, and the script as it was reported (an EMA crossover
  sizing its entry with the query) builds and sizes its entries.

tests/test_strategy_default_quantity_query.py (rewritten): it pinned the
blanket refusal and the public contract row that described it. It now pins the
contract row of the supported form (what the call lowers to, the paired engine
it needs, the refusals and their codes, the earlier refusal as retired); the
behaviour itself is pinned by the file above.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…e the spellings TradingView refuses

strategy.default_entry_qty(fill_price) used to lower to calc_qty(...), a
function no host declares, so a script that read it did not build; the last
change refused every call with PF-E5084 until the strategy host had a query. It
now lowers to the engine host's default_entry_qty(price), which returns the
order size the default quantity settings give at that price. Because the engine
host provides the query, the name joins the host members a script identifier is
renamed around, and the change needs an engine whose strategy host has the
query.

A call that has no value to lower is refused where TradingView refuses it, also
inside the arm of a switch (where the support checker otherwise downgrades a
refusal to a warning): no price, a second price, a price given both by position
and by name, or an unknown argument name (the builtin argument binding, applied
to this one strategy function, PF-E1088); and a string, bool, color or
collection price (four new analyzer errors, PF-E2083 to PF-E2086, only for a
type the syntax proves).

PF-E5084, the blanket refusal, is retired: its catalog entry and pin stay (a
code is never removed) and nothing emits it. diagnostics_catalog.json and the
code pin gain the four new codes (generated, then their explanation and user
message written); tests/test_diagnostic_notes.py declares them among the added
codes. The public contract row and the CHANGELOG describe the supported form,
and the CHANGELOG entry of the refusal is dropped, since both would ship in the
same unreleased series. tests/test_named_array_constructors.py no longer lists
strategy.default_entry_qty among the lowerings that must stay identical to the
reference, since this one now differs by design.

tests/test_strategy_default_entry_qty.py and the rewritten
tests/test_strategy_default_quantity_query.py (the previous commit) go from red
to green.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The keyword allow_switch_warning of _check_builtin_arguments was added with
the default_entry_qty lowering but is never read: the body keeps passing
allow_switch_warning=func.name in sigs.BUILTIN_FUNCTIONS to _err, and none of
the three callers passes it. The signature is the one main has again.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
@luisleo526
luisleo526 merged commit 8fb589a into main Nov 6, 2026
11 checks passed

@graphify-labs graphify-labs Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

No issues found by static checks — no coupling regressions or blocking issues in the code graph. That is not the same as safe to merge: see what was not checked.

Not checked: tests were not run; formal verification proved 0 of 3 changed function(s) (3 not verified).

Formal verification. PR-changed functions: 0/3 verified (0 proven, 0 may-equivalent, 0 distinguished) · 3 not verified (3 unsupported).

Not verified on this run: \_handle\_strategy\_call (unsupported), \_visit\_strategy\_call (unsupported), \_visit\_FuncCall (unsupported).


Graphify review — findings

Adds real support for strategy.default_entry_qty(fill_price), which now lowers to the engine strategy host's default_entry_qty(price) query instead of raising PF-E5084. It needs an engine whose strategy host provides that query, and the name is added to the reserved host member names so user code can't collide with it. The analyzer refuses the calls TradingView refuses: a missing price, a second price, a price given both by position and by name, and an unknown argument name (PF-E1088). A string, bool, color or collection price is also refused (PF-E2083 to PF-E2086), but only when the syntax proves the type, never when it is merely inferred. PF-E5084 stays in the diagnostics catalog but is no longer emitted. The changelog and PUBLIC_CONTRACT.md are updated to match.

No blocking issues surfaced. 5 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 4561 functions depend on the 773 functions this change touches.

Health — grade A; 10 existing hotspot(s) in the area this change touches (pre-existing, not introduced here):

  • Analyzer — 101 callers, 15 callees (high)
  • _generate() — 131 callers, 9 callees (high)
  • SupportChecker — 29 callers, 34 callees (high)
  • _generate_raw() — 29 callers, 9 callees (high)
  • _generate() — 9 callers, 25 callees (high)
  • _analyze() — 30 callers, 6 callees (high)
  • _check() — 31 callers, 3 callees (high)
  • resolve() — 25 callers, 3 callees (high)
  • …and 2 more

Verification — 4561 functions in the blast radius were not formally verified this run (proofs are advisory here).

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: 3777 function(s) in the blast radius were not formally verified this run

Cross-repo impact

Cross-repo impact

This change impacts 2 symbol(s) across 1 peer repo(s) — blast radius over derived cross-repo edges:

  • pineforge-4pass/pineforge-hpo — python/pineforge_hpo/transpile.py (via import_moniker, heuristic), tests/python/test_transpile.py (via import_moniker, heuristic)

1 peer repo(s) in this group are not yet indexed — cross-repo impact may be incomplete.

Cross-repo linkage is derived (SCIP-moniker matches are sound; name/contract matches are heuristic) and ADVISORY — it never affects this check's conclusion.

Formal verification

Could not verify: Could not verify \_handle\_strategy\_call.

The verifier did not have enough to check \_handle\_strategy\_call, 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: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_strategy\_call.

The verifier did not have enough to check \_visit\_strategy\_call, 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: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_FuncCall.

The verifier did not have enough to check \_visit\_FuncCall, 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: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

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