{"id":7265,"date":"2024-04-08T15:03:55","date_gmt":"2024-04-08T22:03:55","guid":{"rendered":"https:\/\/jeremywhittaker.com\/?p=7265"},"modified":"2024-04-08T15:03:59","modified_gmt":"2024-04-08T22:03:59","slug":"a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data","status":"publish","type":"post","link":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/","title":{"rendered":"A Deep Dive into Intraday Trading Costs with Python, Alpaca&#8217;s API, and SIP Data"},"content":{"rendered":"\n<p>In the realm of intraday trading, managing and minimizing trading costs is not just a practice; it&#8217;s a necessity. A strategy that seems profitable on paper can quickly become a losing proposition when real-world costs, particularly the spread between the bid and ask prices, are factored in. Today, I&#8217;d like to share a comprehensive analysis tool I developed to focus on this. <br><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Output <\/h2>\n\n\n\n<p>This is the HTML output plot generated by the program. Below you&#8217;ll see some data followed by some important metrics. Note that the title has the spread standard deviation in dollars and percent. As well the actual values are shown in the plots below with their distribution and boxplots. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics.png\"><img decoding=\"async\" width=\"1024\" height=\"851\" data-src=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics-1024x851.png\" alt=\"\" class=\"wp-image-7274 lazyload\" data-srcset=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics-1024x851.png 1024w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics-300x249.png 300w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics-768x638.png 768w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics-1536x1276.png 1536w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics-361x300.png 361w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics.png 1685w\" data-sizes=\"(max-width: 1024px) 100vw, 1024px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 1024px; --smush-placeholder-aspect-ratio: 1024\/851;\" \/><\/a><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">The Importance of Spread Analysis<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Bid Price<\/h2>\n\n\n\n<p>The &#8220;bid&#8221; is the highest price a buyer is willing to pay for a stock. It essentially represents the demand side of the market for a particular stock. When you&#8217;re selling a stock, the bid price is the most you can hope to get at that moment. It&#8217;s a real-time reflection of what buyers believe the stock is worth, based on their analysis, market conditions, and other factors. The bid price is constantly changing as buyers adjust their willingness to pay in response to market dynamics.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Ask Price<\/h2>\n\n\n\n<p>Conversely, the &#8220;ask&#8221; price is the lowest price at which a seller is willing to sell their stock. It represents the supply side of the equation. When you&#8217;re looking to buy a stock, the ask price is the lowest you can expect to pay at that moment. Like the bid price, the ask is always in flux, influenced by sellers&#8217; perceptions of the stock&#8217;s value, market trends, and various economic indicators.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Bid-Ask Spread<\/h2>\n\n\n\n<p>The difference between the bid and ask price is known as the &#8220;spread.&#8221; The spread can be a critical indicator of a stock&#8217;s liquidity and market volatility. A narrow spread typically indicates a highly liquid market with a high volume of transactions and minimal difference between what buyers are willing to pay and what sellers are asking. Conversely, a wider spread suggests lower liquidity, potentially making it more challenging to execute large trades without affecting the market price.<\/p>\n\n\n\n<p>Now that we&#8217;ve explored the bid-ask spread let&#8217;s establish why spread analysis is crucial. The spread directly impacts your trading costs. For high-frequency traders, even small variances in this spread can significantly affect overall profitability. My tool is designed to subscribe to Alpaca&#8217;s API, fetching real-time quotes and prices alongside their volume. This setup allows us to compute the spread both as a dollar value and as a percentage of the asset&#8217;s value, offering a clear view of the trading costs involved.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">The Tool&#8217;s Anatomy<\/h1>\n\n\n\n<p>The tool comprises two Python files: <code>alpaca_plots.py<\/code> and <code>alpaca_functions.py<\/code>. The former is primarily responsible for the data visualization aspect, while the latter deals with data fetching, processing, and statistics calculation.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Key Functions and Their Roles<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Data Subscription and Handling<\/strong>: At the core, my tool subscribes to quote and trade updates via Alpaca&#8217;s API, focusing on a list of specified symbols. This is crucial for accessing real-time data, essential for accurate spread analysis.<\/li>\n\n\n\n<li><strong>Spread Calculation<\/strong>: Once data is fetched, the tool calculates the spread in both dollar value and percentage. This is done by subtracting the bid price from the ask price for each quote, providing an immediate measure of the trading cost for that specific asset.<\/li>\n\n\n\n<li><strong>Statistical Analysis<\/strong>: Beyond mere calculation, the tool also analyzes the distribution of spread values, including their standard deviation. This statistical approach allows traders to understand not just the average costs, but also the variability and risk associated with the spread.<\/li>\n\n\n\n<li><strong>Data Visualization<\/strong>: A key feature is its ability to generate insightful visualizations, including boxplots. These plots offer a visual representation of the spread distribution, highlighting the median, quartiles, and any outliers. This visual context is invaluable for traders looking to assess the cost implications of their strategies quickly.<\/li>\n<\/ul>\n\n\n\n<h1 class=\"wp-block-heading\">Practical Application and Insights<\/h1>\n\n\n\n<p>By analyzing the spread in both absolute and relative terms, traders can make informed decisions about which assets to trade and when. For example, a high spread as a percentage of the asset&#8217;s value might deter trading in certain assets during specific times, guiding traders towards more cost-effective opportunities.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">In Summary<\/h1>\n\n\n\n<p>This tool is more than just a technical exercise; it&#8217;s a practical solution to a problem many traders face daily. By offering a detailed analysis of Alpaca spreads, it empowers traders to make data-driven decisions, ultimately enhancing the profitability of their trading strategies. Whether you&#8217;re a seasoned trader or just starting, understanding and applying such tools can significantly impact your trading success.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Code<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">alpaca_functions.py<\/h2>\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=\"\">import alpaca_trade_api as tradeapi\nimport pandas as pd\nimport os\nimport plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\nfrom alpaca_config import api_key, api_secret, base_url\nimport logging\nimport asyncio\nfrom pathlib import Path\nfrom datetime import datetime\nimport subprocess\n\nlogging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\n\n# Global DataFrame for accumulating quote data\nquotes_data = pd.DataFrame(columns=['symbol', 'bid_price', 'ask_price', 'bid_size', 'ask_size'])\ntrades_data = pd.DataFrame(columns=['symbol', 'trade_price', 'trade_size'])\n\ndef kill_other_instances(exclude_pid):\n    try:\n        # Get the list of processes matching the script name\n        result = subprocess.run(['pgrep', '-f', 'alpaca_functions.py'], stdout=subprocess.PIPE)\n        if result.stdout:\n            pids = result.stdout.decode('utf-8').strip().split('\\n')\n            for pid in pids:\n                if pid != exclude_pid:\n                    try:\n                        # Terminate the process\n                        subprocess.run(['kill', pid])\n                        logging.warning(f\"Terminated process with PID: {pid}\")\n                    except subprocess.CalledProcessError as e:\n                        logging.error(f\"Could not terminate process with PID: {pid}. Error: {e}\")\n        else:\n            logging.info(\"No other instances found.\")\n    except subprocess.CalledProcessError as e:\n        logging.info(f\"Error finding processes: {e}\")\nasync def get_market_hours(api, date_str):\n    # Convert date_str to date object\n    specific_date = datetime.strptime(date_str, '%Y-%m-%d').date()\n    # Format the date as a string in 'YYYY-MM-DD' format\n    date_str = specific_date.strftime('%Y-%m-%d')\n\n    # Fetch the market calendar for the specific date\n    calendar = api.get_calendar(start=date_str, end=date_str)\n\n    logging.debug(f'{calendar}')\n\n    if calendar:\n        market_open = calendar[0].open.strftime('%H:%M')\n        market_close = calendar[0].close.strftime('%H:%M')\n        logging.info(f\"Market hours for {date_str}: {market_open} - {market_close}\")\n        return market_open, market_close\n    else:\n        logging.warning(f\"No market hours found for {date_str}.\")\n        return None, None\n\nasync def consolidate_parquet_files(quotes_directory, trades_directory):\n    async def process_directory(directory):\n        for day_dir in Path(directory).iterdir():\n            if day_dir.is_dir():\n                symbol_dfs = {}\n\n                parquet_files = list(day_dir.glob(\"*.parquet\"))\n                if not parquet_files:\n                    logging.info(f\"No Parquet files found in {day_dir}.\")\n                    continue\n\n                for file in parquet_files:\n                    if '_' in file.stem:\n                        symbol = file.stem.split('_')[0]\n                        df = pd.read_parquet(file)\n\n                        if symbol in symbol_dfs:\n                            symbol_dfs[symbol] = pd.concat([symbol_dfs[symbol], df])\n                        else:\n                            symbol_dfs[symbol] = df\n\n                for symbol, df in symbol_dfs.items():\n                    consolidated_filename = f\"{symbol}.parquet\"\n                    consolidated_file_path = day_dir \/ consolidated_filename\n\n                    if consolidated_file_path.is_file():\n                        consolidated_df = pd.read_parquet(consolidated_file_path)\n                        consolidated_df = pd.concat([consolidated_df, df])\n                        consolidated_df = consolidated_df[~consolidated_df.index.duplicated(keep='last')]\n                        consolidated_df = consolidated_df.sort_index()  # Modified to eliminate the warning\n                        consolidated_df.to_parquet(consolidated_file_path, index=True)\n                        logging.debug(f\"Updated consolidated file: {consolidated_filename}\")\n                    else:\n                        df = df[~df.index.duplicated(keep='last')]\n                        df = df.sort_index()  # Modified to eliminate the warning\n                        df.to_parquet(consolidated_file_path, index=True)\n                        logging.info(f\"Consolidated {consolidated_filename}\")\n\n                for file in parquet_files:\n                    if '_' in file.stem:\n                        try:\n                            os.remove(file)\n                            logging.debug(f\"Deleted {file}\")\n                        except OSError as e:\n                            logging.error(f\"Error deleting {file}: {e}\")\n            else:\n                logging.info(f\"Date directory {day_dir} not found or is not a directory.\")\n\n    await asyncio.gather(\n        process_directory(quotes_directory),\n        process_directory(trades_directory)\n    )\n# Function to check symbol properties\nasync def check_symbol_properties(api, symbols):\n    not_active, not_tradeable, not_shortable = [], [], []\n    for symbol in symbols:\n        asset = api.get_asset(symbol)  # Removed 'await' as get_asset is not an async function\n        if asset.status != 'active':\n            not_active.append(symbol)\n        if not asset.tradable:\n            not_tradeable.append(symbol)\n        if not asset.shortable:\n            not_shortable.append(symbol)\n    return not_active, not_tradeable, not_shortable\n\ndef process_quote(quote):\n    logging.debug(quote)\n\n    timestamp = pd.to_datetime(quote.timestamp, unit='ns').tz_convert('America\/New_York')\n\n    quote_df = pd.DataFrame({\n        'symbol': [quote.symbol],\n        'bid_price': [quote.bid_price],\n        'ask_price': [quote.ask_price],\n        'bid_size': [quote.bid_size],\n        'ask_size': [quote.ask_size],\n        'timestamp': [timestamp]\n    }).set_index('timestamp')\n\n    return quote_df\n\ndef process_trade(trade):\n    logging.debug(trade)\n\n    timestamp = pd.to_datetime(trade.timestamp, unit='ns').tz_convert('America\/New_York')\n\n    trade_df = pd.DataFrame({\n        'symbol': [trade.symbol],\n        'trade_price': [trade.price],\n        'trade_size': [trade.size],\n        'timestamp': [timestamp]\n    }).set_index('timestamp')\n\n    return trade_df\n\nasync def periodic_save(interval_seconds=3600, quotes_directory='\/home\/shared\/algos\/ml4t\/data\/alpaca_quotes\/', trades_directory='\/home\/shared\/algos\/ml4t\/data\/alpaca_trades\/'):\n    global quotes_data, trades_data\n    while True:\n        try:\n            logging.info('Running periodic save...')\n            current_time = datetime.now()\n            date_str = current_time.strftime('%Y-%m-%d')\n            hour_str = current_time.strftime('%H-%M-%S')\n\n            # Saving quotes data\n            if not quotes_data.empty:\n                quotes_day_directory = os.path.join(quotes_directory, date_str)\n                os.makedirs(quotes_day_directory, exist_ok=True)\n                for symbol, group in quotes_data.groupby('symbol'):\n                    filepath = os.path.join(quotes_day_directory, f\"{symbol}_{date_str}_{hour_str}.parquet\")\n                    group.to_parquet(filepath, index=True)\n                logging.info(f\"Saved all quotes for {date_str} {hour_str} to disk.\")\n                quotes_data.drop(quotes_data.index, inplace=True)  # Clearing the DataFrame\n            else:\n                logging.warning('quotes_data is empty')\n            # Saving trades data\n            if not trades_data.empty:\n                trades_day_directory = os.path.join(trades_directory, date_str)\n                os.makedirs(trades_day_directory, exist_ok=True)\n                for symbol, group in trades_data.groupby('symbol'):\n                    filepath = os.path.join(trades_day_directory, f\"{symbol}_{date_str}_{hour_str}.parquet\")\n                    group.to_parquet(filepath, index=True)\n                logging.info(f\"Saved all trades for {date_str} {hour_str} to disk.\")\n                trades_data.drop(trades_data.index, inplace=True)  # Clearing the DataFrame\n            else:\n                logging.warning('trades_data is empty')\n\n            await asyncio.sleep(interval_seconds)\n\n        except Exception as e:\n                logging.error(f'Error in periodic_save: {e}')  # Properly logging the exception message\n\n\n\nasync def run_alpaca_monitor(symbols, remove_not_shortable=False):\n    # Initialize the Alpaca API\n    api = tradeapi.REST(api_key, api_secret, base_url, api_version='v2')\n\n    total_symbols = len(symbols)\n\n    not_active, not_tradeable, not_shortable = await check_symbol_properties(api, symbols)\n    # Calculate and log percentages...\n    if remove_not_shortable:\n        symbols = [symbol for symbol in symbols if symbol not in not_active + not_tradeable + not_shortable]\n    else:\n        symbols = [symbol for symbol in symbols if symbol not in not_active + not_tradeable]\n\n    logging.info(f'Monitoring the following symbols: {symbols}')\n\n    # Calculate and log percentages\n    percent_not_active = (len(not_active) \/ total_symbols) * 100\n    percent_not_tradeable = (len(not_tradeable) \/ total_symbols) * 100\n    percent_not_shortable = (len(not_shortable) \/ total_symbols) * 100\n\n    logging.info(f\"Percentage of symbols not active: {percent_not_active:.2f}%\")\n    logging.info(f\"Percentage of symbols not tradeable: {percent_not_tradeable:.2f}%\")\n    logging.info(f\"Percentage of symbols not shortable: {percent_not_shortable:.2f}%\")\n\n    # Remove symbols that are not active, tradeable, or shortable\n    symbols = [symbol for symbol in symbols if symbol not in not_active + not_tradeable + not_shortable]\n\n    logging.info(f'Monitoring the following symbols: {symbols}')\n\n    stream = tradeapi.stream.Stream(api_key, api_secret, base_url, data_feed='sip')\n\n    async def handle_quote(q):\n        global quotes_data\n        new_quote = process_quote(q)\n        quotes_data = pd.concat([quotes_data, new_quote], ignore_index=False)\n        logging.debug(f'quotes \\n {quotes_data.tail()}')\n\n    async def handle_trade(t):\n        global trades_data\n        new_trade = process_trade(t)\n        trades_data = pd.concat([trades_data, new_trade], ignore_index=False)\n        logging.debug(f'trades \\n {trades_data.tail()}')\n\n    async def consolidate_periodically(interval, quotes_directory, trades_directory):\n        while True:\n            try:\n                await consolidate_parquet_files(quotes_directory, trades_directory)\n            except Exception as e:\n                logging.error(f\"Error consolidating parquet files: {e}\")\n                # Handle the error as needed, for example, break the loop, or continue\n            await asyncio.sleep(interval)\n\n    save_quotes_task = asyncio.create_task(periodic_save(180, '\/home\/shared\/algos\/ml4t\/data\/alpaca_quotes\/', '\/home\/shared\/algos\/ml4t\/data\/alpaca_trades\/'))\n    consolidate_task = asyncio.create_task(consolidate_periodically(180, '\/home\/shared\/algos\/ml4t\/data\/alpaca_quotes', '\/home\/shared\/algos\/ml4t\/data\/alpaca_trades'))\n\n\n    try:\n        # Subscribe to the streams\n        for symbol in symbols:\n            stream.subscribe_quotes(handle_quote, symbol)\n            stream.subscribe_trades(handle_trade, symbol)\n\n        await stream._run_forever()\n\n    except ValueError as e:\n        if \"auth failed\" in str(e) or \"connection limit exceeded\" in str(e):\n            # Log the specific error message without re-raising the exception to avoid showing traceback\n            logging.error(f\"WebSocket authentication error: {e}\")\n        else:\n            # For other ValueErrors, log them and optionally re-raise if you want to show the traceback\n            logging.error(f\"Error with WebSocket connection: {e}\")\n\n\nif __name__ == \"__main__\":\n    current_pid = str(os.getpid())\n    kill_other_instances(current_pid)\n\n    csv_file = '\/home\/shared\/algos\/ml4t\/data\/selected_pairs_with_values.csv'\n    df = pd.read_csv(csv_file)\n    symbols = list(set(df['s1'].tolist() + df['s2'].tolist()))\n    symbols = [symbol.replace('-', '.') for symbol in symbols]\n\n    quotes_dir='\/home\/shared\/algos\/ml4t\/data\/alpaca_quotes'\n    trades_dir='\/home\/shared\/algos\/ml4t\/data\/alpaca_trades'\n    asyncio.run(run_alpaca_monitor(symbols))\n\n\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">alpaca_plots.py<\/h2>\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=\"\">import asyncio\nimport logging\nfrom datetime import datetime\nimport pandas as pd\nfrom pathlib import Path\nimport plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\nimport alpaca_trade_api as tradeapi\nfrom alpaca_config import api_key, api_secret, base_url\n\nlogging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\n\ndef get_market_hours(api, date_str):\n    # Convert date_str to date object\n    specific_date = datetime.strptime(date_str, '%Y-%m-%d').date()\n    # Format the date as a string in 'YYYY-MM-DD' format\n    date_str = specific_date.strftime('%Y-%m-%d')\n\n    # Fetch the market calendar for the specific date\n    calendar = api.get_calendar(start=date_str, end=date_str)\n\n    logging.debug(f'{calendar}')\n\n    if calendar:\n        market_open = calendar[0].open.strftime('%H:%M')\n        market_close = calendar[0].close.strftime('%H:%M')\n        logging.info(f\"Market hours for {date_str}: {market_open} - {market_close}\")\n        return market_open, market_close\n    else:\n        logging.warning(f\"No market hours found for {date_str}.\")\n        return None, None\n\n\ndef load_and_plot_data(quotes_directory, trades_directory, symbols, api):\n    logging.info(f'Running load_and_plot_data')\n    today = datetime.now().strftime('%Y-%m-%d')\n    #override today for testing\n    # today = '2024-04-05'\n\n    try:\n        # Use today to get market hours\n        market_open, market_close = get_market_hours(api, today)\n        # Check if date directories exist\n        quotes_date_dir = Path(quotes_directory) \/ today\n        trades_date_dir = Path(trades_directory) \/ today\n\n        if not quotes_date_dir.exists():\n            logging.error(f\"Quotes directory for date {today} not found: {quotes_date_dir}\")\n            return\n        if not trades_date_dir.exists():\n            logging.error(f\"Trades directory for date {today} not found: {trades_date_dir}\")\n            return\n\n        for symbol in symbols:\n            # Construct file paths\n            quotes_file_path = quotes_date_dir \/ f\"{symbol}.parquet\"\n            trades_file_path = trades_date_dir \/ f\"{symbol}.parquet\"\n\n            # Load the data\n            if quotes_file_path.exists() and trades_file_path.exists():\n                symbol_quotes = pd.read_parquet(quotes_file_path)\n                symbol_trades = pd.read_parquet(trades_file_path)\n\n                logging.debug(f\"Loaded {symbol_quotes.shape[0]} quotes and {symbol_trades.shape[0]} trades for {symbol} on {today}.\")\n                # Filter symbol_quotes and symbol_trades to market hours\n                market_open_time = datetime.strptime(market_open, '%H:%M').time()\n                market_close_time = datetime.strptime(market_close, '%H:%M').time()\n                symbol_quotes = symbol_quotes.between_time(market_open_time, market_close_time)\n                symbol_trades = symbol_trades.between_time(market_open_time, market_close_time)\n                # Call plot_statistics with filtered data\n                plot_statistics(symbol_quotes, symbol_trades, symbol, market_open, market_close)\n            else:\n                missing_files = []\n                if not quotes_file_path.exists():\n                    missing_files.append(f\"quotes file for {symbol} and path {quotes_file_path}\")\n                if not trades_file_path.exists():\n                    missing_files.append(f\"trades file for {symbol} and path {trades_file_path}\")\n                logging.warning(f\"Missing {', and '.join(missing_files)} on {today}.\")\n        logging.info(f'Finished loading and plotting data')\n    except Exception as e:\n        logging.error(f\"Error loading and plotting data for {today}: {e}\")\n        return\n\ndef plot_statistics(symbol_quotes, symbol_trades, symbol, market_open, market_close):\n    logging.info(f'Running plot_statistics for {symbol}')\n\n    if not symbol_quotes.empty and not symbol_trades.empty:\n        # Calculate 'spread' and 'spread_percentage' directly on symbol_quotes\n        symbol_quotes['spread'] = symbol_quotes['ask_price'] - symbol_quotes['bid_price']\n        symbol_quotes['spread_percentage'] = (symbol_quotes['spread'] \/ symbol_quotes['bid_price']) * 100\n\n        # Calculate standard deviation of spread and spread_percentage\n        spread_std = symbol_quotes['spread'].std()\n        spread_percentage_std = symbol_quotes['spread_percentage'].std()\n\n        # Make ask_size negative\n        symbol_quotes['negative_ask_size'] = -symbol_quotes['ask_size']\n\n        logging.info(f\"Spread Standard Deviation for {symbol}: ${spread_std:.4f} ({spread_percentage_std:.4f}%)\")\n\n        # Prepare the figure with subplots\n        fig = make_subplots(rows=7, cols=2,\n                            subplot_titles=(\"Bid and Ask Prices with Trades\", \"Bid Size &amp; Ask Size\", \"Trade Size\",\n                                            \"Spread ($)\", \"Spread (%)\",\n                                            \"Spread Distribution ($)\", \"Spread Distribution (%)\",\n                                            \"Spread Boxplot ($)\", \"Spread Boxplot (%)\"),\n                            specs=[[{\"colspan\": 2}, None], [{\"colspan\": 2}, None], [{\"colspan\": 2}, None],\n                                   [{}, {}], [{}, {}], [{\"rowspan\": 2}, {\"rowspan\": 2}], [{}, {}]],\n                            shared_xaxes=True, vertical_spacing=0.05)\n\n        # Bid and Ask Prices with Trades\n        fig.add_trace(go.Scatter(x=symbol_quotes.index, y=symbol_quotes['bid_price'], mode='lines',\n                                 name='Bid Price', line=dict(color='green')),\n                      row=1, col=1)\n        fig.add_trace(go.Scatter(x=symbol_quotes.index, y=symbol_quotes['ask_price'], mode='lines',\n                                 name='Ask Price', line=dict(color='red')),\n                      row=1, col=1)\n        fig.add_trace(go.Scatter(x=symbol_trades.index, y=symbol_trades['trade_price'], mode='markers',\n                                 name='Trade Price', marker=dict(color='black', size=4)),\n                      row=1, col=1)\n\n        # # Bid Size &amp; Ask Size as line charts with colors\n        # fig.add_trace(go.Scatter(x=symbol_quotes.index, y=symbol_quotes['bid_size'], mode='lines',\n        #                          name='Bid Size', line=dict(color='red')),\n        #               row=2, col=1)\n        # fig.add_trace(go.Scatter(x=symbol_quotes.index, y=symbol_quotes['ask_size'], mode='lines',\n        #                          name='Ask Size', line=dict(color='green')),\n        #               row=2, col=1)\n\n\n        # Bid Size &amp; Ask Size as line charts with colors, making ask_size negative\n        fig.add_trace(go.Scatter(x=symbol_quotes.index, y=symbol_quotes['bid_size'], mode='lines',\n                                 name='Bid Size', line=dict(color='green')),\n                      row=2, col=1)\n        fig.add_trace(go.Scatter(x=symbol_quotes.index, y=symbol_quotes['negative_ask_size'], mode='lines',\n                                 name='Ask Size', line=dict(color='red')),\n                      row=2, col=1)\n\n\n        # Trade Size as a line chart with color\n        fig.add_trace(go.Scatter(x=symbol_trades.index, y=symbol_trades['trade_size'], mode='lines',\n                                 name='Trade Size', line=dict(color='black')),\n                      row=3, col=1)\n\n        # Spread ($)\n        fig.add_trace(go.Scatter(x=symbol_quotes.index, y=symbol_quotes['spread'], mode='lines', name='Spread ($)'), row=4, col=1)\n        # Spread (%)\n        fig.add_trace(go.Scatter(x=symbol_quotes.index, y=symbol_quotes['spread_percentage'], mode='lines', name='Spread (%)'), row=4, col=2)\n\n        # Spread Distribution ($)\n        fig.add_trace(go.Histogram(x=symbol_quotes['spread'], name='Spread Distribution ($)'), row=5, col=1)\n        # Spread Distribution (%)\n        fig.add_trace(go.Histogram(x=symbol_quotes['spread_percentage'], name='Spread Distribution (%)'), row=5, col=2)\n\n        # Spread Boxplot ($)\n        fig.add_trace(go.Box(y=symbol_quotes['spread'], name='Spread Boxplot ($)'), row=6, col=1)\n        # Spread Boxplot (%)\n        fig.add_trace(go.Box(y=symbol_quotes['spread_percentage'], name='Spread Boxplot (%)'), row=6, col=2)\n\n\n        title = (\n            f\"Statistics for {symbol} from {market_open} to {market_close}&lt;br>\"\n            f\"&lt;span style='font-size: 12px;'>Spread Std ($): {spread_std:.4f}, \"\n            f\"Spread Std (%): {spread_percentage_std:.4f}%&lt;\/span>\"\n        )\n\n        # Adjust layout if needed, e.g., to update margins, titles, or axis labels\n        fig.update_layout(height=1400, title_text=f\"Statistics for {symbol} on {market_open} to {market_close}\")\n        fig.update_layout(height=1400, title_text=title)\n\n\n\n        # Directory check and save plot\n        plots_directory = Path(\".\/plots\/alpaca_quotes\/\")\n        plots_directory.mkdir(parents=True, exist_ok=True)\n        plot_filename = plots_directory \/ f\"{symbol}_quote_trade_statistics.html\"\n        fig.write_html(str(plot_filename))\n        logging.info(f\"Plot for {symbol} saved to {plot_filename}\")\n    else:\n        logging.warning(f'Cannot plot data for {symbol} as dataframes are empty')\n\n\ndef main():\n    api = tradeapi.REST(api_key, api_secret, base_url, api_version='v2')\n\n\n    csv_file = '\/home\/shared\/algos\/ml4t\/data\/selected_pairs_with_values.csv'\n    df = pd.read_csv(csv_file)\n    symbols = list(set(df['s1'].tolist() + df['s2'].tolist()))\n    symbols = [symbol.replace('-', '.') for symbol in symbols]\n\n    quotes_dir = '\/home\/shared\/algos\/ml4t\/data\/alpaca_quotes'\n    trades_dir = '\/home\/shared\/algos\/ml4t\/data\/alpaca_trades'\n\n    load_and_plot_data(quotes_dir, trades_dir, symbols, api)\n\nif __name__ == \"__main__\":\n    main()<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>In the realm of intraday trading, managing and minimizing trading costs is not just a practice; it&#8217;s a necessity. A strategy that seems profitable on paper can quickly&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-7265","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.2 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>A Deep Dive into Intraday Trading Costs with Python, Alpaca&#039;s API, and SIP Data - Jeremy Whittaker<\/title>\n<meta name=\"robots\" content=\"noindex, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"A Deep Dive into Intraday Trading Costs with Python, Alpaca&#039;s API, and SIP Data - Jeremy Whittaker\" \/>\n<meta property=\"og:description\" content=\"In the realm of intraday trading, managing and minimizing trading costs is not just a practice; it&#8217;s a necessity. A strategy that seems profitable on paper can quickly...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/\" \/>\n<meta property=\"og:site_name\" content=\"Jeremy Whittaker\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/WhittakerJeremy\" \/>\n<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/WhittakerJeremy\" \/>\n<meta property=\"article:published_time\" content=\"2024-04-08T22:03:55+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-04-08T22:03:59+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics-1024x851.png\" \/>\n<meta name=\"author\" content=\"JeremyWhittaker\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"JeremyWhittaker\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"4 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/\"},\"author\":{\"name\":\"JeremyWhittaker\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c\"},\"headline\":\"A Deep Dive into Intraday Trading Costs with Python, Alpaca&#8217;s API, and SIP Data\",\"datePublished\":\"2024-04-08T22:03:55+00:00\",\"dateModified\":\"2024-04-08T22:03:59+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/\"},\"wordCount\":802,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c\"},\"image\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics-1024x851.png\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/\",\"url\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/\",\"name\":\"A Deep Dive into Intraday Trading Costs with Python, Alpaca's API, and SIP Data - Jeremy Whittaker\",\"isPartOf\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics-1024x851.png\",\"datePublished\":\"2024-04-08T22:03:55+00:00\",\"dateModified\":\"2024-04-08T22:03:59+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#primaryimage\",\"url\":\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics.png\",\"contentUrl\":\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics.png\",\"width\":1685,\"height\":1400},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/new.jeremywhittaker.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"A Deep Dive into Intraday Trading Costs with Python, Alpaca&#8217;s API, and SIP Data\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/#website\",\"url\":\"https:\/\/new.jeremywhittaker.com\/\",\"name\":\"Jeremy Whittaker\",\"description\":\"Research, software, markets, housing, and energy\",\"publisher\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/new.jeremywhittaker.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c\",\"name\":\"JeremyWhittaker\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g\",\"caption\":\"JeremyWhittaker\"},\"logo\":{\"@id\":\"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g\"},\"sameAs\":[\"http:\/\/www.jeremywhittaker.com\",\"https:\/\/www.facebook.com\/WhittakerJeremy\",\"https:\/\/www.linkedin.com\/in\/jeremywhittaker\/\"],\"url\":\"https:\/\/new.jeremywhittaker.com\/index.php\/author\/jeremywhittaker\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"A Deep Dive into Intraday Trading Costs with Python, Alpaca's API, and SIP Data - Jeremy Whittaker","robots":{"index":"noindex","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"og_locale":"en_US","og_type":"article","og_title":"A Deep Dive into Intraday Trading Costs with Python, Alpaca's API, and SIP Data - Jeremy Whittaker","og_description":"In the realm of intraday trading, managing and minimizing trading costs is not just a practice; it&#8217;s a necessity. A strategy that seems profitable on paper can quickly...","og_url":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/","og_site_name":"Jeremy Whittaker","article_publisher":"https:\/\/www.facebook.com\/WhittakerJeremy","article_author":"https:\/\/www.facebook.com\/WhittakerJeremy","article_published_time":"2024-04-08T22:03:55+00:00","article_modified_time":"2024-04-08T22:03:59+00:00","og_image":[{"url":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics-1024x851.png","type":"","width":"","height":""}],"author":"JeremyWhittaker","twitter_card":"summary_large_image","twitter_misc":{"Written by":"JeremyWhittaker","Est. reading time":"4 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#article","isPartOf":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/"},"author":{"name":"JeremyWhittaker","@id":"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c"},"headline":"A Deep Dive into Intraday Trading Costs with Python, Alpaca&#8217;s API, and SIP Data","datePublished":"2024-04-08T22:03:55+00:00","dateModified":"2024-04-08T22:03:59+00:00","mainEntityOfPage":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/"},"wordCount":802,"commentCount":0,"publisher":{"@id":"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c"},"image":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#primaryimage"},"thumbnailUrl":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics-1024x851.png","inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/","url":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/","name":"A Deep Dive into Intraday Trading Costs with Python, Alpaca's API, and SIP Data - Jeremy Whittaker","isPartOf":{"@id":"https:\/\/new.jeremywhittaker.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#primaryimage"},"image":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#primaryimage"},"thumbnailUrl":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics-1024x851.png","datePublished":"2024-04-08T22:03:55+00:00","dateModified":"2024-04-08T22:03:59+00:00","breadcrumb":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#primaryimage","url":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics.png","contentUrl":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/04\/CWEB-trade-statistics.png","width":1685,"height":1400},{"@type":"BreadcrumbList","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/04\/08\/a-deep-dive-into-intraday-trading-costs-with-python-alpacas-api-and-sip-data\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/new.jeremywhittaker.com\/"},{"@type":"ListItem","position":2,"name":"A Deep Dive into Intraday Trading Costs with Python, Alpaca&#8217;s API, and SIP Data"}]},{"@type":"WebSite","@id":"https:\/\/new.jeremywhittaker.com\/#website","url":"https:\/\/new.jeremywhittaker.com\/","name":"Jeremy Whittaker","description":"Research, software, markets, housing, and energy","publisher":{"@id":"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/new.jeremywhittaker.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c","name":"JeremyWhittaker","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g","caption":"JeremyWhittaker"},"logo":{"@id":"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g"},"sameAs":["http:\/\/www.jeremywhittaker.com","https:\/\/www.facebook.com\/WhittakerJeremy","https:\/\/www.linkedin.com\/in\/jeremywhittaker\/"],"url":"https:\/\/new.jeremywhittaker.com\/index.php\/author\/jeremywhittaker\/"}]}},"_links":{"self":[{"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/posts\/7265","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/comments?post=7265"}],"version-history":[{"count":10,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/posts\/7265\/revisions"}],"predecessor-version":[{"id":7276,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/posts\/7265\/revisions\/7276"}],"wp:attachment":[{"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/media?parent=7265"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/categories?post=7265"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/tags?post=7265"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}