I'm currently backtesting and livetesting a RL-based system using the close of the last 1m bar as both ask and bid. While results are excellent, this is not a very realistic arrangement.

In the absence of real quote data (only historic and live OHLCV candlesticks) I'd like to step up the simulation by generating ask/bid quotes on the fly, based on the latest 1m bars.

I've tried a setup where I peek ahead to the next (i.e. current) 1m bar and use the high and low as ask/bid respectively with a bit of fudging to ensure they're at least one tick apart.

Although reasonable for low liquidity assets, this makes for an unrealistically large spread in faster moving contexts.

I've also tried maintaining a running ask/bid by tracking the actual highs and lows and adjusting one or the other based on an intuitive algorithm, but it still doesn't feel right.

I've tried peeking ahead and dividing the current bar in two at the midpoint, then picking a random value in the upper half as the ask and in the lower half as the bid. But this of course leads to a wildly oscillating spread, which makes training unrealistic.

I've read a few papers on estimating spread (Roll 1984, Corwin & Schultz 2011, etc.) but the spread is only half of the problem/solution.

Is there a recommended way of doing this or an algorithm outline for generating semi-realistic quotes based on the latest OHLCV bars?


2 Answers 2


There is an old literature on estimating the BA-spread using transaction prices: have a look at Estimation of the bid–ask spread and its components: A new approach The Review of Financial Studies, 4(4), 623-656, by George, T. J., Kaul, G., & Nimalendran, M. (1991). It relies on the fact that a bid-ask spread adds a quantity to the autocorrelation of returns, hence you can try to invert the relation and obtain an estimate of the spread.

But it will never be something else that an "average BA-spread" to be used on few days.

My suggestion will be to use it as a baseline, and then to do your best do design a heuristic for two consecutive bins:

  • if you compare the close of on the previous bin with the open of the current one you can guess the spread
  • the minimum of the absolute value of the difference between O, H, L and C are also indications of the spread.

It will not be magic anyway. And in any cas what you want is a way to know if a simulated order "would have been executed" given your data. Simple rules could be

  • if you focus on a limit order, and taking the example of a buy order (on the bid side): generate a trade if any of OHLC are strictly lower than this limit price. You have to make a choice if one of them is exactly at your limit price.
  • if you focus on a market order, you can take the open price plus the average spread you would have estimated another way.
  • $\begingroup$ That makes sense to combine the covariance-based spread estimate with some stochastic heuristics. My only issue with using the close of one bin and the open of the next is that it's not easy to distinguish between them being an ask & bid or simply two asks (for example) that changed. But anyway, I get the point - thanks for the suggestions, I'll give it a try. $\endgroup$
    – guacamole
    Commented Sep 24, 2019 at 10:13
  • $\begingroup$ A recent post "Efficient Estimation of Bid-Ask Spreads from Open, High, Low, and Close Prices" papers.ssrn.com/sol3/papers.cfm?abstract_id=3892335 $\endgroup$ Commented Oct 14, 2021 at 13:21

Following on from lehalle's answer I ended up with the following solution (in case this is useful to anyone).

First for the spread (expressed as an ask:bid ratio) I used the Corwin & Schultz paper as follows (in php):

// Gamma: square of the ratio of the logs of the highest highs and lows
$gamma = pow(log(max($curBar['high'], $prevBar['high']) / max($curBar['low'], $prevBar['low'])), 2);

// Beta: sum of the squares of the logs of the high:low ratios
$beta = pow(log($curBar['high'] / $curBar['low']), 2) + pow(log($prevBar['high'] / $prevBar['low']), 2);

// Alpha: ((3-2√2) x √beta) - √(gamma ÷ (√2-1))
$alpha = 2.414213562373093 * pow($beta, 0.5);
$alpha -= pow($gamma / 0.17157287525381, 0.5);

// Abort negative or null spreads
if ($alpha == 1 || $alpha <= 0)
    return false;

// Calc spread
$alpha = exp($alpha);
$spread = 1 + 2 * ($alpha - 1) / ($alpha + 1);
return $spread;       

Then for tracking a running ask/bid quote I've put together the following (in python):

# First time
if ask == None:
    ask = high
    bid = low

elif high != low:
    if high/low < spread:
        # The spread engulfs the bar
        ask = high
        bid = low
        # The bar engulfs the spread - spread around midpoint
        mid = (high+low)*0.5
        half_spread = (((spread-1.0)*0.5)+1.0)
        ask = mid * half_spread
        bid = mid / half_spread
    # Flat bar (high==low)
    if high > ask:
        # The price moved up - shift the entire spread range up
        bid += high-ask
        ask = high
    elif low < bid:
        # Likewise downward
        ask -= bid-low
        bid = low
        # The price moved within the previous quote range - adjust whichever is nearest
        if abs(ask-high) <= abs(bid-low):
            ask = high
            bid = low

# Quantize to price ticks and ensure non-parity
ask = round(ask, precision)
bid = round(bid, precision)
if ask == bid:
    bid -= price_tick

It's far from perfect but guessing which way to nudge the quotes based on high/low movements whilst limiting the range to the estimated spread gives a plausible, if not accurate idea of where the quotes could be. They at least 'look' right plotted over 1m charts of various liquidities.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.