Screen a watchlist without expanding its stock universe
Screening an existing watchlist is a closed-universe task. Keep every original row, resolve its stock identity, obtain the required observations and write the result back to that row. A broad market screener that adds attractive names from outside the list answers a different request. The final research shortlist may deduplicate stocks, but the row-level output should still explain what happened to every submitted entry.
This tutorial uses six synthetic watchlist rows, a fixed identity mapping and a mock data table. It actually caches repeated requests and performs row-preserving screening. It does not call an identifier service or establish that CoTrading has a custom-watchlist screen. In particular, a reference tool’s preset universe must not silently replace the user’s submitted list.
Preserve row identity before resolving securities
Assign each input row a stable identifier before trimming text, normalizing symbols or removing duplicates. Retain the original request string and any user note. Two identical tickers can occur twice for a reason, such as separate research notes. Fetching data once is useful; discarding the second original row without an explicit policy is not.
For a CSV input, use an actual parser and keep identifiers as text. The Python CSV reference documents row and dictionary readers. A parser can preserve columns, but it cannot decide whether a short ticker names the intended security. Do not convert numeric stock codes to integers and lose leading zeros before identity resolution begins.
The identity record should include the confirmed security, market or exchange, share class where relevant, and the resolver evidence. The OpenFIGI documentation illustrates mapping requests that can include exchange context and responses with security identifiers. This article does not call that API or promise that its results always resolve a particular input. An ambiguous mapping still needs confirmation.
Define a closed-universe resolution policy
Our six rows are AAA.US twice, BBB.HK, ambiguous XYZ, invalid BAD.US and CCC.CN. These suffixes are synthetic fixture labels, not a universal provider symbol standard. A fixed mapping confirms AAA.US, BBB.HK and CCC.CN. XYZ is intentionally unresolved because the request lacks market context. BAD.US is explicitly classified not found in the fixture.
Distinguish not found from a provider transport failure in actual work. A failed resolver request cannot establish that a security does not exist. If several matches are returned, present the candidates or ask for the missing market information instead of choosing the first row. Do not broaden the universe to “similar” stocks just to keep the pipeline moving.
Store the resolution result once per unique request identity, then join it back to all original rows. If two different request strings resolve to the same security, a real implementation can reuse the canonical data request while preserving both original strings. That refinement needs explicit mapping evidence; the fixture only demonstrates an exact repeated string.
Acquire only the confirmed unique securities
The mock source contains PE 18 for AAA.US, PE 22 for BBB.HK and no usable PE for CCC.CN. It also contains OUTSIDE.US at PE 10, deliberately tempting the screen to add a qualified outsider. The correct request set is AAA.US, BBB.HK and CCC.CN only.
if symbol not in cache:
calls.append(symbol)
cache[symbol] = source.get(symbol)
value = cache[symbol]
state = "unknown" if value is None else "pass" if 0 < value < 20 else "fail"
output.append({"row_id": row_id, "requested": requested,
"pe": value, "state": state})
The snippet shows caching and result writing after identity resolution. The full fixture handles ambiguous and not-found branches before this step. The numerical rule requires positive PE below 20 under an agreed field definition. In a real multi-market watchlist, confirm comparable earnings periods and definitions rather than assuming a common column label proves equivalence.
Keep fetch status separate from field availability. CCC.CN represents a confirmed security with an unavailable screening field, not an ambiguous identity. Its next action is data completion or source review. XYZ needs identity confirmation before a meaningful price or financial request can be made.
Inspect the row-preserving output
Download the complete example. Use Python 3.12+, the standard library and synthetic inputs; run python R524.py. The authoring source is docs/growth/research/round-5/examples/R524.py.
| Original row | Input | PE | State |
|---|---|---|---|
| 1 | AAA.US | 18 | pass |
| 2 | AAA.US | 18 | pass |
| 3 | BBB.HK | 22 | fail |
| 4 | XYZ | unavailable | ambiguous |
| 5 | BAD.US | unavailable | not found |
| 6 | CCC.CN | unavailable | unknown field |
Actual output preserves row identifiers 1 through 6 in order. The recorded fetch list contains three unique confirmed securities, with AAA requested once. The deduplicated research list contains only AAA.US. Assertions verify all six states, exact row correspondence, one request per confirmed identity and the absence of OUTSIDE.US from every output row.
Two passing rows do not mean two independent candidate companies. Publish both original rows for traceability and a separate unique-security shortlist for research capacity. This two-output design avoids choosing between losing user context and inflating the number of stocks selected.
Test an attractive outsider as the counterexample
OUTSIDE.US has a better PE than several submitted entries, but it is not on the watchlist. Iterating over the source table instead of the confirmed request set would admit it. The fixture deliberately leaves it in the mock response universe and asserts that it never appears. This catches a closed-universe failure even when the numerical predicate itself is correct.
An ambiguous code is a different failure mechanism. It cannot be fixed by rejecting the row numerically because no confirmed security was evaluated. Keep an identity queue with the original row and missing context. A not-found entry should preserve resolver evidence and time, while an unknown metric should point to the confirmed security’s data gap.
Do not substitute a preset index universe when the user’s list is small or incomplete. That changes both the scope and the count denominator. If a broader search is useful, create a separately labeled task and retain the original closed-list screen as its own result.
Hand off results without losing the input contract
Deliver the original watchlist, resolution map, unique fetch ledger, row-level result and deduplicated shortlist. Reconcile six input rows with six output rows. Separately reconcile confirmed unique securities with fetch attempts and field statuses. Each denominator answers a different question and should be named accordingly.
For a new data snapshot, rerun the same confirmed universe and update row results under a new run identifier. For an identity correction, invalidate only the affected mappings and downstream observations, then refill the corresponding original rows. For a user-added stock, create a new watchlist version rather than silently changing the saved list.
Have a reviewer trace one repeated row, one numerical failure, one ambiguous input and one missing metric. The review should establish that every original entry has an explainable next step and that no outsider was added. This makes a watchlist screen a repeatable research operation with intact user context, rather than a generic market scan wearing the watchlist’s name.
Preserve the distinction between rows and companies
Use the row table when returning results to the user’s original file, and use the unique-security table when assigning research work. The same company can therefore have several linked input rows without generating several acquisition calls or several independent screening opportunities. Store the mapping rather than asking a later reviewer to infer it from repeated names.
Before writing results back, verify that row identifiers remain unique and that original notes still belong to the same row. A numerical screen can be correct while a sorted export attaches the result to another company’s note. Treat that correspondence as an acceptance condition, not an interface detail.
When the source returns an extra security, record that it was outside scope rather than adding it to a fallback list. When it omits a requested security, retain the requested row with the appropriate acquisition status. These symmetric checks protect the closed universe from both expansion and silent shrinkage. They also make the final count reconciliation meaningful when the source response has a different shape from the user’s request.
FAQ
Should duplicate input rows be deleted?
Not automatically. Reuse their data request, preserve original rows and deduplicate only the research shortlist under an explicit policy.
Does an unresolved code mean the stock failed the screen?
No. It means identity was not established. No valid numerical decision has been made for that entry.