Prices to returns: private API

Functions

PortfolioOptimisers.append_carrier_block!Function
append_carrier_block!(P::DataFrames.DataFrame, A::Nothing, ts, sym::Symbol) -> Vector{String}
append_carrier_block!(P::DataFrames.DataFrame, A::TimeSeries.TimeArray, ts, sym::Symbol) -> Vector{String}

Lay one of the carrier's price blocks beside the asset block, on the asset clock.

The carrier states one clock, so a factor or benchmark series is read at the asset timestamps rather than joined onto them: a join adds or drops observations, which is a clock move, and every clock move belongs to price_ingestion. Laying the columns out one by one also frees prices_to_returns of TimeSeries.merge's one-value-type requirement, so a Float32 factor table beside a Float64 asset table converts instead of raising a MethodError.

Algorithm

  1. A block that is nothing contributes no column and no name.
  2. Otherwise check that the block states the asset clock, and refuse by name if it does not.
  3. Spell every absent price NaN with unify_gaps, the verb the ingestion door runs. It is idempotent on a carrier the layer built, and it is what makes a hand-built carrier holding missing convert like one holding NaN.
  4. Write each of the block's columns into P under its own name, and return the names in order.

Arguments

  • P: The table the conversion is assembling, already carrying the clock and the asset columns.
  • A: The factor or benchmark price series, or nothing.
  • ts: The asset timestamps, which are the carrier's clock.
  • sym: The block's name in the refusal, :F or :B.

Validation

Returns

  • n::Vector{String}: The block's column names, empty when the block is nothing.

Related

source
PortfolioOptimisers.gap_return_writableFunction
gap_return_writable(
    p::AbstractVector,
    r::AbstractVector
) -> BitVector

Derive the cells of one column a Gap Return algorithm is allowed to write.

This is the family's invariant, held once rather than re-argued per algorithm. apply_gap_return restores every cell outside the returned set, so no algorithm can rewrite a return computed from two observed prices, manufacture one before an asset's first price, or resurrect a delisting.

The bounds are the Span Rule and its projection, the same ones listing_span and PortfolioOptimisers.project_span state for a whole panel. They are read here off the one price column the conversion is holding, because the writable set is per column and the table reaching prices_to_returns's conversion step is the filtered one rather than the caller's.

Algorithm

  1. Read the offset between the two clocks as length(p) - length(r), which is 0 when padding kept the first observation and 1 when it did not. Return cell j is then the change onto price row j + off.
  2. Locate the column's Listing Span on the price clock: the first observed price and the last. A column with no observed price admits nothing.
  3. Admit return cell j when its price row lies in [first + 1, last] — the span projected onto the returns clock, since a return consumes the earlier price of its pair — and the default rule left the cell non-finite.

Arguments

  • p: One column's prices along the observation axis, gaps included.
  • r: The returns TimeSeries.percentchange computed from p.

Returns

  • w::BitVector: The same length as r, true on the cells an algorithm may write.

Related

source
PortfolioOptimisers.gap_return_valueFunction
gap_return_value(
    ret_method::Symbol,
    pt::Number,
    p0::Number
) -> Any

Compute one return from a pair of prices that need not be consecutive.

The one place the ret_method branches are spelled for the Gap Return family, so a new algorithm states which pair of prices it reads and never which formula turns them into a return. It mirrors TimeSeries.percentchange, which computes both branches through logarithms, so a value written here sits on the same arithmetic as the cells around it.

Arguments

  • ret_method: :simple or :log.
  • pt: The later price.
  • p0: The earlier price, the return's anchor.

Returns

  • r::Number: $\ln P_t - \ln P_0$ under :log, and expm1 of it otherwise.

Related

source
PortfolioOptimisers.apply_gap_returnFunction
apply_gap_return(alg::Nothing, R::DataFrames.DataFrame, P::DataFrames.DataFrame, ret_method::Symbol) -> DataFrames.DataFrame
apply_gap_return(alg::AbstractGapReturnAlgorithm, R::DataFrames.DataFrame, P::DataFrames.DataFrame, ret_method::Symbol) -> DataFrames.DataFrame

Apply the gap_return_alg given to prices_to_returns to the converted table.

The seam that keeps the family optional and holds its invariant. nothing is the default path, and its method returns the table untouched, so the arithmetic TimeSeries.percentchange produced is bit-identical to what it was before the family existed.

The rule is per-column arithmetic on consecutive observations and reads no asset axis, so it applies to every series of the converted table alike — asset, factor and benchmark.

Algorithm

  1. Walk the series columns of R, taking each column's prices from P by name.
  2. Derive the writable cells with gap_return_writable.
  3. Call gap_return on the column and copy back only the writable cells, so every other cell is frozen whatever the algorithm returned.
  4. Report an @info when no column admitted a single cell. A table that holds no gap admits none, which is the ordinary case rather than a mistake, so this is neither a refusal, which would reject a configuration that computes a correct answer, nor a warning, which could not tell that case from one where the caller expected a gap.

Arguments

  • alg: The Gap Return algorithm, or nothing for the default rule.
  • R: The converted table, :timestamp first and one column per series.
  • P: The price table reaching the conversion, with the same series columns.
  • ret_method: :simple or :log.

Returns

  • R::DataFrames.DataFrame: The converted table, with the writable cells resolved.

Related

source