Skip to content

Returns & price transforms

Cumulative return and per-bar simple return on BTCUSDT 5m

The plumbing every other calculation sits on: turning a price series into returns, and collapsing an OHLC bar into a single representative price. Small on purpose — but getting the convention right (percent vs. log, period vs. cumulative) matters, because everything downstream inherits it.

Returns

  • daily_return — simple percentage return bar-to-bar. Intuitive; what a P&L statement shows.
  • daily_log_return — the log return. Log returns add up over time and are closer to normally distributed, which is why volatility and risk models are usually built on them.
  • cumulative_return — total growth since the first bar; the equity-curve view.

Which return should I use?

Use log returns for anything statistical (volatility, correlation, factor models — they're time-additive), and simple returns when you need an actual P&L or want to compound across assets in a portfolio.

Price transforms

Four conventional ways to collapse the four OHLC columns into one price. Each is a one-liner, but a named, conventional one-liner — and several indicators are formally defined on one of them, so being able to say typical_price() beats re-deriving (h + l + c) / 3 at every call site.

  • typical_price(high + low + close) / 3. The standard summary, and the input CCI and the Money Flow Index are defined on. Start here.
  • median_price(high + low) / 2. The midpoint of the range, indifferent to where the bar opened or closed.
  • weighted_close_price(high + low + 2*close) / 4. Double-weights the close, on the view that where a bar settled says more than where it merely traded.
  • average_price(open + high + low + close) / 4. The only one that uses the open, so it reflects the whole bar.

All four take the conventional column names by default, so typical_price() works on a standard OHLC frame with no arguments.


polars_ta.others

Return-based indicators and OHLC price transforms.

Two small families that are plumbing rather than signals:

  • Returns — turning a price series into simple, log, or cumulative returns. Getting the convention right matters because everything downstream inherits it.
  • Price transforms — collapsing the four OHLC columns into one representative price. These are one-liners, but they are named, conventional one-liners: typical_price is the input CCI and the Money Flow Index are defined on, so being able to name it directly keeps a pipeline readable instead of re-deriving (h + l + c) / 3 at each call site.

daily_return

daily_return(close: str | Expr, fillna: bool = False) -> Expr

Daily percentage return, in percent.

Source code in polars_ta/others.py
21
22
23
24
25
def daily_return(close: str | pl.Expr, fillna: bool = False) -> pl.Expr:
    """Daily percentage return, in percent."""
    close = as_expr(close)
    dr = (close / close.shift(1) - 1.0) * 100.0
    return BaseIndicator.check_fillna(dr, fillna, value=0)

daily_log_return

daily_log_return(close: str | Expr, fillna: bool = False) -> Expr

Daily logarithmic return, in percent.

Source code in polars_ta/others.py
28
29
30
31
32
def daily_log_return(close: str | pl.Expr, fillna: bool = False) -> pl.Expr:
    """Daily logarithmic return, in percent."""
    close = as_expr(close)
    dlr = (close / close.shift(1)).log() * 100.0
    return BaseIndicator.check_fillna(dlr, fillna, value=0)

cumulative_return

cumulative_return(close: str | Expr, fillna: bool = False) -> Expr

Cumulative return since the first observation, in percent.

Source code in polars_ta/others.py
35
36
37
38
39
def cumulative_return(close: str | pl.Expr, fillna: bool = False) -> pl.Expr:
    """Cumulative return since the first observation, in percent."""
    close = as_expr(close)
    cr = (close / close.first() - 1.0) * 100.0
    return BaseIndicator.check_fillna(cr, fillna, value=0)

average_price

average_price(open_: str | Expr = 'open', high: str | Expr = 'high', low: str | Expr = 'low', close: str | Expr = 'close') -> Expr

Average Price — (open + high + low + close) / 4.

The only transform that uses the open, so it is the one that reflects the whole bar rather than just its range and close.

Source code in polars_ta/others.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def average_price(
    open_: str | pl.Expr = "open",
    high: str | pl.Expr = "high",
    low: str | pl.Expr = "low",
    close: str | pl.Expr = "close",
) -> pl.Expr:
    """Average Price — `(open + high + low + close) / 4`.

    The only transform that uses the open, so it is the one that reflects the
    whole bar rather than just its range and close.
    """
    open_, high, low, close = (
        as_expr(open_),
        as_expr(high),
        as_expr(low),
        as_expr(close),
    )
    return (open_ + high + low + close) / 4.0

median_price

median_price(high: str | Expr = 'high', low: str | Expr = 'low') -> Expr

Median Price — (high + low) / 2, the midpoint of the bar's range.

Source code in polars_ta/others.py
73
74
75
76
77
78
def median_price(
    high: str | pl.Expr = "high", low: str | pl.Expr = "low"
) -> pl.Expr:
    """Median Price — `(high + low) / 2`, the midpoint of the bar's range."""
    high, low = as_expr(high), as_expr(low)
    return (high + low) / 2.0

typical_price

typical_price(high: str | Expr = 'high', low: str | Expr = 'low', close: str | Expr = 'close') -> Expr

Typical Price — (high + low + close) / 3.

The standard "one price per bar" summary, and the input CCI and the Money Flow Index are defined on.

Source code in polars_ta/others.py
81
82
83
84
85
86
87
88
89
90
91
92
def typical_price(
    high: str | pl.Expr = "high",
    low: str | pl.Expr = "low",
    close: str | pl.Expr = "close",
) -> pl.Expr:
    """Typical Price — `(high + low + close) / 3`.

    The standard "one price per bar" summary, and the input CCI and the Money
    Flow Index are defined on.
    """
    high, low, close = as_expr(high), as_expr(low), as_expr(close)
    return (high + low + close) / 3.0

weighted_close_price

weighted_close_price(high: str | Expr = 'high', low: str | Expr = 'low', close: str | Expr = 'close') -> Expr

Weighted Close Price — (high + low + 2 * close) / 4.

Like typical_price but double-weighting the close, on the view that where a bar settled says more than where it traded.

Source code in polars_ta/others.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def weighted_close_price(
    high: str | pl.Expr = "high",
    low: str | pl.Expr = "low",
    close: str | pl.Expr = "close",
) -> pl.Expr:
    """Weighted Close Price — `(high + low + 2 * close) / 4`.

    Like `typical_price` but double-weighting
    the close, on the view that where a bar settled says more than where it
    traded.
    """
    high, low, close = as_expr(high), as_expr(low), as_expr(close)
    return (high + low + 2.0 * close) / 4.0