Skip to content
All library documents

OLMAR Online Portfolio Rebalancing with Moving Average Reversion

Code Zipline

Summary

The document implements Online Portfolio Moving Average Reversion (OLMAR), a portfolio strategy that adjusts asset weights using relative moving-average prices. For each stock, it divides the window’s average price by the current price, then compares each asset’s relative value with the cross-asset average. A step size uses the current portfolio’s expected return, a target parameter, and the dispersion of those relative deviations to update weights.

The updated weights are projected onto the nonnegative unit simplex, keeping allocations long-only and summing to one. The algorithm then calculates target share amounts from portfolio value and current prices and submits trades to reach them. The example begins with equal weights and includes a seven-stock universe, a five-day default window, and a portfolio-value plot. It is an implementation example rather than an evaluation: it gives no performance results, and its fixed universe, trading assumptions, and unexamined transaction costs limit what can be inferred about live suitability.

Key ideas

  • OLMAR estimates relative price deviations using moving averages divided by current prices.
  • Portfolio weights shift according to the deviation from the cross-asset mean and a target parameter.
  • Simplex projection keeps weights nonnegative and makes their sum equal one.
  • Target holdings are calculated from desired weights, portfolio value, and current prices.
  • The code illustrates a strategy implementation but does not report performance evidence.

Tags

Full text
# olmar.py


```py
import logging
import sys

import numpy as np

from zipline.finance import commission, slippage

# zipline_logging = logging.getLogger("zipline_logging")
# zipline_logging.addHandler(logging.NullHandler())
# zipline_logging.addHandler(
#     logging.StreamHandler(sys.stdout).setLevel(logging.INFO),
# )
# zipline_logging.addHandler(logging.StreamHandler(sys.stderr).setLevel(logging.ERROR))


STOCKS = ["AMD", "CERN", "COST", "DELL", "GPS", "INTC", "MMM"]


# On-Line Portfolio Moving Average Reversion


# More info can be found in the corresponding paper:
# http://icml.cc/2012/papers/168.pdf
def initialize(algo, eps=1, window_length=5):
    algo.stocks = STOCKS
    algo.sids = [algo.symbol(symbol) for symbol in algo.stocks]
    algo.m = len(algo.stocks)
    algo.price = {}
    algo.b_t = np.ones(algo.m) / algo.m
    algo.last_desired_port = np.ones(algo.m) / algo.m
    algo.eps = eps
    algo.init = True
    algo.days = 0
    algo.window_length = window_length

    algo.set_commission(commission.PerShare(cost=0, min_trade_cost=1.0))
    algo.set_slippage(slippage.VolumeShareSlippage())


def handle_data(algo, data):
    algo.days += 1
    if algo.days < algo.window_length:
        return

    if algo.init:
        rebalance_portfolio(algo, data, algo.b_t)
        algo.init = False
        return

    m = algo.m

    x_tilde = np.zeros(m)

    # find relative moving average price for each asset
    mavgs = data.history(algo.sids, "price", algo.window_length, "1d").mean()
    for i, sid in enumerate(algo.sids):
        price = data.current(sid, "price")
        # Relative mean deviation
        x_tilde[i] = mavgs[sid] / price

    ###########################
    # Inside of OLMAR (algo 2)
    x_bar = x_tilde.mean()

    # market relative deviation
    mark_rel_dev = x_tilde - x_bar

    # Expected return with current portfolio
    exp_return = np.dot(algo.b_t, x_tilde)
    weight = algo.eps - exp_return
    variability = (np.linalg.norm(mark_rel_dev)) ** 2

    # test for divide-by-zero case
    if variability == 0.0:
        step_size = 0
    else:
        step_size = max(0, weight / variability)

    b = algo.b_t + step_size * mark_rel_dev
    b_norm = simplex_projection(b)
    np.testing.assert_almost_equal(b_norm.sum(), 1)

    rebalance_portfolio(algo, data, b_norm)

    # update portfolio
    algo.b_t = b_norm


def rebalance_portfolio(algo, data, desired_port):
    # rebalance portfolio
    desired_amount = np.zeros_like(desired_port)
    current_amount = np.zeros_like(desired_port)
    prices = np.zeros_like(desired_port)

    if algo.init:
        positions_value = algo.portfolio.starting_cash
    else:
        positions_value = algo.portfolio.positions_value + algo.portfolio.cash

    for i, sid in enumerate(algo.sids):
        current_amount[i] = algo.portfolio.positions[sid].amount
        prices[i] = data.current(sid, "price")

    desired_amount = np.round(desired_port * positions_value / prices)

    algo.last_desired_port = desired_port
    diff_amount = desired_amount - current_amount

    for i, sid in enumerate(algo.sids):
        algo.order(sid, diff_amount[i])


def simplex_projection(v, b=1):
    r"""Projection vectors to the simplex domain

    Implemented according to the paper: Efficient projections onto the
    l1-ball for learning in high dimensions, John Duchi, et al. ICML 2008.
    Implementation Time: 2011 June 17 by Bin@libin AT pmail.ntu.edu.sg
    Optimization Problem: min_{w}\| w - v \|_{2}^{2}
    s.t. sum_{i=1}^{m}=z, w_{i}\geq 0

    Input: A vector v \in R^{m}, and a scalar z > 0 (default=1)
    Output: Projection vector w

    :Example:
    >>> proj = simplex_projection([.4 ,.3, -.4, .5])
    >>> proj  # doctest: +NORMALIZE_WHITESPACE
    array([ 0.33333333, 0.23333333, 0. , 0.43333333])
    >>> print(proj.sum())
    1.0

    Original matlab implementation: John Duchi (jduchi@cs.berkeley.edu)
    Python-port: Copyright 2013 by Thomas Wiecki (thomas.wiecki@gmail.com).
    """

    v = np.asarray(v)
    p = len(v)

    # Sort v into u in descending order
    v = (v > 0) * v
    u = np.sort(v)[::-1]
    sv = np.cumsum(u)

    rho = np.where(u > (sv - b) / np.arange(1, p + 1))[0][-1]
    theta = np.max([0, (sv[rho] - b) / (rho + 1)])
    w = v - theta
    w[w < 0] = 0
    return w


def analyze(context=None, results=None):
    import matplotlib.pyplot as plt

    fig = plt.figure()
    ax = fig.add_subplot(111)
    results.portfolio_value.plot(ax=ax)
    ax.set_ylabel("Portfolio value (USD)")
    plt.show()


def _test_args():
    """Extra arguments to use when zipline's automated tests run this example."""
    import pandas as pd

    return {"start": pd.Timestamp("2004"), "end": pd.Timestamp("2008")}

```

Shown in full with attribution under the source's licence. Licence: Apache-2.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.