Nasdaq Maker-Taker Fee Pilot

Recently, the SEC sought consultation from industry in relation to the implementation of a pilot to modify exchange access fees in order to evaluate the impact of different fee structures on market quality. While different in scope, in 2015 NASDAQ introduced an asymmetric fee schedule for 14 securities, while the rest of the market maintained the status quo.
Using this event, we seek to assess its impact and extrapolate the likely ramification of the newly proposed pilot on market quality for US markets. Currently, there are two main exchange access fee models operating in US markets: (1) maker-taker, which charges takers of liquidity a fee and provides a rebate to traders who provide liquidity; and (2) taker-maker (inverted markets), which as the name suggests, provides a rebate to traders who take liquidity, and charges a fee to liquidity providers. NASDAQ applies a maker-taker schedule, and in 2015, charged 30 cents per 100 shares traded (CPS) for marketable orders that 'took' liquidity and provided an indicative rebate of 29 CPS for orders that 'made' liquidity. On February 2, 2015, NASDAQ applied a new fee schedule for 14 traded stocks. The take-fee reduced to 5 CPS and the rebate to 4 CPS, while maintaining the net fee of 1 CPS (i.e. the difference between take fee and maker rebate). The pilot ended on May 31, 2015 and the fee reverted to its pre-pilot level. The graphs below highlight summary metrics pre and post both these dates. This event provides an interesting experiment to determine whether the fee level impacts markets (30v5; 29v4), or whether no change is observed as only the net fee is considered by market participants. Recall that this was constant over the pilot (30-29; 5-4).
Utilising the Market Quality Dashboard, reported below are the key findings from an in-depth study conducted by Dr. Yiping Lin (Head of Research and Business Analytics at CMCRC), Professor Peter Swan, and Professor Frederick Harris, entitled: “Why Maker-Taker Fees Improve Exchange Quality: Theory and Natural Experimental Evidence”. This study concludes that a substantial change in NASDAQ component fee levels and rebates does matter with NASDAQ's market share, in terms of trading volume, migrating to other high rebate-paying lit exchanges. This suggests, competition between venues differentiated by differences in maker-taker fee structures can lead to substantial changes in market quality as identified by shifts in: (1) trade volume, (2) depth, (3) transaction costs, and (4) price impact in venues with the highest permitted levels of rebate. These results suggest the new pilot, may have unintended consequences as we spell out in a response letter to the SEC's consultation, which can be found here.
In [1]:
import pandas
from datetime import datetime
from highcharts import Highstock
from IPython.core.display import display, HTML

FILENAME = 'nasdaq_case.csv'
STARTDATE = datetime(2015, 2, 2)
ENDDATE = datetime(2015, 5, 31)
In [2]:
class MakerTakerFeeData:
    """load the csv and spit out data groups"""
    def __init__(self, filename):
        """load the file into a pandas dataframe, just the first 4 columns and convert the date to a date"""
        self.df = pandas.read_csv(filename, usecols=range(4), parse_dates=['date'])

    def get(self, metric, entity):
        """Return the date and value dataframe for the given string args from the other two columns"""
        data = self.df.query('metric == "%s" & entity == "%s"' % (metric, entity))
        return data[['date', 'value']].values.tolist()    
In [3]:
class MakerTakerChart(Highstock):
    """Assemble and display a chart of date/value datas"""
    def __init__(self, title):
        :param:title: a title to render on the chart
        super(MakerTakerChart, self).__init__()
        options = {
            'tooltip': {'valueDecimals': 2, 'crosshairs': [True, True]},
            'yAxis': {'opposite': False},
                    'stacking': 'percent'
            'legend': {'enabled': True},
            'rangeSelector': {'enabled': False},
            'navigator': {'enabled': False},
            'chart': {
                'renderTo': 'container',
                'alignTicks': False,
                'zoomType': 'x',
                'marginTop': 60,
                'height': 400,
                'width': 950
            'scrollbar': {'enabled': False},
            'title': {'text': title,
                     'style': {
                         'color': 'white'
            'xAxis': {
                'plotBands': [{
                    'from': STARTDATE,
                    'to': ENDDATE,
                    'color': 'whitesmoke'
                'plotLines': [{
                       'color': '#c8c8c8',
                       'width': 2,
                       'value': (datetime(2015, 2, 2) -  datetime.utcfromtimestamp(0)).total_seconds() * 1000.0,
                       'zIndex': 1,
                       'label': {
                           'text': 'Nasdaq Maker-Taker Fee Reduced',
                           'rotation': 0,
                           'align': 'center',
                           'x': -22,
                           'y': -5,
                           'style': {
                               'color': '#494747',
                               'fontSize': 11, 
                       'color': '#c8c8c8',
                       'width': 2,
                       'value': (datetime(2015, 5, 31) -  datetime.utcfromtimestamp(0)).total_seconds() * 1000.0,
                       'zIndex': 1,
                       'label': {
                           'text': 'Nasdaq Maker-Taker Fee Reduction Revoked',
                           'rotation': 0,
                           'align': 'center',
                           'x': -22,
                           'y': -5,
                           'style': {
                               'color': '#494747',
                               'fontSize': 11, 
In [4]:
.rendered_html tbody tr:nth-child(odd) {
    background: ##f5f5f5; }
.rendered_html tr, .rendered_html th, .rendered_html td, .rendered_html table {
    text-align: right;
    vertical-align: middle;
    padding: 0.5em 0.5em;
    line-height: normal;
    white-space: normal;
    max-width: none;
    border: none;
In [5]:
data = MakerTakerFeeData(FILENAME)
In [6]:
# flags = [{
#     'x': STARTDATE,
#     'title': "R",
#     'text': "Maker-Taker Fee Reduced"
# }, {
#     'x': ENDDATE,
#     'title': "R",
#     'text': "Maker-Taker Fee Reduction Revoked"
# }]
# flags_kwargs = {
#     'series_type': 'flags', 
#     'color': "lightgray", 
#     'showInLegend': False
# }


Metric: Total trading volume on NASDAQ
Result: NASDAQ total trading volume decreased after the NASDAQ maker-taker fee reduction, and didn't recover to pre-pilot level when the access fee reduction revoked.
In [7]:
chart = MakerTakerChart('NASDAQ Volume')
chart.add_data_set(data.get('nasdaq_volume', 'control_group'), name='Control Group',  color='#b3b3b3')
chart.add_data_set(data.get('nasdaq_volume', 'treatment_group'), name='Treatment Group', color='#1a8cff')
# chart.add_data_set(flags, **flags_kwargs)

Consolidated Volume

Metric: Total trading volume on all U.S. markets
Result: The consolidated trading volume doesn't change after the unilateral access fee change on NASDAQ. Instead, trading volume shifts among lit markets.
In [8]:
chart = MakerTakerChart('Consolidated Volume')
chart.add_data_set(data.get('consolidated_volume', 'control_group'), name='Control Group', color='#b3b3b3')
chart.add_data_set(data.get('consolidated_volume', 'treatment_group'), name='Treatment Group')
# chart.add_data_set(flags, **flags_kwargs)

NASDAQ Market Share

Metric: NASDAQ trading volume over the consolidated trading volume
Result: NASDAQ market share decreased after the access fee pilot and didn't recover to previous level when the fee reduction revoked.
In [9]:
chart = MakerTakerChart('NASDAQ MarketShare')
chart.add_data_set(data.get('nasdaq_mktshare', 'control_group'), name='Control Group', color='#b3b3b3')
chart.add_data_set(data.get('nasdaq_mktshare', 'treatment_group'), name='Treatment Group')
# chart.add_data_set(flags, **flags_kwargs)

NASDAQ Depth Share

Metric: NASDAQ quoted depth over all the depth available on U.S. markets
Result: NASDAQ quoted depth share decreased after the maker rebate reduction as the incentive to provide liquidity decreased.
In [10]:
chart = MakerTakerChart('NASDAQ Depth Share')
chart.add_data_set(data.get('nasdaq_depthshare', 'control_group'), name='Control Group', color='#b3b3b3')
chart.add_data_set(data.get('nasdaq_depthshare', 'treatment_group'), name='Treatment Group')
# chart.add_data_set(flags, **flags_kwargs)

NASDAQ Percentage Time at NBBO

Metric: The percentage of time NASDAQ's quotes providing the national best bid or ask
Result: The NASDAQ percentage of time at NBBO decreased after the access fee reduction as the incentive to add liquidity decreased.
In [11]:
chart = MakerTakerChart('NASDAQ Percent of time at NBBO')
chart.add_data_set(data.get('nasdaq_perc_time_at_nbbo', 'control_group'), name='Control Group', color='#b3b3b3')
chart.add_data_set(data.get('nasdaq_perc_time_at_nbbo', 'treatment_group'), name='Treatment Group')
# chart.add_data_set(flags, **flags_kwargs)

NASDAQ NBBO Quoted Depth

Metric: NASDAQ's quoted depth when quotes are sitting at the NBBO
Result: The NASDAQ NBBO quoted depth decreased after the access fee pilot.
In [12]:
chart = MakerTakerChart('NASDAQ NBBO Quoted Depth')
chart.add_data_set(data.get('nasdaq_nbbo_quoted_depth', 'control_group'), name='Control Group', color='#b3b3b3')
chart.add_data_set(data.get('nasdaq_nbbo_quoted_depth', 'treatment_group'), name='Treatment Group')
# chart.add_data_set(flags, **flags_kwargs)

NASDAQ NBBO Effective Spread

Metric: NASDAQ's effective spread using NBBO quote as reference instead of exchange quote
Result: NASDAQ NBBO effective spread which is a measure of transaction cost decreased after the access fee reduction.
In [13]:
chart = MakerTakerChart('NBBO Effective Spread')
chart.add_data_set(data.get('nbbo_effspread', 'control_group'), name='Control Group', color='#b3b3b3')
chart.add_data_set(data.get('nbbo_effspread', 'treatment_group'), name='Treatment Group')
# chart.add_data_set(flags, **flags_kwargs)

NASDAQ NBBO Realised Spread (5 Seconds)

Metric: NASDAQ's realised spread using NBBO quote (5 seconds delayed) as reference
Result: NASDAQ NBBO realiased spread (5 seconds) which is a measure of liquidity provider's profit increased. The realised spread adjusts in response to the reduced maker rebate. Realised spread with different time intervals are also tested and results are consistent.
In [14]:
chart = MakerTakerChart('NBBO Realised Spread (5 Seconds)')
chart.add_data_set(data.get('nbbo_reaspread5s', 'control_group'), name='Control Group', color='#b3b3b3')
chart.add_data_set(data.get('nbbo_reaspread5s', 'treatment_group'), name='Treatment Group')
# chart.add_data_set(flags, **flags_kwargs)

NBBO Price Impact (5 Seconds)

Metric: NASDAQ's price impact using NBBO quote (5 seconds delayed) as reference
Result: NASDAQ NBBO price impact (5 seconds) which is a measure of information content of trades decreased after the access fee reduction. Price impact with different time intervals are also tested and results are consistent.
In [15]:
chart = MakerTakerChart('NBBO Price Impact (5s)')
chart.add_data_set(data.get('nbbo_primpact5s', 'control_group'), name='Control Group', color='#b3b3b3')
chart.add_data_set(data.get('nbbo_primpact5s', 'treatment_group'), name='Treatment Group')
# chart.add_data_set(flags, **flags_kwargs)