Trading Risk, Margin Modelling and the Standard Initial Margin Model (SIMM)

To mitigate credit risk in trading, one or both parties may be required to post both initial and variation margin. While initial margin is posted by both counterparties at the inception of the trade, variation margin is periodically exchanged (daily or even intra-daily) during the life of the trade due to changes in the mark to market value of the position.

The margin protects against a counterparty defaulting if the trade doesn’t move in their favour. In the case of centrally cleared derivatives, an intermediary manages the margining requirements of the counterparties. For OTC derivatives which are reasonably standardized, central clearing may still be a regulatory requirement. However, for OTC derivatives that are bespoke and illiquid, determining appropriate margining requirements may be more challenging due to the difficulty in regular valuation of the contracts. Margining requirements have become more stringent since the GFC.

While one does not want to be inadequately protected against non-payment by a counterparty, excessive margining also imposes costs on the derivatives markets. The immediate questions is, how does one estimate the amount of margin required by a counterparty to ensure that, at some chosen statistical confidence level, they can meet their obligations? This question is the subject of this article.

While margining is often a regulatory requirement, financial services firms including proprietary trading firms and brokers/exchanges, may wish to conduct their own margin or collateral modelling to ensure they are protected against default by counterparties due to sudden changes in valuation of their trades. An example of this in the context of cryptocurrency exchanges is provided later in the article.

Margin modelling methodologies

Approaches to margin modelling may have much in common with methodologies used for market risk calculations. In particular, value at risk (VAR) and expected shortfall methods. The VAR method assume a distribution type with which to model risk factor (market data) shifts (typically a normal distribution), calibrates the distributions to the market somehow (typically historical simulation, which analyses the shifts in market variables over some specified time interval), and then calculates the 99% quantile worst market move (or some other specified confidence level) of the derivative or portfolio valuation over some given time horizon. Expected shortfall methods are a slight modification on this approach which look at the mean of all outcomes above the 99% quantile.

More frequent margin calls will reduce the credit risk (due to the shorter time horizon available for the market to move), at the expense of more operational inconvenience.

These methods require careful choice of a historical window for calibration. If only the most recent data is used, rather than a historically stressed period, it may cause over leveraging during more stable market periods, leading to higher exposure when the markets become more tumultuous.

Another question that arises is how to calculate margin requirements for an entire portfolio instead of a single trade. A portfolio may have offsetting trades, and a decision must be made about what correlations to assume and what level of diversification benefit to allow.

The ISDA standard initial margin model (SIMM)

To avoid disputes over margining amounts, it’s important to have a single, transparent and standardized approach that all parties must adhere to. The International Swaps and Derivatives Association prescribes an approach known as the standard initial margin model. The ISDA documentation for this approach can be found here.

The ISDA includes four product classes: Interest rates and FX (ratesFX), credit, equity and commodity.

Six “risk classes” are specified. These are market data categories on which trade valuations depend. They are interest rate, credit (qualifying), credit (non qualifying), equity, commodity, and FX. Each trade depends on one or more “risk factors” from each category, such as interest rate curves, equity prices and exchange rates. The model postulates that the moves in any given market data or “risk factor” are normally distributed, which is also the approach commonly taken for market risk. The changes in trade valuations are also assumed to be normally distributed, something which is not actually true for nonlinear products like options even when the risk factor shifts are normal, and which becomes less true the larger the shifts are. The valuation shifts depend on the changes in the risk factors through trade sensitivities.

We first discuss how to calculate the initial margin for an individual trade. The approach is to calculate the delta, vega and interest rate sensitivities of the trade. The ISDA has calculated numbers representing how much they believe each risk factor will move at 99% confidence, which then multiply the sensitivities. The process by which the user finds the correct number is fairly intricate, and we won’t attempt to reproduce every detail here. The approach also takes into account the ISDA’s view on correlations between risk factors.

For options, a “curvature” term is also added. It is actually the vega contribution scaled by a “scaling factor”, which represent’s the ISDA’s view on how gamma and vega are related for vanilla options.

Having calculated the initial margin for an individual trade, we now discuss how to aggregate trades to calculate the initial margin of the entire portfolio. A fantastic simplifying feature of normal distributions, is that the sum of two normal distributions is another normal distribution with some new standard deviation. The new standard deviation is a function of the old ones, calculated using what I will call the “square root rule”: \(\)

\[ \sigma_{agg} = \sqrt{\sum_i \sigma_i + \sum_r \sum_{s \neq r} \psi_{rs} \sigma_r \sigma_s},\]

where \(\psi\) is a matrix of correlations between the distributions. One can aggregate the normally distributed valuation shifts for different risk factors for any given trade, giving a total normal distribution for each trade. One can then aggregate all these normal distributions across all trades in the same way, yielding a new normal distribution corresponding to the total valuation shift in the whole portfolio..

The ISDA methodology has three differences from the procedure described above. Firstly, it applies the square root rule to the initial margins, rather than the standard deviations of the distributions as described above. Since an initial margin is just some quantile of a normal distribution distribution, it is proportional to the standard deviation, so that the square root rule applies to the initial margins just as like it does to the standard deviations.

Secondly, It’s easy to verify that for the square root rule, the order in which we aggregate all the normal distributions doesn’t matter. The ISDA approach aggregates the normal distributions in a specific order. For each product class, and within each risk class \(X\) within the product class, it first aggregates the delta margin across all trades, and similarly the vega margin, curvature margin and base correlation margin. It then aggregates these four distributions as a simple sum:

\[ IM_X = DeltaMargin_X + VegaMargin_X + CurvatureMargin_X + BaseCorrMargin_X \]

This is the third difference. This sum is more conservative than that obtained by using the square root rule, and assumes that there is no diversification benefit between these four types of moves. The ISDA may believe that in stressed conditions, these different classes of risk factors may move together.

The ISDA then uses the square root rule to aggregate these initial margins across all risk classes within the product class. Just like with the four margin types, the ISDA stipulates that no diversification benefit may be assumed between the product classes, so that the total amount of initial margin required is the sum of those for each of the four product classes:

\[ SIMM_{Product} = SIMM_{RatesFX}+SIMM_{Credit}+SIMM_{Equity}+SIMM_{Commodity}\]

That is, above the product class level, we don’t apply the square root rule but a straight sum. I’m not sure whether this is based in part on some analysis the ISDA has done concerning correlations between product classes, or is simply conservatism.

In any event, we’ve now calculated the total initial margin across our entire portfolio, as stipulated by the ISDA’s standard initial margin model.

FX sensitivities

Consider an FX trade with underlying \(S = CCY1CCY2\) and present value \(PV(S)\) expressed in CCY2. When calculating sensitivities, SIMM considers a 1% shift \(dS=.01S\). The sensitivity with respect to \(CCY1\) is defined to be the corresponding shift

\[sen_{CCY1} = dPV.\]

The sensitivity with respect to \(CCY2\) is defined to be

\[sen_{CCY1} = -Sd(PV/S).\]

In other words, we convert \(PV\) to \(CCY1\), find the shift, and then convert back. Using the chain rule for differentiation, it’s easy to show that

\[sen_{CCY1} + sen_{CCY2} = .01PV.\]

Once one sensitivity is calculated, the other can be easily found using this formula.

SIMM contribution of option premium settlement

For options, the option premium is typically settled two business days after the trade date. When a SIMM calculation is performed for an option trade before the option premium has settled, the premium cashflow is included in the calculation of the sensitivities. Since the option premium is equal and opposite to the usual PV of the trade, this means that the “total PV” including the premium payment is zero at the inception of the trade. Using the above formula, this means that the two sensitivities are equal and opposite.

The contribution that the premium payment makes to \(sen_{CCY1}\) and \(sen_{CCY2}\) depends on which currency settlement occurs in. Either way, since it is a fixed and known payment, its sensitivity to one of the currencies will be zero, and its sensitivity to the other currency will be \(.01Premium\).

Bitmex’s insurance fund

Given the high volatilities of cryptocurrencies, market risk and margin modelling is particularly challenging. When some major cryptocurrency exchanges combine this with very high leverages, managing their risk becomes particularly challenging.

On a traditional exchange, if the loss on a trade exceeds the posted margin, the exchange will insist that the trader put forward additional margin amount to cover the loss, and chase the debt if necessary.

Bitmex have taken a margining approach different to that used in traditional finance. They call this their insurance fund. In essence it means that Bitmex will liquidate your position at half margin, and appropriate the amount remaining after liquidating your position at the best available price. This money is then used to compensate for other trades by other participants which lost more than the margin amount. This means that traders which lost more than their margin are compensated, at the expense of traders who didn’t lose all of their margin.

To new participants, it seems surprising that the exchange could “steal” half your margin and keep it. However, Bitmex point out that, on the upside, traders are guaranteed to never lose more than their margin amount.

Local Volatility, Stochastic Volatility and Local Stochastic Volatility Models

A curious feature of quantitative finance is the way pricing models are calibrated to the market. For bonds, one adds a Z-spread to the rates which matches market quotes. The Z-spread allows the model to incorporate the market’s views about credit default risk and future interest rates. For options priced using the Black-Scholes model, one prices using the implied volatilities that reproduce market prices. In this way, pricing models are really just interpolation tools to interpolate between the prices of products trading in the market in order to price different contracts with different parameters which are not currently represented in the market.

For options priced using the constant volatility Black-Scholes model, this creates the strange situation where options with different strikes and expiries are imagined to be based on underlying assets with different volatilities – even though they all have the same underlying which can only realise a single volatility.

Another issue concerns the pricing of more exotic derivatives like Asian options and barrier options. When adding a barrier to a European option, it’s no longer clear whether it’s appropriate to use implied volatilities backed out from European options. Unfortunately, barrier options may not themselves be sufficiently liquid in the market to generate their own implied volatilities (such an implied volatility surface would now have three dimensions – strike, expiry and barrier level). Furthermore, unlike vanilla options, barrier option valuation depends on volatility term structure, so that replacing a variable volatility with it’s constant average value isn’t appropriate. Similarly, for Asian options, valuation depends on the path taken by the underlying and by its volatility, not just on their average values.

We know that volatilities are not, in reality, constant. And the assumption that they are leads to difficulties in reproducing the market prices of European options, and meaningfully pricing less liquid exotic derivatives. Furthermore, empirical analysis of stock data may indicate that volatility has, statistically speaking, a dependence on the current underlying level. This naturally leads us to consider various volatility option pricing models where the volatility depends on both time and the underlying level.

Local Volatility Models

In the local volatility model (LV), the constant volatility of Black-Scholes is replaced by an instantaneous volatility \(\sigma(S_t,t)\) which depends on both the current underlying value and time. The underlying evolves under the stochastic differential equation

\[dS_t = (r_t – d_t)S_t dt + \sigma(S_t,t) S_t dW_t,\]

where \(r\) is the risk free rate, \(d\) is the dividend yield, and \(W_t\) is a Wiener process (i.e. a brownian motion or random walk).

As long as the volatility surface is arbitrage free, local volatility models can be calibrated to exactly reproduce the market option prices and volatility smile at each pillar maturity trading in the market. Calibration can be done computationally efficiently using Dupire’s formula. Dupire’s formula gives the volatility \(\sigma(K,T)\) at some strike \(K\) and expiry \(T\), in terms of the price \(C(K,T)\) of a call option. The formula is

\[\sigma(K,T)^2 = \frac{\frac{\partial C}{\partial K} – (r-q)(C – K\frac{\partial C}{\partial K})}{\frac{1}{2}K^2\frac{\partial^2 C}{\partial K^2}},\]

where \(r\) is the risk free rate and \(q\) is the dividend yield. Slightly confusingly, this function seems to define the local volatility function \(\sigma(S,t)\) at a strike \(K\) instead of at some underlying level \(S\). However, the assumption is simply that, in order to calculate the local volatility at \(S\), you set \(K=S\) in this formula.

Thus, the problem of calibrating the model to market prices (i.e. obtaining the function \(\sigma(S,t)\)), is reduced to the problem of generating the function of call prices \(C(K,T)\), for every strike and expiry. This of course must be done starting with market prices for only a finite number of strikes and expiries. There are a variety of methods proposed for constructing an arbitrage free surface of call prices, and we refer to Rasmussen for a survey.

Once the local volatility function has been obtained, one proceeds to price derivatives numerically. For example, one can use Monte Carlo method to generate a large number of possible paths using the stochastic differential equation above. Alternatively, one might use PDE finite differencing methods to price the derivative.

A significant downside of local vol models is that \(dV/dS\) has the opposite from that observed in the market. When spot increases, a local vol model predicts that volatility should decrease, and vice versa. Since the observed market behaviour is the opposite, this makes the model unsuitable for hedging.. Also, smile flattens with maturity.

Another issue which arises when pricing certain types of derivatives, is the flattening of the forward smile. Local volatility models are fitted to the prices of European options. It’s known that the vol smile of European options becomes flatter for longer maturities (in other words, the implied vol is more constant, having less dependence on strike). This means that when you consider the forward volatility between \(T_1\) and \(T_2\), you find that it is also quite flat. This is not what’s observed in practice, where volatility smiles in the future are not in general less flat than those observed today.

To address these issues, we have stochastic volatility models.

Stochastic Volatility Models

Local volatility models are determinist in that, given future values for \(t\) and \(S_t\), we know the volatility will be precisely that prescribed by the local volatility function \(\sigma(S,T)\). Stochastic volatility (SV) models allow the volatility to evolve according to its own stochastic differential equation with a random term, just like the underlying \(S\). Stochastic volatility models are of the form

\[dS_t = (r_t – d_t)S_t dt + \sigma_t S_t dW_t,\]

\[d \sigma_t^2 = \alpha(\sigma_t^2,t) dt + \beta(\sigma_t^2,t) dB_t,\]

where \(B\) is a second Wiener process, correlated to \(W\) with some correlation \(\rho\).

Stochastic volatility models are typically more challenging to calibrate to market data than a local volatility model. With few exceptions (such as the Hagen formula for the SABR model), stochastic volatility models are solved numerically, and calibrated to the market numerically. Standard optimization routines can be used to solve the model, check the level of agreement with market prices, adjust the model parameters, and repeat until a sufficiently close fit is obtained. For example, a least squares objective function may be used to minimise the difference between the prices generated by the model and those seen in the market.

A prominent example of a stochastic volatility model which is the market standard for vanilla interest rate derivatives, see our main article on the SABR model. Another stochastic volatility model, which is commonly used for equities, is the Heston model, which we discuss in the next section.

Heston Model

The Heston model is a particular example of a stochastic volatility model which is popular for equity derivatives. The second equation above takes the form

\[d \sigma_t^2 = \theta (\omega – \sigma_t^2) dt + \varepsilon \sigma_t dB_t,\]

where \(\theta\), \(\omega\) and \(\varepsilon\) are constants. Note that the first term causes the variance to tend to mean revert to the value \(\omega\).

Local Stochastic Volatility Models

Finally, we mention models which are a kind of hybrid between local volatility models and stochastic volatility models. These are sometimes known as “local stochastic volatililty” or LSV models. They aim to incorporate the best characteristics of both models. See for example the following papers:

The SABR Model (Stochastic Alpha Beta Rho)

The SABR model, introduced by Hagen et al, is a stochastic volatility model which is commonly used for valuing interest rate derivatives. It models the time evolution of some forward rate \(F_t\), of some maturity \(T\) and tenor \(L\), as follows:

\[ dF_t = \alpha_t F_t^\beta dW_t,\]

\[ d\alpha_t = v \alpha_t dZ_t,\]

where \(W_t\) and \(Z_t\) are two Wiener processes with constant correlation \(\rho\).

Unlike local volatility models, the model reproduces the correct smile dynamics, so it can be used for hedging. The SABR model is popular because it is simpler than some other stochastic volatility models, and allows an approximate pricing formula, in terms of the SABR model parameters, which gives the price in terms of the implied Black volatility. That is, the implied volatility that when inserted into Black’s model reproduces the price generated by the SABR model. This makes the model much easier to calibrate, and in general it fits the market well. The Hagen formula is straight forward but long, so we don’t reproduce it here.

The SABR model quickly became the market standard for vanilla interest rate derivatives like caps, floors and swaptions.

The first equation above shows that the forward \(F_t\) will remain positive under the evolution of the equation. Thus, the SABR model needs to be modified if zero and negative rates are considered possible.

Fitting the model

The SABR model is fitted for a single forward rate, with some expiry T and some tenor L. As mentioned already, a direct formula, in terms of the SABR model parameters, gives you a good approximation for the Black implied vol for any given strike. Therefore, using a set of strikes for which there exist market prices (and therefore implied vols), one can do a least squares fitting which minimises the deviation of the SABR approximate Black vols from the market implied vols. That is, one wishes to find SABR parameters which minimise

\[ \sum_{i} (\sigma_{K_i}^{SABR} – \sigma_{K_i}^{BS}),\]

where the sum is over the set of strikes for a given expiry. The SABR volatilities are computed from the SABR parameters using the Hagen implied volatility formula, avoiding the need to numerically simulate the SABR model. Since \(\beta\) and \(\rho\) both affect the slope of the volatility smile, it’s common to fix \(\beta\) according to some view of the market, and fit only the remaining three parameters using this method.

This procedure can be repeated for all required expiries and tenors for which there are market prices.

Finally, if one wishes to price an option on a forward rate of an expiry and tenor for which there is no analogue in the market, one can construct a set of SABR parameters by interpolating between the SABR parameters of nearby expiries and tenors. Alternatively, one can interpolate the implied volatilities directly.

Swaptions and caps/floors

For swaptions, one uses the swap rate as the underlying. That is, the rate that makes the swap value equal to zero. For caps and floors, one must first do a bootstrapping process to obtain the individual caplet/floorlet vols from the trading caps and floors.

The parameters alpha, beta and rho.

The variable \(\alpha_t\) represents the volatility of the forward process. The first SABR parameter \(\alpha\) is a non-negative parameter which is defined as the initial value of \(\alpha_t\) at time \(t=0\). It’s effect is approximately a parallel shift of the volatility smile vertically.

The parameter \(\beta\) satisfies \(0 \leq \beta \leq 1\). As \(\beta\) increases, the slope of the volatility smile decreases. Note that for \(\beta\) close to zero, the SABR model is close to a normal, rather than lognormal, model.

As mentioned already, \(\rho\) represents the correlation between changes in the underlying and changes in it’s volatility. Like \(\beta\), the effect of \(\rho\) is to change the slope of the volatility smile.

The parameter \(v\) is the volatility of the volatility. A higher value for \(v\) gives a greater curvature to the volatility smile.

Limitations of the SABR model

The SABR model has some limitations worth noting. Firstly, as it only models a single forward rate, it should not be used to price derivatives whose valuation depends on more than one forward rate. This is because forward rates with nearby maturities and tenors are not independent. However, note that swaptions are nonetheless commonly priced using the SABR model by modelling the swap rate, instead of the constituent swaplets. Secondly, the equation lacks a mean reversion term, limiting its ability to accurately model interest rates which tend to be eventually pulled back to normal values. Finally, the convenient Black implied vol formula can become inaccurate under certain circumstances.

For pricing non-vanilla derivatives which depend on multiple forward rates (of various expiries and tenors), the SABR model must be modified so that forward rates of nearby tenors and expiries are not independent. See the SABR libor market model.

Generating a volatility surface in C++ from options data

This is some code I wrote to generate a volatility surface in C++ using options and futures data, and use it to calculate price and Greeks for an ATM option. To simulate live trading, the code updates the volatility surface and option price as new data becomes available.

Note that the code is currently configured to execute for the first 150,000 lines of market data. This can be changed on by altering max_remaining_lines on line 62

Simplifications

Currently, the code creates only a single volatility smile for a single maturity. Of course, the code can quickly be adapted to generate a whole volatility surface, as this is just a number of smiles at different expiries with a suitable method of interpolating between them.

I used the future as a proxy for the forward, just based on the data I had. This is not strictly correct, as they can differ by a convexity correction. Also, rather than incorporate interest rate data, interest rates are currently assumed to be zero. Since the expiry used was short, this was acceptable.

Calculating Implied Volatilities

Implied volatilities are calculated using Newton’s method.

I’ve found that Newton’s method can fail to converge when converting volatility pillars from moneyness to delta, particularly for problematic vol surface data such as data containing arbitrage. However, these issues may not arise for a single implied volatility calculation. But convergence guarantees, and appropriate behaviour for all input data, should be further investigated for live trading code.

It’s also worth checking out our article on removing arbitrage from volatility surfaces.

Volatility Surface Construction

I took ATM to be the strike equal to the Futures value at expiry (that is, ATMF). Another possible convention is to use the strike for which a call and put have a delta of the same absolute magnitude.

For simplicity, I adopted the sticky strike convention to avoid having to convert volatility surfaces to delta. That is, the pillars of the vol surface are fixed strikes. I also adopted the common market convention of parameterising the vol surface using calls for strikes above ATM, and puts for strikes below ATM.

While cubic spline is market standard, this would have required either using a third party library or implementing it manually. Given my time requirements for this project, I chose linear interpolation between strike pillars. Flat extrapolation was chosen before the first and after the last pillar.

Volatility interpolation will not proceed if one of the two required pillar vols is missing. If the additional complexity was worthwhile, one could proceed to use the next pillar over if available. One could also attempt to use put in place of call (or vice versa) if the intended market price was not available. Note that the volatility surface output displays “-1” when volatility data is not yet available. As long as a valid volatility has been calculated at least once in the past, this will continue to be used. A possible modification would involve removing volatilities based on market prices that are either too old or no longer existing in the market.

Data Quality

The code uses the mid price if both bid and ask quantities are non-zero. If only one is non-zero, it will use that value. If both quantities are zero, the data is ignored and the volatility surface is not updated. That is, the row is interpreted as missing data.

Some data rows in the options input data seemed erroneous. In this case, Newton’s method could not find a volatility solution. I imposed a max_iterations of 100, and discarded such rows. Similarly, a volatility > 10 (1000%) was assumed to be due to erroneous data.

Greeks

The greeks delta, gamma, vega and theta are calculated using finite differences and a reasonable choice of step size. Although infinitesimal greeks are available via the Black-Scholes formula, traders may be more interested in the consequences of small, finite movements rather than infinitesimal movements. The greeks can be quite sensitive to the chosen step size.

Because I used Black’s formula for pricing which takes the forward as input, and spot data was not available, I outputed forward delta and forward gamma (that is, I shifted the forward value instead of the spot value). Delta will differ from spot by forward/spot and gamma by that amount squared. Theta is chosen to be a 1-day theta.

The decision was made to output updated option price and greeks whenever option call price changed by a tolerance of at least 0.00001. This condition could be modified depending on what exactly the reason is for outputting changed prices.

Code Structure

The primary auxillary functions are:

  • “Black” to price calls and puts
  • “Implied Vol” to use Newton’s method to calculate a volatility from a market price
  • “PriceAndGreeks” to return a 5-vector price, delta, gamma, vega, theta.
  • “InterpolateVol” to use linear interpolation and flat extrapolation to extract a volatility from the vol surface data.
  • “UpdateVol” to update a volatility at a particular strike if either the corresponding market price has changed, or the forward has changed.

Presently, the remainder of the code, including file input/output and the primary loop through market data lines, occurs in the “main” function. Given more time, the main function could be broken down into multiple functions for cleaner, more reader and more reusable code (although the value of this depends on the ultimate purpose and future direction of the project).

Code Testing

The code begins with a few simple unit tests to test pricing, calculation of implied volatilities and volatility interpolation. Basic reasonableness examinations have been done on the output data, however more extensive testing should be done before putting such code into production.

C++ code

#include <iostream>
#include <fstream>
#include <vector>
#include <math.h>
#include <limits>

using namespace std;

vector<vector<string>> getData(string fileName, string delimiter);
vector<string> getDataSingleLine(string fileName, string delimiter);
void writeData(vector<vector<string>> Z, string fileName, string delimiter);
vector<string> split(const string& str, const string& delim);
double Black(double V, double F, double K, double T, double r, char callput);
double Ncdf(double x);
double ImpliedVol(double initialVolGuess, double targetPrice, double tol, double F, double K, double T, double r, char callput);
vector<double> PriceAndGreeks(double V, double F, double K, double T, double r, char callput);
double interpolateVol(double strike, double F, vector<double> CallStrikes, vector<double> CallVols, vector<double> PutVols);
void UpdateVol(int updated_symbol, double FuturePrice, double T, vector<double>& CallStrikes, vector<double>& CallVols, vector<double>& PutStrikes, vector<double>& PutVols, vector<double>& CallPrices, vector<double>& PutPrices);

int main()
{
    // Unit tests
    cout << "Unit tests" << '\n';
    cout << "Call price should be 4.26894926: " << Black(0.1,100,99,1,0.05,'C') << '\n';
    cout << "Call implied vol should be 0.1: " << ImpliedVol(0.05, 4.26894926, 0.00001, 100, 99, 1, 0.05, 'C') << '\n';
    cout << "Put price should be 3.31771983: " << Black(0.1,100,99,1,0.05,'P') << '\n';
    cout << "Put implied vol should be 0.1: " << ImpliedVol(0.15, 3.31771983, 0.00001, 100, 99, 1, 0.05, 'P') << '\n';
    vector<double> teststrikes{1,2,3,4,5};
    vector<double> test_vols_call{.10,.12,.14,.16,.18};
    vector<double> test_vols_put{.102,.122,.142,.162,.182};
    cout << "Interpolated vol should be .102: " << interpolateVol(0.9, 3, teststrikes, test_vols_call, test_vols_put) << '\n';
    cout << "Interpolated vol should be .18: " << interpolateVol(5.1, 3, teststrikes, test_vols_call, test_vols_put) << '\n';
    cout << "Interpolated vol should be .131: " << interpolateVol(2.5, 3, teststrikes, test_vols_call, test_vols_put) << '\n';

    // Load data
    string market_data_filename = "market_data.csv";
    string static_info_filename = "static_info.csv";

    // Output files
    ofstream fittingfile("fitting_output.csv");
    ofstream optionfile("option_output.csv");

    // Format static data.
    // This code assumes order of headings and that calls are labelled 1 to 145 and puts 146 to 290 with none missing.
    // Code would need to be modified to handle more general input files or potential missing data.
    vector<vector<string>> static_info = getData(static_info_filename, ",");
    double expiry_time = stod(static_info[1][5]); // Assumes all options have the same expiry

    vector<double> CallStrikes(145);
    vector<double> PutStrikes(145);

    for(int i = 1; i < static_info.size(); i++)
    {
        string symbol = static_info[i][0].substr(4, symbol.size() - 4);
        if (static_info[i][3].compare("C") == 0) {CallStrikes[stoi(symbol)-1] = stod(static_info[i][4]);}
        else if (static_info[i][3].compare("P") == 0) {PutStrikes[stoi(symbol)-146] = stod(static_info[i][4]);}
    }

    // Load market data
    // To simulate the arrival of live trading data, data is loaded one line at a time, with volatility surface calculations updated every line.

    ifstream file(market_data_filename);
    int max_remaining_lines = 150000; // Used to run code for a limited number of rows only
	string line = "";

	double current_time;
	int updated_symbol; // symbol that is updated this data row
	double T; // time to expiry
	getline(file, line); // First row are headings

	// Most recent price update for each strike. -1 represents no data yet.
    vector<double> CallPrices(145,-1);
    vector<double> PutPrices(145,-1);
    vector<double> CallVols(145,-1);
    vector<double> PutVols(145,-1);
    vector<double> OptionCallPrice(5,-1);

    double FuturePrice = -1;

    // Output files
    vector<string> heading_row;
    heading_row.push_back("Timestamp");
    for(int i = 1; i < CallStrikes.size(); i++)
    {
        heading_row.push_back(to_string(CallStrikes[i])); // Assuming call strikes and put strikes are the same, which seems to be the case
    }
    for(int l = 0; l < heading_row.size(); l++)
    {
        fittingfile << heading_row[l] << ",";
    }
    fittingfile << "\n";

    vector<string> heading_row2 {"Timestamp","Vol","Call price","Call delta","Call gamma","Call vega","Call theta","Put price","Put delta","Put gamma","Put vega","Put theta"};
    for(int l = 0; l < heading_row2.size(); l++)
    {
        optionfile << heading_row2[l] << ",";
    }
    optionfile << "\n";

    vector<vector<string>> pricing_output;

    // Main loop. Reads each line in market data input and updates futures price, vol surface and option price/greeks where appropriate.
	while (getline(file, line) && max_remaining_lines > 0)
	{
		vector<string> column = split(line, ",");

		max_remaining_lines = max_remaining_lines - 1;

		current_time = stod(column[1]);
        T = (expiry_time - current_time)*pow(10,-9)/31536000; // convert nanoseconds to years

        updated_symbol = stoi(column[0].substr(4, column[0].size() - 4));

        // Code to find most appropriate price to use for current symbol
        double current_bid = stod(column[2]);
        int bid_quantity = stod(column[3]);
        double current_ask = stod(column[4]);
        int ask_quantity = stod(column[5]);
        if (bid_quantity == 0 && ask_quantity == 0) {continue;} // Interpreted as no data update
        if (current_bid == 0 && current_ask == 0) {continue;} // Interpreted as no data update
        double mid = 0;
        // if one has quantity zero, use the other
        if (bid_quantity == 0 and ask_quantity > 0){mid = current_ask;}
        else if (bid_quantity > 0 and ask_quantity == 0){mid = mid + current_bid;}
        else {mid = (current_bid + current_ask)/2;}

        // Update prices
        if (updated_symbol == 0) {FuturePrice = mid;}
        else if (updated_symbol <= 145)
        {
            CallPrices[updated_symbol-1] = mid;
        }
        else
        {
            PutPrices[updated_symbol-146] = mid;
        }

        // Update volatilities
        if (FuturePrice < 0) {continue;} // unable to calculate volatilities without a forward

        if (updated_symbol == 0) // If Future price has changed, all vols must be updated
        {
             for(int l = 1; l < CallVols.size()+ PutVols.size() + 1; l++)
             {
                 UpdateVol(l, FuturePrice, T, CallStrikes, CallVols, PutStrikes, PutVols, CallPrices, PutPrices);
             }
        }
        else{UpdateVol(updated_symbol, FuturePrice, T, CallStrikes, CallVols, PutStrikes, PutVols, CallPrices, PutPrices);}

        // If V or F have changed, output ATM option price and greeks.
        double impliedATMvol = interpolateVol(FuturePrice, FuturePrice, CallStrikes, CallVols, PutVols);

        // If required vol pillars are still missing from vol surface data, no option price can be output
        if (impliedATMvol > 0)
        {
            double oldprice = OptionCallPrice[0];
            OptionCallPrice = PriceAndGreeks(impliedATMvol, FuturePrice, FuturePrice, T, 0, 'C');
            if(abs(OptionCallPrice[0] - oldprice) > 0.00001) // only write a row to file if price has changed.
            {
                vector<double> OptionPutPrice = PriceAndGreeks(impliedATMvol, FuturePrice, FuturePrice, T, 0, 'P');
                optionfile << to_string(current_time) << ",";
                optionfile << to_string(impliedATMvol) << ",";
                for(int l = 0; l < OptionCallPrice.size(); l++)
                {
                    optionfile << to_string(OptionCallPrice[l]) << ",";
                }
                for(int l = 0; l < OptionPutPrice.size(); l++)
                {
                    optionfile << to_string(OptionPutPrice[l]) << ",";
                }
                optionfile << "\n";
            }
        }

        // Output fitting csv
        // epoch nanoseconds, fitted volatility at each strike
        vector<string> fitting_row;
        fitting_row.push_back(to_string(current_time));
        for(int i = 1; i < CallVols.size(); i++)
        {
            if (CallStrikes[i] < FuturePrice) {fitting_row.push_back(to_string(PutVols[i]));}
            else {fitting_row.push_back(to_string(CallVols[i]));}
        }

        for(int l = 0; l < fitting_row.size(); l++)
        {
            fittingfile << fitting_row[l] << ",";
        }
        fittingfile << "\n";

    }

	file.close();
	fittingfile.close();
	optionfile.close();

    return 0;
}

// Update the vol at a given strike
void UpdateVol(int updated_symbol, double FuturePrice, double T, vector<double>& CallStrikes, vector<double>& CallVols, vector<double>& PutStrikes, vector<double>& PutVols, vector<double>& CallPrices, vector<double>& PutPrices)
{
        double tolerance = 0.0000001;
        double r = 0; // Assumption of zero interest rates
        double initialVolGuess;

        if (updated_symbol <= 145)
        {
            if(CallPrices[updated_symbol-1] < 0) {return;}

            double mid = CallPrices[updated_symbol-1];
            if (CallVols[updated_symbol-1] < 0) {initialVolGuess = 0.15;}
            else {initialVolGuess = CallVols[updated_symbol-1];}
            double impvol = ImpliedVol(initialVolGuess, mid, tolerance, FuturePrice, CallStrikes[updated_symbol-1], T, 0, 'C');
            if (impvol > 0 && impvol < 10)
            {
                CallVols[updated_symbol-1] = impvol; // ImpliedVol returns -1 if Newton fails to converge
            }
        }
        else
        {
            if(PutPrices[updated_symbol-146] < 0) {return;}

            double mid = PutPrices[updated_symbol-1];
            if (PutVols[updated_symbol-146] < 0) {initialVolGuess = 0.15;}
            else {initialVolGuess = PutVols[updated_symbol-146];}
            double impvol = ImpliedVol(initialVolGuess, mid, tolerance, FuturePrice, PutStrikes[updated_symbol-146], T, 0, 'P');
            if (impvol > 0 && impvol < 10)
            {
                PutVols[updated_symbol-146] = impvol;
            }
        }

}

// Linearly interpolate the vol smile for some strike
double interpolateVol(double strike, double F, vector<double> CallStrikes, vector<double> CallVols, vector<double> PutVols)
{
    // Assume callstrike list is identical to putstrike list, and callstrike list is ordered

    // Flat extrapolation
    if (strike <= CallStrikes[0]) {return PutVols[0];}
    if (strike >= CallStrikes[CallStrikes.size() - 1]) {return CallVols[CallVols.size() - 1];}

    int rightindex = 0;
    for(int i = 0; i < CallStrikes.size(); i++)
    {
        if (strike < CallStrikes[i]){rightindex = i; break;}
    }

    double right_strike = CallStrikes[rightindex];
    double left_strike = CallStrikes[rightindex - 1];
    double right_vol;
    double left_vol;

    // Use put for strike below ATM and call otherwise
    if (left_strike < F) {left_vol = PutVols[rightindex - 1];}
    else {left_vol = CallVols[rightindex - 1];}

    // If vols don't exist yet return -1
    if (left_vol < 0 or right_vol < 0) {return -1;}

    if (right_strike < F) {right_vol = PutVols[rightindex];}
    else {right_vol = CallVols[rightindex];}

    return left_vol + (right_vol - left_vol)*(strike - left_strike)/(right_strike - left_strike);
}

vector<double> PriceAndGreeks(double V, double F, double K, double T, double r, char callput)
{
    double deltastep = 0.0001*F;
    double gammastep = 0.01*F;
    double vegastep = 0.0001*V;
    double thetastep = min(1.0/365,T);

    vector<double> result(5,0);
    double price = Black(V, F, K, T, r, callput);
    result[0] = price;
    result[1] = (Black(V, F + deltastep/2, K, T, r, callput) - Black(V, F - deltastep/2, K, T, r, callput))/(deltastep/2);
    result[2] = (Black(V, F + gammastep, K, T, r, callput) + Black(V, F - gammastep, K, T, r, callput) - 2*price) / pow(gammastep, 2);
    result[3] = (Black(V + vegastep/2, F, K, T, r, callput) - Black(V - vegastep/2, F, K, T, r, callput))/(vegastep/2);
    result[4] = (Black(V, F, K, T - thetastep, r, callput) - price)/thetastep;

    return result; // price, delta, gamma, vega, theta
}

// Uses Newton's method to calculate implied volatility from market price
double ImpliedVol(double initialVolGuess, double targetPrice, double tol, double F, double K, double T, double r, char callput)
{
    double derivativeStep = min(0.00001, tol/100);
    double currentVol = initialVolGuess;
    double functionValue = Black(currentVol, F, K, T, r, callput) - targetPrice;
    double derivativeValue;
    int max_iterations = 100;

    while (abs(functionValue) > tol){
        derivativeValue = (Black(currentVol + derivativeStep, F, K, T, r, callput) - functionValue - targetPrice)/derivativeStep;
        currentVol = currentVol - functionValue / derivativeValue;
        functionValue = Black(currentVol, F, K, T, r, callput) - targetPrice;
        max_iterations = max_iterations - 1;
        if (max_iterations == 0) {return -1;}
    }

    return currentVol;
}

// Prices a put or call option using Black's formula and the forward value
double Black(double V, double F, double K, double T, double r, char callput)
{
    constexpr double lowest_double = std::numeric_limits<double>::lowest();
    if (T < lowest_double) {return F-K;} // avoid divide by 0
    double d1 = (log(F/K) + pow(V,2)*T/2)/(V*sqrt(T));
    double d2 = d1 - V*sqrt(T);
    if (callput == 'C')
    {
        return exp(-r*T)*(F*Ncdf(d1)-K*(Ncdf(d2)));
    }
    else if (callput == 'P')
    {
        return exp(-r*T)*(-F*Ncdf(-d1)+K*(Ncdf(-d2)));
    }
}

// Normal cumulative cdf function
double Ncdf(double x)
{
    return erfc(-x / sqrt(2))/2.0;
}

void writeData(vector<vector<string>> Z, string fileName, string delimiter)
{
	ofstream file(fileName);

    for(int k = 0; k < Z.size(); k++)
    {
        for(int l = 0; l < Z[0].size()-1; l++)
        {
            file << Z[k][l] << delimiter;
        }
        file << Z[k][Z[0].size()-1] << "\n";
    }

	file.close();
}

vector<vector<string>> getData(string fileName, string delimiter)
{
	ifstream file(fileName);

	vector<vector<string> > Z;

	string line = "";

	while (getline(file, line))
	{
		vector<string> vec = split(line, delimiter);
		Z.push_back(vec);
	}

	file.close();

	return Z;
}

vector<string> getDataSingleLine(string fileName, string delimiter)
{
	ifstream file(fileName);

	vector<string> data;

	string line = "";

    getline(file, line);
    data = split(line, delimiter);

	file.close();

	return data;
}

vector<string> split(const string& str, const string& delim)
{
    vector<string> tokens;
    size_t prev = 0, pos = 0;
    do
    {
        pos = str.find(delim, prev);
        if (pos == string::npos) pos = str.length();
        string token = str.substr(prev, pos-prev);
        tokens.push_back(token);
        prev = pos + delim.length();
    }
    while (pos < str.length() && prev < str.length());
    return tokens;
}

How to Hire a Quant or Mathematician (whether Freelancer, Consultant or Permanent!)

Hiring managers are put in the position of speculating on the future job performance of candidates. This is true whether they are looking to hire a permanent staff member, or engage a consultant or freelancer for a shorter time period or on a less than fulltime basis.

Yet, their field of expertise is in executing their own jobs, not in appraising the capabilities of another person. While hiring managers might receive interview training from their firm’s HR department, this merely shifts the burden of developing an effective candidate assessment process onto HR personnel. Anyone who has done one of those personality tests which ask you the same highly ambiguous questions over and over in slightly different ways, will know that the only people with no scepticism in the validity of this methodology are HR themselves.

While some of what I say may be applicable to other kinds of roles, I want to focus on hiring for quants (quantitative finance) and mathematicians. As a quant and mathematician myself, and having been through a few interviews myself during my career, and I’ve got a few opinions on this, so strap in!

Jim Simons, mathematician and founder of Renaissance Technologies, described how he chose the 150 PhDs working for him by saying he looked for people who had “done research and done it well”. In other words, he looked for people who had shown they could succeed at challenging projects, even though their previous work had nothing to do with the work they would be doing. This is pretty much the opposite of most hiring managers who ideally want to hire someone who has done as close as possible to the same job before. As the long term annual return of 62% of shows, it worked out pretty well for him.

So how do managers try to interview quants and mathematicians, and why doesn’t it work?

A difficult task under any circumstances

Judging other people is a difficult task at the best of times. Consider the following examples:

  • J.K. Rowling was rejected by 12 publishers before she found success. Given that the Harry Potter franchise is now worth tens of billions, it’s safe to say that those publishers were not as good at picking the winners as they might have thought they were. Even though picking which authors will make them money is supposed to be the primary skill of a publishing house.
  • Most elite trading firms (and even the less elite ones!) like to screen their candidates with online coding and maths tests, apparently believing that this will allow them to select the “smartest” people. And in the news, I occasionally see articles alleging that some kid has an IQ higher than Einstein. The implication being that this kid should go on produce achievements at least as great as Einstein’s. Yet, documentaries which have followed up on high-IQ children year later have found that they’ve become competent professionals, but not the singular individuals their singular IQ scores suggested they would. And Einstein struggled to find a teaching position after graduating, and instead spent 7 years working at a patent office – retrospectively, probably not the best use of his abilities. Why did not one person in society, including academics and professors, anticipate his potential? Couldn’t they have just sent him to do an online test like the Susquehannas, Citadels or Towers of the world? As a slow, deep thinker, I suspect Einstein would not have done unusually well on those kinds of tests.
  • Rachmaninoff’s first symphony was savaged by critics, with one comparing it to the seven plagues of Egypt. Yet today he is one of the most enduringly popular romantic composers.

Google famously analysed how job performance aligned with interview performance:

“”We looked at tens of thousands of interviews, and everyone who had done the interviews and what they scored the candidate, and how that person ultimately performed in their job. We found zero relationship. It’s a complete random mess” He also admitted that brainteasers are useless and only good for making the interviewers feel smart.”

So why don’t interview questions work?

It’s important to remember that the purpose of an interview is to try to determine how a candidate will perform in the work environment. Therefore, the candidate should be observed under circumstances as close as possible to the work environment. Yet, the interview differs profoundly from the normal work environment in several critical ways:

The interviewer fails to understand how specific their questions are, and underestimates transferrable skills.

In my opinion, many managers, and perhaps the majority, make the mistake of disregarding general skills and abilities, and general candidate quality, in favour of very specific past experience.

A former manager of mine called it “looking for the person who was doing the job before”. Ideally, the manager is looking for the person who just quit that very role. Or, failing that, someone who has been doing almost exactly the same role at a competitor.

This is reflected in the interview by the asking of very specific questions. Since the number of topics in either mathematics or quantitative finance is almost unlimited, the candidate may well not have spent time on those very specific topics. Or, they may have done so many years ago but can no longer recall off the top of their head.

For example, if you were to ask me for the definition of a (mathematical) group, I would struggle to recall off the top of my head. Likewise if you were to ask me to write down the Cauchy Riemann equations. Although these are both first year university topics, I simply haven’t looked at them in quite a while. However, if I needed one of these during the course of my work day, I look it up, and seconds later I’m moving forward with my work. It’s very unwise to interview experienced professionals by testing whether they can recall first year university topics off the top of their heads under pressure. Yet, interviews for quants (as well as software developers) are often conducted in this way. And I’ll give some real world examples of this below.

I remember when I was doing Josh Waitzkin’s chess tutorials that come with the Chess Master computer program, he talked about how, after studying a huge number of chess games, he had forgotten all the specifics of those games. Yet, something remained. A kind of intuition or deep understanding that didn’t depend on remembering any particular specifics.

An interviewer can be very impressed with a candidate’s knowledge, or surprised that they don’t know anything, all based on the luck of whether they ask questions the candidate happened to have thought about fairly recently. Furthermore, since the interviewer chooses questions that they know well or have prepared, it can easily appear to them that they seem to know a lot more than any of the candidates that they interview. If the candidate were able to choose questions for the interviewer to answer, an identical dynamic may occur. Sometimes, the interviewer’s limited knowledge leads them to test candidates memory of elementary facts, while the candidates knowledge is much broader than they realise. Interview questions constitute a set of measure zero in the set of all knowledge in the field.

Another thing to keep in mind is that, just because someone has been doing a certain kind of role for many years, doesn’t necessarily mean they are good at it. There are many university lecturers who have been teaching for 30 years, and yet the students find their courses poorly structured and confusing. This means that hiring someone with past experience in the exact same role may not be preferable to choosing a high quality candidate with who’s work history is not exactly the same as the present role.

I’ve also found that some quants and software developers can have difficulty with seemingly mundane tasks like understanding and responding to emails, proofreading their reports, even though they may pass technical interview questions.

The candidate has no opportunity to prepare for the tasks.

In the workplace, people don’t come up to you and insist you produce a random fact off the top of your head in 10 seconds. Nor do they insist you engage in rapid problem solving challenges while they wait and glare at you.

When you are assigned a task in the workplace, you probably won’t instantly understand all aspects of it. It might require information you don’t yet know, or information you once knew but have forgotten because you haven’t needed to use it in your job for a few years. Either way, you use google, wikipedia, you look up some books, and soon you’re moving the task towards completion.

Usually when you start a new role, the first couple of months involve a period of learning. This is because, even though you may have many years of experience in similar roles, every firm and every role has it’s own set of specific financial products, calculational methodologies and coding tools.

Some people suggest “preparing” for interviews. This is both difficult and a waste of time, since you could spend time preparing information and find the interviewer asks you something completely different. It’s silly to try to know everything all at once. A reasonable person researches the specific facts they need for a task, when they need to. Indeed, researching a new problem or task which is not exactly the same as something you did before, is a very important skill, much more important that memorisation. And it’s a skill which is totally untested in an interview.

Now, universities also try to assess people – they do this using exams. But there is one key difference between the assessment of universities and the assessment of interviewers. When you are given an exam at university, you are first told what knowledge and skills you need to master, and afforded the opportunity to do so throughout the semester. Of course, you won’t know exactly which questions will be asked in the exam but, if the lecturer has done a good job, the exam questions should a subset of those you were made aware that you needed to learn to do. You are not being assessed on whether you know x or can do y off the top of your head in seconds. Rather you are being assessed on, if the need to know x or do y arises in your job, can you go away and learn x or learn to do y?

Studies have shown that interviews not only add nothing of value above just considering a candidate’s university marks, but can actually be worse than just judging candidates by their university marks (see this article in the New York Times). Why? Because university exams are an objective measure of whether a candidate is able to achieve a task assigned to them, given an appropriate amount of time to think, research and learn. Exactly like the workplace! Interviews, on the other hand, are not representative of either the university environment or the workplace environment.

When I was a university lecturer in mathematics, I watched some students struggle when transitioning from early undergraduate courses to more advanced courses. These students had perfected a learning strategy of memorizing how to do the specific kinds of problems they suspected were going to be on the exam. But in advanced courses, they were asked to creatively generate their own proofs that did not necessarily match a pattern of anything they had seen before. What was needed here was an approach of developing general skills and conceptual understanding, not memorising how to do certain very specific problems.

And as a mathematics or physics researcher, there is no point in memorising specific topics. Because you have no idea what knowledge or skills the next research project you undertake will require. Rather, the skillset you acquire is the ability to quickly look up and learn things, when you need to know them.

A prospective consulting client once presented to me a paper on quantitative finance that he had been reading, and asked me if I was “familiar with it”. When you consider that someone could spend their entire lives reading papers in a given discipline and still not be familiar with almost all of them, it’s unlikely this client will find a consultant who has coincidentally read the exact paper he’s been looking at. Another client was looking for an expert in “Markov chains”. Not an expert in mathematics with a PhD, who could apply their general skills to many different problems including Markov chains, but someone who specifically specialized in the exact topic the client was interested in. Just like the kinds of interviews I’ve been discussing, these clients were focused on very specific knowledge rather than the broad applicability of general capabilities.

As a very experienced classical pianist, I can provide a good analogy here. If an interviewer were to try to test my claim of being an experienced pianist by challenging me to play Fur Elise, I can tell you that I wouldn’t be able to do so very well. The reason is that, although this is an easy piece, I haven’t played it in ten or fifteen years. In fact, I may never have properly learnt this piece even as a student. Even though it is an easy piece, I still need time to prepare it and learn/relearn what the notes are. However, I can perform Rachmaninoff’s third piano concerto for the interviewer, one of the most challenging pieces written for piano, simply because I have prepared this piece. A pianist does not have the ability to play any piece, even an easy one, off the top of their heads. The skillset of a pianist is rather to go away and prepare and master a piece, when they are assigned the task of doing so. I believe the same is true of a mathematician or a quant.

The candidate is under a lot of pressure in an interview.

Finally, another key issue that interviewers need to be very aware of is that the interview may be testing how the candidate behaves when under a specific kind of pressure that doesn’t arise in the real workplace. Furthermore, when under pressure, memory may function but careful thinking may be difficult. This would again cause the interviewer to select people who have memorised certain facts, over people who can think about them and figure them out when they need them.

I’ve had interviewers ask me probability questions that 14 year olds would solve in their high school maths classes. It’s strange that an experienced quantitative professional would test another experienced quantitative professional with questions from early high school. This can only really be testing one of two things: 1) Can you remember how to solve, off the top of your head, a problem you haven’t thought about in 20 years? 2) Can we prove that when you’re nervous you might make a mistake on a simple problem? I believe that neither of these is a useful gauge of workplace performance.

Case studies

As case studies, I offer some of the interviews and discussions with clients that I myself have encountered!

Get them to do the work before deciding whether you want to pay them to do the work.

Occasionally I get clients who want to know, right off the bat, how I’ll solve the problem, what the outcome will be, and how long it will take. Needless to say, these questions cannot be answered at time t=0 of a research project. Rather, the first step is for the consultant to begin to read through the documents and papers provided by the client, and begin to build up an understanding of the project. Answers about which technical approach is appropriate, or whether the project is even possible, will begin to take shape over time. In fact, clarifying these questions may be most of the work of the project, rather than something that happens before the project begins.

It reminds me of academics who finish their research project, before applying for an academic grant to fund the now finished research. They then use this money to fund their next project instead. The idea is that, once the research is finished, you can show the grant board exactly what methods you plan to use, how long it will take you, and that you’re certain this approach will work. If you instead go to the grant board saying you’re going to “attempt” to solve the problem, using as yet unknown method, and have no idea how long it will take or if you’ll even succeed, then it will be much harder to convince them to fund you!

Building a model is a totally different skill to checking whether the model has been designed correctly. Apparently.

At one point, I was interviewing for a model validation role. The interviewer didn’t like that I hadn’t done derivative model validation before. It didn’t matter that I had a mathematics PhD, great coding skills and several years experience in derivative modelling. He believed that building mathematical models within a fairly mature derivative pricing system was not the same thing as validating a model from scratch. And, apparently, that skills required for the two roles did not have sufficient overlap.

Shortly thereafter, I got a job doing model validation at a different bank – and of course my general skills and abilities allowed me to perform the role well.

Then a bit later, I heard from a recruiter about a firm that would not consider people working in model validation for a particular role. They held this view because they were looking for someone to “build” models instead of validate them.

For those who don’t know, model validation usually involves building an independent model against which to benchmark the system. It therefore is essentially “building models” anyway.

Then I saw a job advert from Murex which stated that the candidate must have experience developing for Murex 1.3. They were not looking for an experienced quant dev. Or even an experienced dev that was working at the same firm 2 years ago and had a lot of experience developing for Murex 1.29.

By endingly subdividing the industry into more and more specific categories, no candidate is ever quite the right fit for a role.

Mathematics PhDs know less about maths than traders?

I once had an interview for a machine learning role at a prop trading firm. The interviewer was not a mathematician – he was a trader who had at some point studied some machine learning.

“How would you solve a linear regression?”, he asked.

Now, keep in mind that he is talking to someone with a PhD in pure mathematics, who has actually taught 3rd and 4th year mathematics courses at university, and who has several years of postdoctoral research experience. Isn’t it obvious from my background that I don’t need to be assessed on my ability to work with one of the most simple concepts from statistics? I told him that there was an exact formula involving matrices.

“Okay, walk me through that” he persisted.

I told him that I did not recall the formula off the top of my head, but would simply look it up if I needed it.

He next wanted to know if there was anything one needed to do to the data before performing a linear regression. I recalled that the last time I did a linear regression I had to scale the data so that all variables had numbers of a similar order of magnitude.

“Well thaaats interestinggggg! Because it’s scale invariant!”

The trader was probably quite pleased with himself for seemingly tripping me up, and for getting to use a fancy sounded term he had learnt.

I remembered later that the last time I had implemented a linear regression in C++ I had used the gradient descent method. You see, implementing matrix inverses and determinants in C++ is a bit of a pain, and gradient descent converges in only about 5 iterations. It was actually the gradient descent part of the algorithm that required the data scaling. If you solve a linear regression using the matrix formula, you probably don’t need to scale the data. So you see that in a way I was right, but only when solving the regression using the specific method that I had been using. A fact which couldn’t come to light in the short timeframe and pressured questioning of an interview.

“You’ve got machine learning on your CV!”, the trader exclaimed, implying that I clearly knew nothing about machine learning.

As I’ve described already, a mathematics PhD can pick these concepts up very quickly when they need them, but don’t necessarily know them off the tops of their heads. And whether someone has memorised very elementary facts has nothing to do with whether they have the skills to engage in complex research.

There was another trading firm that I interviewed with for what appeared to be a heavily machine learning focused role. I say this because the job description mentioned machine learning not once, but three times. So in the interview, I brought up the topic of machine learning. At first, he didn’t know what I was talking about. Apparently he didn’t know that the job description mentioned machine learning (who wrote the job description, ChatGPT?). Then he said they don’t do machine learning because it overfits. Well, why did they put it in the job description three times then? This is a bit off topic, but it’s so funny I couldn’t resist bringing it up.

Relying on what other people think because you can’t think for yourself

I once had a phone interview with a well-known fund manager in Sydney. I won’t say who he is, other than to say he’s often in the financial news giving his opinions about the economy. He said to me, “If you were paid a lot of money by Westpac, then I’d know you were worth a lot of money!” For those Northern Hemisphere readers, Westpac is an Australian bank that I wasn’t working for at the time of that conversation. The idea was, that if someone else was willing to pay me a lot of money, then he’d believe he should offer me a lot of money. Otherwise he wouldn’t. Relying on the judgement of others to the complete exclusion of your own doesn’t seem wise.

It reminds me of study that found women mainly want to go out with men who already have girlfriends. The authors of the study found women would rate men more attractive if they were told he had a girlfriend, or even if the photo of him showed a random woman smiling in his direction. Apparently, the fact that those men had already been chosen by another girl, convinced other girls that he was worth choosing. Unfortunately, none of those men were available so it seems a poor strategy.

Letting HR design the interview for a role they don’t understand

Years ago, before I started working in quantitative finance, I interviewed with a large Australian telecommunications company called Telstra.

Some manager there had attended a conference where he’d heard about people using statistical methods to model the occurrence of faults in networks, allowing them to move their workers around more efficiently to keep the network operating. Thus, he’d had the idea of hiring a quantitative PhD to do this kind of modelling at Telstra.

What astonished me, is that the interview included not one question about my ability to do statistical modelling. The managers believed that the skills required for statistical modelling didn’t need to be tested and could simply be taken for granted. Indeed, the two managers interviewing me knew little about statistical modelling and simply weren’t qualified to determine whether I was qualified. While I would say that statistical modelling skills were 90% of what was required for the role, these two managers considered them largely irrelevant.

Instead, the interview was a series of HR questions such as, “name a time you’ve worked well on a team”, and “what would you do if you needed someone to do something and they didn’t want to do it”. I remember the female manger kept giggling about she was soon going on holiday to drink cocktails on the beach.

I was entirely unprepared for these sorts of silly questions. Apparently, so were all the other candidates. Indeed, an HR guy from Telstra called me to inform me that they’d decided not to move forward with any of the PhDs they had interviewed because none of them seemed “ready for the role”. While Telstra thought these PhDs could be taught what they were lacking, Telstra was “looking for someone to hit the ground running”.

In the coming years, I kept reading in the news about how Telstra’s network was down again.

Smart people should know everything. Even things you haven’t told them yet!

I’ll end with an Anecdote from the physicist Richard Feynman.

In one of his books, Feynman describes an anecdote from when he was working on the atomic bomb project.

Some engineers presented him with a stack of blueprints representing a proposed chemical facility, and gave him a whirlwind explanation of these very complicated blueprints, leaving him in a daze. He was struggling to guess what the squares with crosses in the middle represented – were they valves or perhaps windows? Since everyone was looking at him, waiting for him to say something, he eventually pointed randomly at one of the squares with crosses in and said, “what happens if this one gets stuck?”

After some frantic whispering, the engineers said “you’re absolutely right, sir”, and rolled up their blueprints and exited the room.

“I knew you were a genius”, the lieutenant said.

Just like in your average job interview, Feynman was being asked to perform a task very quickly, with inadequate information, under pressure. In this case, Feynman got lucky.

Remember, if someone can do something quickly, it’s not because they are a genius – it’s because they’ve done it before.

Market Risk Consulting and FRTB

Looking for market risk consulting and advisory services? Or looking for quant developers to build market risk software? Our PhD qualified quants have you covered! Contact us to learn more.

See also our model validation consulting services.

Market risk is the risk of losses in a portfolio due to adverse movements in market variables such as interest rates, exchange rates, equity prices, and volatilities of equities and exchange rates.

The standard approach is to base the calculation of the empirical distribution of daily shifts in market variables over some period, typically the most recent year of the data or data from a particularly stressed historical period. Each set of daily shifts are applied to the inputs of the pricing models for each asset in the bank’s portfolio. Assuming, say, 250 business days worth of shift data, this gives rise to 250 possible valuation changes in the portfolio. Market risk capital is often taken to be a certain quantile of this empirical distribution, say the 99% quantile or worst move out of 100 . This is called VaR (value at risk). However, there is an alterative approach called expected shortfall.

A significant amount of the work involved in market risk management therefore goes into developing, validating and revalidating the asset pricing models. This typically includes:

  • Spot, forward and futures trades
  • Fixed income products and interest rate products like bonds, inflation bonds, FRNs and swaps.
  • Derivatives on equities, FX and interest rates, including European/American exercise and features such as barriers.

For some asset classes, accurate models may be computationally prohibitive. This is the case for barrier options for example, whose sensitivity to volatility term structure requires the use of a local volatility or stochastic volatility model. This may necessitate pricing barrier options using a constant volatility assumption, and then developing an auxillary model to calculate and compensate for the capital error. In many cases, historical volatility data may have to be processed through a cleaning algorithm to remove arbitrage and unusual data from volatility surfaces.

In addition to the building asset pricing models, developing or validating a market risk system involves correctly calculating shifts in market data. There are many subtle pitfalls in this process, including dealing with interpolation between curve pillars. Cubic spline interpolation can lead to strange behaviour which must be carefully considered. Some market risk systems will switch from cubic spline to linear interpolation to increase valuation speed, which introduces an error that must be quantified and shown to be acceptable.

Furthermore, when calculating market risk for bonds, it’s necessary to calculate shifts in both the underlying interest rate curve and the zspread on top of it (alternatively, some systems prefer to work in terms of survival curves). When validating zspread shifts for bonds, we’ve found that there are some subtle issues in calculating these which need to be handled carefully. Since zspread shifts are calibrated primarily to the bond maturity, once must be careful to shift all historical curves forward to today’s date before calculating the zspread shift. One must also be careful about whether one is using absolute or relative shifts for the interest rate, if the interest rate shift is to be consistent with the zspread shift.

The Fundamental review of the trading book (FRTB) is a new international for market risk measurement. Developed by the Basel Committee, it’s designed to improve upon deficiencies in market risk management that came to light during the GFC. Most larger banks would aim to implement the Internal Modelling Approach (IMA), as the alternative Standardized Approach is typically far more punitive in terms of the amount of market risk capital that must be held. The implementation of the FRTB regulation is generally expected to increase market risk capital, particularly around products that are illiquid or hard to model.

Because of its importance to regulators, the requirement to implement and comply with new FRTB regulation, and the complexity involved in calculating market risk for a large and diverse portfolio, market risk management is currently a highly active field. We offer a wide range of market risk consulting services including:

  • Development of VaR and expected shortfall calculations
  • Development and validation of asset pricing models
  • Development and validation of market data shift calculations including interest rate curves, FX curves and zspreads / survival curves.

You may also like to check out our article on Margin modelling and the standard initial margin model (SIMM).

To discuss how our sophisticated cloud-based quant consulting and advisory business can supercharge your financial services firm, contact us today.

Does backtesting work? / Why doesn’t backtesting work?

If a trading strategy seems to backtest successfully, why doesn’t it always work in live trading?

It’s widely acknowledged that a strategy that worked in the past may not work in the future. Market conditions change, other participants change their algorithms, adapt to your attempts to pick them off, and so on. This means you need to continually monitor and adjust even profitable strategies.

But there’s something even more problematic about backtesting strategies, which fewer people understand clearly. This is that a profitable backtest does not prove that a strategy “worked”, even in the past. This is because most backtests do not achieve any kind of “statistical significance”.

As everyone knows, it’s trivial to tailor a strategy that works beautifully on any given piece of historical data. It’s easy to contrive a strategy that fits the idiosyncratic features of a particular historical dataset, and then show that it is profitable when backtested. But when no mechanism actually exists relating the signal to future movement, the strategy will fail in live testing.

So how does one tell the difference? How can one show that a backtest is not only profitable, but statistically significant?

See also our backtesting software development and our algorithmic trading consulting services.

Statistical hypothesis testing in trading

If you’ve studied some basic statistics, you’ve probably heard of hypothesis testing.

In hypothesis testing, it’s not enough for a model to fit the data. It’s got to fit the data in a way that is “statistically significant”. This means that it’s unlikely that the model would fit the data to the extent that it does, by chance or for any other reason than that the model really is valid. The only way for the model not to be valid is to invoke an “unlikely coincidence”.

One proposes some hypothesis about the data, and then considers the probability (called the p-value) that the apparent agreement between the data and the hypothesis occurred by chance. By convention, if the p-value is less than 5%, the hypothesis is considered statistically significant.

It’s worthwhile to place backtesting within this framework of hypothesis testing to help understand what, if anything, we can really conclude from a given backtest.

Coin toss trading

Let’s keep it simple to start with. Let’s suppose we have an algorithm which predicts, at time steps \(t_1,…,t_n\), whether the asset will subsequently move up (change \(\geq 0\)) or down (change \(<0\)) over some time interval \(\Delta T\). We then run a backtest and find that our algorithm was right some fraction \(0 \leq x \leq 1\) of the time.

If our algorithm was right more than half of the time during the backtest, what’s the probability that our algorithm was right only by chance? This is calculated using the binomial distribution. To see some numbers, let’s suppose our algorithm makes 20 predictions (\(n=20\)) and is right for 12 of them. The probability of this happening entirely by chance is about 25%. If it’s right for 14 of them, the probability of this happening by chance is about 5.8%. This is approaching statistical significance according to convention. The idea is that it’s “unlikely” that our strategy is right by chance, therefore the mechanism proposed by the strategy is likely correct. So if our algorithm got 15 or more correct during the backtest, we’re in the money, right? Not so fast.

To take an extreme example, let’s suppose that our piece of historical data was a spectacular bitcoin bull run that went up 20 times in a row. And let’s suppose that our strategy is “Bitcoin only goes up!” Then our calculation above would prove that the strategy works with a statistical significance of 0.0001%! What’s gone wrong here?

When calculating the p-value for a linear regression, standard statistics usually assumes that the “noise” in the data is random and normally distributed. One mistake we have made in the above analysis is assuming that the actual price trajectory is like a coin toss – equally likely to go up or down. But market movements are not random. They can, for example, be highly autocorrelated. And they can go up in a highly non-random way for quite some time, before turning around and going down.

Secondly, we presumably looked at the data before deciding on the strategy. If you’re allowed to look at the data first, it’s easy to contrive a strategy that exactly matches what the data happened to do. In this case, it’s not “unlikely” that our strategy is profitable by mere coincidence, because we simply chose the strategy that we could see matched the data.

Another thing that can destroy statistical validity is testing multiple models. Suppose a given model has a p value of 0.05, that is, it has only a 5% chance of appearing correct by chance. But now suppose you test 20 different models. Suddenly it’s quite likely that one of them will backtest successfully by chance alone. This sort of scenario can easily arise when one tests their strategy for many different choices of parameter, and chooses the one that works. This is why strategy “optimization” needs to be done carefully.

So how do you backtest successfully?

In practice, we wouldn’t be checking whether the asset goes up or down. Instead, we’d likely check, across all pairs of buy and sell decisions, whether the sellprice minus the buyprice amounted to a profit greater than buy and hold. We would then ask, what is the probability that this apparent fit occurred by chance, and the strategy doesn’t really work? If it seems unlikely that the observed fit could be a coincidence, we may be onto a winner.

On the other hand, a trader may have some external or pre-existing reason for believing that a strategy could work. In this case, he/she may not require the same degree of statistical significant. This is analogous to Bayesian statistics where one incorporates a prior belief into their statistical analysis.

Now, HFT (high frequency trading) backtests can often achieve statistical significant much more easily because of the large amount of data and the large number of buy/sell decisions in a short space of time. More pedestrian strategies will have a harder time.

So does machine learning work for trading?

People often ask whether machine learning techniques are effective for developing trading strategies. The answer is: it depends on how they’re applied. When machine learning models are fit to data, they produce certain “p-value” statistics which are vulnerable to all the issues we’ve discussed. Therefore, some care is needed to ensure the models are in fact statistically significant.

Custom-built Backtesting Software for Traders

We create custom-built backtesting software in languages like C++ and python for individual traders and institutions to test and optimize their strategies. We also offer general algorithmic trading consulting services.

Finding that backtesting doesn’t seem to work for you? This article may help you understand the statistical reasons for this.

We create backtesting software for all asset classes including backtesting strategies on equities, FX, options, futures and cryptocurrencies.

Whether you’re a lone day trader looking to test your strategy, or a sizable organisation looking for get your feet wet with algorithmic trading and machine learning, our cloud-based quant consulting service has got you covered. This includes:

  • Python scripts which can evaluate your strategy for different parameters and determine the parameters that give optimal profitability.
  • Applications which use analyse large amounts of data, use machine learning techniques to find statistically significant signals, and find the optimal way to combine all meaningful signals into a single strategy.
  • Software to automate your strategies by connecting directly to the exchange to grab the latest price information and post buy/sell orders.

There are many advantages of custom-built backtesting software over the simple built-in functionality offered by some exchanges:

  • Code offers unfettered ability to do complex calculations on historical data, including analysing for the presence sophisticated technical analysis patterns.
  • The software can analyse a wide variety of datasets when making trading decisions, including data from other assets and data from outside the exchange.
  • The power of python – make use of python’s mathematical tools, machine learning and data analysis libraries
  • The software can grow in scope and complexity as your business grows, as you expand into new strategies and products.

Partner with our experienced PhD quants to supercharge your trading business. Contact us today to discuss how we can design custom-built backtesting software to meet your needs.

To learn more about what’s involved in automating a strategy, see our simple guides for using python to connect to Interactive Brokers and Binance.

Check out our articles on backtesting moving average crossover strategies on Forex and on Bitcoin, and our article on cryptocurrency correlation strategies.

Learn more about our algorithmic trading consulting services. More generally, we offer a wide range of Quant consulting services for financial organisations of all sizes.

Cryptocurrency Research and Analysis – Risk and Quant Consulting Services

Looking for PhD quantitative support and risk management for your cryptocurrency business? Look no further! Contact us to learn how our quants can help.

As decentralized finance continues to grow in size, there is increasingly a need to quants (quantitative analysts) to bring to bear their skills from the world of traditional finance. Due to the relative immaturity of the industry, there is a huge opportunity for cryptocurrency startups to gain a competitive advantage through quantitative skills and tools.

Applications include derivative pricing models, risk modelling including market risk, credit risk and liquidity risk, and developing and backtesting trading algorithms. There are even many novel applications including the mathematics of decentralized oracles and so-called automated market makers.

We offer cloud-based PhD quant consulting and advisory services to the defi industry, all conveniently delivered remotely to anywhere in the world.

Decentralized finance needs a decentralized quant consulting service!

Derivative pricing models

The cryptocurrency derivatives market is still in its early stages. From TradFi, we already have the mathematical techniques to price options in the form of Black-Scholes. And we even have the tools to price exotic derivatives like American, Asian and barrier options. However, we do need sufficient liquidity in the options market in order to derive implied volatilities. We can develop robust libraries of derivative pricing models so your firm can price any kind of cryptocurrency derivative. See our main article on Cryptocurrency Derivatives.

Risk modelling for defi

With the crypto industry continuing to grow in size, risk management should play as important a role in managing customers and assets as it currently does in conventional finance. Given a number of high profile collapses in the industry, effective and reputable risk management could help to allay customer concerns about holding digital assets or interacting with your firm. It’s particularly useful to consider how the extensive existing literature on risk and risk modelling can be carried over to the crypto space. Market risk and liquidity risk modelling are standard challenges arising in other kinds of finance, and one can consult the literature in order to develop similar frameworks for the crypto space. We do however need to take due notice of the higher volatility which creates some additional challenges.

Market risk

There’s some uncertainty about whether digital assets should be modelled more like exchange rates or more like equities. But there’s not doubt that exchange rates between two digital currencies or between a digital currency and a conventional currency exhibit a high degree of volatility, raising some new challenges for market risk modelling

Borrowing and lending businesses have to take collateral to insure their loans. Similarly, exchanges need to take margin to ensure counterparties can meet their obligations when trades move against them. In both cases, firms are exposed to market risk on the value of the collateral, which could also be interpreted as FX risk between the relevant currencies where multiple cryptocurrencies are involved. In particular for borrowing and lending, one needs to be concerned about changes in relative value between collateral in one token and loaned amount in another. A mathematical model is needed which can can set parameters like the LTV (loan to value ratio) or liquidation trigger level in order to avoid the value of the collateral ever falling below the value of the loaned amount.

A standard way to model market risk is VaR (value at risk). We calculate the relative shift in each asset or market variable over each of the last 250 days, and apply each shift to today’s portfolio. We can then calculate the 99% worst quantile (typically assuming normally-distributed price moves) and make sure margin/LTV is sufficient. Actually, it may be advisable to work out what the liquidation window would be, and use that as our timeframe for VaR calculations. This may have implications for how much collateral you’re comfortable holding in any given coin.

Liquidity and execution risk

In addition to market risk, crypto firms are exposed to liquidity risk when trying to dispose of assets and collateral. The larger a firm grows and the larger its market share becomes, the more liquidity risk becomes a key concern. Liquidity risk may be of particular concern for emerging markets such as digital currencies.

Modelling liquidity risk involves looking not just at the mid price of the assets (as market risk tends to), but also considering the market depth and bid-ask spread. Both of these quantities can be examined in a VaR framework in a similar way to market risk. Large spreads are likely correlated to adverse price moves. Some research indicates they may not be normally distributed as is often assumed in market risk. Data analysis can be performed to determine the appropriate spread modelling assumptions for cryptocurrencies.

One can also consider modelling market risk and liquidity risk together in one model, looking at the 99% quantile of adverse price/spread/market depth moves and backtesting a portfolio or risk management protocols against the historical data or hypothetical scenarios.

Of relevance here also are liquidation algorithms / order splitting. While some illiquidity scenarios will be completely outside our control, in other scenarios the crisis arises only if we try to transact too much too quickly (which of course we may have good reason to attempt in a stressed scenario). Thus researching liquidation algorithms and backtesting them are important also.

In particular, it’s important to backtest against a stressed period in the market’s history, to understand how we would respond. This would include situations where collateral value declines dramatically or quickly, many customers wish to withdraw their collateral simultaneously, or periods of higher than usual volatility.

Another important tool is scenario analysis. This is where we consider a range of hypothetical qualitative and non-modellable scenarios, such as liquidity providers shutting down completely, to evaluate how we would respond.

Correlation and principal component analysis (PCA)

Since cryptocurrencies move together to a significant extent, we can separate market risk into systematic risk (i.e. FX rates between cryptocurrencies and USD, which could perhaps be taken as BTCUSD) vs idiosyncratic risk (cryptocurrencies moving independently of each other, ETHBTC for example).

If both the asset and the collateral are digital currencies (and not pegged to a conventional currency), then their price relationship is not affected by an overall move in the crypto space. Thus we would be interested in looking at the risk of relative movement only.

We can start with a correlation analysis of price moves over different time intervals of both traditional and digital currencies. Below shows the correlations of one day price moves over a one year period. It’s clear that there is significant correlation.

We can also do PCA (principal component analysis) to determine how the various coins relevant to the firm move together / contrary to each other. This helps us to understand what benefit is derived from diversification. A PCA analysis is often done on rates curves to determine to what extent short / long tenors move together.

The below PCA analysis shows that 75% of the variance in the price shifts can be explained by all coins moving in the same direction (notice how in the first vector/row all values have the same sign). Interestingly, 9% of the variance can be explained by DOGE moving contrary to all the other coins (notice how it is the only one with a positive value in the second row). The next two rows, explaining 4.7% and 4.5% of the variance respectively, show the coins moving in different directions to each other.

[75%, 9%, 4.7%, 4.5%]

Backtesting and trading algorithms

We design software to backtest, optimize or automate trading or investing strategies.

Backtesting can be performed on historical data, or on hypothetical synthetic data to test the strategy against a wide range of possible market conditions. Usually, trading strategies have a range of possible parameters which we can set for optimal profitability by examining their behaviour on historical data. If you’re still placing your buy and sell orders manually, we can automate execution by writing code to interact directly with the exchange. This not only allows faster reaction, it also allows sophisticated data analysis and machine learning to be incorporated into your strategy. And automation is particularly important for crypto markets which still operate even while you sleep.

The benefits of backtesting actually extends beyond trading. Almost any business or risk strategy can be backtested against historical or hypothetical data in order to test the profitability or robustness of the business.

For more details, see for example our article on statistical arbitrage / pairs trading for crypto, and backtesting a moving average crossover strategy on bitcoin. More generally, we offer algorithmic trading consulting services on both traditional and digital asset classes.

Decentralized oracles

How does an decentralized oracle convert multiple data sources into a single price or datum? For example, some data sources (for example, different exchanges) may receive different weighting, and more recent data points may be weighted differently than older ones. The oracle might also trim the data between two quantiles to remove the influence of outliers. Some decentralized oracles offer a reward for participants that submit data close to the final price, such as Flare Time Series Oracle (FTSO). How would you go about succeeding as such a participant, or just predicting the final price for your own use? This is where machine learning algorithms come in.

We can build predictive algorithms for decentralized oracles, using machine learning, which predict how oracles combine a large number of inputs into a final output.

Automated Market Makers

Automated market makers, or AMMs, provide liquidity to the market by allowing exchange between two or more cryptocurrencies. They do this by incentivizing people to contribute coins to the pool, and by penalizing the exchange rate if the liquidity ratio swings too far in the direction of one of the coins.

The mathematics around profit and risk of automated market makers and the liquidity tokens they issue require some careful thought. We can conduct this analysis and conduct backtesting of your business strategy.

For details on the Uniswap algorithm, see this article on the CURVE exchange.

Statistical Arbitrage / Pairs Trading on Cryptocurrency

The relative immaturity of crypto markets may mean there are more opportunities for arbitrage than on more conventional markets. In this article, we investigate whether price moves in crypto coins are correlated. Specifically, we see whether the last three price moves in a selection of coins can be used to predict the next move in a coin of interest. You could consider this strategy to be a type of statistical arbitrage, or a pairs trading strategy (albeit over a small interval of time).

We implement a vector autoregression model on a select of nine major crypto coins, whose tickers are: SOL-USD, BTC-USD, ETH-USD, BNB-USD, XRP-USD, ADA-USD, MATIC-USD, DOGE-USD, DOT-USD. All data is grabbed directly from Yahoo Finance using the yfinance python package. We use one week of data (the most recent at the time of writing) and a 1 minute time interval.

A VAR model is a variety of linear regression that attempts to predict the next move of a particular coin, based on the last few price moves of the coin and all the other coins. The idea is two-fold. Firstly, if two coins tend to correlate but one moves first, it may portend a move of the other coin. Secondly, the VAR model will attempt to find a trend in the coin itself. In fact, a VAR model includes a moving average crossover as a subset of what it can fit. An interesting feature is that it can potentially use moving averages in other coins as a predictive signal. However, I only used the three previous price moves as inputs to the model as using more than this didn’t appear to improve the result in this case.

The results show that the algorithm is effective at predicting the next move of many coins, but does not appear to be effective for bitcoin.

Results

The code produces a scatterplot of the actual vs predicted price moves, along with the correlation and p value between the two. Note that since the code grabs the most recent data at the time of execution, the numbers may differ between runs. Below I show two coins where the algorithm is effective and one where it isn’t.

XRP-USD

LinregressResult(slope=0.34169299506953943, intercept=2.8755621199597785e-06, rvalue=0.5873282315680474, pvalue=1.551167162089073e-22, stderr=0.031321080004877454, intercept_stderr=5.55906329231313e-06)

XRP-USD shows a strong correlation of 0.59 between the actual and predicted next move, with a negligible p value demonstrating statistical significance.

SOL-USD

LinregressResult(slope=0.475485553436831, intercept=0.00019601607035287644, rvalue=0.7072734886815194, pvalue=3.3905483806325366e-36, stderr=0.031474953721131224, intercept_stderr=0.0004961483745867841)

SOL-USD shows a strong correlation of 0.71, with a negligible p value demonstrating statistical significance.

BTC-USD

LinregressResult(slope=-0.002493212361084034, intercept=1.8926030981056112, rvalue=-0.015123422722563179, pvalue=0.8195518097975965, stderr=0.010916717835828317, intercept_stderr=0.2832675090566032)

By contrast, BTC-USD shows a poor correlation of 0.015 and a p value of 0.8 showing no statistical significance at all. My interpretation of this is that the smaller coins are more likely to be affected by price moves in Bitcoin, rather than the other way around.

For many coins, the algorithm is able to predict the next price move with strong correlation. Thus, the algorithm could be the starting point for an effective strategy for a variety of cryptocoins.

Future development

A good next step for developing this idea would be to explore using a time interval of less than one minute. Particularly in live prediction, one would not want to wait up to a minute to analyse the data and make a decision. Ideally, the algorithm would analyse and take action every time the exchange updated the price of one or more coins. It would also be interesting to develop a model that accesses data for a very large number of assets (including not just crypto but other asset types, economic parameters etc) and search for correlations. One could eventually explore using big data / machine learning techniques to search for these relationships.

Python code

Below is the python code used for this article. You can specify which coin you are trying to predict using the index_to_predict variable. In order to protect against overfitting to a particular piece of historical data, the variable test_fraction specifies how much of the data to set aside for testing (I’ve used the last 20%).

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.api import VAR
from statsmodels.tsa.statespace.tools import diff
from scipy.stats import linregress
import yfinance as yf

# Set data and period interval
period = "1w"
# Valid intervals: [1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo]
interval = '1m'

# Number of previous moves to use for fitting
VAR_order = 3

tickers = ["SOL-USD","BTC-USD","ETH-USD","BNB-USD","XRP-USD","ADA-USD","MATIC-USD","DOGE-USD","DOT-USD"]
# Specify which coin to forecast
index_to_predict = 0
test_fraction = 0.2 # fraction of data to use for testing

data = yf.download(tickers = tickers,  # list of tickers
                period = period,         # time period
                interval = interval,       # trading interval
                ignore_tz = True,      # ignore timezone when aligning data from different exchanges?
                prepost = False)       # download pre/post market hours data?

X = np.zeros((data.shape[0],len(tickers)))

for (i,asset) in enumerate(tickers):
    X[:,i] = list(data['Close'][asset])
 
# Deal with missing data.
NANs = np.argwhere(np.isnan(X))
for i in range(len(NANs)):
    row = NANs[i][0]
    X[row,:] = X[row-1,:]
 
# Difference data
Xd = diff(X) 

# Determine test and fitting ranges
test_start = round(len(Xd)*(1-test_fraction))
Xd_fit = Xd[:test_start]
Xd_test = Xd[test_start:]

model = VAR(Xd_fit)
results = model.fit(VAR_order)
summary = results.summary()
print(summary)

lag = results.k_ar

predicted = []
actual = []
for i in range(lag,len(Xd_test)):
    actual.append(Xd_test[i,index_to_predict ])
    predicted.append(results.forecast(Xd_test[i-lag:i], 1)[0][index_to_predict])
    
plt.title(tickers[index_to_predict])
plt.scatter(actual, predicted)
plt.xlabel("Actual")
plt.ylabel("Predicted")

print(linregress(actual, predicted))