Price gap fill
The price gap fill
A Held Price is the last priced observation of an asset, carried forward across a gap. Stating one is what PriceGapFill is for: it is the ingestion layer's only fill, it is off unless a caller adds it, and it exists to state a price convention across a suspension rather than to remove a gap — a gap is carried through the conversion, so nothing downstream needs it gone.
The fill is bounded by the Listing Span, so it touches Held Gaps alone and can never fabricate a price where an asset was not yet listed or has been delisted. It is fitted on a training window and replayed: CarriedPrice records the last observed training price, which seeds a carry-forward on a window that opens inside a gap, and a Num_VecToScaM records the reduction of that window's observed prices. A per-asset constant manufactures two moves the market never printed, which is why the carried price is the convention a caller reaching for a fill normally wants.
It runs at the price level, before PricesToReturns, because a carried price cannot be stated after the conversion: zeroing the returns a gap left non-finite discards the move across the gap entirely. A filled cell is therefore finite in the returns and its estimation mask entry is true — a caller who filled has said the asset traded. See docs/adr/0130-a-universe-policy-is-fitted-and-the-only-fill-is-a-span-bounded-price-convention.md.
PortfolioOptimisers.CarriedPrice — Type
struct CarriedPriceStates that a price did not move across a gap, so the last priced observation is held forward.
CarriedPrice is the Held Price convention: inside a Held Gap the price is the last observed one, so the returns across the gap are flat and the whole move lands on the observation that ends it. Wealth is conserved, which a per-asset constant cannot do — filling a suspension with an asset's median manufactures one large move into the gap and another out of it.
It names a convention rather than a value, so it is the one member of PriceGapFill's fill slot that is not a number: the value it fills with is read off the data, and the fitted state is only the seed a later window that opens inside a gap starts from.
Constructors
CarriedPrice() -> CarriedPriceExamples
julia> CarriedPrice()CarriedPrice()Related
PortfolioOptimisers.PriceGapFill — Type
struct PriceGapFill{__T_fill, __T_strict, __T_cache} <: AbstractPricesPreprocessingEstimatorFills the price gaps inside an asset's listing with a stated convention, and touches nothing outside it.
PriceGapFill is the ingestion layer's one fill, and it is off unless a caller adds it. A fill exists to state a price convention across a suspension or a holiday, not to remove a gap: a gap is carried through the conversion, so nothing downstream needs it gone. The fill is bounded by the Listing Span, so it touches Held Gaps alone and can never fabricate a price where an asset was not yet listed or has been delisted.
It runs at the price level, before PricesToReturns, because a carried price is a statement about a price and cannot be expressed after the conversion: across a gapped run p₀, _, _, p₃ the unfilled returns are all non-finite, and zeroing them discards the p₀ → p₃ move entirely. Carried forward at the price level the same run gives 0, 0, p₃/p₀ - 1. A filled cell is therefore finite in the returns and its estimation mask entry is true, which is the design rather than an oversight: a caller who filled has said the asset traded.
Algorithm
Fit
- For each asset column, collect the observed prices of the training window. An entry
is_missing_valueaccepts is left out. - Skip an asset whose column holds no observed price. It gets no fitted value, and no entry in the result, so it is left untouched at apply time.
- Reduce the observed prices of the column to one value with
PortfolioOptimisers.gap_fill_seed, giving that asset's fitted value. - Return a
PriceGapFillResultholding the fitted asset names, their values, the last timestamp of the training window, the convention andstrict.
Apply
- Copy the price values of the window, so the input is not mutated.
- Read the Listing Span that bounds the fill with
PortfolioOptimisers.gap_fill_span. A carrier that states none bounds the fill by nothing, so no cell is written. - Find the first observation of the window after the training window's end,
t0. On the training window itself there is none; on a window that follows it,t0is the first row. - For each fitted asset name, find its column in the window. Skip a name the window does not carry.
- Run the convention forward through that column with
PortfolioOptimisers.gap_fill_column!, seeded by the fitted value fromt0and bounded by the span. - Rebuild
Xfrom the filled values, keeping the timestamps and the column names, then rebuild thePricesResult. Every other field passes through untouched.
The fitted value is a seed, and under CarriedPrice it is only that: a window that opens inside a gap after the training window fills from the last observed training price, and every later gap of that window fills from the most recent price the window itself observed. The seed is the last training price, so it precedes every observation it is written onto, and it is written onto none before that: on the training window itself, which a Pipeline transforms with the step it just fitted, a gap that opens the window has no price before it and stays a Held Gap rather than taking a price from the window's end. So no fill reads the future, and the convention still tracks the data it is replayed on.
Fields
fill: The convention a gap inside the Listing Span takes.CarriedPriceholds the last priced observation forward; aNum_VecToScaMreduces the asset's observed training prices to the one value every gap of that column takes.
strict: Whether a price carrier that states no Listing Span is refused (true) or warned about (false).
cache: Optional partial-fit state. It isnothinguntilpartial_fit!writes one, and the estimator's read-out verb reads it when the caller gives no data matrix. Each propagation channel does one thing with it:factorycarries it unchanged, because a factory call resolves configuration rather than the sample;port_opt_viewslices it to the selected assets by index copy, so the viewed estimator answers over those assets alone; andobs_weights_viewdrops it, because no slice of a state exists on the observation axis. A family whose state has no exact asset slice drops it on both axes and names the reason.
Constructors
PriceGapFill(; fill::Union{CarriedPrice, Num_VecToScaM} = CarriedPrice(), strict::Bool = false, cache::Option{<:AbstractPartialFitState} = nothing,) -> PriceGapFillKeywords correspond to the struct's fields.
Online form
A PriceGapFill with a CarriedPrice fill takes the online step: partial_fit_transform fills a block of prices from the carried prices and advances them, and fit_preprocessing with no data reads the PriceGapFillResult of the whole history out of cache. A statistic fill has no online form, because a longer window re-prices every earlier gap; supports_partial_fit answers false for it, and a Pipeline refuses it at warm-up by name.
Examples
julia> X = TimeArray(Date(2020, 1, 1):Day(1):Date(2020, 1, 4), [100.0 1.0; NaN 3.0; NaN 5.0; 130.0 7.0], ["A", "B"]);julia> res = fit_preprocessing(PriceGapFill(), PricesResult(; X = X));julia> res.v2-element Vector{Float64}: 130.0 7.0Related
PortfolioOptimisers.PriceGapFillResult — Type
struct PriceGapFillResult{__T_nx, __T_v, __T_te, __T_fill, __T_strict} <: AbstractPricesPreprocessingResultFitted result of a PriceGapFill.
Carries the per-asset value fitted on the training window, the timestamp that window ended on, and the convention the value is read under. One result type serves both conventions, as AssetSelectorResult serves the whole selector family: the convention is a field rather than a second type, because it is what apply_preprocessing dispatches on and nothing else about the two differs.
Fields
nx: Names of the assets with a fitted value.
v: Fitted values, aligned withnx. The last observed training price underCarriedPrice, and the reduced scalar under aNum_VecToScaM.
te: The last timestamp of the training window. UnderCarriedPricethe seed invis written onto an observation after it and onto none at or before it, so a window that opens inside a gap fills from the seed only when the seed precedes it; aNum_VecToScaMreads it by nothing.
fill: The conventionvis read under, copied from the estimator.
strict: Whether a price carrier that states no Listing Span is refused (true) or warned about (false).
Related