{"id":221026,"date":"2025-03-31T11:30:20","date_gmt":"2025-03-31T15:30:20","guid":{"rendered":"https:\/\/ibkrcampus.com\/campus\/?p=221026"},"modified":"2025-03-31T11:30:28","modified_gmt":"2025-03-31T15:30:28","slug":"build-a-pairs-trading-strategy-in-python-a-step-by-step-guide","status":"publish","type":"post","link":"https:\/\/www.interactivebrokers.com\/campus\/ibkr-quant-news\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\/","title":{"rendered":"Build a Pairs Trading Strategy in Python: A Step-by-Step Guide"},"content":{"rendered":"\n<p><em>The post &#8220;Build a Pairs Trading Strategy in Python: A Step-by-Step Guide&#8221; was originally published on <a href=\"https:\/\/databento.com\/blog\/build-a-pairs-trading-strategy-in-python\">Databento<\/a>.<\/em><\/p>\n\n\n\n<p><em>This article presents a commonly used strategy and is intended solely for demonstration and educational purposes. It is often taught in classrooms as an example of developing a trading strategy. This information should not be viewed as a guaranteed method for achieving success.<\/em><\/p>\n\n\n\n<p><em>The author of this article is not affiliated with Interactive Brokers. This software is in no way affiliated, endorsed, or approved by Interactive Brokers or any of its affiliates. It comes with absolutely no warranty and should not be used in actual trading unless the user can read and understand the source. The IBKR API team does not support this software<\/em>.<\/p>\n\n\n\n<p><strong>Pairs trading<\/strong>&nbsp;is a form of statistical arbitrage that takes advantage of mean reversion or convergence in the prices of two instruments.<\/p>\n\n\n\n<p>The simplest variation of this strategy involves taking long and short positions simultaneously on a pair of&nbsp;<strong>cointegrated<\/strong>&nbsp;instruments. But more generally, you can construct a spread from any linear combination of the two instruments with the approach in this tutorial \u2014 even going long simultaneously on two instruments.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-setup-and-dependencies\">Setup and dependencies<\/h4>\n\n\n\n<p>We\u2019ll make use of the following external libraries:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/databento.com\/docs\">databento<\/a>&nbsp;for market data.<\/li>\n\n\n\n<li><a href=\"https:\/\/pandas.pydata.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">pandas<\/a>&nbsp;for data manipulation.<\/li>\n\n\n\n<li><a href=\"https:\/\/matplotlib.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">matplotlib<\/a>&nbsp;for plotting.<\/li>\n\n\n\n<li><a href=\"https:\/\/www.statsmodels.org\/stable\/index.html\" target=\"_blank\" rel=\"noreferrer noopener\">statsmodels<\/a>&nbsp;for the Engle-Granger two-step cointegration test.<\/li>\n\n\n\n<li><a href=\"https:\/\/scikit-learn.org\/stable\/\" target=\"_blank\" rel=\"noreferrer noopener\">scikit-learn<\/a>&nbsp;to fit the linear regression between the two price series.<\/li>\n<\/ul>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import databento as db\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom statsmodels.tsa.stattools\nimport coint\nfrom sklearn.linear_model\nimport LinearRegression<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-cme-wti-cl-vs-ice-brent-crude-oil-brn\">CME WTI (CL) vs. ICE Brent crude oil (BRN)<\/h4>\n\n\n\n<p>Most existing literature shows the application of this strategy to equities, however the strategy can be applied to any asset class.<\/p>\n\n\n\n<p>We\u2019ll demonstrate the pairs trading strategy on the two most liquid crude oil products traded on CME and ICE, the&nbsp;<a href=\"https:\/\/databento.com\/portal\/catalog\/cme\/GLBX.MDP3\/futures\/CL\">WTI crude oil futures<\/a>&nbsp;(CL) and&nbsp;<a href=\"https:\/\/databento.com\/portal\/catalog\/ifeu\/IFEU.IMPACT\/futures\/BRN\">Brent crude oil futures<\/a>&nbsp;(BRN) contracts respectively.<\/p>\n\n\n\n<p>This also showcases the value of Databento\u2019s supplemental receive timestamps,&nbsp;<code>ts_recv<\/code>, which are used to construct our OHLCV aggregates. Most academic literature avoids cross-venue arbitrage like this because timestamps from two different exchanges are based on the different reference clocks that cannot be synced easily.&nbsp;<code>ts_recv<\/code>&nbsp;mitigates this problem by providing a common reference time with sub-microsecond accuracy between geographically-distant venues.<\/p>\n\n\n\n<p><em>Note: Databento timestamps CME crude and ICE Brent data on the same Aurora I gateway. If you\u2019re using a different data source, you may have to apply a delay offset for the time between Aurora I and 350 E Cermak where their matching engines are situated respectively.<\/em><\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-configuration\">Configuration<\/h4>\n\n\n\n<p>Exchange and clearing fees can be found on&nbsp;<a href=\"https:\/\/www.cmegroup.com\/company\/clearing-fees.html\" target=\"_blank\" rel=\"noreferrer noopener\">CME<\/a>&nbsp;and&nbsp;<a href=\"https:\/\/www.ice.com\/publicdocs\/IFEU_Exchange_Clearing_Fees.pdf\" target=\"_blank\" rel=\"noreferrer noopener\">ICE\u2019s<\/a>&nbsp;websites. Tick sizes and notional tick values can be obtained from&nbsp;<a href=\"https:\/\/databento.com\/docs\/schemas-and-data-formats\/instrument-definitions?historical=python&amp;live=python&amp;reference=python?utm_source=twitter\">instrument definitions<\/a>.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># Instrument-specific configuration\nSYMBOL_X = \"CLM4\"\nSYMBOL_Y = \"BRN FMN0024!\"\nTICK_SIZE_X = 0.01\nTICK_SIZE_Y = 0.01\nTICK_VALUE_X = 10.0\nTICK_VALUE_Y = 10.0\nFEES_PER_SIDE_X = 0.70      # NYMEX corporate member fees\nFEES_PER_SIDE_Y = 0.88\n\n# Global configuration\nCOMMISSIONS_PER_SIDE = 0.08\n\n# Backtest configuration\nSTART = \"2024-04-01\"\nEND = \"2024-05-15\"<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-data-extraction\">Data extraction<\/h4>\n\n\n\n<p>We\u2019ll fetch 1-minute&nbsp;<a href=\"https:\/\/databento.com\/docs\/schemas-and-data-formats\/ohlcv?historical=python&amp;live=python&amp;reference=python?utm_source=twitter\">OHLCV aggregates<\/a>&nbsp;for ease of demonstration, but you may specify&nbsp;<code>schema=\"mbp-1\"<\/code>&nbsp;to use&nbsp;<a href=\"https:\/\/databento.com\/docs\/schemas-and-data-formats\/mbp-1?historical=python&amp;live=python&amp;reference=python?utm_source=twitter\">MBP-1<\/a>&nbsp;instead:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def fetch_data():\n\n    client = db.Historical()\n\n    data = client.timeseries.get_range(\n        dataset=\"GLBX.MDP3\",\n        schema=\"ohlcv-1m\",\n        stype_in=\"raw_symbol\",\n        symbols=[SYMBOL_X],\n        start=START,\n        end=END,\n    )\n\n    df_x = data.to_df()[\"close\"].to_frame(name=\"x\")\n\n    data = client.timeseries.get_range(\n        dataset=\"IFEU.IMPACT\",\n        schema=\"ohlcv-1m\",\n        stype_in=\"raw_symbol\",\n        symbols=[SYMBOL_Y],\n        start=START,\n        end=END,\n    )\n\n    df_y = data.to_df()[\"close\"].to_frame(name=\"y\")\n\n    return df_x.join(df_y, how=\"outer\").ffill()<\/pre>\n\n\n\n<p>Since OHLCV aggregates only print when there\u2019s a trade in the interval, you\u2019ll need to use the&nbsp;<code>.ffill()<\/code>&nbsp;method to synchronize the two time series in cases where one leg doesn&#8217;t print.<\/p>\n\n\n\n<p><em>Tip: Although CL and BRN are liquid futures contracts, there\u2019s the possibility that both sides don\u2019t print in a given minute interval. A more complete implementation should resample during every minute of the trading session.<\/em><\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-engle-granger-cointegration-test\">Engle-Granger cointegration test<\/h4>\n\n\n\n<p>The basis of this strategy is to check that the residuals of a linear regression between the prices of the two instruments exhibits&nbsp;<strong>stationarity,<\/strong>&nbsp;also called the&nbsp;<strong>Engle-Granger two-step cointegration test.<\/strong>&nbsp;This implies that the two instruments\u2019 price series are cointegrated, and that the spread constructed based on this linear association of the two instruments\u2019 prices is stationary. In turn, this implies that the spread is&nbsp;<strong>mean-reverting.<\/strong><\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-cointegration-vs-correlation\">Cointegration vs. correlation<\/h4>\n\n\n\n<p>Cointegration is preferred over correlation when constructing a pairs trade, since correlation doesn\u2019t apply to non-stationary time series, and financial instrument prices are typically non-stationary and trending. High correlation between such series can occur even without a meaningful relationship.<\/p>\n\n\n\n<p>For instance, two price series which are trending in the same direction but diverging in the long run may exhibit a spurious, positive correlation.<\/p>\n\n\n\n<p>On the other hand, cointegrated prices series may exhibit positive, negative, or even zero correlation over any period due to short-term fluctuations, but tend to track closely over the long run.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-implementation\">Implementation<\/h4>\n\n\n\n<p>The strategy is simple: First, over the lookback period (last 100 records), we run the cointegration test. If the two series are found to be cointegrated, then the coefficient of linear regression between the two series provides the ratio of the two instruments needed to construct the spread, also called the&nbsp;<strong>hedge ratio.<\/strong>&nbsp;We\u2019ll also compute the sample mean and standard deviation over this period.<\/p>\n\n\n\n<p>Then, over the forward window (also 100 records), we compute the spread (residual) and normalize it by taking its z-score.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">df = fetch_data()\n\n# Model and monetization parameters\nLOOKBACK = 100\nENTRY_THRESHOLD = 1.5\nEXIT_THRESHOLD = 0.5\nP_THRESHOLD = 0.05\n\n# Initialization\ndf[\"cointegrated\"] = 0\ndf[\"residual\"] = 0.0\ndf[\"zscore\"] = 0.0\ndf[\"position_x\"] = 0\ndf[\"position_y\"] = 0\nis_cointegrated = False\nlr = LinearRegression()\n\nfor i in range(LOOKBACK, len(df), LOOKBACK):\n\n    x = df[\"x\"].iloc[i-LOOKBACK:i].values[:,None]\n    y = df[\"y\"].iloc[i-LOOKBACK:i].values[:,None]\n\n    if is_cointegrated:\n        # Compute and normalize signal on forward window\n        x_new = df[\"x\"].iloc[i:i+LOOKBACK].values[:,None]\n        y_new = df[\"y\"].iloc[i:i+LOOKBACK].values[:,None]\n        spread_back = y - lr.coef_ * x\n        spread_forward = y_new - lr.coef_ * x_new\n        zscore = (spread_forward - spread_back.mean()) \/ spread_back.std()\n        df.iloc[i:i+LOOKBACK, df.columns.get_loc(\"cointegrated\")] = 1\n        df.iloc[i:i+LOOKBACK, df.columns.get_loc(\"residual\")] = spread_forward\n        df.iloc[i:i+LOOKBACK, df.columns.get_loc(\"zscore\")] = zscore\n\n    _, p, _ = coint(x,y)\n    is_cointegrated = p &lt; P_THRESHOLD\n    lr.fit(x,y)<\/pre>\n\n\n\n<p>If the normalized residual is negative, it means the instrument associated with y is underpriced, i.e. we\u2019ll buy y and sell x to monetize the prediction that their prices will converge in the future. Conversely, if the normalized residual is positive, we\u2019ll sell y and buy x.<\/p>\n\n\n\n<p>Since the two products have a similar notional value and futures have large minimum order size, we\u2019ll trade a static hedge ratio of 1. If you\u2019re modifying this strategy to trade stocks or ETFs or products that have significant differences in volatility or notional value, you should consider using the dynamic hedge ratio&nbsp;<code>lr.coef_<\/code>&nbsp;to scale your position.<\/p>\n\n\n\n<p><em>Tip: The hedge ratio&nbsp;<code>lr.coef_<\/code>&nbsp;may be negative. This regularly occurs, for instance, if one leg of the pair is an inverse ETF.<\/em><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># Standardized residual is negative =&gt; y is underpriced =&gt; sell x, buy y\n# Standardized residual is positive =&gt; y is overpriced =&gt; buy x, sell y\ndf.loc[df.zscore &lt; -ENTRY_THRESHOLD, 'position_y'] = 1\ndf.loc[df.zscore &gt; ENTRY_THRESHOLD, 'position_y'] = -1\ndf['position_x'] = -df['position_y']<\/pre>\n\n\n\n<p>It\u2019s common to specify a score-based or time-based exit condition. For example, we can exit the position when the spread converges to within +\/-0.5 standard deviations. This has the effect of longer holding periods and lower transaction costs.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># Exit positions when z-score crosses +\/-0.5\nhold_y_long = df[\"zscore\"].apply(lambda z: 1 if z &lt;= -EXIT_THRESHOLD else 0)\nhold_y_short = df[\"zscore\"].apply(lambda z: 1 if z &gt;= EXIT_THRESHOLD else 0)\n\n# Carry forward positions until exit condition is met\ndf[\"position_y\"] = df[\"position_y\"].mask((df[\"position_y\"].shift() == -1) &amp; (hold_y_short == 1), -1)\ndf[\"position_y\"] = df[\"position_y\"].mask((df[\"position_y\"].shift() == 1) &amp; (hold_y_long == 1), 1)\ndf[\"position_x\"] = -df[\"position_y\"]<\/pre>\n\n\n\n<p>Here\u2019s a visualization of the strategy. The outer dashed lines show the entry conditions while the inner dashed lines show the exit conditions:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1100\" height=\"583\" data-src=\"https:\/\/www.interactivebrokers.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/Engle_Granger_Visualization_Databento-1100x583.jpg\" alt=\"\" class=\"wp-image-221032 lazyload\" data-srcset=\"https:\/\/ibkrcampus.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/Engle_Granger_Visualization_Databento-1100x583.jpg 1100w, https:\/\/ibkrcampus.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/Engle_Granger_Visualization_Databento-700x371.jpg 700w, https:\/\/ibkrcampus.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/Engle_Granger_Visualization_Databento-300x159.jpg 300w, https:\/\/ibkrcampus.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/Engle_Granger_Visualization_Databento-768x407.jpg 768w, https:\/\/ibkrcampus.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/Engle_Granger_Visualization_Databento-1536x814.jpg 1536w, https:\/\/ibkrcampus.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/Engle_Granger_Visualization_Databento-2048x1085.jpg 2048w\" data-sizes=\"(max-width: 1100px) 100vw, 1100px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 1100px; aspect-ratio: 1100\/583;\" \/><\/figure>\n\n\n\n<p>Source: Databento<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-estimating-slippage\">Estimating slippage<\/h4>\n\n\n\n<p>Exact instantaneous slippage from crossing the spread can be calculated using MBP-1 data and execution timestamps from the backtest. In the interest of time, you can gauge the viability of the strategy by applying a constant spread cost per contract side. This is typically assumed to be half a tick or one tick for liquid futures, but we can also estimate it quickly from a small slice of MBP-1 data:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def estimate_slippage():\n\n    import datetime\n\n    client = db.Historical()\n\n    data = client.timeseries.get_range(\n        dataset=\"GLBX.MDP3\",\n        schema=\"mbp-1\",\n        stype_in=\"raw_symbol\",\n        symbols=[SYMBOL_X],\n        start=START,\n    )\n\n    df = data.to_df()\n    avg_spread_x = (df[\"ask_px_00\"] - df[\"bid_px_00\"]).mean()\n\n    data = client.timeseries.get_range(\n        dataset=\"IFEU.IMPACT\",\n        schema=\"mbp-1\",\n        stype_in=\"raw_symbol\",\n        symbols=[SYMBOL_Y],\n        start=START,\n    )\n\n    df = data.to_df()\n    avg_spread_y = (df[\"ask_px_00\"] - df[\"bid_px_00\"]).mean()\n\n    return avg_spread_x, avg_spread_y\n\navg_spread_x, avg_spread_y = estimate_slippage()<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-backtest\">Backtest<\/h4>\n\n\n\n<p>We can now compute the gross PnL and transaction costs:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># Compute backtest results\ndf[\"pnl_x\"] = 0.0\ndf[\"pnl_y\"] = 0.0\ndf.iloc[:-1, df.columns.get_loc(\"pnl_x\")] = df['position_x'].iloc[:-1].values * df[\"x\"].diff()[1:].values \/ TICK_SIZE_X * TICK_VALUE_X\ndf.iloc[:-1, df.columns.get_loc(\"pnl_y\")] = df['position_y'].iloc[:-1].values * df[\"y\"].diff()[1:].values \/ TICK_SIZE_Y * TICK_VALUE_Y\n\ndf[\"volume_x\"] = df[\"position_x\"].diff().abs()\ndf[\"volume_y\"] = df[\"position_y\"].diff().abs()\ndf[\"tcost\"] = df[\"volume_x\"] * FEES_PER_SIDE_X + df[\"volume_y\"] * FEES_PER_SIDE_Y + \\\n              (df[\"volume_x\"] + df[\"volume_y\"]) * COMMISSIONS_PER_SIDE + \\\n              (df[\"volume_x\"] * avg_spread_x \/ TICK_SIZE_X \/ 2 * TICK_VALUE_X) + \\\n              (df[\"volume_y\"] * avg_spread_y \/ TICK_SIZE_Y \/ 2 * TICK_VALUE_Y).cumsum()\ndf[\"gross_pnl\"] = (df[\"pnl_x\"] + df[\"pnl_y\"]).cumsum()\ndf[\"net_pnl\"] = df[\"gross_pnl\"] - df[\"tcost\"]<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-results\">Results<\/h3>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># Plot and print results\ndaily_pnl = df[\"net_pnl\"].groupby(df.index.date).sum()\nsr = daily_pnl.mean() \/ daily_pnl.std() * 252**.5\nprint(f\"Sharpe ratio: {sr:.2f}\")\n\nplt.plot(df[\"gross_pnl\"], label=\"Gross PnL\")\nplt.plot(df[\"net_pnl\"], label=\"Net PnL\")\nplt.plot(df[\"tcost\"], label=\"t-cost\")\nplt.xlabel(\"Date\")\nplt.ylabel(\"PnL (USD)\")\nplt.legend()\nplt.show()<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Sharpe ratio: 8.57<\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1100\" height=\"582\" data-src=\"https:\/\/www.interactivebrokers.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/Backtest_Visualization_Databento-1100x582.jpg\" alt=\"\" class=\"wp-image-221033 lazyload\" data-srcset=\"https:\/\/ibkrcampus.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/Backtest_Visualization_Databento-1100x582.jpg 1100w, https:\/\/ibkrcampus.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/Backtest_Visualization_Databento-700x370.jpg 700w, https:\/\/ibkrcampus.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/Backtest_Visualization_Databento-300x159.jpg 300w, https:\/\/ibkrcampus.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/Backtest_Visualization_Databento-768x406.jpg 768w, https:\/\/ibkrcampus.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/Backtest_Visualization_Databento-1536x813.jpg 1536w, https:\/\/ibkrcampus.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/Backtest_Visualization_Databento-2048x1083.jpg 2048w\" data-sizes=\"(max-width: 1100px) 100vw, 1100px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 1100px; aspect-ratio: 1100\/582;\" \/><\/figure>\n\n\n\n<p>Source: Databento<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-full-code\">Full code<\/h4>\n\n\n\n<p>The full code example can be found on our GitHub&nbsp;<a href=\"https:\/\/gist.github.com\/databento-bot\/23ea8f2ef0de6f1290c1c6d2c87b60c3\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-practical-considerations-and-improvements\">Practical considerations and improvements<\/h4>\n\n\n\n<p>This tutorial is kept intentionally short using a simple vectorized implementation. Ideally, you should use&nbsp;<a href=\"https:\/\/databento.com\/docs\/schemas-and-data-formats\/mbp-1?historical=python&amp;live=python&amp;reference=python?utm_source=twitter\">MBP-1<\/a>&nbsp;data with an event-driven backtest loop that incorporates the delays between your system and the exchange gateways. This can be done using the&nbsp;<a href=\"https:\/\/databento.com\/docs\/api-reference-historical\/helpers\/dbn-store-replay\">market replay<\/a>&nbsp;method.<\/p>\n\n\n\n<p>This strategy is very sensitive to slippage and the exact expiration month that\u2019s traded; non-lead month contracts will have wider spreads which can make the strategy infeasible.<\/p>\n\n\n\n<p>We assumed exchange fees and commissions that are realistic for a medium-sized trading firm or a team within a larger firm that\u2019s already a CME member firm. In practice, achieving similar rates requires a&nbsp;<a href=\"https:\/\/www.cmegroup.com\/company\/membership\/corporate\/nymex.html\" target=\"_blank\" rel=\"noreferrer noopener\">NYMEX 106.J corporate membership<\/a>&nbsp;and thousands of contract sides executed per day.<\/p>\n\n\n\n<p>Scaling up to larger trade sizes will significantly expand the strategy\u2019s state machine, since you\u2019ll have to handle partial executions and different sizes on each leg of the spread to achieve optimal hedge ratio. MBP-1 data can also inform trade sizing.<\/p>\n\n\n\n<p>We introduced a few free model and monetization parameters, namely&nbsp;<code>LOOKBACK<\/code>,&nbsp;<code>ENTRY_THRESHOLD<\/code>,&nbsp;<code>EXIT_THRESHOLD<\/code>. These can be optimized using any hyperparameter tuning method (e.g. grid search) and cross-validated.<\/p>\n\n\n\n<p>There are also a few common variations of this strategy to consider:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Basket trading<\/strong>&nbsp;by constructing combinations of more than two instruments, using another procedure such as the Johansen test.<\/li>\n\n\n\n<li>Calibrating an Ornstein-Uhlenbeck process, which gives a parametric model for time-based exit.<\/li>\n\n\n\n<li>Using&nbsp;<strong>total least squares<\/strong>&nbsp;instead of ordinary least squares to determine the hedge ratio.<\/li>\n\n\n\n<li>Scanning a wide universe systematically for cointegrated pairs, to discover unintuitive relationships. However, this runs the risk of poor generalization if too many pairs are tested. Dimensionality reduction methods like PCA can improve the search and generalization error.<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-further-reading\">Further reading<\/h4>\n\n\n\n<p>Alexander, C. (2008). Market Risk Analysis: Practical Financial Econometrics (Vol. 2). Wiley.<\/p>\n\n\n\n<p>Tsay, R. S. (2010). Analysis of Financial Time Series (3rd edition). Wiley.<\/p>\n\n\n\n<p>Engle, R. F., &amp; Granger, C. W. J. (1987). Co-integration and error correction: Representation, estimation, and testing. Econometrica, 55(2), 251\u2013276.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We\u2019ll demonstrate the pairs trading strategy on the two most liquid crude oil products traded on CME and ICE, the WTI crude oil futures (CL) and Brent crude oil futures (BRN) contracts respectively.<\/p>\n","protected":false},"author":186,"featured_media":221034,"comment_status":"open","ping_status":"closed","sticky":true,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[339,343,349,338,341],"tags":[851,4873,18935,18937,4404,4659,18934,1224,595,4412,18936,5519],"contributors-categories":[18930],"class_list":{"0":"post-221026","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-data-science","8":"category-programing-languages","9":"category-python-development","10":"category-ibkr-quant-news","11":"category-quant-development","12":"tag-algo-trading","13":"tag-backtesting","14":"tag-databento-library","15":"tag-engle-granger-two-step-cointegration-test","16":"tag-linear-regression","17":"tag-matplotlib","18":"tag-pairs-trading-strategy","19":"tag-pandas","20":"tag-python","21":"tag-scikit-learn","22":"tag-statsmodels-library","23":"tag-time-series","24":"contributors-categories-databento"},"pp_statuses_selecting_workflow":false,"pp_workflow_action":"current","pp_status_selection":"publish","acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.9 (Yoast SEO v27.5) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Build a Pairs Trading Strategy in Python: A Step-by-Step Guide<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.interactivebrokers.com\/campus\/wp-json\/wp\/v2\/posts\/221026\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Build a Pairs Trading Strategy in Python: A Step-by-Step Guide\" \/>\n<meta property=\"og:description\" content=\"We\u2019ll demonstrate the pairs trading strategy on the two most liquid crude oil products traded on CME and ICE, the WTI crude oil futures (CL) and Brent crude oil futures (BRN) contracts respectively.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.interactivebrokers.com\/campus\/ibkr-quant-news\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\/\" \/>\n<meta property=\"og:site_name\" content=\"IBKR Campus US\" \/>\n<meta property=\"article:published_time\" content=\"2025-03-31T15:30:20+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-03-31T15:30:28+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.interactivebrokers.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/python-code-black-background.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1000\" \/>\n\t<meta property=\"og:image:height\" content=\"563\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Contributor Author\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Contributor Author\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"7 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\n\t    \"@context\": \"https:\\\/\\\/schema.org\",\n\t    \"@graph\": [\n\t        {\n\t            \"@type\": \"NewsArticle\",\n\t            \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/ibkr-quant-news\\\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\\\/#article\",\n\t            \"isPartOf\": {\n\t                \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/ibkr-quant-news\\\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\\\/\"\n\t            },\n\t            \"author\": {\n\t                \"name\": \"Contributor Author\",\n\t                \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/#\\\/schema\\\/person\\\/e823e46b42ca381080387e794318a485\"\n\t            },\n\t            \"headline\": \"Build a Pairs Trading Strategy in Python: A Step-by-Step Guide\",\n\t            \"datePublished\": \"2025-03-31T15:30:20+00:00\",\n\t            \"dateModified\": \"2025-03-31T15:30:28+00:00\",\n\t            \"mainEntityOfPage\": {\n\t                \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/ibkr-quant-news\\\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\\\/\"\n\t            },\n\t            \"wordCount\": 1452,\n\t            \"commentCount\": 0,\n\t            \"publisher\": {\n\t                \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/#organization\"\n\t            },\n\t            \"image\": {\n\t                \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/ibkr-quant-news\\\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\\\/#primaryimage\"\n\t            },\n\t            \"thumbnailUrl\": \"https:\\\/\\\/www.interactivebrokers.com\\\/campus\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2025\\\/03\\\/python-code-black-background.png\",\n\t            \"keywords\": [\n\t                \"Algo Trading\",\n\t                \"backtesting\",\n\t                \"databento library\",\n\t                \"Engle-Granger two-step cointegration test\",\n\t                \"Linear Regression\",\n\t                \"Matplotlib\",\n\t                \"Pairs Trading Strategy\",\n\t                \"Pandas\",\n\t                \"Python\",\n\t                \"Scikit-learn\",\n\t                \"statsmodels library\",\n\t                \"Time Series\"\n\t            ],\n\t            \"articleSection\": [\n\t                \"Data Science\",\n\t                \"Programming Languages\",\n\t                \"Python Development\",\n\t                \"Quant\",\n\t                \"Quant Development\"\n\t            ],\n\t            \"inLanguage\": \"en-US\",\n\t            \"potentialAction\": [\n\t                {\n\t                    \"@type\": \"CommentAction\",\n\t                    \"name\": \"Comment\",\n\t                    \"target\": [\n\t                        \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/ibkr-quant-news\\\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\\\/#respond\"\n\t                    ]\n\t                }\n\t            ]\n\t        },\n\t        {\n\t            \"@type\": \"WebPage\",\n\t            \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/ibkr-quant-news\\\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\\\/\",\n\t            \"url\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/ibkr-quant-news\\\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\\\/\",\n\t            \"name\": \"Build a Pairs Trading Strategy in Python: A Step-by-Step Guide | IBKR Campus US\",\n\t            \"isPartOf\": {\n\t                \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/#website\"\n\t            },\n\t            \"primaryImageOfPage\": {\n\t                \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/ibkr-quant-news\\\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\\\/#primaryimage\"\n\t            },\n\t            \"image\": {\n\t                \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/ibkr-quant-news\\\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\\\/#primaryimage\"\n\t            },\n\t            \"thumbnailUrl\": \"https:\\\/\\\/www.interactivebrokers.com\\\/campus\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2025\\\/03\\\/python-code-black-background.png\",\n\t            \"datePublished\": \"2025-03-31T15:30:20+00:00\",\n\t            \"dateModified\": \"2025-03-31T15:30:28+00:00\",\n\t            \"inLanguage\": \"en-US\",\n\t            \"potentialAction\": [\n\t                {\n\t                    \"@type\": \"ReadAction\",\n\t                    \"target\": [\n\t                        \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/ibkr-quant-news\\\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\\\/\"\n\t                    ]\n\t                }\n\t            ]\n\t        },\n\t        {\n\t            \"@type\": \"ImageObject\",\n\t            \"inLanguage\": \"en-US\",\n\t            \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/ibkr-quant-news\\\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\\\/#primaryimage\",\n\t            \"url\": \"https:\\\/\\\/www.interactivebrokers.com\\\/campus\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2025\\\/03\\\/python-code-black-background.png\",\n\t            \"contentUrl\": \"https:\\\/\\\/www.interactivebrokers.com\\\/campus\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2025\\\/03\\\/python-code-black-background.png\",\n\t            \"width\": 1000,\n\t            \"height\": 563,\n\t            \"caption\": \"Python\"\n\t        },\n\t        {\n\t            \"@type\": \"WebSite\",\n\t            \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/#website\",\n\t            \"url\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/\",\n\t            \"name\": \"IBKR Campus US\",\n\t            \"description\": \"Financial Education from Interactive Brokers\",\n\t            \"publisher\": {\n\t                \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/#organization\"\n\t            },\n\t            \"potentialAction\": [\n\t                {\n\t                    \"@type\": \"SearchAction\",\n\t                    \"target\": {\n\t                        \"@type\": \"EntryPoint\",\n\t                        \"urlTemplate\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/?s={search_term_string}\"\n\t                    },\n\t                    \"query-input\": {\n\t                        \"@type\": \"PropertyValueSpecification\",\n\t                        \"valueRequired\": true,\n\t                        \"valueName\": \"search_term_string\"\n\t                    }\n\t                }\n\t            ],\n\t            \"inLanguage\": \"en-US\"\n\t        },\n\t        {\n\t            \"@type\": \"Organization\",\n\t            \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/#organization\",\n\t            \"name\": \"Interactive Brokers\",\n\t            \"alternateName\": \"IBKR\",\n\t            \"url\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/\",\n\t            \"logo\": {\n\t                \"@type\": \"ImageObject\",\n\t                \"inLanguage\": \"en-US\",\n\t                \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/#\\\/schema\\\/logo\\\/image\\\/\",\n\t                \"url\": \"https:\\\/\\\/www.interactivebrokers.com\\\/campus\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2024\\\/05\\\/ibkr-campus-logo.jpg\",\n\t                \"contentUrl\": \"https:\\\/\\\/www.interactivebrokers.com\\\/campus\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2024\\\/05\\\/ibkr-campus-logo.jpg\",\n\t                \"width\": 669,\n\t                \"height\": 669,\n\t                \"caption\": \"Interactive Brokers\"\n\t            },\n\t            \"image\": {\n\t                \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/#\\\/schema\\\/logo\\\/image\\\/\"\n\t            },\n\t            \"publishingPrinciples\": \"https:\\\/\\\/www.interactivebrokers.com\\\/campus\\\/about-ibkr-campus\\\/\",\n\t            \"ethicsPolicy\": \"https:\\\/\\\/www.interactivebrokers.com\\\/campus\\\/cyber-security-notice\\\/\"\n\t        },\n\t        {\n\t            \"@type\": \"Person\",\n\t            \"@id\": \"https:\\\/\\\/ibkrcampus.com\\\/campus\\\/#\\\/schema\\\/person\\\/e823e46b42ca381080387e794318a485\",\n\t            \"name\": \"Contributor Author\",\n\t            \"url\": \"https:\\\/\\\/www.interactivebrokers.com\\\/campus\\\/author\\\/contributor-author\\\/\"\n\t        }\n\t    ]\n\t}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Build a Pairs Trading Strategy in Python: A Step-by-Step Guide","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.interactivebrokers.com\/campus\/wp-json\/wp\/v2\/posts\/221026\/","og_locale":"en_US","og_type":"article","og_title":"Build a Pairs Trading Strategy in Python: A Step-by-Step Guide","og_description":"We\u2019ll demonstrate the pairs trading strategy on the two most liquid crude oil products traded on CME and ICE, the WTI crude oil futures (CL) and Brent crude oil futures (BRN) contracts respectively.","og_url":"https:\/\/www.interactivebrokers.com\/campus\/ibkr-quant-news\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\/","og_site_name":"IBKR Campus US","article_published_time":"2025-03-31T15:30:20+00:00","article_modified_time":"2025-03-31T15:30:28+00:00","og_image":[{"width":1000,"height":563,"url":"https:\/\/www.interactivebrokers.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/python-code-black-background.png","type":"image\/png"}],"author":"Contributor Author","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Contributor Author","Est. reading time":"7 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/ibkrcampus.com\/campus\/ibkr-quant-news\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\/#article","isPartOf":{"@id":"https:\/\/ibkrcampus.com\/campus\/ibkr-quant-news\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\/"},"author":{"name":"Contributor Author","@id":"https:\/\/ibkrcampus.com\/campus\/#\/schema\/person\/e823e46b42ca381080387e794318a485"},"headline":"Build a Pairs Trading Strategy in Python: A Step-by-Step Guide","datePublished":"2025-03-31T15:30:20+00:00","dateModified":"2025-03-31T15:30:28+00:00","mainEntityOfPage":{"@id":"https:\/\/ibkrcampus.com\/campus\/ibkr-quant-news\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\/"},"wordCount":1452,"commentCount":0,"publisher":{"@id":"https:\/\/ibkrcampus.com\/campus\/#organization"},"image":{"@id":"https:\/\/ibkrcampus.com\/campus\/ibkr-quant-news\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\/#primaryimage"},"thumbnailUrl":"https:\/\/www.interactivebrokers.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/python-code-black-background.png","keywords":["Algo Trading","backtesting","databento library","Engle-Granger two-step cointegration test","Linear Regression","Matplotlib","Pairs Trading Strategy","Pandas","Python","Scikit-learn","statsmodels library","Time Series"],"articleSection":["Data Science","Programming Languages","Python Development","Quant","Quant Development"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/ibkrcampus.com\/campus\/ibkr-quant-news\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/ibkrcampus.com\/campus\/ibkr-quant-news\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\/","url":"https:\/\/ibkrcampus.com\/campus\/ibkr-quant-news\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\/","name":"Build a Pairs Trading Strategy in Python: A Step-by-Step Guide | IBKR Campus US","isPartOf":{"@id":"https:\/\/ibkrcampus.com\/campus\/#website"},"primaryImageOfPage":{"@id":"https:\/\/ibkrcampus.com\/campus\/ibkr-quant-news\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\/#primaryimage"},"image":{"@id":"https:\/\/ibkrcampus.com\/campus\/ibkr-quant-news\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\/#primaryimage"},"thumbnailUrl":"https:\/\/www.interactivebrokers.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/python-code-black-background.png","datePublished":"2025-03-31T15:30:20+00:00","dateModified":"2025-03-31T15:30:28+00:00","inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/ibkrcampus.com\/campus\/ibkr-quant-news\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ibkrcampus.com\/campus\/ibkr-quant-news\/build-a-pairs-trading-strategy-in-python-a-step-by-step-guide\/#primaryimage","url":"https:\/\/www.interactivebrokers.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/python-code-black-background.png","contentUrl":"https:\/\/www.interactivebrokers.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/python-code-black-background.png","width":1000,"height":563,"caption":"Python"},{"@type":"WebSite","@id":"https:\/\/ibkrcampus.com\/campus\/#website","url":"https:\/\/ibkrcampus.com\/campus\/","name":"IBKR Campus US","description":"Financial Education from Interactive Brokers","publisher":{"@id":"https:\/\/ibkrcampus.com\/campus\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/ibkrcampus.com\/campus\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/ibkrcampus.com\/campus\/#organization","name":"Interactive Brokers","alternateName":"IBKR","url":"https:\/\/ibkrcampus.com\/campus\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ibkrcampus.com\/campus\/#\/schema\/logo\/image\/","url":"https:\/\/www.interactivebrokers.com\/campus\/wp-content\/uploads\/sites\/2\/2024\/05\/ibkr-campus-logo.jpg","contentUrl":"https:\/\/www.interactivebrokers.com\/campus\/wp-content\/uploads\/sites\/2\/2024\/05\/ibkr-campus-logo.jpg","width":669,"height":669,"caption":"Interactive Brokers"},"image":{"@id":"https:\/\/ibkrcampus.com\/campus\/#\/schema\/logo\/image\/"},"publishingPrinciples":"https:\/\/www.interactivebrokers.com\/campus\/about-ibkr-campus\/","ethicsPolicy":"https:\/\/www.interactivebrokers.com\/campus\/cyber-security-notice\/"},{"@type":"Person","@id":"https:\/\/ibkrcampus.com\/campus\/#\/schema\/person\/e823e46b42ca381080387e794318a485","name":"Contributor Author","url":"https:\/\/www.interactivebrokers.com\/campus\/author\/contributor-author\/"}]}},"jetpack_featured_media_url":"https:\/\/www.interactivebrokers.com\/campus\/wp-content\/uploads\/sites\/2\/2025\/03\/python-code-black-background.png","_links":{"self":[{"href":"https:\/\/ibkrcampus.com\/campus\/wp-json\/wp\/v2\/posts\/221026","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ibkrcampus.com\/campus\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ibkrcampus.com\/campus\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ibkrcampus.com\/campus\/wp-json\/wp\/v2\/users\/186"}],"replies":[{"embeddable":true,"href":"https:\/\/ibkrcampus.com\/campus\/wp-json\/wp\/v2\/comments?post=221026"}],"version-history":[{"count":0,"href":"https:\/\/ibkrcampus.com\/campus\/wp-json\/wp\/v2\/posts\/221026\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ibkrcampus.com\/campus\/wp-json\/wp\/v2\/media\/221034"}],"wp:attachment":[{"href":"https:\/\/ibkrcampus.com\/campus\/wp-json\/wp\/v2\/media?parent=221026"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ibkrcampus.com\/campus\/wp-json\/wp\/v2\/categories?post=221026"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ibkrcampus.com\/campus\/wp-json\/wp\/v2\/tags?post=221026"},{"taxonomy":"contributors-categories","embeddable":true,"href":"https:\/\/ibkrcampus.com\/campus\/wp-json\/wp\/v2\/contributors-categories?post=221026"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}