<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged mongo</title>
<link>https://coderlegion.com/tag/mongo</link>
<description>Powered by Question2Answer</description>
<item>
<title>Market Data Integrator: Consuming Real-Time Data with Python, MongoDB, and Kafka</title>
<link>https://coderlegion.com/16542/market-data-integrator-consuming-real-time-data-with-python-mongodb-and-kafka</link>
<description>&lt;p&gt;Hey folks!&lt;/p&gt;
&lt;p&gt;Continuing the &lt;strong&gt;My Broker B3&lt;/strong&gt; series, today we'll build the first microservice in the ecosystem: the &lt;strong&gt;trading-broker-market-data&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This service has a simple and well-defined responsibility: &lt;strong&gt;fetch real quotes for Brazilian assets from the Brapi API and distribute them to the ecosystem&lt;/strong&gt; — persisting the history in MongoDB and publishing updated prices to Kafka for other services to consume.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-this-service-s-role-in-the-ecosystem&quot;&gt;️ This Service's Role in the Ecosystem&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;[brapi.dev] ◀── HTTP ── [broker-market-data-api]
                                  │
                    ┌─────────────┴──────────────┐
                    ▼                             ▼
             MongoDB                           Kafka
        (price_history)          (trading-assets-market-data-v1)
                                               │
                                    [broker-asset-api] (consumer)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It's the entry point for real data into the system. Without it, the ecosystem operates on static prices.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;mvp-focus&quot;&gt;MVP Focus&lt;/h2&gt;
&lt;p&gt;The goal here is clear: &lt;strong&gt;have real quotes available in Kafka&lt;/strong&gt; for other services to react to. In this phase, I prioritized:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scheduled ingestion of 50 assets (Blue Chips, REITs and ETFs)&lt;/li&gt;
&lt;li&gt;Historical persistence in MongoDB for audit&lt;/li&gt;
&lt;li&gt;Kafka event publishing for downstream consumers&lt;/li&gt;
&lt;li&gt;Rate limit protection for the Brapi API (free plan)&lt;/li&gt;
&lt;li&gt;Immediate execution on service startup&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-tech-stack&quot;&gt;️ Tech Stack&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Technology &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Usage&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Python 3.12&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Service runtime&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;requests&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;HTTP client for Brapi API&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;pymongo&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;MongoDB persistence&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;confluent-kafka&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Kafka producer&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;schedule&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Periodic scheduling&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;python-dotenv&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Environment variables&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-the-implementation&quot;&gt;️ The Implementation&lt;/h2&gt;
&lt;h3 id=&quot;the-ingestion-loop&quot;&gt;The Ingestion Loop&lt;/h3&gt;
&lt;p&gt;The heart of the service is the &lt;code&gt;run_ingestion()&lt;/code&gt; function, which iterates over the watchlist, fetches each asset's price and distributes the data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def run_ingestion():
    &quot;&quot;&quot;Fetches market data from Brapi, persists to MongoDB and publishes to Kafka.&quot;&quot;&quot;
    logger.info(&quot;Market data ingestion job started&quot;)

    token = os.getenv(&quot;BRAPI_TOKEN&quot;)
    if not token:
        logger.error(&quot;BRAPI_TOKEN not set. Aborting.&quot;)
        return

    # Producer created per run to avoid crash on import if Kafka is unavailable
    producer = Producer({
        'bootstrap.servers': os.getenv(&quot;KAFKA_BOOTSTRAP_SERVERS&quot;, &quot;localhost:9092&quot;),
        'client.id': 'broker-market-data-api'
    })

    mongo = MongoRepository()
    success_count = 0
    error_count = 0

    for ticker in WATCHLIST:
        try:
            url = f&quot;https://brapi.dev/api/quote/{ticker}?token={token}&quot;
            response = requests.get(url, timeout=10)
            response.raise_for_status()

            result = response.json()[&quot;results&quot;][0]
            payload = {
                &quot;ticker&quot;: result.get(&quot;symbol&quot;),
                &quot;name&quot;: result.get(&quot;longName&quot;),
                &quot;short_name&quot;: result.get(&quot;shortName&quot;),
                &quot;price&quot;: result.get(&quot;regularMarketPrice&quot;),
                &quot;volume&quot;: result.get(&quot;regularMarketVolume&quot;),
                &quot;updated_at&quot;: result.get(&quot;regularMarketTime&quot;)
            }

            # 1. Persist historical record in MongoDB
            mongo.save(payload.copy())

            # 2. Publish event to Kafka
            producer.produce(
                topic=TOPIC_NAME,
                key=ticker,
                value=json.dumps(payload).encode('utf-8'),
                callback=delivery_report
            )
            producer.poll(0)

            success_count += 1
            time.sleep(0.5)  # Rate limiting — respect Brapi free plan

        except Exception as e:
            error_count += 1
            logger.error(f&quot;Error processing ticker {ticker}: {e}&quot;)

    producer.flush()
    logger.info(f&quot;Ingestion completed: {success_count} succeeded, {error_count} failed&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important design decisions:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why create the Producer inside the function?&lt;/strong&gt; If it were at module level, an unavailable Kafka at &lt;code&gt;import&lt;/code&gt; time would crash the script before executing any logic. Creating it inside the function means a Kafka failure doesn't prevent execution — only that run fails.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;try/catch&lt;/code&gt; per ticker?&lt;/strong&gt; If one asset fails (timeout, invalid ticker), the loop continues for the rest. The &lt;code&gt;error_count&lt;/code&gt; at the end shows how many failed without stopping the work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The &lt;code&gt;time.sleep(0.5)&lt;/code&gt;&lt;/strong&gt; — respects the rate limit of Brapi's free plan. For 50 assets, the full run takes ~25 seconds.&lt;/p&gt;
&lt;h3 id=&quot;the-scheduling&quot;&gt;The Scheduling&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if __name__ == &quot;__main__&quot;:
    logger.info(f&quot;Starting — sync every {SYNC_INTERVAL_MINUTES} minutes&quot;)

    # Run immediately on startup, then on schedule
    run_ingestion()

    schedule.every(SYNC_INTERVAL_MINUTES).minutes.do(run_ingestion)

    while True:
        schedule.run_pending()
        time.sleep(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why run immediately on startup?&lt;/strong&gt; If the service restarts, we don't want to wait 30 minutes for data in Kafka. The first execution is immediate, subsequent ones follow the schedule.&lt;/p&gt;
&lt;h3 id=&quot;the-kafka-payload&quot;&gt;The Kafka Payload&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;ticker&quot;: &quot;PETR4&quot;,
  &quot;name&quot;: &quot;Petróleo Brasileiro S.A. - Petrobras&quot;,
  &quot;short_name&quot;: &quot;PETROBRAS PN&quot;,
  &quot;price&quot;: 35.50,
  &quot;volume&quot;: 15234567,
  &quot;updated_at&quot;: 1714234567
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Topic:&lt;/strong&gt; &lt;code&gt;trading-assets-market-data-v1&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Key:&lt;/strong&gt; asset symbol (e.g. &lt;code&gt;PETR4&lt;/code&gt;) — ensures events for the same asset go to the same partition&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-watchlist&quot;&gt;The Watchlist&lt;/h2&gt;
&lt;p&gt;50 assets covering the main B3 segments:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;WATCHLIST = [
    # Blue Chips
    &quot;PETR4&quot;, &quot;PETR3&quot;, &quot;VALE3&quot;, &quot;ITUB4&quot;, &quot;BBDC4&quot;, &quot;BBAS3&quot;, ...
    # REITs / FIIs
    &quot;MXRF11&quot;, &quot;HGLG11&quot;, &quot;KNIP11&quot;, &quot;XPLG11&quot;, ...
    # ETFs
    &quot;BOVA11&quot;, &quot;IVVB11&quot;
]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;security-token-out-of-code&quot;&gt;Security: Token Out of Code&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# .env — never committed
BRAPI_TOKEN=your_token_here
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# application — no fallback, required variable
token = os.getenv(&quot;BRAPI_TOKEN&quot;)
if not token:
    logger.error(&quot;BRAPI_TOKEN not set. Aborting.&quot;)
    return
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Never expose credentials as default values in public repositories.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-validating-the-execution&quot;&gt;✅ Validating the Execution&lt;/h2&gt;
&lt;p&gt;With the service running locally:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ First run executes immediately on startup&lt;/li&gt;
&lt;li&gt;✅ MongoDB receives documents with &lt;code&gt;created_at&lt;/code&gt; automatically&lt;/li&gt;
&lt;li&gt;✅ Kafka receives messages with key = ticker&lt;/li&gt;
&lt;li&gt;✅ Scheduler waits 30 minutes for the next round&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-s-next-&quot;&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;With &lt;code&gt;broker-market-data-api&lt;/code&gt; publishing quotes to Kafka, the next step is &lt;code&gt;b3-market-sync-api&lt;/code&gt; — the Java service that consumes these events and synchronizes prices to Redis, feeding the Matching Engine with real-time data.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;about-the-series&quot;&gt;About the Series&lt;/h2&gt;
&lt;p&gt;⬅️ Previous Post: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/infrastructure-as-code-deploying-a-financial-ecosystem-with-docker-compose-2p3d&quot;&gt;Infrastructure as Code&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;➡️ Next Post: Syncing the Real Market: Consuming Brapi and Feeding Redis with Spring Boot&lt;/p&gt;
&lt;p&gt; Series Index: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/series-roadmap-building-a-stock-brokerage-simulator-with-microservices-kgh&quot;&gt;Series Roadmap&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/roberto-de-vargas/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rvneto/trading-broker-market-data&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16542/market-data-integrator-consuming-real-time-data-with-python-mongodb-and-kafka</guid>
<pubDate>Sun, 03 May 2026 20:33:47 +0000</pubDate>
</item>
</channel>
</rss>