Quant Journey

Quant Journey

Framework

How to sell stocks wisely - the code for Almgren-Chriss optimal execution

Jakub's avatar
Jakub
Mar 06, 2024
∙ Paid
4
Share

In this post you will read about:

  • The Almgren-Chriss model, which helps investors sell stocks wisely to avoid price drops,

  • How to set up this model in Python for smart stock selling

  • How to do it for single and multiple stocks over a set period.

Imagine you have a big bag of marbles that you want to sell at your school fair. But if you try to sell them all at once, your friends might not have enough pocket money to buy them, and you might have to sell them for a very low price. This is what grown-ups call "market impact."

Now, imagine if you sold just a few marbles each day of the fair. That way, you don't have too many marbles at once, and your friends can buy them without spending all their money. This is what the Almgren-Chriss model helps with — it's like a smart plan that tells you how many marbles to sell each day so you can get the most money by the end of the fair without making it hard for your friends to buy them.

The Framework

The Almgren-Chriss framework, developed by Robert Almgren and Neil Chriss, is a widely used model in the field of optimal execution for portfolio transactions. The aim of the model is to minimize a combination of volatility risk and transaction costs arising from permanent and temporary market impact. In this post, we'll explore how to implement the Almgren-Chriss model in Python for single and multiple assets.

The Almgren-Chriss model is based on a few key assumptions (see also below with equation):

  1. Temporary market impact: The execution of a trade creates a temporary impact on the price of the asset, which decays over time according to a specific function.

  2. Permanent market impact: A portion of the market impact caused by the trade is permanent and persists even after the trade is completed.

  3. Risk aversion: The trader is risk-averse and seeks to minimize the expected cost of execution, accounting for both the trading cost and the risk associated with the unexecuted portion of the order.

The model provides an analytical solution for the optimal trading trajectory, which specifies the rate at which the order should be executed over time to minimize the expected cost. The solution involves a balance between minimizing the market impact costs and minimizing the risk associated with the unexecuted portion of the order.

Model for Single Asset

First, we illustrate the Almgren-Chriss model numerically using financial data for a single asset. Specifically, we analyze the optimal daily liquidation trajectory using the daily historical prices of Apple over the past six months.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import yfinance as yf
from scipy.linalg import sqrtm

# Download AAPL for last 6 months
data = yf.Ticker('AAPL').history(period='6mo')

Let’s check code just to see the price and volume:

def plot_stock(data, volume, label, title):
    # Create figure and axes for the subplots
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))

    # Plot closing prices on the first subplot
    ax1.plot(data.index, data, label=label)
    ax1.set_ylabel(label, fontsize=12)
    ax1.tick_params(axis='x', labelbottom=False)  # Hide x labels
    ax1.set_title(title, fontsize=14)

    # Plot trading volume on the second subplot
    ax2.bar(data.index, volume, width=1, color="navy")
    ax2.set_ylabel('Volume', fontsize=12)

    # Adjust spacing and layout
    plt.subplots_adjust(hspace=0.5)  # Adjust space between plots
    fig.tight_layout()  # Adjust layout

    plt.show()

# plot_stock(data['Close'], data['Volume'], 'Price (close)', 'AAPL Stock Data')

AlmgrenChriss Single Class

Let's start by building a class for Almgren-Chriss and explaining all the important parameters:

import numpy as np
import datetime

class AlmgrenChriss_Single:
    """
    Implementation of a simplified Almgren-Chriss optimal execution model for single asset transactions.

    This class provides methods to calculate the optimal trading trajectory for liquidating a position
    in a single asset over a specified time horizon. It aims to minimize the expected cost of execution
    taking into account market impact and risk aversion.

    The model simplifies market impact into temporary and permanent components and assumes risk aversion
    is linearly related to the square of the execution speed.

    Parameters are adjusted to include a simplified market impact model without explicit reference to
    trading days, hours, or specific asset details.
    """

    def __init__(self, params):
        """
        Initialize the Almgren-Chriss optimal execution model with specified parameters.

        Args:
            params (dict): Dictionary containing model parameters:
                - lambda (float): The temporary market impact parameter.
                - sigma (float): The volatility of the asset.
                - epsilon (float): The fixed cost of trading.
                - eta (float): The permanent market impact parameter.
                - gamma (float): The risk aversion parameter.
                - tau (float): The time interval between trades.
        """
        self._lambda = params['lambda']
        self._sigma = params['sigma']
        self._epsilon = params['epsilon']
        self._eta = params['eta']
        self._gamma = params['gamma']
        self._tau = params['tau']

        self._eta_tilda = self._eta - 0.5*self._gamma*self._tau

        assert self._eta_tilda > 0, "Model assumption violated: Adjusted permanent impact must be positive."

				# Parameter noted as (1) below
        self._kappa_tilda_squared = (self._lambda * self._sigma ** 2) / self._eta_tilda
        self._kappa = np.arccosh(0.5 * (self._kappa_tilda_squared * self._tau ** 2) + 1) / self._tau

    def trajectory(self, X, T):
        """
        Calculate the optimal liquidation trajectory for the given position over the specified time horizon.

        Args:
            X (int): The total number of shares to be liquidated.
            T (int): The total time horizon for liquidation in discrete intervals.

        Returns:
            np.array: An array of integers representing the number of shares to hold after each time interval.
        """

				# Parameter noted as (2) below
        holds = [int(np.sinh(self._kappa * (T - t)) / np.sinh(self._kappa * T) * X) for t in range(T)]
        holds.append(0)  # Ensure complete liquidation by the end of the horizon
        return np.array(holds)

    def strategy(self, X, T):
        """
        Calculate the optimal liquidation trade list based on the Almgren-Chriss model.

        Args:
            X (int): The total number of shares to be liquidated.
            T (int): The total time horizon for liquidation in discrete intervals.

        Returns:
            np.array: An array representing the number of shares to execute at each time interval.
        """
        return np.diff(self. Trajectory(X, T))

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2025 Quant Journey with Code
Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture