The disagg dataset (2019–2026, 468 markets) was in the DB but invisible.
This wires it into every layer of the app:
Backend:
- models.py: add has_disagg to CommodityMeta; add DisaggPositionPoint,
DisaggHistoryResponse, DisaggScreenerRow models
- commodities.py: join disagg_reports to populate has_disagg flag and
correct first/last dates; HAVING filter removes markets with no data
- disagg.py (new): /api/disagg/{code}/history, /api/disagg/screener,
/api/disagg/{code}/net-position-percentile, /api/disagg/compare
- main.py: register disagg router
Frontend:
- Metric selector shows Disaggregated optgroup (Managed Money, Prod/Merchant,
Swap Dealer, Other Rept) when market has has_disagg=true, hides Legacy group
- Detail view auto-switches to disagg endpoint and defaults to m_money_net
for disagg markets; shows green "Disaggregated" badge
- Screener always uses disagg endpoint (Managed Money percentile rank)
- Compare uses /api/disagg/compare for disagg metrics
- style.css: add .badge-disagg green variant
Result: wheat markets (SRW, HRW, HRSpring, Black Sea) now show 7 years of
disaggregated positioning data with Managed Money as the default metric.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
191 lines
4.7 KiB
Python
191 lines
4.7 KiB
Python
from typing import Optional
|
|
from pydantic import BaseModel
|
|
|
|
|
|
class CommodityMeta(BaseModel):
|
|
cftc_code: str
|
|
name: str
|
|
exchange: str
|
|
exchange_abbr: str
|
|
contract_unit: Optional[str]
|
|
first_date: Optional[str]
|
|
last_date: Optional[str]
|
|
week_count: int
|
|
has_disagg: bool = False
|
|
|
|
|
|
class ExchangeInfo(BaseModel):
|
|
exchange_abbr: str
|
|
exchange: str
|
|
commodity_count: int
|
|
|
|
|
|
class PositionPoint(BaseModel):
|
|
report_date: str
|
|
open_interest: Optional[int]
|
|
noncomm_long: Optional[int]
|
|
noncomm_short: Optional[int]
|
|
noncomm_spreading: Optional[int]
|
|
noncomm_net: Optional[int]
|
|
comm_long: Optional[int]
|
|
comm_short: Optional[int]
|
|
comm_net: Optional[int]
|
|
nonrept_long: Optional[int]
|
|
nonrept_short: Optional[int]
|
|
nonrept_net: Optional[int]
|
|
chg_open_interest: Optional[int]
|
|
chg_noncomm_long: Optional[int]
|
|
chg_noncomm_short: Optional[int]
|
|
chg_comm_long: Optional[int]
|
|
chg_comm_short: Optional[int]
|
|
pct_noncomm_long: Optional[float]
|
|
pct_noncomm_short: Optional[float]
|
|
pct_comm_long: Optional[float]
|
|
pct_comm_short: Optional[float]
|
|
traders_total: Optional[int]
|
|
traders_noncomm_long: Optional[int]
|
|
traders_noncomm_short: Optional[int]
|
|
traders_comm_long: Optional[int]
|
|
traders_comm_short: Optional[int]
|
|
|
|
|
|
class HistoryResponse(BaseModel):
|
|
commodity: CommodityMeta
|
|
row_type: str
|
|
data: list[PositionPoint]
|
|
|
|
|
|
class LatestRowData(BaseModel):
|
|
row_type: str
|
|
positions: PositionPoint
|
|
concentration: Optional[dict]
|
|
|
|
|
|
class LatestResponse(BaseModel):
|
|
commodity: CommodityMeta
|
|
report_date: str
|
|
rows: list[LatestRowData]
|
|
|
|
|
|
class ExtremePoint(BaseModel):
|
|
value: Optional[float]
|
|
date: Optional[str]
|
|
|
|
|
|
class ExtremesResponse(BaseModel):
|
|
cftc_code: str
|
|
commodity: str
|
|
noncomm_net: dict
|
|
open_interest: dict
|
|
comm_net: dict
|
|
|
|
|
|
class ScreenerRow(BaseModel):
|
|
cftc_code: str
|
|
commodity: str
|
|
exchange: str
|
|
latest_date: str
|
|
noncomm_net: Optional[int]
|
|
open_interest: Optional[int]
|
|
pct_rank: Optional[float]
|
|
chg_noncomm_long: Optional[int]
|
|
chg_noncomm_short: Optional[int]
|
|
|
|
|
|
class ComparePoint(BaseModel):
|
|
report_date: str
|
|
value: Optional[float]
|
|
|
|
|
|
class CompareResponse(BaseModel):
|
|
metric: str
|
|
commodities: list[CommodityMeta]
|
|
series: dict[str, list[ComparePoint]]
|
|
|
|
|
|
class PercentileResponse(BaseModel):
|
|
cftc_code: str
|
|
commodity: str
|
|
current_net: Optional[int]
|
|
percentile: Optional[float]
|
|
z_score: Optional[float]
|
|
lookback_weeks: int
|
|
period_min: Optional[int]
|
|
period_max: Optional[int]
|
|
|
|
|
|
class DisaggPositionPoint(BaseModel):
|
|
report_date: str
|
|
open_interest: Optional[int]
|
|
prod_merc_long: Optional[int]
|
|
prod_merc_short: Optional[int]
|
|
prod_merc_net: Optional[int]
|
|
swap_long: Optional[int]
|
|
swap_short: Optional[int]
|
|
swap_spread: Optional[int]
|
|
swap_net: Optional[int]
|
|
m_money_long: Optional[int]
|
|
m_money_short: Optional[int]
|
|
m_money_spread: Optional[int]
|
|
m_money_net: Optional[int]
|
|
other_rept_long: Optional[int]
|
|
other_rept_short: Optional[int]
|
|
other_rept_net: Optional[int]
|
|
nonrept_long: Optional[int]
|
|
nonrept_short: Optional[int]
|
|
nonrept_net: Optional[int]
|
|
chg_open_interest: Optional[int]
|
|
chg_m_money_long: Optional[int]
|
|
chg_m_money_short: Optional[int]
|
|
chg_prod_merc_long: Optional[int]
|
|
chg_prod_merc_short: Optional[int]
|
|
chg_swap_long: Optional[int]
|
|
chg_swap_short: Optional[int]
|
|
pct_open_interest: Optional[float]
|
|
pct_m_money_long: Optional[float]
|
|
pct_m_money_short: Optional[float]
|
|
pct_prod_merc_long: Optional[float]
|
|
pct_prod_merc_short: Optional[float]
|
|
pct_swap_long: Optional[float]
|
|
pct_swap_short: Optional[float]
|
|
traders_total: Optional[int]
|
|
traders_m_money_long: Optional[int]
|
|
traders_m_money_short: Optional[int]
|
|
traders_prod_merc_long: Optional[int]
|
|
traders_prod_merc_short: Optional[int]
|
|
|
|
|
|
class DisaggHistoryResponse(BaseModel):
|
|
commodity: CommodityMeta
|
|
row_type: str
|
|
data: list[DisaggPositionPoint]
|
|
|
|
|
|
class DisaggScreenerRow(BaseModel):
|
|
cftc_code: str
|
|
commodity: str
|
|
exchange: str
|
|
latest_date: str
|
|
m_money_net: Optional[int]
|
|
open_interest: Optional[int]
|
|
pct_rank: Optional[float]
|
|
chg_m_money_long: Optional[int]
|
|
chg_m_money_short: Optional[int]
|
|
|
|
|
|
class ReportDateInfo(BaseModel):
|
|
date: str
|
|
commodity_count: int
|
|
|
|
|
|
class ReportSnapshotRow(BaseModel):
|
|
cftc_code: str
|
|
commodity: str
|
|
exchange: str
|
|
open_interest: Optional[int]
|
|
noncomm_net: Optional[int]
|
|
comm_net: Optional[int]
|
|
pct_noncomm_long: Optional[float]
|
|
pct_noncomm_short: Optional[float]
|
|
traders_total: Optional[int]
|