The CSV transaction log must be a UTF-8 encoded text file with a header row containing the following mandatory fields:
| Field Name | Type | Description | Example | Constraints |
|---|---|---|---|---|
transaction_id |
String | Unique transaction identifier | TXN-001 |
Non-empty, unique within the dataset |
timestamp |
String | Execution date and time | 2026-01-15T09:30:00Z |
Valid ISO-8601 string, normalized to UTC |
symbol |
String | Financial asset ticker | AAPL |
Non-empty alphanumeric string |
direction |
String | Order execution side | BUY |
Case-insensitive; must be BUY or SELL
|
quantity |
Decimal | Number of asset units | 10 |
Positive decimal number ( |
price |
Decimal | Unit price in execution currency | 150.25 |
Positive decimal number ( |
fee |
Decimal | Execution commission / fee | 1.50 |
Non-negative decimal number (0.00 if omitted |
currency |
String | Three-letter currency code | USD |
3-character uppercase ISO currency code |
transaction_id,timestamp,symbol,direction,quantity,price,fee,currency
TXN-1001,2026-01-15T09:30:00Z,AAPL,BUY,100,150.00,1.00,USD
TXN-1002,2026-01-15T10:00:00Z,MSFT,BUY,50,300.00,1.50,USD
TXN-1003,2026-01-15T14:30:00Z,AAPL,SELL,60,155.00,1.00,USD
TXN-1004,2026-01-16T11:00:00Z,MSFT,SELL,50,295.00,1.50,USDThe historical market quote CSV file contains historical closing prices for portfolio valuation:
| Field Name | Type | Description | Example | Constraints |
|---|---|---|---|---|
symbol |
String | Financial asset ticker | AAPL |
Non-empty alphanumeric string |
timestamp |
String | Quote date and time | 2026-01-16T16:00:00Z |
Valid ISO-8601 string, normalized to UTC |
close_price |
Decimal | Closing market price | 160.00 |
Positive decimal number ( |
currency |
String | Three-letter currency code | USD |
3-character uppercase ISO currency code |
symbol,timestamp,close_price,currency
AAPL,2026-01-15T16:00:00Z,154.50,USD
AAPL,2026-01-16T16:00:00Z,160.00,USD
MSFT,2026-01-16T16:00:00Z,312.00,USD
AMZN,2026-01-16T16:00:00Z,175.00,USD
GOOG,2026-01-16T16:00:00Z,175.00,USDThe cash flow CSV file specifies external cash events (opening balances, deposits, and withdrawals):
| Field Name | Type | Description | Example | Constraints |
|---|---|---|---|---|
event_id |
String | Unique cash event identifier | CASH-001 |
Non-empty, unique string |
timestamp |
String | Effective date and time | 2026-01-15T08:00:00Z |
Valid ISO-8601 string, normalized to UTC |
event_type |
String | Nature of cash movement | OPENING_BALANCE |
Must be OPENING_BALANCE, DEPOSIT, or WITHDRAWAL
|
amount |
Decimal | Magnitude of cash movement | 5000.00 |
Positive decimal number ( |
currency |
String | Three-letter currency code | USD |
3-character uppercase ISO currency code |
description |
String | Optional movement description | Deposit |
Freeform text |
event_id,timestamp,event_type,amount,currency,description
CASH-001,2026-01-15T08:00:00Z,OPENING_BALANCE,100000.00,USD,Initial cash balance
CASH-002,2026-01-16T08:00:00Z,DEPOSIT,5000.00,USD,Additional funding
CASH-003,2026-01-16T15:00:00Z,WITHDRAWAL,2000.00,USD,Owner withdrawalThe corporate actions CSV file specifies cash dividend declarations and stock split events:
| Field Name | Type | Description | Example | Constraints |
|---|---|---|---|---|
event_id |
String | Unique event identifier | DIV-001 |
Non-empty unique string |
timestamp |
String | Ex-date for dividend or effective split date | 2026-05-01T12:00:00Z |
Valid ISO-8601 string, normalized to UTC |
event_type |
String | Type of corporate action | CASH_DIVIDEND |
Must be CASH_DIVIDEND or STOCK_SPLIT
|
symbol |
String | Asset symbol | AAPL |
Non-empty uppercase alphanumeric string |
amount |
Decimal | Dividend per share or split ratio R | 0.50 |
Positive decimal number ( |
currency |
String | Currency code | USD |
3-letter ISO code (mandatory for dividends) |
payment_date |
String | Cash payment date for dividends | 2026-05-02T14:00:00Z |
Required for dividends; must be timestamp
|
description |
String | Optional description | Q1 dividend |
Freeform text |
event_id,timestamp,event_type,symbol,amount,currency,payment_date,description
DIV-001,2026-05-01T12:00:00Z,CASH_DIVIDEND,AAPL,0.50,USD,2026-05-02T14:00:00Z,AAPL Q1 Dividend
SPLIT-001,2026-05-02T09:00:00Z,STOCK_SPLIT,AMZN,2.0,USD,,AMZN 2-for-1 Forward SplitInvalid input rows are never silently dropped. Each invalid record produces a structured error record containing:
row_number: 1-based index corresponding to the line in the input file (header is row 1, first data row is 2).transaction_id: Identifier if parseable, ornull.field: The specific field that failed validation, orrowfor structural failures.error_type: Standardized machine-readable error code.message: Diagnostic description explaining the rejection.raw_data: Key-value map of the unparsed row strings.
-
MISSING_FIELD: A required column is missing from the header or a row contains an empty required cell. -
INVALID_TIMESTAMP: The timestamp cannot be parsed as a valid ISO-8601 datetime. -
INVALID_DIRECTION: The direction is notBUYorSELL. -
INVALID_NUMERIC: Quantity, price, or fee cannot be parsed as a valid decimal number. -
NON_POSITIVE_VALUE: Quantity or price is$\le 0$ . -
NEGATIVE_FEE: Fee is$< 0$ . -
DUPLICATE_TRANSACTION_ID: Thetransaction_idhas already been observed in a preceding row. -
INVALID_CURRENCY: The currency is missing or does not conform to the 3-letter ISO format. -
INSUFFICIENT_HOLDINGS: ASELLtransaction specifies a quantity greater than currently available in long inventory. -
CURRENCY_MISMATCH: A transaction or quote currency does not match the configured portfolio base currency. -
DUPLICATE_QUOTE: A quote with the exact same(symbol, timestamp)key was already observed in a preceding row. -
DUPLICATE_EVENT_ID: A cash event ID has already been observed in a preceding row. -
DUPLICATE_OPENING_BALANCE: More than oneOPENING_BALANCEevent was supplied (only one opening balance is permitted). -
INVALID_EVENT_TYPE: An unrecognized or unsupported cash event type was provided. -
INSUFFICIENT_CASH: A cash outflow (BUY trade or WITHDRAWAL) exceeds the available cash balance in a cash account. -
DUPLICATE_CORPORATE_ACTION: A corporate action for the same symbol and effective date was already observed. -
INVALID_DATE_SEQUENCE: A dividend payment_date is earlier than its ex-dividend date.
transaction_id:strtimestamp:datetime(UTC)symbol:str(uppercase)direction:Literal["BUY", "SELL"]quantity:Decimalprice:Decimalfee:Decimalcurrency:str(uppercase)row_number:int(1-based source CSV line index for error traceability)
-
symbol:str(uppercase) -
timestamp:datetime(UTC) -
close_price:Decimal($> 0$ ) -
currency:str(uppercase 3-letter ISO) -
row_number:int(1-based source CSV line index for error traceability)
-
symbol:str -
buy_transaction_id:str -
sell_transaction_id:str -
quantity:Decimal -
buy_price:Decimal -
sell_price:Decimal -
buy_fee:Decimal -
sell_fee:Decimal -
gross_pnl:Decimal($(\text{sell_price} - \text{buy_price}) \times \text{quantity}$ ) -
net_pnl:Decimal($\text{gross_pnl} - \text{buy_fee} - \text{sell_fee}$ ) -
return_pct:Decimal($\frac{\text{net_pnl}}{\text{buy_price} \times \text{quantity} + \text{buy_fee}} \times 100$ )
symbol:strquantity:Decimalcost_basis:Decimalaverage_cost_per_share:Decimal
-
total_transactions:int(count of valid transactions processed) -
total_buys:int -
total_sells:int -
total_volume:Decimal($\sum \text{quantity} \times \text{price}$ ) -
total_fees:Decimal($\sum \text{fee}$ ) -
realized_pnl:Decimal($\sum \text{net_pnl}$ ) -
closed_trades_count:int -
winning_trades_count:int(trades withnet_pnl > 0) -
losing_trades_count:int(trades withnet_pnl < 0) -
breakeven_trades_count:int(trades withnet_pnl == 0) -
win_rate:Decimal | None($\frac{\text{winning_trades}}{\text{closed_trades}}$ )- If
closed_trades_count == 0:None(nullin JSON). - If
closed_trades_count > 0: rounded to 4 decimal places (e.g.0.5000= 50.00%).
- If
-
profit_factor:Decimal | None($\frac{\sum_{\text{winning}} \text{net_pnl}}{\sum_{\text{losing}} |\text{net_pnl}|}$ )- Net Profit Factor: Ratio of aggregate net gains from winning trades to aggregate absolute net losses from losing trades.
- If
gross_losses > 0: rounded to 2 decimal places. - If
gross_losses == 0andgross_gains > 0:None(nullin JSON, represented as N/A). - If
gross_losses == 0andgross_gains == 0:None(nullin JSON). - If
gross_losses > 0andgross_gains == 0:Decimal("0.00")("0.00"in JSON).
-
open_positions:list[Position](sorted deterministically by symbol)
-
symbol:str -
quantity:Decimal -
cost_basis:Decimal -
average_cost_per_share:Decimal -
market_price:Decimal | None -
quote_timestamp:datetime | None -
quote_age_seconds:int | None -
market_value:Decimal | None($\text{quantity} \times \text{market_price}$ ) -
unrealized_pnl:Decimal | None($\text{market_value} - \text{cost_basis}$ ) -
unrealized_return_pct:Decimal | None($\frac{\text{unrealized_pnl}}{\text{cost_basis}} \times 100$ ) -
status:Literal["OK", "MISSING_QUOTE", "STALE_QUOTE", "CURRENCY_MISMATCH"] -
warnings:list[str]
valuation_at:datetime(UTC point-in-time)status:Literal["COMPLETE", "INCOMPLETE", "NO_OPEN_POSITIONS"]max_quote_age_seconds:int(staleness threshold)total_positions_market_value:Decimal | None(sum of position market values when complete)total_unrealized_pnl:Decimal | None(sum of position unrealized P&L when complete)positions:list[ValuedPosition]warnings:list[str]disclaimer:str
-
currency:str -
opening_balance:Decimal | None -
opening_timestamp:datetime | None -
ending_balance:Decimal(point-in-time cash balance) -
as_of:datetime -
total_deposits:Decimal -
total_withdrawals:Decimal -
total_buy_outflows:Decimal -
total_sell_inflows:Decimal -
total_fees_paid:Decimal -
net_cash_flow:Decimal(period change:$\text{ending_balance} - \text{opening_balance}$ ) -
entries_count:int -
status:Literal["COMPLETE", "DEFICIT"]
-
as_of:datetime -
status:Literal["COMPLETE", "INCOMPLETE", "CASH_UNMODELED"] -
currency:str -
cash_balance:Decimal | None -
positions_market_value:Decimal | None -
portfolio_equity:Decimal | None($\text{cash_balance} + \text{positions_market_value}$ ) -
realized_pnl:Decimal -
unrealized_pnl:Decimal | None -
warnings:list[str] -
disclaimer:str
-
event_id:str -
symbol:str -
ex_date:datetime(UTC) -
payment_date:datetime(UTC,$\ge \text{ex_date}$ ) -
amount_per_share:Decimal($> 0$ ) -
currency:str(uppercase 3-letter ISO)
-
event_id:str -
symbol:str -
timestamp:datetime(UTC) -
split_ratio:Decimal($> 0$ ,$R > 1$ for forward split,$R < 1$ for reverse split)
-
total_dividend_income:Decimal(sum of paid dividends$\le \text{as_of}$ ) -
pending_dividend_receivable:Decimal(sum of entitled dividends where$\text{ex_date} \le \text{as_of} < \text{payment_date}$ ) -
dividends:list[ProcessedDividend] -
splits:list[ProcessedStockSplit] -
warnings:list[str]
start_date:datetime(UTC)end_date:datetime(UTC)start_equity:Decimalend_equity_pre_flow:Decimalexternal_cash_flow:Decimalend_equity_post_flow:Decimalsubperiod_return_pct:Decimal
status:Literal["COMPLETE", "INCOMPLETE", "CASH_UNMODELED", "INSUFFICIENT_DATA"]twr_pct:Decimal | Noneannualized_twr_pct:Decimal | Nonesubperiods_count:intsubperiods:list[SubperiodReturn]warnings:list[str]
status:Literal["COMPLETE", "INCOMPLETE", "CASH_UNMODELED", "INSUFFICIENT_DATA"]mwr_pct:Decimal | None(annualized XIRR)cash_flows_count:intterminal_value:Decimal | Noneconverged:booliterations:intwarnings:list[str]
status:Literal["COMPLETE", "INCOMPLETE", "INSUFFICIENT_DATA"]max_drawdown_pct:Decimal | Nonepeak_date:datetime | Nonepeak_equity:Decimal | Nonetrough_date:datetime | Nonetrough_equity:Decimal | Nonerecovery_date:datetime | Nonecurrent_drawdown_pct:Decimal | Noneseries:list[DrawdownPoint]warnings:list[str]
opening_cash:Decimal | Nonenet_deposits_withdrawals:Decimaltotal_external_capital:Decimalrealized_trade_pnl:Decimaldividend_income:Decimalgross_unrealized_pnl:Decimal | Noneopen_lot_acquisition_fees:Decimalreconciled_equity:Decimal | Nonereported_equity:Decimal | Nonereconciliation_difference:Decimal | Noneis_balanced:boolexplanation:str
as_of:datetime(UTC)status:Literal["COMPLETE", "INCOMPLETE", "CASH_UNMODELED"]time_weighted_return:TimeWeightedReturnmoney_weighted_return:MoneyWeightedReturndrawdown:DrawdownMetricscapital_reconciliation:CapitalReconciliationwarnings:list[str]
benchmark_id:str(non-empty identifier, e.g., "SPY", "SPX")timestamp:datetime(UTC normalized)close_level:Decimal(strictly positive, e.g.,485.00)currency:str(ISO 3-letter currency code, e.g., "USD")is_total_return:bool(indicates whether observation series includes reinvested dividends)row_number:int(1-indexed CSV line number)
benchmark_id:strstatus:Literal["COMPLETE", "INCOMPLETE", "INSUFFICIENT_DATA", "CURRENCY_MISMATCH"]measurement_interval_start:datetime | Nonemeasurement_interval_end:datetime | Nonebenchmark_start_level:Decimal | Nonebenchmark_end_level:Decimal | Nonebenchmark_return_pct:Decimal | Noneportfolio_twr_pct:Decimal | Noneexcess_return_pct:Decimal | Nonebeta:Decimal | Nonealpha_periodic_pct:Decimal | Nonealpha_annualized_pct:Decimal | Noneobservations_count:intreturn_type_label:str("TOTAL_RETURN"or"PRICE_RETURN")warnings:list[str]disclaimer:str
DAILY = "DAILY"MONTHLY = "MONTHLY"
COMPLETE = "COMPLETE"INCOMPLETE = "INCOMPLETE"INSUFFICIENT_DATA = "INSUFFICIENT_DATA"MISSING_DATA = "MISSING_DATA"STALE_DATA = "STALE_DATA"CURRENCY_MISMATCH = "CURRENCY_MISMATCH"MISMATCHED_INTERVAL = "MISMATCHED_INTERVAL"
period_start:datetime(UTC)period_end:datetime(UTC)return_pct:Decimal | Nonecurrency:strstatus:PeriodReturnStatussource:str("PORTFOLIO"or"BENCHMARK")metadata:dict[str, Any]warnings:list[str]
frequency:PeriodFrequencyseries_type:str("PORTFOLIO"or"BENCHMARK")observations:list[PeriodReturnObservation]status:PeriodReturnStatuscurrency:strbenchmark_id:str | Nonereturn_type_label:str | Nonemissing_periods_count:intwarnings:list[str]
period_start:datetime(UTC)period_end:datetime(UTC)portfolio_return_pct:Decimal | Nonebenchmark_return_pct:Decimal | Noneexcess_return_pct:Decimal | Nonestatus:PeriodReturnStatusportfolio_status:PeriodReturnStatusbenchmark_status:PeriodReturnStatuswarnings:list[str]
frequency:PeriodFrequencycurrency:strbenchmark_id:strreturn_type_label:strobservations:list[AlignedPeriodReturnObservation]status:PeriodReturnStatusaligned_observations_count:intmissing_portfolio_periods_count:intmissing_benchmark_periods_count:intwarnings:list[str]
frequency:PeriodFrequencyportfolio_series:PeriodReturnSeriesbenchmark_series:PeriodReturnSeries | Nonealigned_series:AlignedPeriodReturnSeries | Nonestatus:PeriodReturnStatuswarnings:list[str]
COMPLETE = "COMPLETE"INCOMPLETE = "INCOMPLETE"INSUFFICIENT_DATA = "INSUFFICIENT_DATA"INSOLVENT = "INSOLVENT"ZERO_VOLATILITY = "ZERO_VOLATILITY"ZERO_DOWNSIDE = "ZERO_DOWNSIDE"ZERO_DRAWDOWN = "ZERO_DRAWDOWN"CURRENCY_MISMATCH = "CURRENCY_MISMATCH"MIXED_FREQUENCY = "MIXED_FREQUENCY"
confidence_level:Decimaltail_probability:Decimalholding_period_days:intvar_pct:Decimal | Nonevar_return_pct:Decimal | Noneexpected_shortfall_pct:Decimal | Nonetail_observations_count:intstatus:RiskMetricsStatuswarnings:list[str]
status:RiskMetricsStatusfrequency:PeriodFrequencyannualization_factor:intrisk_free_rate_ann_pct:Decimaltarget_mar_ann_pct:Decimalsortino_denominator_convention:str("N")observations_total:intobservations_used:intpartial_observations_excluded:intinvalid_observations_excluded:intmean_return_periodic_pct:Decimal | Nonemean_return_annualized_pct:Decimal | Nonevolatility_periodic_pct:Decimal | Nonevolatility_annualized_pct:Decimal | Nonedownside_deviation_periodic_pct:Decimal | Nonedownside_deviation_annualized_pct:Decimal | Nonedownside_deviation_n1_periodic_pct:Decimal | Nonesharpe_ratio:Decimal | Nonesortino_ratio:Decimal | Nonesortino_ratio_n1:Decimal | Nonecalmar_ratio:Decimal | Nonemax_twr_drawdown_pct:Decimal | Nonecagr_pct:Decimal | Nonecalendar_days:float | Nonevar_95:HistoricalVaRMetrics | Nonevar_99:HistoricalVaRMetrics | Nonewarnings:list[str]disclaimers:list[str]
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "OpenTradeLabPortfolioReport",
"type": "object",
"properties": {
"schema_version": { "type": "string", "enum": ["1.0.0"] },
"generated_at": { "type": "string", "format": "date-time" },
"source_file": { "type": "string" },
"summary": {
"type": "object",
"properties": {
"total_records_processed": { "type": "integer" },
"valid_records_count": { "type": "integer" },
"invalid_records_count": { "type": "integer" }
},
"required": ["total_records_processed", "valid_records_count", "invalid_records_count"]
},
"portfolio": {
"type": "object",
"properties": {
"base_currency": { "type": "string" },
"metrics": {
"type": "object",
"properties": {
"total_transactions": { "type": "integer" },
"total_buys": { "type": "integer" },
"total_sells": { "type": "integer" },
"total_volume": { "type": "string" },
"total_fees": { "type": "string" },
"realized_pnl": { "type": "string" },
"closed_trades_count": { "type": "integer" },
"winning_trades_count": { "type": "integer" },
"losing_trades_count": { "type": "integer" },
"breakeven_trades_count": { "type": "integer" },
"win_rate": { "type": ["string", "null"] },
"profit_factor": { "type": ["string", "null"] }
},
"required": [
"total_transactions",
"total_buys",
"total_sells",
"total_volume",
"total_fees",
"realized_pnl",
"closed_trades_count",
"winning_trades_count",
"losing_trades_count",
"breakeven_trades_count"
]
},
"open_positions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"symbol": { "type": "string" },
"quantity": { "type": "string" },
"cost_basis": { "type": "string" },
"average_cost_per_share": { "type": "string" }
},
"required": ["symbol", "quantity", "cost_basis", "average_cost_per_share"]
}
}
},
"required": ["base_currency", "metrics", "open_positions"]
},
"validation_errors": {
"type": "array",
"items": {
"type": "object",
"properties": {
"row_number": { "type": "integer" },
"transaction_id": { "type": ["string", "null"] },
"field": { "type": "string" },
"error_type": { "type": "string" },
"message": { "type": "string" },
"raw_data": { "type": "object" }
},
"required": ["row_number", "field", "error_type", "message", "raw_data"]
}
},
"valuation": { "type": "object" },
"cash_ledger": { "type": "object" },
"equity": { "type": "object" },
"corporate_actions": { "type": "object" },
"performance": { "type": "object" },
"benchmark": { "type": "object" },
"periodic_returns": { "type": "object" },
"risk": { "type": "object" }
},
"required": ["schema_version", "generated_at", "source_file", "summary", "portfolio", "validation_errors"]
}