Skip to content

Learn

Stock Screening

Explain a stock screen with its actual execution trace

An explanation of a stock screening result should be built from the rule execution that actually selected the stock. Retain the observed value, threshold, operator, data source and outcome for each condition, then identify a valid path through the grouped rule. Generate a short explanation from that path and block reasons that were false, unknown or never evaluated.

This guide implements a structured trace and a deterministic explanation check on three synthetic stocks. It does not use an AI model to verify arbitrary prose. CoTrading’s corresponding explanation interface remains planned or unverified. The method is useful before adding generated writing because it creates a concrete contract that a later writer can follow and a reviewer can inspect.

Define what counts as a supporting path

Our rule is positive PE below 20 AND either revenue growth at least 10% OR dividend yield at least 4%. Ratios are stored as 0.10 and 0.04. A valid passing explanation must mention the PE gate and at least one true alternative. Stating only a true PE condition is incomplete; listing every observed strength can be wrong if some failed the threshold.

The Python expression reference documents Boolean grouping and evaluation behavior. The fixture implements explicit three-state helpers because ordinary Python truthiness does not preserve the intended distinction between unavailable and false. An OR node is true if either child is true, unknown if neither is true and one is unknown, and false only when both are false.

The AND node is false if any mandatory child is false, unknown when none is false and one is unknown, and true only when all are true. These helpers are deliberately narrow. They evaluate the declared rule, not arbitrary expressions or a general explanation language.

Store leaf evidence before composing the narrative

For each stock, the trace contains the raw numeric observation, declared threshold, comparison operator, positive lower bound for PE, leaf outcome and a source locator. The synthetic locator has the form fixture-1:stock:field. In actual work it should reach the source record used by this exact run, not an unrelated current page that might contain revised data.

StockPEGrowthYieldFinal outcome
AAA180.080.05pass via yield
BBB19unknown0.02unknown
CCC240.200.05fail PE

AAA passes PE and dividend yield but fails growth. BBB passes PE, fails yield and lacks growth, so the alternative node remains unresolved. CCC satisfies both alternatives but fails the mandatory PE gate. These deliberately contrasting records prevent an explanation generator from assuming that every favorable metric implies selection.

Save the trace for failed and unknown stocks as well as selected ones. A near candidate needs a precise statement of what blocked it, and an unknown field must remain unknown in the explanation. Do not replace missing growth with “stable growth” because a model considers the company generally mature or defensive.

Calculate grouped outcomes from the same leaves

def tri_or(values):
    return True if True in values else None if None in values else False

def tri_and(values):
    return False if False in values else None if None in values else True

decision = tri_and([leaves["pe"]["result"],
                   tri_or([leaves["growth"]["result"],
                           leaves["yield"]["result"]])])

Use this output as the source of the selected state. Do not independently calculate a final pass flag elsewhere and then attach a narrative to whatever leaf data happen to be available. A discrepancy between grouped outcome and stored selection should stop explanation publication until the first differing condition is found.

If performance requires short-circuit execution, record which leaves were skipped. A skipped condition is not false, unknown because of missing data or a measured strength. The present fixture evaluates every declared leaf for an inspectable table, so all three leaf outcomes are available in each trace.

Check structured reasons before rendering words

The fixture receives a list of proposed supporting condition identifiers. It requires a complete witness for this rule: PE plus at least one alternative. Every supplied identifier must exist in the trace and have outcome true, and the stock’s final decision must be true. That rejects an empty explanation, a PE-only explanation and unsupported extra reasons.

witness = "pe" in claims and bool({"growth", "yield"} & set(claims))
accepted = witness and record["decision"] is True and all(
    name in record["leaves"] and record["leaves"][name]["result"] is True
    for name in claims
)

Download the complete example. Use Python 3.12+ with the standard library and synthetic records, then run python R525.py. Its authoring source is docs/growth/research/round-5/examples/R525.py.

The executed renderer produces AAA passes via pe=18 and yield=0.05. Assertions verify AAA true, BBB unknown and CCC false. They also reject AAA’s growth reason, an invented management reason, BBB’s missing growth and any selected explanation for CCC. Attempting to render AAA from PE plus failed growth raises unsupported_explanation.

This is structured reason validation, not semantic understanding of arbitrary natural language. A later AI writer must preserve the accepted identifiers and numbers, and its final text still needs review for claims outside that structure. The fixture proves that the template renderer cannot promote the tested false or unknown conditions into its supporting path.

Inspect the different-mechanism counterexample

The tempting explanation is “AAA was selected for strong growth and low valuation.” PE is supported, but growth 0.08 falls below the 0.10 condition. AAA was selected through dividend yield, not growth. The correct outcome does not rescue the wrong reason. Rejecting that explanation protects the distinction between why a company sounds attractive and why this screen actually admitted it.

CCC demonstrates another failure. It has observed growth 0.20 and yield 0.05, but PE 24 blocks the root AND. Describing it as selected on growth would ignore the mandatory gate. A failed stock can have true leaves; the explanation must follow the whole grouped rule rather than cherry-pick those leaves.

BBB’s missing growth cannot be described as a weakness or advantage. Its explanation should say the alternative branch cannot yet be completed, identify the field and source status, and route it to the data queue. Resolving that field can change the outcome, while rewriting the prose cannot.

Keep explanations synchronized with revisions

Attach rule version, input snapshot and trace identifier to the explanation. When an input changes, regenerate affected leaf outcomes and grouped decisions before rendering a replacement explanation. Preserve the old text with its old run rather than leaving a revised table beside an obsolete narrative.

If the rule itself changes, a previously valid witness may no longer be sufficient. Recalculate the accepted path under the new tree, especially when a shared exclusion is added or an OR alternative becomes mandatory. Do not treat a prior explanation as reusable merely because its individual sentences remain numerically true.

The handoff contains the per-condition trace, final grouped result, accepted reason identifiers, rendered explanation and rejected-reason tests. Have a reviewer trace AAA’s yield path and CCC’s failed gate directly from the table. A rule pass establishes only conformity to this screen; it does not establish a purchase recommendation, future return probability or a complete investment thesis. Its explanation should remain equally specific.

Review the explanation as a versioned output

Keep the condition identifiers alongside the rendered sentence, even when the user sees only a short explanation. A reviewer can then distinguish a wording change from a changed supporting path. If AAA later qualifies through growth instead of yield, regenerate the witness and attach the new trace instead of editing a few adjectives in the old text.

Check whether the displayed threshold wording preserves strictness and units. “Below” must not become “at most,” and a stored ratio must not be printed as the wrong percentage. The renderer in this fixture is intentionally plain; a polished interface can format the same accepted values without inventing a stronger claim.

For failed and unknown records, use different templates with the actual blocking condition. An unknown observation is not a failed measurement, while a true alternative cannot override a false mandatory gate. Retaining these paths prevents explanation generation from becoming a second, inconsistent screening engine. Its job is to present the execution evidence already accepted, not to reconsider selection using unstated preferences.

FAQ

Is a true condition enough to explain selection?

Not necessarily. It must form a sufficient path through the actual rule, including mandatory gates.

Does the checker validate any AI-written paragraph?

No. It checks structured condition identifiers and a deterministic renderer. Free prose needs additional review.