Position output contract
Language: English · 简体中文
This page describes the stable fields, validation behavior, and downstream use of positions_by_rebalance.csv. The contract and writer are implemented in contracts.py and positions_artifact.py; behavior is covered by contract tests and artifact tests.
Canonical file and contract
The canonical file name is:
positions_by_rebalance.csv
Its contract name is portfolio_backtester.positions_by_rebalance, schema version 1.
Required fields
| Field | Validation | Meaning |
|---|---|---|
rebalance_date |
Must parse as a date | Rebalance date for the target positions |
symbol |
Must be non-empty after string conversion | Security identifier |
weight |
Must be convertible to numeric | Target weight |
Dates in YYYYMMDD, YYYY-MM-DD, and other pandas-parseable formats are accepted. Use YYYYMMDD for consistent cross-system exchange.
Common optional fields
| Field | Meaning |
|---|---|
entry_date |
Planned entry date |
side |
Position direction, such as long |
signal |
Score used when constructing positions |
rank |
Security rank in that rebalance's candidate set |
Specialized portfolio constructors may add fields such as sleeve, theme, industry, or model version. Readers should preserve unknown columns and depend only on agreed fields.
Example
rebalance_date,entry_date,symbol,weight,side,signal,rank
20260102,20260105,000001.SZ,0.40,long,1.25,1
20260102,20260105,600000.SH,0.35,long,1.10,2
20260102,20260105,000002.SZ,0.25,long,0.98,3
Validation scope
validate_positions_by_rebalance_frame checks:
- Whether the three required columns exist
- Whether
rebalance_dateparses as a date - Whether
symbolis non-empty - Whether
weightcan be converted to numeric
The contract does not automatically check:
- Duplicate symbols on the same date
- Whether weights sum to 1 for each rebalance
- Negative weights
- Whether
sidecontains only allowed values - Whether
entry_dateis after the rebalance date - Whether symbols match a specific market format
These rules depend on portfolio type and caller requirements. Add stricter checks in the position-producing module and cover them with tests when needed.
Use the validation helpers as follows:
from portfolio_backtester import (
assert_positions_by_rebalance_frame,
validate_positions_by_rebalance_frame,
)
issues = validate_positions_by_rebalance_frame(positions)
assert_positions_by_rebalance_frame(positions)
validate_positions_by_rebalance_frame returns a list of issues. assert_positions_by_rebalance_frame raises ValueError when the frame is invalid.
Writing the artifact and envelope v2
write_positions_by_rebalance_artifact writes positions_by_rebalance.csv and the companion positions_by_rebalance.meta.json. Under its artifact_envelope key, the metadata carries a research.artifact-envelope.v2 envelope whose content_sha256 is the SHA-256 of the written CSV.
from portfolio_backtester import write_positions_by_rebalance_artifact
csv_path, meta_path = write_positions_by_rebalance_artifact(
positions,
output_dir,
run_id="run-20260818",
configuration={"top_k": 20, "weighting": "equal"},
lineage=[("signals.parquet", signals_sha256)],
)
run_id is required. configuration and lineage are optional; they are used for configuration_sha256 and the envelope lineage, respectively. The envelope is an additional metadata field alongside artifact_type, schema_version, file name, row count, and required-column list.
How position replay uses the frame
run_position_backtest first applies the base contract validation and then:
- Converts
symbolto string andweightto numeric - If a
sidecolumn is present, keeps rows whose value islong(case-insensitive) - Keeps only positive weights
- Aggregates duplicate symbols within a rebalance date by summing their weights
- Renormalizes the remaining weights by default
- Allows under-invested weights to remain as cash when
preserve_gross_exposure=True
PositionBacktestConfig still contains long_only, but long_only=False does not enable short replay. Normalization still keeps only side=long and positive weights; the flag is recorded in the result summary. Use a score-driven entry point that supports long-short construction when that is required. Changing this behavior requires coordinated implementation, test, and contract updates.
Holding periods come from a separate periods table, which must contain at least rebalance_date, entry_date, and exit_date. The entry_date in the positions file is primarily descriptive and for interchange; replay uses the periods table.
Missing prices
If an entry or exit price cannot be resolved under the configured price and exit policies, the affected position is removed from that period. By default, the remaining weights are renormalized.
This changes the number of securities included in the replay. Inspect the period outputs for:
missing_price_countposition_countgross_exposurecash_weight
To retain uninvested cash, configure:
PositionBacktestConfig(preserve_gross_exposure=True)
Other file names
positions_current.csv, trades_by_rebalance.csv, and latest.json may be produced by higher-level orchestration or publication flows. They are not stable output contracts of this package.
Public integrations should prefer positions_by_rebalance.csv and the contract validation helpers. Higher-level projects may define their own snapshots, trade deltas, and convenience pointers, but should document their fields and update rules separately.
Compatibility guidance
- Adding optional fields is usually backward-compatible.
- Removing or renaming required fields breaks the contract.
- Changing date meaning, weight meaning, or duplicate-row handling requires a contract-version update.
- Keep column names stable when writing files.
- Record the model version and parameters used to generate positions.
- Document impacts on downstream readers in the pull request.