Skip to content

Learn

Stock Data Workflows

Import a stock price CSV into a usable price table

Importing a stock CSV means converting a particular export into a declared price-table contract. Opening it successfully is only the first step. You must identify the columns, preserve security identifiers, parse the intended dates, normalize numeric units and separate rows that cannot safely enter calculations. The deliverable is a standardized file plus a rejection report and the mapping that produced both.

This guide uses a four-row fictional CSV with Chinese headers. One row is accepted and three fail for different reasons. It demonstrates local parsing with Python’s standard library, not an arbitrary-file upload feature in CoTrading or Vibe. Those product workflows remain planned or unverified. Use a separate mapping for each source export rather than assuming all stock files share one format.

Inspect the export before converting it

Keep the original bytes and record the export time, source, date range, delimiter, text encoding and price basis. Read a small sample with the declared encoding. If a file includes a byte-order mark, a compatible decoding choice can prevent it from becoming part of the first header. A decoding failure should stop the import; silently replacing unreadable characters can damage identifiers or headers.

Use a CSV parser rather than splitting each line on commas. The sample includes a quoted volume string "1,200", whose comma belongs inside a field. Python’s CSV documentation describes dialect handling, dictionary readers and explicit newline handling for real files. Parsing fields still does not convert them into verified financial values.

Create a mapping from source headers to canonical names: 代码 to symbol, 日期 to date, 开盘 to open, 最高 to high, 最低 to low, 收盘 to close, and 成交量 to volume. The extra 单位 column declares the volume unit. Keep identifiers as text so leading zeros survive. Add market-qualified identifiers outside this tiny fixture when real short codes could be ambiguous.

Declare units and adjustment basis

Our target table has symbol, ISO date, open, high, low, close, volume, currency and adjustment. Prices use CNY and the declared basis is raw. Volume is measured in shares. These are fixture metadata, not deductions from the numbers. If a source uses board lots, obtain the applicable conversion explicitly; do not assume a universal lot size across stocks or markets.

The Alpha Vantage documentation distinguishes raw daily OHLCV from adjusted close and corporate-action fields. That supplier example illustrates why an “adjusted” column should not automatically replace all raw price fields. A file containing raw open and high but adjusted close is not a coherent adjusted OHLC bar. Keep the original basis unless you have a defined, consistent conversion for every affected field.

Dates also need a contract. This fixture accepts only %Y/%m/%d and emits YYYY-MM-DD. It rejects an impossible calendar date instead of letting a permissive parser move it into another month. For intraday exports, add timezone and session definitions; this daily-date fixture does not solve timestamp conversion.

Parse each row and validate the bar

The accepted row is AAA on 2026/10/19, with open 10, high 12, low 9, close 11 and volume 1,200 shares. The second row repeats the same symbol-date key. The third uses 2026/02/30. The fourth declares lots_unknown for volume. These defects test identity, calendar and measurement compatibility separately.

day = datetime.strptime(row["日期"], "%Y/%m/%d").date().isoformat()
key = (row["代码"], day)
if key in seen:
    raise ValueError("duplicate_key")
if row["单位"] != "shares":
    raise ValueError("unknown_volume_unit")
prices = [D(row[k]) for k in ("开盘", "最高", "最低", "收盘")]
volume = int(row["成交量"].replace(",", ""))

The complete fixture also checks positive prices, nonnegative volume and a valid high-low enclosure: low must not exceed open or close, and high must not fall below either. A malformed numeric string should produce a rejected row with its location. Extend production validation to nonfinite decimals, extra columns and missing headers; the shown fixture intentionally focuses on its declared four input rows.

Do not add the key to the accepted-key set until the row has passed validation. Otherwise an invalid early row can prevent a later valid observation from being considered. For conflicting duplicate bars, our policy is to reject the later row and require review. Other policies can be legitimate, but “keep last” without a recorded reason can hide a source revision.

Inspect the actual conversion outputs

The executable file is docs/growth/research/round-5/examples/R528.py in the authoring repository. Running it accepts AAA with date 2026-10-19, prices 10, 12, 9, 11, volume 1200, currency CNY and raw adjustment. Source line 3 is rejected as duplicate_key, line 4 has an out-of-range day, and line 5 is rejected as unknown_volume_unit.

Download the complete example. Use Python 3.12+ with the standard library only; all inputs are synthetic. After downloading, run python R528.py.

The assertions reconcile four input data rows with one accepted and three rejected rows. They also check that the quoted thousands separator becomes 1200 rather than two fields or a value of 1. A result with the correct number of rows but incorrectly parsed volume would not pass that numerical check.

Write the standardized file with a fixed column order, explicit text encoding and a CSV writer. Keep raw values and source line numbers in the rejection file. This fixture writes a canonical CSV string in memory, reads it back with DictReader and asserts its date and volume. It includes that string in the printed JSON without creating disk files; a real importer can save the same accepted and rejected structures as separate artifacts. File creation is an output step, not a reason to bypass validation.

Repair the cause rather than the symptom

The unknown-unit counterexample is intentionally not a formatting problem. Removing commas or changing the text encoding cannot determine how many shares one reported lot represents. Obtain the unit definition, record the mapping version and rerun the rejected row. Until then, it must not enter a backtest or liquidity calculation.

For the impossible date, consult the original export rather than replacing it with an adjacent trading day. For the duplicate, compare both source observations and decide whether the export repeated a row or contains a correction. Repaired rows need a link to the original rejection and the evidence for the change. Leave unaffected accepted rows unchanged.

Before handing the file to a consumer, compare the first and last dates, per-symbol row counts and a few original numeric values against the standardized file. Verify identifier text, volume scale, currency and adjustment metadata. A successful import establishes this data handoff; it does not prove full market coverage, corporate-action correctness or strategy profitability. New source headers, encoding changes or export-version changes should reopen the mapping tests before the next import.

Version the importer with its rejection policy

Keep a compact import manifest containing the raw file reference, mapping version, declared encoding, delimiter, date format, currency, price basis and volume unit. Record accepted and rejected counts separately. If a later supplier export changes only its header spelling, update the mapping and replay the same four-row fixture before accepting a larger file.

Treat changes to duplicate precedence or unknown-unit handling as policy changes, not cosmetic parser maintenance. A user who previously received a rejected row should be able to discover why the same source now enters the accepted output. Preserve the old rejection alongside the new decision.

Finally, reopen the written standardized file using the declared contract and compare its values with the in-memory accepted records. This additional readback catches output encoding, column-order and quoting defects that successful input parsing cannot detect. The runnable fixture prints structures; file write-and-read validation belongs to the importer that adopts them.

Keep parsing, standardization and downstream readback as separate completion states. A file can pass parsing while still containing rejected rows, and an accepted table can still be incompatible with its consumer. The handoff should state exactly which boundary has passed and which owner must resolve the remaining issue.

FAQ

Can automatic delimiter detection replace a source mapping?

It can suggest a dialect, but still verify the headers, quoted fields and numeric conventions against real rows.

Should duplicate dates always be deleted?

No. Determine whether they are repeated rows, revisions or distinct observations that require a richer key.