{"id":11916,"date":"2024-09-24T13:42:44","date_gmt":"2024-09-24T20:42:44","guid":{"rendered":"https:\/\/jeremywhittaker.com\/?p=11916"},"modified":"2024-09-24T13:42:51","modified_gmt":"2024-09-24T20:42:51","slug":"analyzing-any-polymarket-users-trades-using-polygon","status":"publish","type":"post","link":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/","title":{"rendered":"Analyzing Any Polymarket User&#8217;s Trades Using Polygon"},"content":{"rendered":"\n<p><a href=\"https:\/\/Polymarket.com\">Polymarket.com<\/a>, a prediction market platform, operates on the Ethereum blockchain through the Polygon network, making it possible to analyze user transactions directly from the blockchain. By accessing a user\u2019s wallet address, we can examine their trades in detail, track profit\/loss, and monitor position changes over time. In this post, I&#8217;ll show how you can leverage the Polygon blockchain data to analyze trades on Polymarket using wallet IDs and some helpful Python code.<\/p>\n\n\n\n<p>Below are examples of the kinds of charts the script generates and their significance.<\/p>\n\n\n\n<p><strong>Shares by Market<\/strong><\/p>\n\n\n\n<p>This chart focuses on the number of shares the user holds across different markets. It\u2019s another way to visualize their exposure to various outcomes but focuses on the number of shares rather than their total purchase value.<\/p>\n\n\n\n<p><strong>Insight<\/strong>: Larger bars suggest higher exposure to specific markets. The average price paid per share is also annotated, providing further context.<\/p>\n\n\n\n<p><strong>Purpose<\/strong>: Useful for understanding the user&#8217;s position size in each market.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42.png\"><img decoding=\"async\" width=\"1024\" height=\"771\" data-src=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42-1024x771.png\" alt=\"\" class=\"wp-image-11918 lazyload\" data-srcset=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42-1024x771.png 1024w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42-300x226.png 300w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42-768x578.png 768w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42-1536x1156.png 1536w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42-399x300.png 399w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42.png 1702w\" data-sizes=\"(max-width: 1024px) 100vw, 1024px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 1024px; --smush-placeholder-aspect-ratio: 1024\/771;\" \/><\/a><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Total Purchase Value by Market<\/strong><\/h3>\n\n\n\n<p>In this bar chart, the user\u2019s total purchase value is broken down by market. The height of each bar indicates how much the user has invested in each specific market.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Purpose<\/strong>: It allows for a clear visualization of where the user is concentrating their funds, showing which markets hold the largest portion of their portfolio.<\/li>\n\n\n\n<li><strong>Insight<\/strong>: The accompanying labels provide information about the average price paid per share in each market, helping understand whether the user is buying low or high within a market.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-41.png\"><img decoding=\"async\" width=\"1024\" height=\"771\" data-src=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-41-1024x771.png\" alt=\"\" class=\"wp-image-11920 lazyload\" data-srcset=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-41-1024x771.png 1024w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-41-300x226.png 300w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-41-768x578.png 768w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-41-1536x1156.png 1536w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-41-399x300.png 399w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-41.png 1702w\" data-sizes=\"(max-width: 1024px) 100vw, 1024px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 1024px; --smush-placeholder-aspect-ratio: 1024\/771;\" \/><\/a><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-40.png\"><img decoding=\"async\" width=\"1024\" height=\"771\" data-src=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-40-1024x771.png\" alt=\"\" class=\"wp-image-11919 lazyload\" data-srcset=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-40-1024x771.png 1024w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-40-300x226.png 300w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-40-768x578.png 768w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-40-1536x1156.png 1536w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-40-399x300.png 399w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-40.png 1702w\" data-sizes=\"(max-width: 1024px) 100vw, 1024px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 1024px; --smush-placeholder-aspect-ratio: 1024\/771;\" \/><\/a><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Total Purchase Value Timeline<\/strong><\/h3>\n\n\n\n<p>This scatter plot shows the timeline of the user\u2019s trades by plotting the total purchase value of trades over time.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Purpose<\/strong>: This chart reveals when the user made their largest investments, showing the fluctuations in purchase value across trades.<\/li>\n\n\n\n<li><strong>Insight<\/strong>: Each dot represents a trade, with its position on the Y-axis showing the value and on the X-axis showing the timestamp of the transaction. You can use this chart to understand when the user made big moves in the market.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-39.png\"><img decoding=\"async\" width=\"1024\" height=\"771\" data-src=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-39-1024x771.png\" alt=\"\" class=\"wp-image-11921 lazyload\" style=\"--smush-placeholder-width: 1024px; --smush-placeholder-aspect-ratio: 1024\/771;width:614px;height:auto\" data-srcset=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-39-1024x771.png 1024w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-39-300x226.png 300w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-39-768x578.png 768w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-39-1536x1156.png 1536w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-39-399x300.png 399w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-39.png 1702w\" data-sizes=\"(max-width: 1024px) 100vw, 1024px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" \/><\/a><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Holdings by Market and Outcome (Treemap)<\/strong><\/h3>\n\n\n\n<p>The treemap provides a more detailed look at the user&#8217;s holdings, breaking down their positions by both market and outcome. Each rectangle represents the shares held in a particular market-outcome pair, with the size of the rectangle proportional to the user&#8217;s investment.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Purpose<\/strong>: Ideal for visually assessing how much the user has allocated to each market and outcome combination.<\/li>\n\n\n\n<li><strong>Insight<\/strong>: It highlights not just which markets the user has invested in but also how they&#8217;ve distributed their bets across different outcomes within those markets.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-38.png\"><img decoding=\"async\" width=\"1024\" height=\"771\" data-src=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-38-1024x771.png\" alt=\"\" class=\"wp-image-11922 lazyload\" data-srcset=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-38-1024x771.png 1024w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-38-300x226.png 300w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-38-768x578.png 768w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-38-1536x1156.png 1536w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-38-399x300.png 399w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-38.png 1702w\" data-sizes=\"(max-width: 1024px) 100vw, 1024px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 1024px; --smush-placeholder-aspect-ratio: 1024\/771;\" \/><\/a><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Holdings Distribution by Market (Pie Chart)<\/strong><\/h3>\n\n\n\n<p>This pie chart visualizes the user\u2019s current holdings, showing the percentage distribution of shares across different markets.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Purpose<\/strong>: Offers a high-level overview of the user&#8217;s portfolio diversification across different markets.<\/li>\n\n\n\n<li><strong>Insight<\/strong>: Larger slices indicate heavier investments in specific markets, allowing you to quickly see where the user has concentrated their bets.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-37.png\"><img decoding=\"async\" width=\"1024\" height=\"771\" data-src=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-37-1024x771.png\" alt=\"\" class=\"wp-image-11923 lazyload\" data-srcset=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-37-1024x771.png 1024w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-37-300x226.png 300w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-37-768x578.png 768w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-37-1536x1156.png 1536w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-37-399x300.png 399w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-37.png 1702w\" data-sizes=\"(max-width: 1024px) 100vw, 1024px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 1024px; --smush-placeholder-aspect-ratio: 1024\/771;\" \/><\/a><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Cumulative Shares Over Time<\/strong><\/h3>\n\n\n\n<p>This line chart tracks the cumulative number of shares held by the user in various markets over time. It helps visualize when the user bought or sold shares and how their position has evolved in each market.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Purpose<\/strong>: This chart is essential for understanding the user\u2019s trading strategy over time, revealing periods of heavy buying or selling.<\/li>\n\n\n\n<li><strong>Insight<\/strong>: Each line represents a different market and outcome combination. Peaks in the lines indicate increased positions, while dips show reductions or sales.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-36.png\"><img decoding=\"async\" width=\"1024\" height=\"771\" data-src=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-36-1024x771.png\" alt=\"\" class=\"wp-image-11924 lazyload\" data-srcset=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-36-1024x771.png 1024w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-36-300x226.png 300w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-36-768x578.png 768w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-36-1536x1156.png 1536w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-36-399x300.png 399w, https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-36.png 1702w\" data-sizes=\"(max-width: 1024px) 100vw, 1024px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 1024px; --smush-placeholder-aspect-ratio: 1024\/771;\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>The Code<\/strong><\/h2>\n\n\n\n<p>The code behind these charts fetches data from Polygon\u2019s blockchain and processes the transactions associated with a given wallet ID. It retrieves ERC-1155 and ERC-20 token transaction data, enriches it with market information, and generates visual insights based on trading activity. You can use this code to analyze any Polymarket user\u2019s trades simply by knowing their wallet address.<\/p>\n\n\n\n<p>Here\u2019s a breakdown of the main functions:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>fetch_user_transactions(wallet_address, api_key)<\/code><\/strong>: Fetches all ERC-1155 and ERC-20 transactions for a given wallet from Polygon.<\/li>\n\n\n\n<li><strong><code>add_financial_columns()<\/code><\/strong>: Processes transactions to calculate key financial metrics like profit\/loss, total purchase value, and shares held.<\/li>\n\n\n\n<li><strong><code>plot_profit_loss_by_trade()<\/code><\/strong>: Generates a bar plot showing profit or loss for each trade.<\/li>\n\n\n\n<li><strong><code>plot_shares_over_time()<\/code><\/strong>: Creates a line plot showing cumulative shares over time.<\/li>\n\n\n\n<li><strong><code>create_and_save_pie_chart()<\/code><\/strong>: Generates a pie chart that breaks down holdings by market.<\/li>\n\n\n\n<li><strong><code>create_and_save_treemap()<\/code><\/strong>: Produces a treemap for holdings based on market and outcome.<\/li>\n\n\n\n<li><strong><code>plot_total_purchase_value()<\/code><\/strong>: Generates a scatter plot showing total purchase value over time.<\/li>\n<\/ul>\n\n\n\n<p>This tool offers deep insights into any user\u2019s trading behavior and performance on Polymarket.<\/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=\"\">import os\nimport requests\nimport logging\nimport pandas as pd\nimport subprocess\nimport json\nimport time\nfrom dotenv import load_dotenv\nimport plotly.express as px\nimport re\nfrom bs4 import BeautifulSoup\nfrom importlib import reload\nimport numpy as np\nimport argparse\nimport os\nimport subprocess\nimport json\nimport logging\nimport pandas as pd\nfrom dotenv import load_dotenv\n\nlogging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')\nlogger = logging.getLogger(__name__)\n\n\n# Load environment variables\nload_dotenv(\"keys.env\")\n\nprice_cache = {}\n\n# EXCHANGES\nCTF_EXCHANGE = '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174'\nNEG_RISK_CTF_EXCHANGE = '0x4d97dcd97ec945f40cf65f87097ace5ea0476045'\n\n# SPENDERS FOR EXCHANGES\nNEG_RISK_CTF_EXCHANGE_SPENDER = '0xC5d563A36AE78145C45a50134d48A1215220f80a'\nNEG_RISK_ADAPTER = '0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296'\nCTF_EXCHANGE_SPENDER = '0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E'\n\nCACHE_EXPIRATION_TIME = 60 * 30  # Cache expiration time in seconds (5 minutes)\nPRICE_CACHE_FILE = '.\/data\/live_price_cache.json'\n\n# Dictionary to cache live prices\nlive_price_cache = {}\n\n\ndef load_price_cache():\n    \"\"\"Load the live price cache from a JSON file.\"\"\"\n    if os.path.exists(PRICE_CACHE_FILE):\n        try:\n            with open(PRICE_CACHE_FILE, 'r') as file:\n                return json.load(file)\n        except json.JSONDecodeError as e:\n            logger.error(f\"Error loading price cache: {e}\")\n            return {}\n    return {}\n\ndef save_price_cache(cache):\n    \"\"\"Save the live price cache to a JSON file.\"\"\"\n    with open(PRICE_CACHE_FILE, 'w') as file:\n        json.dump(cache, file)\n\ndef is_cache_valid(cache_entry, expiration_time=CACHE_EXPIRATION_TIME):\n    \"\"\"\n    Check if the cache entry is still valid based on the current time and expiration time.\n    \"\"\"\n    if not cache_entry:\n        return False\n    cached_time = cache_entry.get('timestamp', 0)\n    return (time.time() - cached_time) &lt; expiration_time\n\n\ndef call_get_live_price(token_id, expiration_time=CACHE_EXPIRATION_TIME):\n    \"\"\"\n    Get live price from cache or update it if expired.\n    \"\"\"\n    logger.info(f'Getting live price for token {token_id}')\n\n    # Load existing cache\n    price_cache = load_price_cache()\n    cache_key = f\"{token_id}\"\n\n    # Check if cache is valid\n    if cache_key in price_cache and is_cache_valid(price_cache[cache_key], expiration_time):\n        logger.info(f'Returning cached price for {cache_key}')\n        return price_cache[cache_key]['price']\n\n    # If cache is expired or doesn't exist, fetch live price\n    try:\n        result = subprocess.run(\n            ['python3', 'get_live_price.py', token_id],\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE,\n            text=True,\n            check=True\n        )\n        # Parse the live price from the subprocess output\n        output_lines = result.stdout.strip().split(\"\\n\")\n        live_price_line = next((line for line in output_lines if \"Live price for token\" in line), None)\n        if live_price_line:\n            live_price = float(live_price_line.strip().split(\":\")[-1].strip())\n        else:\n            logger.error(\"Live price not found in subprocess output.\")\n            return None\n\n        logger.debug(f\"Subprocess get_live_price output: {result.stdout}\")\n\n        # Update cache with the new price and timestamp\n        price_cache[cache_key] = {'price': live_price, 'timestamp': time.time()}\n        save_price_cache(price_cache)\n\n        return live_price\n\n    except subprocess.CalledProcessError as e:\n        logger.error(f\"Subprocess get_live_price error: {e.stderr}\")\n        return None\n    except Exception as e:\n        logger.error(f\"Error fetching live price: {str(e)}\")\n        return None\n\ndef update_live_price_and_pl(merged_df, contract_token_id, market_slug=None, outcome=None):\n    \"\"\"\n    Calculate the live price and profit\/loss (pl) for each trade in the DataFrame.\n    \"\"\"\n    # Ensure tokenID in merged_df is string\n    merged_df['tokenID'] = merged_df['tokenID'].astype(str)\n    contract_token_id = str(contract_token_id)\n\n    # Check for NaN or empty token IDs\n    if not contract_token_id or contract_token_id == 'nan':\n        logger.warning(\"Encountered NaN or empty contract_token_id. Skipping.\")\n        return merged_df\n\n    # Add live_price and pl columns if they don't exist\n    if 'live_price' not in merged_df.columns:\n        merged_df['live_price'] = np.nan\n    if 'pl' not in merged_df.columns:\n        merged_df['pl'] = np.nan\n\n    # Filter rows with the same contract_token_id and outcome\n    merged_df['outcome'] = merged_df['outcome'].astype(str)\n    matching_rows = merged_df[(merged_df['tokenID'] == contract_token_id) &amp;\n                              (merged_df['outcome'].str.lower() == outcome.lower())]\n\n    if not matching_rows.empty:\n        logger.info(f'Fetching live price for token {contract_token_id}')\n        live_price = call_get_live_price(contract_token_id)\n        logger.info(f'Live price for token {contract_token_id}: {live_price}')\n\n        if live_price is not None:\n            try:\n                # Calculate profit\/loss based on the live price\n                price_paid_per_token = matching_rows['price_paid_per_token']\n                total_purchase_value = matching_rows['total_purchase_value']\n                pl = ((live_price - price_paid_per_token) \/ price_paid_per_token) * total_purchase_value\n\n                # Update the DataFrame with live price and pl\n                merged_df.loc[matching_rows.index, 'live_price'] = live_price\n                merged_df.loc[matching_rows.index, 'pl'] = pl\n            except Exception as e:\n                logger.error(f\"Error calculating live price and profit\/loss: {e}\")\n        else:\n            logger.warning(f\"Live price not found for tokenID {contract_token_id}\")\n            merged_df.loc[matching_rows.index, 'pl'] = np.nan\n\n    return merged_df\n\n\n\n\ndef find_token_id(market_slug, outcome, market_lookup):\n    \"\"\"Find the token_id based on market_slug and outcome.\"\"\"\n    for market in market_lookup.values():\n        if market['market_slug'] == market_slug:\n            for token in market['tokens']:\n                if token['outcome'].lower() == outcome.lower():\n                    return token['token_id']\n    return None\n\n\ndef fetch_data(url):\n    \"\"\"Fetch data from a given URL and return the JSON response.\"\"\"\n    try:\n        response = requests.get(url, timeout=10)  # You can specify a timeout\n        response.raise_for_status()  # Raise an error for bad responses (4xx, 5xx)\n        return response.json()\n    except requests.exceptions.RequestException as e:\n        logger.error(f\"Error fetching data from URL: {url}. Exception: {e}\")\n        return None\n\ndef fetch_all_pages(api_key, token_ids, market_slug_outcome_map, csv_output_dir='.\/data\/polymarket_trades\/'):\n    page = 1\n    offset = 100\n    retry_attempts = 0\n    all_data = []  # Store all data here\n\n    while True:\n        url = f\"https:\/\/api.polygonscan.com\/api?module=account&amp;action=token1155tx&amp;contractaddress={NEG_RISK_CTF_EXCHANGE}&amp;page={page}&amp;offset={offset}&amp;startblock=0&amp;endblock=99999999&amp;sort=desc&amp;apikey={api_key}\"\n        logger.info(f\"Fetching transaction data for tokens {token_ids}, page: {page}\")\n\n        data = fetch_data(url)\n\n        if data and data['status'] == '1':\n            df = pd.DataFrame(data['result'])\n\n            if df.empty:\n                logger.info(\"No more transactions found, ending pagination.\")\n                break  # Exit if there are no more transactions\n\n            all_data.append(df)\n            page += 1  # Go to the next page\n        else:\n            logger.error(f\"API response error or no data found for page {page}\")\n            if retry_attempts &lt; 5:\n                retry_attempts += 1\n                time.sleep(retry_attempts)\n            else:\n                break\n\n    if all_data:\n        final_df = pd.concat(all_data, ignore_index=True)  # Combine all pages\n        logger.info(f\"Fetched {len(final_df)} transactions across all pages.\")\n        return final_df\n    return None\n\ndef validate_market_lookup(token_ids, market_lookup):\n    valid_token_ids = []\n    invalid_token_ids = []\n\n    for token_id in token_ids:\n        market_slug, outcome = find_market_info(token_id, market_lookup)\n        if market_slug and outcome:\n            valid_token_ids.append(token_id)\n        else:\n            invalid_token_ids.append(token_id)\n\n    logger.info(f\"Valid token IDs: {valid_token_ids}\")\n    if invalid_token_ids:\n        logger.warning(f\"Invalid or missing market info for token IDs: {invalid_token_ids}\")\n\n    return valid_token_ids\n\n\ndef sanitize_filename(filename):\n    \"\"\"\n    Sanitize the filename by removing or replacing invalid characters.\n    \"\"\"\n    # Replace invalid characters with an underscore\n    return re.sub(r'[\\\\\/*?:\"&lt;>|]', '_', filename)\n\ndef sanitize_directory(directory):\n    \"\"\"\n    Sanitize the directory name by removing or replacing invalid characters.\n    \"\"\"\n    # Replace invalid characters with an underscore\n    return re.sub(r'[\\\\\/*?:\"&lt;>|]', '_', directory)\n\ndef extract_wallet_ids(leaderboard_url):\n    \"\"\"Scrape the Polymarket leaderboard to extract wallet IDs.\"\"\"\n    logging.info(f\"Fetching leaderboard page: {leaderboard_url}\")\n\n    response = requests.get(leaderboard_url)\n    if response.status_code != 200:\n        logging.error(f\"Failed to load page {leaderboard_url}, status code: {response.status_code}\")\n        return []\n\n    logging.debug(f\"Page loaded successfully, status code: {response.status_code}\")\n\n    soup = BeautifulSoup(response.content, 'html.parser')\n    logging.debug(\"Page content parsed with BeautifulSoup\")\n\n    wallet_ids = []\n\n    # Debug: Check if &lt;a> tags are being found correctly\n    a_tags = soup.find_all('a', href=True)\n    logging.debug(f\"Found {len(a_tags)} &lt;a> tags in the page.\")\n\n    for a_tag in a_tags:\n        href = a_tag['href']\n        logging.debug(f\"Processing href: {href}\")\n        if href.startswith('\/profile\/'):\n            wallet_id = href.split('\/')[-1]\n            wallet_ids.append(wallet_id)\n            logging.info(f\"Extracted wallet ID: {wallet_id}\")\n        else:\n            logging.debug(f\"Skipped href: {href}\")\n\n    return wallet_ids\ndef load_market_lookup(json_path):\n    \"\"\"Load market lookup data from a JSON file.\"\"\"\n    with open(json_path, 'r') as json_file:\n        return json.load(json_file)\n\n\n\n\ndef find_market_info(token_id, market_lookup):\n    \"\"\"Find market_slug and outcome based on tokenID.\"\"\"\n    token_id = str(token_id)  # Ensure token_id is a string\n    if not token_id or token_id == 'nan':\n        logger.warning(\"Token ID is NaN or empty. Skipping lookup.\")\n        return None, None\n\n    logger.debug(f\"Looking up market info for tokenID: {token_id}\")\n\n    for market in market_lookup.values():\n        for token in market['tokens']:\n            if str(token['token_id']) == token_id:\n                logger.debug(\n                    f\"Found market info for tokenID {token_id}: market_slug = {market['market_slug']}, outcome = {token['outcome']}\")\n                return market['market_slug'], token['outcome']\n\n    logger.warning(f\"No market info found for tokenID: {token_id}\")\n    return None, None\n\n\n\n\n\ndef fetch_data(url):\n    \"\"\"Fetch data from a given URL and return the JSON response.\"\"\"\n    response = requests.get(url)\n    return response.json()\n\n\ndef save_to_csv(filename, data, headers, output_dir):\n    \"\"\"Save data to a CSV file in the specified output directory.\"\"\"\n    filepath = os.path.join(output_dir, filename)\n    with open(filepath, 'w', newline='') as file:\n        writer = csv.DictWriter(file, fieldnames=headers)\n        writer.writeheader()\n        for entry in data:\n            writer.writerow(entry)\n    logger.info(f\"Saved data to {filepath}\")\n\n\n\ndef add_timestamps(erc1155_df, erc20_df):\n    \"\"\"\n    Rename timestamp columns and convert them from UNIX to datetime.\n    \"\"\"\n    # Rename the timestamp columns to avoid conflicts during merge\n    erc1155_df.rename(columns={'timeStamp': 'timeStamp_erc1155'}, inplace=True)\n    erc20_df.rename(columns={'timeStamp': 'timeStamp_erc20'}, inplace=True)\n\n    # Convert UNIX timestamps to datetime format\n    erc1155_df['timeStamp_erc1155'] = pd.to_numeric(erc1155_df['timeStamp_erc1155'], errors='coerce')\n    erc20_df['timeStamp_erc20'] = pd.to_numeric(erc20_df['timeStamp_erc20'], errors='coerce')\n\n    erc1155_df['timeStamp_erc1155'] = pd.to_datetime(erc1155_df['timeStamp_erc1155'], unit='s', errors='coerce')\n    erc20_df['timeStamp_erc20'] = pd.to_datetime(erc20_df['timeStamp_erc20'], unit='s', errors='coerce')\n\n    return erc1155_df, erc20_df\n\n\ndef enrich_erc1155_data(erc1155_df, market_lookup):\n    \"\"\"\n    Enrich the ERC-1155 DataFrame with market_slug and outcome based on market lookup.\n    \"\"\"\n\n    def get_market_info(token_id):\n        if pd.isna(token_id) or str(token_id) == 'nan':\n            return 'Unknown', 'Unknown'\n        for market in market_lookup.values():\n            for token in market['tokens']:\n                if str(token['token_id']) == str(token_id):\n                    return market['market_slug'], token['outcome']\n        return 'Unknown', 'Unknown'\n\n    erc1155_df['market_slug'], erc1155_df['outcome'] = zip(\n        *erc1155_df['tokenID'].apply(lambda x: get_market_info(x))\n    )\n\n    return erc1155_df\n\n\n\ndef get_transaction_details_by_hash(transaction_hash, api_key, output_dir='.\/data\/polymarket_trades\/'):\n    \"\"\"\n    Fetch the transaction details by hash from Polygonscan, parse the logs, and save the flattened data as a CSV.\n\n    Args:\n    - transaction_hash (str): The hash of the transaction.\n    - api_key (str): The Polygonscan API key.\n    - output_dir (str): The directory to save the CSV file.\n\n    Returns:\n    - None: Saves the transaction details to a CSV.\n    \"\"\"\n    # Ensure output directory exists\n    os.makedirs(output_dir, exist_ok=True)\n\n    # Construct the API URL for fetching transaction receipt details by hash\n    url = f\"https:\/\/api.polygonscan.com\/api?module=proxy&amp;action=eth_getTransactionReceipt&amp;txhash={transaction_hash}&amp;apikey={api_key}\"\n\n    logger.info(f\"Fetching transaction details for hash: {transaction_hash}\")\n    logger.debug(f\"Request URL: {url}\")\n\n    try:\n        # Fetch transaction details\n        response = requests.get(url)\n        logger.debug(f\"Polygonscan API response status: {response.status_code}\")\n\n        if response.status_code != 200:\n            logger.error(f\"Non-200 status code received: {response.status_code}\")\n            return None\n\n        # Parse the JSON response\n        data = response.json()\n        logger.debug(f\"Response JSON: {data}\")\n\n        # Check if the status is successful\n        if data.get('result') is None:\n            logger.error(f\"Error in API response: {data.get('message', 'Unknown error')}\")\n            return None\n\n        # Extract the logs\n        logs = data['result']['logs']\n        logs_df = pd.json_normalize(logs)\n\n        # Save the logs to a CSV file for easier review\n        csv_filename = os.path.join(output_dir, f\"transaction_logs_{transaction_hash}.csv\")\n        logs_df.to_csv(csv_filename, index=False)\n        logger.info(f\"Parsed logs saved to {csv_filename}\")\n\n        return logs_df\n\n    except Exception as e:\n        logger.error(f\"Exception occurred while fetching transaction details for hash {transaction_hash}: {e}\")\n        return None\ndef add_financial_columns(erc1155_df, erc20_df, wallet_id, market_lookup):\n    \"\"\"\n    Merge the ERC-1155 and ERC-20 dataframes, calculate financial columns,\n    including whether a trade was won or lost, and fetch the latest price for each contract and tokenID.\n    \"\"\"\n    # Merge the two dataframes on the 'hash' column\n    merged_df = pd.merge(erc1155_df, erc20_df, how='outer', on='hash', suffixes=('_erc1155', '_erc20'))\n\n    # Convert wallet ID and columns to lowercase for case-insensitive comparison\n    wallet_id = wallet_id.lower()\n    merged_df['to_erc1155'] = merged_df['to_erc1155'].astype(str).str.lower()\n    merged_df['from_erc1155'] = merged_df['from_erc1155'].astype(str).str.lower()\n\n    # Remove rows where 'tokenID' is NaN or 'nan'\n    merged_df['tokenID'] = merged_df['tokenID'].astype(str)\n    merged_df = merged_df[~merged_df['tokenID'].isnull() &amp; (merged_df['tokenID'] != 'nan')]\n\n\n    # Set transaction type based on wallet address\n    merged_df['transaction_type'] = 'other'\n    merged_df.loc[merged_df['to_erc1155'] == wallet_id, 'transaction_type'] = 'buy'\n    merged_df.loc[merged_df['from_erc1155'] == wallet_id, 'transaction_type'] = 'sell'\n\n    # Calculate the purchase price per token and total dollar value\n    if 'value' in merged_df.columns and 'tokenValue' in merged_df.columns:\n        merged_df['price_paid_per_token'] = merged_df['value'].astype(float) \/ merged_df['tokenValue'].astype(float)\n        merged_df['total_purchase_value'] = merged_df['value'].astype(float) \/ 10**6  # USDC has 6 decimal places\n        merged_df['shares'] = merged_df['total_purchase_value'] \/ merged_df['price_paid_per_token']\n    else:\n        logger.error(\"The necessary columns for calculating purchase price are missing.\")\n        return merged_df\n\n    # Create the 'lost' and 'won' columns\n    merged_df['lost'] = (\n        (merged_df['to_erc1155'] == '0x0000000000000000000000000000000000000000') &amp;\n        (merged_df['transaction_type'] == 'sell') &amp;\n        (merged_df['price_paid_per_token'].isna() | (merged_df['price_paid_per_token'] == 0))\n    ).astype(int)\n\n    merged_df['won'] = (\n        (merged_df['transaction_type'] == 'sell') &amp;\n        (merged_df['price_paid_per_token'] == 1)\n    ).astype(int)\n\n    merged_df.loc[merged_df['lost'] == 1, 'shares'] = 0\n    merged_df.loc[merged_df['lost'] == 1, 'total_purchase_value'] = 0\n\n    # Fetch live prices and calculate profit\/loss (pl)\n    merged_df['tokenID'] = merged_df['tokenID'].astype(str)\n    merged_df = update_latest_prices(merged_df, market_lookup)\n\n    return merged_df\n\ndef plot_profit_loss_by_trade(df, user_info):\n    \"\"\"\n    Create a bar plot to visualize aggregated Profit\/Loss (PL) by trade, with values rounded to two decimal places and formatted as currency.\n\n    Args:\n        df (DataFrame): DataFrame containing trade data, including 'market_slug', 'outcome', and 'pl'.\n        user_info (dict): Dictionary containing user information, such as username, wallet address, and other relevant details.\n    \"\"\"\n    if 'pl' not in df.columns or df['pl'].isnull().all():\n        logger.warning(\"No PL data available for plotting. Skipping plot.\")\n        return\n\n    username = user_info.get(\"username\", \"Unknown User\")\n    wallet_id = user_info.get(\"wallet_address\", \"N\/A\")\n    positions_value = user_info.get(\"positions_value\", \"N\/A\")\n    profit_loss = user_info.get(\"profit_loss\", \"N\/A\")\n    volume_traded = user_info.get(\"volume_traded\", \"N\/A\")\n    markets_traded = user_info.get(\"markets_traded\", \"N\/A\")\n\n    # Combine market_slug and outcome to create a trade identifier\n    df['trade'] = df['market_slug'] + ' (' + df['outcome'] + ')'\n\n    # Aggregate the Profit\/Loss (pl) for each unique trade\n    aggregated_df = df.groupby('trade', as_index=False).agg({'pl': 'sum'})\n\n    # Round PL values to two decimal places\n    aggregated_df['pl'] = aggregated_df['pl'].round(2)\n\n    # Format the PL values with a dollar sign for display\n    aggregated_df['pl_display'] = aggregated_df['pl'].apply(lambda x: f\"${x:,.2f}\")\n\n    # Define a color mapping based on Profit\/Loss sign\n    aggregated_df['color'] = aggregated_df['pl'].apply(lambda x: 'green' if x >= 0 else 'red')\n\n    # Create the plot without using the color axis\n    fig = px.bar(\n        aggregated_df,\n        x='trade',\n        y='pl',\n        title='',\n        labels={'pl': 'Profit\/Loss ($)', 'trade': 'Trade (Market Slug \/ Outcome)'},\n        text='pl_display',\n        color='color',  # Use the color column\n        color_discrete_map={'green': 'green', 'red': 'red'},\n    )\n\n    # Remove the legend if you don't want it\n    fig.update_layout(showlegend=False)\n\n    # Rotate x-axis labels for better readability and set the main title\n    fig.update_layout(\n        title={\n            'text': 'Aggregated Profit\/Loss by Trade',\n            'y': 0.95,\n            'x': 0.5,\n            'xanchor': 'center',\n            'yanchor': 'top',\n            'font': {'size': 24}\n        },\n        xaxis_tickangle=-45,\n        margin=dict(t=150, l=50, r=50, b=100)\n    )\n\n    # Prepare the subtitle text with user information\n    subtitle_text = (\n        f\"Username: {username} | Positions Value: {positions_value} | \"\n        f\"Profit\/Loss: {profit_loss} | Volume Traded: {volume_traded} | \"\n        f\"Markets Traded: {markets_traded} | Wallet ID: {wallet_id}\"\n    )\n\n    # Add the subtitle as an annotation\n    fig.add_annotation(\n        text=subtitle_text,\n        xref=\"paper\",\n        yref=\"paper\",\n        x=0.5,\n        y=1.02,\n        xanchor='center',\n        yanchor='top',\n        showarrow=False,\n        font=dict(size=14)\n    )\n\n    # Save the plot\n    plot_dir = \".\/plots\/user_trades\"\n    os.makedirs(plot_dir, exist_ok=True)\n    sanitized_username = sanitize_filename(username)\n    plot_file = os.path.join(plot_dir, f\"{sanitized_username}_aggregated_profit_loss_by_trade.html\")\n    fig.write_html(plot_file)\n\n    logger.info(f\"Aggregated Profit\/Loss by trade plot saved to {plot_file}\")\n\n\n\ndef plot_shares_over_time(df, user_info):\n    \"\"\"\n    Create a line plot to visualize the cumulative number of shares for each token over time.\n    Buy orders add to the position, and sell orders subtract from it.\n\n    Args:\n        df (DataFrame): DataFrame containing trade data, including 'timeStamp_erc1155', 'shares', 'market_slug', 'outcome', and 'transaction_type' ('buy' or 'sell').\n        user_info (dict): Dictionary containing user information, such as username, wallet address, and other relevant details.\n    \"\"\"\n    if 'shares' not in df.columns or df['shares'].isnull().all():\n        logger.warning(\"No 'shares' data available for plotting. Skipping plot.\")\n        return\n\n    username = user_info.get(\"username\", \"Unknown User\")\n\n    # Ensure 'timeStamp_erc1155' is a datetime type, just in case it needs to be converted\n    if df['timeStamp_erc1155'].dtype != 'datetime64[ns]':\n        df['timeStamp_erc1155'] = pd.to_datetime(df['timeStamp_erc1155'], errors='coerce')\n\n    # Drop rows with NaN values in 'timeStamp_erc1155', 'shares', 'market_slug', 'outcome', or 'transaction_type'\n    df = df.dropna(subset=['timeStamp_erc1155', 'shares', 'market_slug', 'outcome', 'transaction_type'])\n\n    # Sort the dataframe by time to ensure the line chart shows the data in chronological order\n    df = df.sort_values(by='timeStamp_erc1155')\n\n    # Combine 'market_slug' and 'outcome' to create a unique label for each token\n    df['token_label'] = df['market_slug'] + \" - \" + df['outcome']\n\n    # Create a column for 'position_change' which adds shares for buys and subtracts shares for sells based on 'transaction_type'\n    df['position_change'] = df.apply(lambda row: row['shares'] if row['transaction_type'] == 'buy' else -row['shares'], axis=1)\n\n    # Group by 'token_label' and calculate the cumulative position\n    df['cumulative_position'] = df.groupby('token_label')['position_change'].cumsum()\n\n    # Forward fill the cumulative position to maintain it between trades\n    df['cumulative_position'] = df.groupby('token_label')['cumulative_position'].ffill()\n\n    # Create the line plot, grouping by 'token_label' for separate lines per token ID\n    fig = px.line(\n        df,\n        x='timeStamp_erc1155',\n        y='cumulative_position',\n        color='token_label',  # This ensures each token ID (market_slug + outcome) gets its own line\n        title=f'Cumulative Shares Over Time for {username}',\n        labels={'timeStamp_erc1155': 'Trade Time', 'cumulative_position': 'Cumulative Position', 'token_label': 'Token (Market Slug - Outcome)'},\n        line_shape='linear'\n    )\n\n    # Update layout for better aesthetics\n    fig.update_layout(\n        title={\n            'text': f\"Cumulative Number of Shares Over Time for {username}\",\n            'y': 0.95,\n            'x': 0.5,\n            'xanchor': 'center',\n            'yanchor': 'top',\n            'font': {'size': 20}\n        },\n        margin=dict(t=60),\n        xaxis_title=\"Trade Time\",\n        yaxis_title=\"Cumulative Number of Shares\",\n        legend_title=\"Token (Market Slug - Outcome)\"\n    )\n\n    # Save the plot\n    plot_dir = \".\/plots\/user_trades\"\n    os.makedirs(plot_dir, exist_ok=True)\n    sanitized_username = sanitize_filename(username)\n    plot_file = os.path.join(plot_dir, f\"{sanitized_username}_shares_over_time.html\")\n    fig.write_html(plot_file)\n\n    logger.info(f\"Cumulative shares over time plot saved to {plot_file}\")\n\n\ndef plot_user_trades(df, user_info):\n    \"\"\"Plot user trades and save plots, adjusting for trades that were lost.\"\"\"\n    username = user_info[\"username\"]\n    wallet_id = user_info[\"wallet_address\"]\n\n    # Sanitize only the filename, not the directory\n    sanitized_username = sanitize_filename(username)\n\n    info_text = (\n        f\"Username: {username} | Positions Value: {user_info['positions_value']} | \"\n        f\"Profit\/Loss: {user_info['profit_loss']} | Volume Traded: {user_info['volume_traded']} | \"\n        f\"Markets Traded: {user_info['markets_traded']} | Wallet ID: {wallet_id}\"\n    )\n\n    # Ensure the directory exists\n    os.makedirs(\".\/plots\/user_trades\", exist_ok=True)\n    plot_dir = \".\/plots\/user_trades\"\n\n    # Flag loss trades where to_erc1155 is zero address, transaction_type is sell, and price_paid_per_token is NaN\n    df['is_loss'] = df.apply(\n        lambda row: (row['to_erc1155'] == '0x0000000000000000000000000000000000000000')\n                    and (row['transaction_type'] == 'sell')\n                    and pd.isna(row['price_paid_per_token']), axis=1)\n\n    # Set shares and total purchase value to zero for loss trades\n    df.loc[df['is_loss'], 'shares'] = 0\n    df.loc[df['is_loss'], 'total_purchase_value'] = 0\n\n    ### Modify for Total Purchase Value by Market (Current holdings)\n    df['total_purchase_value_adjusted'] = df.apply(\n        lambda row: row['total_purchase_value'] if row['transaction_type'] == 'buy' else -row['total_purchase_value'],\n        axis=1\n    )\n\n    grouped_df_value = df.groupby(['market_slug']).agg({\n        'total_purchase_value_adjusted': 'sum',\n        'shares': 'sum',\n    }).reset_index()\n\n    # Calculate the weighted average price_paid_per_token\n    grouped_df_value['weighted_price_paid_per_token'] = (\n        grouped_df_value['total_purchase_value_adjusted'] \/ grouped_df_value['shares']\n    )\n\n    # Sort by total_purchase_value in descending order (ignoring outcome)\n    grouped_df_value = grouped_df_value.sort_values(by='total_purchase_value_adjusted', ascending=False)\n\n    # Format the label for the bars (removing outcome)\n    grouped_df_value['bar_label'] = (\n        \"Avg Price: $\" + grouped_df_value['weighted_price_paid_per_token'].round(2).astype(str)\n    )\n\n    fig = px.bar(\n        grouped_df_value,\n        x='market_slug',\n        y='total_purchase_value_adjusted',\n        barmode='group',\n        title=f\"Current Total Purchase Value by Market for {username}\",\n        labels={'total_purchase_value_adjusted': 'Current Total Purchase Value', 'market_slug': 'Market'},\n        text=grouped_df_value['bar_label'],\n        hover_data={'weighted_price_paid_per_token': ':.2f'},\n    )\n\n    fig.update_layout(\n        title={\n            'text': f\"Current Total Purchase Value by Market for {username}\",\n            'y': 0.95,\n            'x': 0.5,\n            'xanchor': 'center',\n            'yanchor': 'top',\n            'font': {'size': 20}\n        },\n        margin=dict(t=60),\n        showlegend=False  # Remove the legend as you requested\n    )\n\n    fig.add_annotation(\n        text=info_text,\n        xref=\"paper\", yref=\"paper\", showarrow=False, x=0.5, y=1.05, font=dict(size=12)\n    )\n\n    # Save the bar plot as an HTML file\n    plot_file = os.path.join(plot_dir, f\"{sanitized_username}_current_market_purchase_value.html\")\n    fig.write_html(plot_file)\n    logger.info(f\"Current market purchase value plot saved to {plot_file}\")\n\n    ### Modify for Trade Quantity by Market (Current holdings)\n    df['shares_adjusted'] = df.apply(\n        lambda row: row['shares'] if row['transaction_type'] == 'buy' else -row['shares'], axis=1)\n\n    grouped_df_quantity = df.groupby(['market_slug']).agg({\n        'shares_adjusted': 'sum',\n        'total_purchase_value': 'sum',\n    }).reset_index()\n\n    # Calculate the weighted average price_paid_per_token\n    grouped_df_quantity['weighted_price_paid_per_token'] = (\n        grouped_df_quantity['total_purchase_value'] \/ grouped_df_quantity['shares_adjusted']\n    )\n\n    grouped_df_quantity = grouped_df_quantity.sort_values(by='shares_adjusted', ascending=False)\n\n    grouped_df_quantity['bar_label'] = (\n        \"Quantity: \" + grouped_df_quantity['shares_adjusted'].round().astype(int).astype(str) + \"&lt;br>\" +\n        \"Avg Price: $\" + grouped_df_quantity['weighted_price_paid_per_token'].round(2).astype(str)\n    )\n\n    fig = px.bar(\n        grouped_df_quantity,\n        x='market_slug',\n        y='shares_adjusted',\n        barmode='group',\n        title=f\"Current Trade Quantity by Market for {username}\",\n        labels={'shares_adjusted': 'Current Trade Quantity', 'market_slug': 'Market'},\n        text=grouped_df_quantity['bar_label'],\n    )\n\n    fig.update_layout(\n        title={\n            'text': f\"Current Trade Quantity by Market for {username}\",\n            'y': 0.95,\n            'x': 0.5,\n            'xanchor': 'center',\n            'yanchor': 'top',\n            'font': {'size': 20}\n        },\n        margin=dict(t=60),\n        showlegend=False  # Remove the legend as you requested\n    )\n\n    fig.add_annotation(\n        text=info_text,\n        xref=\"paper\", yref=\"paper\", showarrow=False, x=0.5, y=1.05, font=dict(size=12)\n    )\n\n    # Save the trade quantity plot as an HTML file\n    plot_file = os.path.join(plot_dir, f\"{sanitized_username}_current_market_trade_quantity.html\")\n    fig.write_html(plot_file)\n    logger.info(f\"Current market trade quantity plot saved to {plot_file}\")\n\n    ### Modify for Total Purchase Value Timeline\n    df['total_purchase_value_timeline_adjusted'] = df.apply(\n        lambda row: row['total_purchase_value'] if row['transaction_type'] == 'buy' else -row['total_purchase_value'],\n        axis=1\n    )\n\n    # Combine 'market_slug' and 'outcome' into a unique label\n    df['market_outcome_label'] = df['market_slug'] + ' (' + df['outcome'] + ')'\n\n    # Create the scatter plot, now coloring by 'market_outcome_label'\n    fig = px.scatter(\n        df,\n        x='timeStamp_erc1155',\n        y='total_purchase_value_timeline_adjusted',\n        color='market_outcome_label',  # Use the combined label for market and outcome\n        title=f\"Total Purchase Value Timeline for {username}\",\n        labels={\n            'total_purchase_value_timeline_adjusted': 'Total Purchase Value',\n            'timeStamp_erc1155': 'Transaction Time',\n            'market_outcome_label': 'Market\/Outcome'\n        },\n        hover_data=['market_slug', 'price_paid_per_token', 'outcome', 'hash'],\n    )\n\n    fig.update_layout(\n        title={\n            'text': f\"Total Purchase Value Timeline for {username}\",\n            'y': 0.95,\n            'x': 0.5,\n            'xanchor': 'center',\n            'yanchor': 'top',\n            'font': {'size': 20}\n        },\n        margin=dict(t=60)\n    )\n\n    fig.add_annotation(\n        text=info_text,\n        xref=\"paper\", yref=\"paper\", showarrow=False, x=0.5, y=1.05, font=dict(size=12)\n    )\n\n    # Save the updated plot\n    plot_file = os.path.join(plot_dir, f\"{sanitized_username}_total_purchase_value_timeline_adjusted.html\")\n    fig.write_html(plot_file)\n    logger.info(f\"Total purchase value timeline plot saved to {plot_file}\")\n\ndef plot_total_purchase_value(df, user_info):\n    \"\"\"Create and save a scatter plot for total purchase value, accounting for buy and sell transactions.\"\"\"\n    # Ensure the directory exists\n    os.makedirs(\".\/plots\/user_trades\", exist_ok=True)\n    plot_dir = \".\/plots\/user_trades\"\n\n    username = user_info[\"username\"]\n    wallet_id = user_info[\"wallet_address\"]\n\n    # Sanitize only the filename, not the directory\n    sanitized_username = sanitize_filename(username)\n\n    info_text = (\n        f\"Username: {username} | Positions Value: {user_info['positions_value']} | \"\n        f\"Profit\/Loss: {user_info['profit_loss']} | Volume Traded: {user_info['volume_traded']} | \"\n        f\"Markets Traded: {user_info['markets_traded']} | Wallet ID: {wallet_id}\"\n    )\n\n    # Flag loss trades where to_erc1155 is zero address, transaction_type is sell, and price_paid_per_token is NaN\n    df['is_loss'] = df.apply(\n        lambda row: (row['to_erc1155'] == '0x0000000000000000000000000000000000000000')\n                    and (row['transaction_type'] == 'sell')\n                    and pd.isna(row['price_paid_per_token']), axis=1)\n\n    # Set shares and total purchase value to zero for loss trades\n    df.loc[df['is_loss'], 'shares'] = 0\n    df.loc[df['is_loss'], 'total_purchase_value'] = 0\n\n    # Adjust the total purchase value based on the transaction type\n    df['total_purchase_value_adjusted'] = df.apply(\n        lambda row: row['total_purchase_value'] if row['transaction_type'] == 'buy' else -row['total_purchase_value'],\n        axis=1\n    )\n\n    # Create the scatter plot for total purchase value over time\n    fig = px.scatter(\n        df,\n        x='timeStamp_erc1155',  # Assuming this is the correct timestamp field\n        y='total_purchase_value_adjusted',  # Adjusted values for buys and sells\n        color='market_slug',  # Use market_slug with outcome as the color\n        title=f\"Current Purchase Value Timeline for {username}\",  # Update title to reflect \"current\"\n        labels={'total_purchase_value_adjusted': 'Adjusted Purchase Value ($)', 'timeStamp_erc1155': 'Transaction Time'},\n        hover_data=['market_slug', 'price_paid_per_token', 'outcome', 'hash'],\n    )\n\n    # Adjust title positioning and font size\n    fig.update_layout(\n        title={\n            'text': f\"Current Purchase Value Timeline for {username}\",  # Update to \"Current\"\n            'y': 0.95,\n            'x': 0.5,\n            'xanchor': 'center',\n            'yanchor': 'top',\n            'font': {'size': 20}\n        },\n        margin=dict(t=60)\n    )\n\n    fig.add_annotation(\n        text=info_text,\n        xref=\"paper\", yref=\"paper\", showarrow=False, x=0.5, y=1.05, font=dict(size=12)\n    )\n\n    # Save the scatter plot as an HTML file\n    plot_file = os.path.join(plot_dir, f\"{sanitized_username}_current_purchase_value_timeline.html\")\n    fig.write_html(plot_file)\n    logger.info(f\"Current purchase value timeline plot saved to {plot_file}\")\n\ndef create_and_save_pie_chart(df, user_info):\n    \"\"\"Create and save a pie chart for user's current holdings.\"\"\"\n    # Ensure the directory exists\n    os.makedirs(\".\/plots\/user_trades\", exist_ok=True)\n    plot_dir = \".\/plots\/user_trades\"\n    username = user_info[\"username\"]\n    wallet_id = user_info[\"wallet_address\"]\n\n    sanitized_username = sanitize_filename(username)\n\n    info_text = (\n        f\"Username: {username} | Positions Value: {user_info['positions_value']} | \"\n        f\"Profit\/Loss: {user_info['profit_loss']} | Volume Traded: {user_info['volume_traded']} | \"\n        f\"Markets Traded: {user_info['markets_traded']} | Wallet ID: {wallet_id}\"\n    )\n\n    # Flag loss trades where to_erc1155 is zero address, transaction_type is sell, and price_paid_per_token is NaN\n    df['is_loss'] = df.apply(\n        lambda row: (row['to_erc1155'] == '0x0000000000000000000000000000000000000000')\n                    and (row['transaction_type'] == 'sell')\n                    and pd.isna(row['price_paid_per_token']), axis=1)\n\n    # Set shares and total purchase value to zero for loss trades\n    df.loc[df['is_loss'], 'shares'] = 0\n\n    df['shares_adjusted'] = df.apply(\n        lambda row: row['shares'] if row['transaction_type'] == 'buy' else -row['shares'], axis=1)\n\n    holdings = df.groupby('market_slug').agg({'shares_adjusted': 'sum'}).reset_index()\n\n    holdings = holdings.sort_values('shares_adjusted', ascending=False)\n    threshold = 0.02\n    large_slices = holdings[holdings['shares_adjusted'] > holdings['shares_adjusted'].sum() * threshold]\n    small_slices = holdings[holdings['shares_adjusted'] &lt;= holdings['shares_adjusted'].sum() * threshold]\n\n    if not small_slices.empty:\n        other_sum = small_slices['shares_adjusted'].sum()\n        others_df = pd.DataFrame([{'market_slug': 'Others', 'shares_adjusted': other_sum}])\n        large_slices = pd.concat([large_slices, others_df], ignore_index=True)\n\n    fig = px.pie(\n        large_slices,\n        names='market_slug',\n        values='shares_adjusted',\n        title=f\"Current Holdings Distribution by Market for {username}\",\n    )\n\n    fig.update_layout(\n        title={\n            'text': f\"Current Holdings Distribution by Market for {username}\",\n            'y': 0.95,\n            'x': 0.5,\n            'xanchor': 'center',\n            'yanchor': 'top',\n            'font': {'size': 20}\n        },\n        margin=dict(t=60)\n    )\n\n    fig.add_annotation(\n        text=info_text,\n        xref=\"paper\", yref=\"paper\", showarrow=False, x=0.5, y=1.05, font=dict(size=12)\n    )\n\n    # Save the pie chart as an HTML file\n    plot_file = os.path.join(plot_dir, f\"{sanitized_username}_current_holdings_pie_chart.html\")\n    fig.write_html(plot_file)\n    logger.info(f\"Current holdings pie chart saved to {plot_file}\")\n\n\ndef create_and_save_treemap(df, user_info):\n    \"\"\"Create and save a treemap for user's current holdings.\"\"\"\n    plot_dir = '.\/plots\/user_trades'\n    username = user_info[\"username\"]\n    wallet_id = user_info[\"wallet_address\"]\n\n    sanitized_username = sanitize_filename(username)\n\n    info_text = (\n        f\"Username: {username} | Positions Value: {user_info['positions_value']} | \"\n        f\"Profit\/Loss: {user_info['profit_loss']} | Volume Traded: {user_info['volume_traded']} | \"\n        f\"Markets Traded: {user_info['markets_traded']} | Wallet ID: {wallet_id}\"\n    )\n\n    # Flag loss trades where to_erc1155 is zero address, transaction_type is sell, and price_paid_per_token is NaN\n    df['is_loss'] = df.apply(\n        lambda row: (row['to_erc1155'] == '0x0000000000000000000000000000000000000000')\n                    and (row['transaction_type'] == 'sell')\n                    and pd.isna(row['price_paid_per_token']), axis=1)\n\n    # Set shares and total purchase value to zero for loss trades\n    df.loc[df['is_loss'], 'shares'] = 0\n\n    # Adjust shares based on transaction type (buy vs sell)\n    df['shares_adjusted'] = df.apply(\n        lambda row: row['shares'] if row['transaction_type'] == 'buy' else -row['shares'], axis=1)\n\n    # Group by market_slug and outcome for treemap\n    holdings = df.groupby(['market_slug', 'outcome']).agg({'shares_adjusted': 'sum'}).reset_index()\n\n    # Create the treemap\n    fig = px.treemap(\n        holdings,\n        path=['market_slug', 'outcome'],\n        values='shares_adjusted',\n        title=f\"Current Holdings Distribution by Market and Outcome for {username}\",\n    )\n\n    # Adjust title positioning and font size\n    fig.update_layout(\n        title={\n            'text': f\"Current Holdings Distribution by Market and Outcome for {username}\",\n            'y': 0.95,\n            'x': 0.5,\n            'xanchor': 'center',\n            'yanchor': 'top',\n            'font': {'size': 20}\n        },\n        margin=dict(t=60)\n    )\n\n    fig.add_annotation(\n        text=info_text,\n        xref=\"paper\", yref=\"paper\", showarrow=False, x=0.5, y=1.05, font=dict(size=12)\n    )\n\n    # Save the treemap as an HTML file\n    plot_file = os.path.join(plot_dir, f\"{sanitized_username}_current_holdings_treemap.html\")\n    fig.write_html(plot_file)\n    logger.info(f\"Current holdings treemap saved to {plot_file}\")\n\ndef update_latest_prices(merged_df, market_lookup):\n    \"\"\"\n    Fetch and update the latest prices for each contract and tokenID pair in the merged_df,\n    and calculate profit\/loss (pl) based on the live price.\n    \"\"\"\n    # Ensure 'pl' column exists in the DataFrame\n    if 'pl' not in merged_df.columns:\n        merged_df['pl'] = np.nan  # Import numpy as np at the top of your script\n\n    # Ensure tokenID is a string and filter out NaN tokenIDs\n    merged_df['tokenID'] = merged_df['tokenID'].astype(str)\n    merged_df = merged_df[~merged_df['tokenID'].isnull() &amp; (merged_df['tokenID'] != 'nan')]\n\n    unique_contract_token_pairs = merged_df[['contractAddress_erc1155', 'tokenID']].drop_duplicates()\n\n    for contract_address, token_id in unique_contract_token_pairs.itertuples(index=False):\n        # Ensure token_id is a string\n        token_id_str = str(token_id)\n        if not token_id_str or token_id_str == 'nan':\n            logger.warning(\"Encountered NaN or empty token_id. Skipping.\")\n            continue\n\n        # Find market_slug and outcome using the market_lookup\n        market_slug, outcome = find_market_info(token_id_str, market_lookup)\n\n        if market_slug and outcome:\n            # Update live price and pl in the DataFrame\n            merged_df = update_live_price_and_pl(merged_df, token_id_str, market_slug=market_slug, outcome=outcome)\n        else:\n            logger.warning(f\"Market info not found for token ID: {token_id_str}. Skipping PL calculation for these rows.\")\n            # Optionally, set 'pl' to 0 or np.nan for these rows\n            merged_df.loc[merged_df['tokenID'] == token_id_str, 'pl'] = np.nan\n\n    return merged_df\n\n\n\ndef call_get_user_profile(wallet_id):\n    \"\"\"\n    Call subprocess to get user profile data by wallet_id.\n    \"\"\"\n    if not wallet_id:\n        logger.error(\"No wallet ID provided.\")\n        return None\n\n    try:\n        logger.info(f\"Calling subprocess to fetch user profile for wallet ID: {wallet_id}\")\n\n        # Execute get_user_profile.py using subprocess and pass wallet_id\n        result = subprocess.run(\n            ['python3', 'get_user_profile.py', wallet_id],  # Make sure wallet_id is passed as an argument\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE,\n            check=True,\n            text=True,\n            timeout=30  # Set a timeout for the subprocess\n        )\n\n        logger.debug(f\"Subprocess stdout: {result.stdout}\")\n        logger.debug(f\"Subprocess stderr: {result.stderr}\")\n\n        # Parse the JSON response from stdout\n        user_data = json.loads(result.stdout)\n        return user_data\n\n    except subprocess.TimeoutExpired:\n        logger.error(f\"Subprocess timed out when fetching user profile for wallet ID: {wallet_id}\")\n        return None\n\n    except subprocess.CalledProcessError as e:\n        logger.error(f\"Subprocess error when fetching user profile for wallet ID {wallet_id}: {e.stderr}\")\n        return None\n\n    except json.JSONDecodeError as e:\n        logger.error(f\"Failed to parse JSON from subprocess for wallet ID {wallet_id}: {e}\")\n        return None\n\n\ndef replace_hex_values(df, columns):\n    \"\"\"\n    Replace specific hex values in the given columns with their corresponding names.\n\n    Args:\n    - df (pd.DataFrame): The DataFrame containing the transaction data.\n    - columns (list): List of column names where the hex values should be replaced.\n\n    Returns:\n    - pd.DataFrame: The DataFrame with the replaced values.\n    \"\"\"\n    # Mapping of hex values to their corresponding names\n    replacement_dict = {\n        '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174': 'CTF_EXCHANGE',\n        '0x4d97dcd97ec945f40cf65f87097ace5ea0476045': 'NEG_RISK_CTF_EXCHANGE',\n        '0xC5d563A36AE78145C45a50134d48A1215220f80a': 'NEG_RISK_CTF_EXCHANGE_SPENDER',\n        '0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296': 'NEG_RISK_ADAPTER',\n        '0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E': 'CTF_EXCHANGE_SPENDER',\n    }\n\n    for column in columns:\n        if column in df.columns:\n            df[column] = df[column].replace(replacement_dict)\n    return df\n\n\n\ndef process_wallet_data(wallet_addresses, api_key, plot=True, latest_price_mode=False):\n    \"\"\"\n    Processes user wallet data to generate user transaction information. If `latest_price_mode` is set to True,\n    the function will only retrieve the latest prices for tokens without generating user reports.\n\n    Args:\n    - wallet_addresses (list): List of wallet addresses to process.\n    - api_key (str): The Polygonscan API key.\n    - plot (bool): Whether to generate plots for the user data.\n    - latest_price_mode (bool): If True, only retrieve the latest transaction prices for the given wallets.\n    \"\"\"\n    # Load environment variables\n    load_dotenv(\"keys.env\")\n\n    # Ensure the output directory exists\n    output_dir = '.\/data\/user_trades\/'\n    os.makedirs(output_dir, exist_ok=True)\n\n    # Load the market lookup JSON data\n    market_lookup_path = '.\/data\/market_lookup.json'\n    market_lookup = load_market_lookup(market_lookup_path)\n\n    for wallet_address in wallet_addresses:\n        # Fetch user info (username) based on wallet ID\n        user_info = call_get_user_profile(wallet_address)  # Pass wallet_address to the function\n        username = user_info['username'] if user_info else \"Unknown\"\n\n        # Sanitize the username to create a valid filename\n        sanitized_username = sanitize_filename(username)\n\n        logger.info(f\"Processing wallet for user: {username}\")\n\n        # API URLs for ERC-20 and ERC-1155 transactions\n        erc20_url = f\"https:\/\/api.polygonscan.com\/api?module=account&amp;action=tokentx&amp;address={wallet_address}&amp;startblock=0&amp;endblock=99999999&amp;sort=asc&amp;apikey={api_key}\"\n        erc1155_url = f\"https:\/\/api.polygonscan.com\/api?module=account&amp;action=token1155tx&amp;address={wallet_address}&amp;startblock=0&amp;endblock=99999999&amp;sort=asc&amp;apikey={api_key}\"\n\n        # Fetch ERC-20 and ERC-1155 transactions\n        erc20_response = fetch_data(erc20_url)\n        erc1155_response = fetch_data(erc1155_url)\n\n        if erc20_response['status'] == '1' and erc1155_response['status'] == '1':\n            erc20_data = erc20_response['result']\n            erc1155_data = erc1155_response['result']\n\n            # Convert data to DataFrames\n            erc20_df = pd.DataFrame(erc20_data)\n            erc1155_df = pd.DataFrame(erc1155_data)\n\n            # Enrich ERC-1155 data with market_slug and outcome\n            erc1155_df = enrich_erc1155_data(erc1155_df, market_lookup)\n\n            # Add timestamps\n            erc1155_df, erc20_df = add_timestamps(erc1155_df, erc20_df)\n\n            # Merge and add financial columns\n            merged_df = add_financial_columns(erc1155_df, erc20_df, wallet_address, market_lookup)\n\n            if 'pl' in merged_df.columns:\n                logger.info(f\"'pl' column exists with {merged_df['pl'].count()} non-null values.\")\n            else:\n                logger.error(\"'pl' column does not exist in merged_df after update_latest_prices.\")\n\n\n            # Replace hex values with the corresponding names\n            columns_to_replace = ['contractAddress_erc1155', 'from_erc1155', 'to_erc1155']\n            merged_df = replace_hex_values(merged_df, columns_to_replace)\n\n            # Save the merged and enriched data\n            output_file = f'{output_dir}{sanitized_username}_enriched_transactions.csv'\n            merged_df.to_csv(output_file, index=False)\n            logger.info(f\"Enriched data saved to {output_file}\")\n\n            # Check if 'pl' column exists and has non-null values\n            if 'pl' in merged_df.columns and merged_df['pl'].notnull().any():\n                logger.info(f\"'pl' column exists with {merged_df['pl'].count()} non-null values.\")\n                if not latest_price_mode:\n                    # Generate and save the Profit\/Loss by trade plot\n                    plot_profit_loss_by_trade(merged_df, user_info)\n            else:\n                logger.warning(f\"'pl' column is missing or empty for user {username}. Skipping PL plot.\")\n\n        logger.info(\"Data processing completed.\")\n\ndef call_scrape_wallet_ids(top_volume=True, top_profit=True):\n    \"\"\"\n    Scrape leaderboard and return wallet IDs based on top volume or top profit.\n\n    Args:\n    - top_volume (bool): Whether to fetch top volume users.\n    - top_profit (bool): Whether to fetch top profit users.\n\n    Returns:\n    - List of wallet IDs.\n    \"\"\"\n    wallet_ids = []\n\n    # Construct the command to call get_leaderboard_wallet_ids.py with appropriate flags\n    command = ['python3', 'get_leaderboard_wallet_ids.py']\n\n    if top_volume:\n        command.append('--top-volume')\n    if top_profit:\n        command.append('--top-profit')\n\n    try:\n        # Run the script with the constructed command\n        result = subprocess.run(\n            command,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE,\n            check=True,\n            text=True\n        )\n        logger.debug(f\"Leaderboard wallet script stdout: {result.stdout}\")\n\n        # Parse the output as JSON and extend the wallet_ids list\n        wallet_ids.extend(json.loads(result.stdout))\n    except subprocess.CalledProcessError as e:\n        logger.error(f\"Error running get_leaderboard_wallet_ids.py: {e.stderr}\")\n    except json.JSONDecodeError as json_err:\n        logger.error(f\"Failed to parse JSON from get_leaderboard_wallet_ids.py: {json_err}\")\n\n    # Log the combined wallet IDs\n    logger.info(f\"Fetched {len(wallet_ids)} wallet IDs based on volume\/profit flags.\")\n\n    return wallet_ids\n\ndef process_and_plot_user_data(wallet_addresses, api_key, plot=True, latest_price_mode=False):\n    \"\"\"\n    Process wallet data for each user, calculate financial data, and optionally generate plots.\n\n    Args:\n        wallet_addresses (list): List of wallet addresses.\n        api_key (str): Polygonscan API key.\n        plot (bool): Whether to generate plots for the user data.\n        latest_price_mode (bool): If True, only retrieve the latest prices, no plotting.\n    \"\"\"\n    # Load market lookup data\n    market_lookup_path = '.\/data\/market_lookup.json'\n    market_lookup = load_market_lookup(market_lookup_path)\n\n    # Define the columns to keep\n    columns_to_keep = [\n        'timeStamp_erc1155', 'tokenID', 'tokenValue', 'market_slug', 'outcome',\n        'value', 'tokenDecimal', 'transaction_type', 'price_paid_per_token',\n        'total_purchase_value', 'shares', 'lost', 'won', 'pl', 'live_price'\n    ]\n\n    for wallet_address in wallet_addresses:\n        # Fetch user info (username) based on wallet ID\n        user_info = call_get_user_profile(wallet_address)\n        username = user_info.get('username', \"Unknown\")\n\n        logger.info(f\"Processing wallet for user: {username} ({wallet_address})\")\n\n        # Fetch ERC-20 and ERC-1155 transactions\n        erc20_df, erc1155_df = fetch_user_transactions(wallet_address, api_key)\n\n        if erc20_df is not None and erc1155_df is not None:\n            # Enrich ERC-1155 data with market_slug and outcome\n            erc1155_df = enrich_erc1155_data(erc1155_df, market_lookup)\n\n            # Add timestamps\n            erc1155_df, erc20_df = add_timestamps(erc1155_df, erc20_df)\n\n            # Merge and add financial columns\n            merged_df = add_financial_columns(erc1155_df, erc20_df, wallet_address, market_lookup)\n\n            # Check for Profit\/Loss data\n            if 'pl' in merged_df.columns and merged_df['pl'].notnull().any():\n                if not latest_price_mode and plot:\n                    # Generate all plots for the user\n                    generate_all_user_plots(merged_df, user_info)\n\n                # Save the merged and enriched data\n                sanitized_username = sanitize_filename(username)\n                output_dir = '.\/data\/user_trades\/'\n                os.makedirs(output_dir, exist_ok=True)\n\n                # Save to Parquet (default format)\n                output_file_parquet = f'{output_dir}{sanitized_username}_enriched_transactions.parquet'\n                merged_df.to_parquet(output_file_parquet, index=False)\n                logger.info(f\"Enriched data saved to {output_file_parquet}\")\n\n                # Save to CSV\n                # Keep only the specified columns and sort by timeStamp_erc1155\n                merged_df = merged_df[columns_to_keep].sort_values(by='timeStamp_erc1155', ascending=True)\n                output_file_csv = f'{output_dir}{sanitized_username}_enriched_transactions.csv'\n                merged_df.to_csv(output_file_csv, index=False)\n                logger.info(f\"Enriched data saved to {output_file_csv}\")\n\n            else:\n                logger.warning(f\"Profit\/Loss column missing or empty for user: {username}\")\n        else:\n            logger.error(f\"Failed to fetch transaction data for wallet: {wallet_address}\")\n\n\ndef generate_all_user_plots(merged_df, user_info):\n    \"\"\"\n    Generate all necessary plots for a user.\n\n    Args:\n        merged_df (DataFrame): The merged DataFrame with user transactions and financial info.\n        user_info (dict): Dictionary containing user information.\n    \"\"\"\n    # Generate Profit\/Loss by Trade plot\n    plot_profit_loss_by_trade(merged_df, user_info)\n\n    # Generate Shares Over Time plot\n    plot_shares_over_time(merged_df, user_info)\n\n    # Generate Total Purchase Value by Market plot\n    plot_user_trades(merged_df, user_info)\n\n    # Generate Pie Chart for Holdings\n    create_and_save_pie_chart(merged_df, user_info)\n\n    # Generate Treemap for Holdings\n    create_and_save_treemap(merged_df, user_info)\n\n    logger.info(f\"All plots generated for user: {user_info['username']}\")\n\n\ndef fetch_user_transactions(wallet_address, api_key):\n    \"\"\"\n    Fetch ERC-20 and ERC-1155 transaction data for a user with pagination.\n\n    Args:\n        wallet_address (str): Wallet address to fetch transactions for.\n        api_key (str): Polygonscan API key.\n\n    Returns:\n        (DataFrame, DataFrame): DataFrames for ERC-20 and ERC-1155 transactions.\n    \"\"\"\n\n    def fetch_paginated_data(url):\n        \"\"\"\n        Fetch paginated data from the provided URL.\n\n        Args:\n            url (str): Base URL for the API request.\n\n        Returns:\n            DataFrame: DataFrame with all paginated results.\n        \"\"\"\n        page = 1\n        offset = 1000  # Set the offset\/page size based on the API's limits (e.g., 1000)\n        all_data = []\n\n        while True:\n            paginated_url = f\"{url}&amp;page={page}&amp;offset={offset}\"\n            data = fetch_data(paginated_url)\n            if data and data['status'] == '1' and len(data['result']) > 0:\n                all_data.extend(data['result'])\n                page += 1\n            else:\n                break  # Stop if no more data is returned\n\n        return pd.DataFrame(all_data)\n\n    # Fetch ERC-20 transactions with pagination\n    erc20_url = (f\"https:\/\/api.polygonscan.com\/api\"\n                 f\"?module=account\"\n                 f\"&amp;action=tokentx\"\n                 f\"&amp;address={wallet_address}\"\n                 f\"&amp;startblock=0\"\n                 f\"&amp;endblock=99999999\"\n                 f\"&amp;sort=desc\"\n                 f\"&amp;apikey={api_key}\")\n\n    erc20_df = fetch_paginated_data(erc20_url)\n\n    # Fetch ERC-1155 transactions with pagination\n    erc1155_url = (f\"https:\/\/api.polygonscan.com\/api\"\n                   f\"?module=account\"\n                   f\"&amp;action=token1155tx\"\n                   f\"&amp;address={wallet_address}\"\n                   f\"&amp;startblock=0\"\n                   f\"&amp;endblock=99999999\"\n                   f\"&amp;sort=desc\"\n                   f\"&amp;apikey={api_key}\")\n\n    erc1155_df = fetch_paginated_data(erc1155_url)\n\n    if not erc20_df.empty and not erc1155_df.empty:\n        return erc20_df, erc1155_df\n    else:\n        return None, None\n\n\ndef fetch_wallet_addresses(skip_leaderboard, top_volume, top_profit):\n    \"\"\"\n    Fetch wallet addresses based on leaderboard data or manual input.\n\n    Args:\n        skip_leaderboard (bool): Whether to skip leaderboard fetching.\n        top_volume (bool): Fetch top volume users.\n        top_profit (bool): Fetch top profit users.\n\n    Returns:\n        list: A list of wallet addresses to process.\n    \"\"\"\n    # Manually specified wallet addresses\n    manual_wallet_ids = [\n        '0x76527252D7FEd00dC4D08d794aFa1cCC36069C2a',\n        # Add more wallet IDs as needed\n    ]\n\n    if not skip_leaderboard:\n        leaderboard_wallet_ids = call_scrape_wallet_ids(top_volume=top_volume, top_profit=top_profit)\n        wallet_addresses = list(set(manual_wallet_ids + leaderboard_wallet_ids))  # Remove duplicates\n    else:\n        wallet_addresses = manual_wallet_ids\n\n    return wallet_addresses\n\ndef main(wallet_addresses=None, skip_leaderboard=False, top_volume=False, top_profit=False, plot=True, latest_price_mode=False):\n\n    \"\"\"\n    Main function to process wallet data and generate plots.\n\n    Args:\n        wallet_addresses (list): A list of wallet addresses to process (if provided).\n        skip_leaderboard (bool): Whether to skip fetching leaderboard data.\n        top_volume (bool): Whether to fetch top volume users.\n        top_profit (bool): Whether to fetch top profit users.\n        plot (bool): Whether to generate plots for the user data.\n        latest_price_mode (bool): If True, only retrieve the latest prices, no plotting.\n    \"\"\"\n    # Load environment variables\n    load_dotenv(\"keys.env\")\n    api_key = os.getenv('POLYGONSCAN_API_KEY')\n\n    if not wallet_addresses:\n        # Fetch wallet addresses if not provided\n        wallet_addresses = fetch_wallet_addresses(skip_leaderboard, top_volume, top_profit)\n\n    # Process wallet data and optionally generate plots\n    process_and_plot_user_data(wallet_addresses, api_key, plot=plot, latest_price_mode=latest_price_mode)\n\n\nif __name__ == \"__main__\":\n    # Use argparse to accept command-line arguments\n    parser = argparse.ArgumentParser(description='Process wallet data for specific wallet addresses.')\n\n    parser.add_argument(\n        '--wallets',\n        nargs='+',  # This will accept multiple wallet IDs\n        help='List of wallet addresses to process.'\n    )\n    parser.add_argument('--skip-leaderboard', action='store_true', help='Skip leaderboard fetching.')\n    parser.add_argument('--top-volume', action='store_true', help='Fetch top volume users.')\n    parser.add_argument('--top-profit', action='store_true', help='Fetch top profit users.')\n    parser.add_argument('--no-plot', action='store_true', help='Disable plot generation.')\n    parser.add_argument('--latest-price-mode', action='store_true',\n                        help='Only retrieve the latest prices, no plotting.')\n\n    args = parser.parse_args()\n\n    # Call the main function with the parsed arguments\n    main(\n        wallet_addresses=args.wallets,\n        skip_leaderboard=args.skip_leaderboard,\n        top_volume=args.top_volume,\n        top_profit=args.top_profit,\n        plot=not args.no_plot,\n        latest_price_mode=args.latest_price_mode\n    )<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Polymarket.com, a prediction market platform, operates on the Ethereum blockchain through the Polygon network, making it possible to analyze user transactions directly from the blockchain. By accessing a&#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-11916","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>Analyzing Any Polymarket User&#039;s Trades Using Polygon - 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=\"Analyzing Any Polymarket User&#039;s Trades Using Polygon - Jeremy Whittaker\" \/>\n<meta property=\"og:description\" content=\"Polymarket.com, a prediction market platform, operates on the Ethereum blockchain through the Polygon network, making it possible to analyze user transactions directly from the blockchain. By accessing a...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/\" \/>\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-09-24T20:42:44+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-09-24T20:42:51+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42-1024x771.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=\"5 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\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/\"},\"author\":{\"name\":\"JeremyWhittaker\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c\"},\"headline\":\"Analyzing Any Polymarket User&#8217;s Trades Using Polygon\",\"datePublished\":\"2024-09-24T20:42:44+00:00\",\"dateModified\":\"2024-09-24T20:42:51+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/\"},\"wordCount\":747,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c\"},\"image\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42-1024x771.png\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/\",\"url\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/\",\"name\":\"Analyzing Any Polymarket User's Trades Using Polygon - Jeremy Whittaker\",\"isPartOf\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42-1024x771.png\",\"datePublished\":\"2024-09-24T20:42:44+00:00\",\"dateModified\":\"2024-09-24T20:42:51+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#primaryimage\",\"url\":\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42.png\",\"contentUrl\":\"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42.png\",\"width\":1702,\"height\":1281},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/new.jeremywhittaker.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Analyzing Any Polymarket User&#8217;s Trades Using Polygon\"}]},{\"@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":"Analyzing Any Polymarket User's Trades Using Polygon - 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":"Analyzing Any Polymarket User's Trades Using Polygon - Jeremy Whittaker","og_description":"Polymarket.com, a prediction market platform, operates on the Ethereum blockchain through the Polygon network, making it possible to analyze user transactions directly from the blockchain. By accessing a...","og_url":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/","og_site_name":"Jeremy Whittaker","article_publisher":"https:\/\/www.facebook.com\/WhittakerJeremy","article_author":"https:\/\/www.facebook.com\/WhittakerJeremy","article_published_time":"2024-09-24T20:42:44+00:00","article_modified_time":"2024-09-24T20:42:51+00:00","og_image":[{"url":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42-1024x771.png","type":"","width":"","height":""}],"author":"JeremyWhittaker","twitter_card":"summary_large_image","twitter_misc":{"Written by":"JeremyWhittaker","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#article","isPartOf":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/"},"author":{"name":"JeremyWhittaker","@id":"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c"},"headline":"Analyzing Any Polymarket User&#8217;s Trades Using Polygon","datePublished":"2024-09-24T20:42:44+00:00","dateModified":"2024-09-24T20:42:51+00:00","mainEntityOfPage":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/"},"wordCount":747,"commentCount":0,"publisher":{"@id":"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c"},"image":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#primaryimage"},"thumbnailUrl":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42-1024x771.png","inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/","url":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/","name":"Analyzing Any Polymarket User's Trades Using Polygon - Jeremy Whittaker","isPartOf":{"@id":"https:\/\/new.jeremywhittaker.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#primaryimage"},"image":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#primaryimage"},"thumbnailUrl":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42-1024x771.png","datePublished":"2024-09-24T20:42:44+00:00","dateModified":"2024-09-24T20:42:51+00:00","breadcrumb":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#primaryimage","url":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42.png","contentUrl":"https:\/\/new.jeremywhittaker.com\/wp-content\/uploads\/2024\/09\/newplot-42.png","width":1702,"height":1281},{"@type":"BreadcrumbList","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2024\/09\/24\/analyzing-any-polymarket-users-trades-using-polygon\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/new.jeremywhittaker.com\/"},{"@type":"ListItem","position":2,"name":"Analyzing Any Polymarket User&#8217;s Trades Using Polygon"}]},{"@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\/11916","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=11916"}],"version-history":[{"count":5,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/posts\/11916\/revisions"}],"predecessor-version":[{"id":11929,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/posts\/11916\/revisions\/11929"}],"wp:attachment":[{"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/media?parent=11916"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/categories?post=11916"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/tags?post=11916"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}