Update stock valuation rows only when broker EPS changes
Automated broker earnings forecast monitoring should distinguish a newly observed report from a changed forecast. Identify the report, compare the same broker’s EPS for the same company and fiscal year, and recalculate only the valuation rows whose numerical input changed. A different rating or title can be worth reading, but it does not by itself establish an earnings upgrade.
This guide implements a manual replay of synthetic report arrivals with a fixed comparison quote. It does not poll a provider, call a model or enable a scheduler. CoTrading forecast-monitoring capabilities remain planned or unverified. An A-share report source with a limited date window also cannot be assumed to provide a complete global or historical consensus archive.
Define the forecast and quote context
Begin with an issuer and security identity, broker identity, target fiscal year, EPS basis, unit and currency. Our fixture uses fictional AAA, broker B, 2027 diluted earnings per share in CNY, and a fixed quote of 20. The price observation is dated October 19; the research replay ends October 26. Forecasts arriving during that interval are compared against the same earlier quote to isolate the forecast effect.
That is a scenario comparison at the replay cutoff, not a claim that later forecasts were known on the quote date. A current valuation table needs a separate price-update trigger and freshness policy. Never relabel a frozen quote as the current market price just because the forecast side was refreshed successfully.
The example rejects incompatible issuer or currency context and accepts only its declared diluted, per-share profile. It checks a finite positive quote and finite EPS. Negative EPS remains a valid loss forecast, but does not produce the positive-base PE used in this comparison. Those gates prevent a missing value, changed basis or unit error from masquerading as a forecast revision.
Keep two identities with different purposes
The normalized report observation identity is (symbol, broker, report_id, fiscal_year). It supports replay detection while allowing one report to contain several annual forecasts. The forecast series identity is (symbol, broker, fiscal_year). It supports comparison with that broker’s previous observation. Combining these purposes into one key would either lose historical reports or make every new report look like an entirely new forecast series.
Store a canonical payload digest beside each processed report identity. Python’s hashing documentation supplies the digest primitive; the choice of canonical fields is part of this editorial workflow. A digest does not prove a broker wrote the report or that the extraction is accurate. Keep an original source reference and the extraction check separately.
If the same identity returns the same payload, skip it. If it returns different content, stop with a conflict rather than treating it as a harmless duplicate. A provider correction needs an explicit revision relationship. Fetch time is not a report publication time and should not silently decide which forecast is the newest.
Admit observations before changing stored state
The complete script deep-copies the current state, validates and processes the batch there, then replaces the active state only after successful processing. If a conflicting identity or nonfinite EPS appears, the caller’s state remains unchanged. This is an in-memory transaction demonstration, not a durable database transaction or a crash-recovery guarantee.
Order admitted observations by their aware publication timestamps. Ignore future reports relative to the declared research cutoff. A late-arriving older report can be preserved in the processed ledger without replacing the latest forecast. When two reports share the latest timestamp but disagree on EPS, reject the ambiguity instead of selecting whichever input row happens to occur first.
The narrowed update rule is:
key = (report["symbol"], report["broker"], report["year"])
previous = staged["latest"].get(key)
staged["latest"][key] = report.copy()
if previous is not None and eps == Decimal(previous["eps"]):
reason = "numeric_unchanged"
else:
forward_pe = price / eps if eps > 0 else None
The surrounding code performs identity, time and finite-number checks before this branch. Copying only these few lines would omit the protection. Decimal finite checks help reject NaN and infinity; they do not establish that a forecast uses the correct financial definition.
Reproduce a rating change and an earnings change
Download the complete example and run python R558.py with Python 3.12 or later. It uses the standard library and invented data. The emitted events make the decision path visible:
| Event | EPS | Qualitative change | Numerical action |
|---|---|---|---|
| r1, October 20 | 1.00 | Initial observation | Create PE 20 |
| r2, October 21 | 1.00 | Rating changes | Record report, no PE recalculation |
| r3, October 22 | 1.25 | Forecast changes | Recalculate PE 16 |
| r3 returned again | 1.25 | Exact replay | No recalculation |
The calculation is 20 / 1.25 = 16. It is not an estimate of a future share price or evidence that the stock became attractive. The table simply isolates how one changed earnings assumption affects one ratio under a fixed quote.
Distinguish the latest observed report from the source that last required a numerical calculation. After r2, the latest report is r2 while the unchanged ratio can still identify r1 as its calculation source. The report ledger preserves both. Do not rewrite an old calculation timestamp merely to make an unchanged number appear newly researched.
Test conflicts, late data and a loss forecast
The script injects a different EPS under the already processed r3 identity and separately injects NaN. Both raise errors and leave the pre-batch state intact. Assertions compare the actual state, rather than merely checking that an error message was printed. A correction cannot be admitted by changing a digest to whatever the new payload happens to contain.
A previously unseen but older report is marked late_older_report. It does not roll the current forecast backward. The final event, r4, changes EPS to −0.2. That is a numerical change and is recorded, but the PE becomes absent with nonpositive_eps; it does not become a negative ratio ranked alongside ordinary positive earnings multiples.
Report these outcomes separately. Duplicate, numeric-unchanged, older, future, conflicting and nonpositive-base are different explanations for an unchanged or unavailable valuation. A single “no update” label would conceal whether the data was successfully examined and whether a human needs to resolve a problem.
Preserve coverage and operational limits
A periodic request can return only the newest limited number of reports. Local deduplication proves what was processed, not that no report was missed outside the retrieval window. Save requested dates, pagination behavior, provider status and the observation cutoff. If a gap exists between successful runs, label the unexamined interval and backfill it through an authorized source.
Do not assume a missing current forecast means a company has no analyst coverage. A source may have failed, changed its schema or omitted the required fiscal year. Likewise, a report-count increase does not establish an EPS increase. Inspect numerical observations and retain unresolved context before assigning an operating interpretation.
The replay uses no durable store and no concurrent writers. Production adaptation would need atomic persistence of processed identities and calculated rows, stable timestamp rules, retries and explicit price-trigger handling. First validate those additions on the same replay cases; otherwise restarting a task could create duplicate work or silently preserve stale valuations.
Accept a useful update packet
Deliver processed identities, ignored reasons, changed series and valuation rows together. For each changed row, retain old and new inputs, the quote timestamp, target year and calculation source. For an unchanged row, show whether a new report was actually checked. This makes the automation’s output inspectable without relying on the fluency of its summary.
Keep the scope narrow when a report changes several forecast years. Compare each year with its own series key and rerun only affected calculations. A separate fixture splits the same r1 into 2027 EPS1 and 2028 EPS2: it calculates PE20 and PE10, skips an exact reversed replay and rejects a changed payload for the same report and year without changing state. Changes of EPS basis, share units or currency require a new comparability decision. An update monitor does not turn new research documents into trading instructions.
FAQ
Should a new rating automatically recalculate the PE table?
Not under this fixed-quote, EPS-driven rule. Record and review the rating separately; only a changed numerical EPS triggers this calculation.
Is a processed-report ledger a complete historical forecast database?
No. It records the source window and observations actually retrieved. Completeness requires separate coverage evidence.