{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tick data\n",
    "\n",
    "For optimum results this notebook should be run during the Forex trading session."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<IB connected to 127.0.0.1:7497 clientId=15>"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from ib_insync import *\n",
    "util.startLoop()\n",
    "\n",
    "ib = IB()\n",
    "ib.connect('127.0.0.1', 7497, clientId=15)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Streaming tick data\n",
    "\n",
    "Create some Forex contracts:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "contracts = [Forex(pair) for pair in ('EURUSD', 'USDJPY', 'GBPUSD', 'USDCHF', 'USDCAD', 'AUDUSD')]\n",
    "ib.qualifyContracts(*contracts)\n",
    "\n",
    "eurusd = contracts[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Request streaming ticks for them:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "for contract in contracts:\n",
    "    ib.reqMktData(contract, '', False, False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Wait a few seconds for the tickers to get filled."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Ticker(contract=Forex('EURUSD', conId=12087792, exchange='IDEALPRO', localSymbol='EUR.USD', tradingClass='EUR.USD'), time=datetime.datetime(2019, 12, 31, 17, 5, 2, 127038, tzinfo=datetime.timezone.utc), bid=1.12245, bidSize=10700000, ask=1.1225, askSize=2000000, high=1.1239, low=1.11985, close=1.12, halted=0.0)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ticker = ib.ticker(eurusd)\n",
    "ib.sleep(2)\n",
    "\n",
    "ticker"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The price of Forex ticks is always nan. To get a midpoint price use ``midpoint()`` or ``marketPrice()``.\n",
    "\n",
    "The tickers are kept live updated, try this a few times to see if the price changes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.1224750000000001"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ticker.marketPrice()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following cell will start a 30 second loop that prints a live updated ticker table.\n",
    "It is updated on every ticker change."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>bidSize</th>\n",
       "      <th>bid</th>\n",
       "      <th>ask</th>\n",
       "      <th>askSize</th>\n",
       "      <th>high</th>\n",
       "      <th>low</th>\n",
       "      <th>close</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>EURUSD</th>\n",
       "      <td>11500000</td>\n",
       "      <td>1.1225</td>\n",
       "      <td>1.12255</td>\n",
       "      <td>1000000</td>\n",
       "      <td>1.1239</td>\n",
       "      <td>1.11985</td>\n",
       "      <td>1.12</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>USDJPY</th>\n",
       "      <td>8000000</td>\n",
       "      <td>108.665</td>\n",
       "      <td>108.675</td>\n",
       "      <td>16000000</td>\n",
       "      <td>108.885</td>\n",
       "      <td>108.475</td>\n",
       "      <td>108.87</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>GBPUSD</th>\n",
       "      <td>6000000</td>\n",
       "      <td>1.327</td>\n",
       "      <td>1.3271</td>\n",
       "      <td>500000</td>\n",
       "      <td>1.32845</td>\n",
       "      <td>1.3106</td>\n",
       "      <td>1.3111</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>USDCHF</th>\n",
       "      <td>10000000</td>\n",
       "      <td>0.9677</td>\n",
       "      <td>0.9678</td>\n",
       "      <td>7000000</td>\n",
       "      <td>0.9698</td>\n",
       "      <td>0.96465</td>\n",
       "      <td>0.96935</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>USDCAD</th>\n",
       "      <td>1000000</td>\n",
       "      <td>1.2966</td>\n",
       "      <td>1.29665</td>\n",
       "      <td>2000000</td>\n",
       "      <td>1.30695</td>\n",
       "      <td>1.29515</td>\n",
       "      <td>1.3068</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>AUDUSD</th>\n",
       "      <td>9500000</td>\n",
       "      <td>0.70305</td>\n",
       "      <td>0.7031</td>\n",
       "      <td>1000000</td>\n",
       "      <td>0.70325</td>\n",
       "      <td>0.69945</td>\n",
       "      <td>0.6995</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "         bidSize      bid      ask   askSize     high      low    close\n",
       "EURUSD  11500000   1.1225  1.12255   1000000   1.1239  1.11985     1.12\n",
       "USDJPY   8000000  108.665  108.675  16000000  108.885  108.475   108.87\n",
       "GBPUSD   6000000    1.327   1.3271    500000  1.32845   1.3106   1.3111\n",
       "USDCHF  10000000   0.9677   0.9678   7000000   0.9698  0.96465  0.96935\n",
       "USDCAD   1000000   1.2966  1.29665   2000000  1.30695  1.29515   1.3068\n",
       "AUDUSD   9500000  0.70305   0.7031   1000000  0.70325  0.69945   0.6995"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import display, clear_output\n",
    "import pandas as pd\n",
    "\n",
    "df = pd.DataFrame(\n",
    "    index=[c.pair() for c in contracts],\n",
    "    columns=['bidSize', 'bid', 'ask', 'askSize', 'high', 'low', 'close'])\n",
    "\n",
    "def onPendingTickers(tickers):\n",
    "    for t in tickers:\n",
    "        df.loc[t.contract.pair()] = (\n",
    "            t.bidSize, t.bid, t.ask, t.askSize, t.high, t.low, t.close)\n",
    "        clear_output(wait=True)\n",
    "    display(df)        \n",
    "\n",
    "ib.pendingTickersEvent += onPendingTickers\n",
    "ib.sleep(30)\n",
    "ib.pendingTickersEvent -= onPendingTickers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "New tick data is available in the 'ticks' attribute of the pending tickers.\n",
    "The tick data will be cleared before the next update."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To stop the live tick subscriptions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "for contract in contracts:\n",
    "    ib.cancelMktData(contract)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Tick by Tick data ###\n",
    "\n",
    "The ticks in the previous section are time-sampled by IB in order to cut on bandwidth. So with ``reqMktdData`` not every tick from the exchanges is sent. The promise of ``reqTickByTickData`` is to send every tick, just how it appears in the TWS Time & Sales window. This functionality is severly nerfed by a total of just three simultaneous subscriptions, where bid-ask ticks and sale ticks also use up a subscription each.\n",
    "\n",
    "The tick-by-tick updates are available from ``ticker.tickByTicks`` and are signalled by ``ib.pendingTickersEvent`` or ``ticker.updateEvent``."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Ticker(contract=Forex('EURUSD', conId=12087792, exchange='IDEALPRO', localSymbol='EUR.USD', tradingClass='EUR.USD'), time=datetime.datetime(2019, 12, 31, 17, 5, 35, 432737, tzinfo=datetime.timezone.utc), bid=1.1225, bidSize=11000000, ask=1.1226, askSize=11500000, prevBid=1.12255, prevBidSize=11500000, prevAsk=1.12255, prevAskSize=1000000, high=1.1239, low=1.11985, close=1.12, halted=0.0, tickByTicks=[TickByTickBidAsk(time=datetime.datetime(2019, 12, 31, 17, 5, 35, 432737, tzinfo=datetime.timezone.utc), bidPrice=1.1225, askPrice=1.12255, bidSize=11500000, askSize=1000000, tickAttribBidAsk=TickAttribBidAsk(bidPastLow=False, askPastHigh=False)), TickByTickBidAsk(time=datetime.datetime(2019, 12, 31, 17, 5, 35, 432737, tzinfo=datetime.timezone.utc), bidPrice=1.1225, askPrice=1.12255, bidSize=11000000, askSize=1000000, tickAttribBidAsk=TickAttribBidAsk(bidPastLow=False, askPastHigh=False)), TickByTickBidAsk(time=datetime.datetime(2019, 12, 31, 17, 5, 35, 432737, tzinfo=datetime.timezone.utc), bidPrice=1.1225, askPrice=1.1226, bidSize=11000000, askSize=11500000, tickAttribBidAsk=TickAttribBidAsk(bidPastLow=False, askPastHigh=False))])\n"
     ]
    }
   ],
   "source": [
    "ticker = ib.reqTickByTickData(eurusd, 'BidAsk')\n",
    "ib.sleep(2)\n",
    "print(ticker)\n",
    "\n",
    "ib.cancelTickByTickData(ticker.contract, 'BidAsk')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Historical tick data\n",
    "\n",
    "Historical tick data can be fetched with a maximum of 1000 ticks at a time. Either the start time or the end time must be given, and one of them must remain empty:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "HistoricalTickBidAsk(time=datetime.datetime(2019, 12, 31, 17, 5, 34, tzinfo=datetime.timezone.utc), tickAttribBidAsk=TickAttribBidAsk(bidPastLow=False, askPastHigh=False), priceBid=1.1225, priceAsk=1.1226, sizeBid=11000000, sizeAsk=11500000)"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import datetime\n",
    "\n",
    "start = ''\n",
    "end = datetime.datetime.now()\n",
    "ticks = ib.reqHistoricalTicks(eurusd, start, end, 1000, 'BID_ASK', useRth=False)\n",
    "\n",
    "ticks[-1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "ib.disconnect()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
