DEV Community

Cover image for Building a Real-Time Coinbase BTC Price Feed for a Polymarket Momentum Bot
Benjamin-Cup
Benjamin-Cup

Posted on

Building a Real-Time Coinbase BTC Price Feed for a Polymarket Momentum Bot

Part 2 — Turning real-time BTC price updates into a short-term momentum signal

In the previous part of this series, I explained how the introduction of 60-second TWAP settlement changed the architecture of my Polymarket crypto trading bots.

The key idea is that an external exchange such as Coinbase should not be treated as the settlement source.

You can view real-time footage of the bot in operation via this video link.

Instead, it can provide an early market signal.

When BTC moves quickly on an external exchange, that information can potentially appear before the corresponding movement is fully reflected in the Chainlink/TWAP-related state used by Polymarket.

So the next engineering problem is:

How do we turn a real-time BTC price stream into a useful short-term momentum signal?

That's what we'll build in this article.


The Architecture

The overall system looks like this:

Coinbase
    │
    ▼
WebSocket Price Feed
    │
    ▼
Market Data Processor
    │
    ▼
Short-Term Price History
    │
    ▼
Momentum Engine
    │
    ▼
Momentum Signal
    │
    ▼
Polymarket Decision Engine
Enter fullscreen mode Exit fullscreen mode

This article focuses on everything up to the momentum signal.

The Polymarket decision layer comes later.


Why Real-Time Data?

For a short-duration trading strategy, the latest BTC price isn't enough.

Consider:

BTC = $100,000
Enter fullscreen mode Exit fullscreen mode

Then:

$100,000
    ↓
$100,040
    ↓
$100,090
    ↓
$100,150
Enter fullscreen mode Exit fullscreen mode

If we only request the latest price periodically, we may see:

BTC = $100,150
Enter fullscreen mode Exit fullscreen mode

But we lose information about how the market got there.

A real-time stream gives us the sequence of movements.

That allows the strategy to ask:

  • Is BTC moving up or down?
  • How quickly is it moving?
  • Is momentum increasing?
  • Is momentum fading?
  • Did the market reverse?
  • How large is the movement relative to recent prices?

This is much more useful than looking at a single price.


Coinbase as an Early Market Signal

There is an important distinction in this strategy.

Coinbase is not being used as the Polymarket settlement price.

The simplified relationship is:

Coinbase
    │
    ▼
Fast BTC Market Information
    │
    ▼
Momentum Analysis
    │
    ▼
Expected Movement
Enter fullscreen mode Exit fullscreen mode

While the settlement relationship is conceptually:

BTC Market
    │
    ▼
Chainlink
    │
    ▼
60-Second TWAP
    │
    ▼
Polymarket Resolution
Enter fullscreen mode Exit fullscreen mode

The trading system therefore has two different types of information:

Fast information
    +
Settlement context
Enter fullscreen mode Exit fullscreen mode

The purpose of today's component is to extract the first one.


1. Connect to the Coinbase WebSocket

For a real-time system, a persistent WebSocket connection is a natural starting point.

Instead of repeatedly polling:

Bot → Request price
Bot → Request price
Bot → Request price
Bot → Request price
Enter fullscreen mode Exit fullscreen mode

we want:

Coinbase
   │
   ├── Update
   ├── Update
   ├── Update
   └── Update
        ↓
       Bot
Enter fullscreen mode Exit fullscreen mode

A simplified Python implementation:

import asyncio
import json
import websockets


async def price_stream():
    url = "wss://ws-feed.exchange.coinbase.com"

    async with websockets.connect(url) as ws:
        await ws.send(json.dumps({
            "type": "subscribe",
            "product_ids": ["BTC-USD"],
            "channels": ["ticker"]
        }))

        while True:
            message = await ws.recv()
            data = json.loads(message)

            print(data)


asyncio.run(price_stream())
Enter fullscreen mode Exit fullscreen mode

This gives the application a continuous stream of BTC market updates.

For a production system, we would also need to consider reconnect handling, stale data detection, timestamps, validation, and monitoring.

But first, let's keep the model simple.


2. Normalize the Market Data

Raw WebSocket messages contain more information than the momentum engine needs.

We can convert each update into an internal representation:

price_update = {
    "timestamp": timestamp,
    "price": price
}
Enter fullscreen mode Exit fullscreen mode

For example:

12:30:01.120 → $100,000.25
12:30:01.240 → $100,005.50
12:30:01.370 → $100,012.00
12:30:01.510 → $100,018.25
Enter fullscreen mode Exit fullscreen mode

Now the rest of the application doesn't need to understand the exchange's raw message format.

It can work with a normalized market-data structure.

This separation becomes important later when adding additional data sources.


3. Maintain a Short-Term Price Window

A single price cannot tell us whether there is momentum.

We need recent history.

For example:

Time       Price
--------------------
T0         100000
T1         100005
T2         100012
T3         100018
T4         100025
Enter fullscreen mode Exit fullscreen mode

From this small window, we can calculate several features:

Return
Direction
Rate of change
Momentum strength
Acceleration
Reversal
Enter fullscreen mode Exit fullscreen mode

The price window becomes the input to our momentum engine.


4. Calculate Short-Term Returns

The simplest momentum feature is price return.

Suppose:

Current BTC price = $100,100
Price 1 second ago = $100,050
Enter fullscreen mode Exit fullscreen mode

The absolute change is:

$100,100 - $100,050 = $50
Enter fullscreen mode Exit fullscreen mode

The percentage return can be calculated as:

return_pct = (
    (current_price - previous_price)
    / previous_price
) * 100
Enter fullscreen mode Exit fullscreen mode

Now the system knows more than:

BTC = $100,100
Enter fullscreen mode Exit fullscreen mode

It knows:

BTC changed approximately +0.05%
Enter fullscreen mode Exit fullscreen mode

That's the beginning of a useful signal.


5. Use Multiple Time Windows

One return is not enough.

A movement over one second can tell us something different from a movement over thirty seconds.

For example, we can track:

1-second return
5-second return
10-second return
30-second return
Enter fullscreen mode Exit fullscreen mode

Imagine the engine observes:

1s   → +0.012%
5s   → +0.038%
10s  → +0.071%
30s  → +0.052%
Enter fullscreen mode Exit fullscreen mode

This tells us much more about the current market than a single price.

We can begin to see:

  • Short-term direction
  • Movement persistence
  • Recent acceleration
  • Whether the latest movement is unusually strong

This is the type of information the decision engine will eventually consume.


6. Determine Direction

The first simple classification is:

UP
DOWN
NEUTRAL
Enter fullscreen mode Exit fullscreen mode

For example:

if return_10s > threshold:
    direction = "UP"

elif return_10s < -threshold:
    direction = "DOWN"

else:
    direction = "NEUTRAL"
Enter fullscreen mode Exit fullscreen mode

This gives us a basic market state.

For example:

+0.08% → UP
-0.07% → DOWN
+0.01% → NEUTRAL
Enter fullscreen mode Exit fullscreen mode

However, this is not a trading decision.

It only tells us what the BTC market is doing.


7. Measure Momentum Strength

Direction alone isn't enough.

Consider two price sequences.

Weak movement

100000
100001
100002
100003
100004
Enter fullscreen mode Exit fullscreen mode

Strong movement

100000
100020
100045
100080
100120
Enter fullscreen mode Exit fullscreen mode

Both are moving upward.

But the second sequence contains significantly stronger movement.

So instead of:

Direction = UP
Enter fullscreen mode Exit fullscreen mode

we want something closer to:

Direction = UP
Strength = HIGH
Enter fullscreen mode Exit fullscreen mode

A normalized signal could eventually look like:

Momentum = +0.82
Enter fullscreen mode Exit fullscreen mode

or:

Momentum = +0.25
Enter fullscreen mode Exit fullscreen mode

The exact formula should be determined through testing rather than arbitrarily assuming that one value will work in every market regime.


8. Detect Acceleration

Momentum is also about how the movement changes over time.

Consider:

+0.01%
+0.02%
+0.04%
+0.07%
Enter fullscreen mode Exit fullscreen mode

The upward movement is accelerating.

Compare it with:

+0.07%
+0.06%
+0.05%
+0.04%
Enter fullscreen mode Exit fullscreen mode

The price is still moving upward, but the momentum is weakening.

These two situations should not necessarily produce the same signal.

Therefore, the momentum engine can track changes between consecutive returns.

Conceptually:

Price
  ↓
Return
  ↓
Change in Return
  ↓
Acceleration
Enter fullscreen mode Exit fullscreen mode

This gives us another dimension of market state.


9. Detect Reversals

Short-term momentum can disappear quickly.

For example:

100000
100030
100070
100110
100130
100115
100080
100040
Enter fullscreen mode Exit fullscreen mode

The first part shows strong upward movement.

Then the market starts moving back down.

If the strategy continues using the old signal, it can become stale.

So the momentum engine should continuously update its state.

Instead of storing only:

momentum = "UP"
Enter fullscreen mode Exit fullscreen mode

we can maintain:

momentum_state = {
    "direction": direction,
    "strength": strength,
    "recent_return": recent_return,
    "previous_return": previous_return,
    "price": current_price,
    "timestamp": timestamp
}
Enter fullscreen mode Exit fullscreen mode

Now the strategy can recognize transitions instead of treating momentum as a permanent state.


10. Create a Normalized Momentum State

At this point, our internal state might look like:

momentum_state = {
    "price": current_price,
    "return_1s": return_1s,
    "return_5s": return_5s,
    "return_10s": return_10s,
    "return_30s": return_30s,
    "direction": direction,
    "strength": strength,
    "timestamp": timestamp
}
Enter fullscreen mode Exit fullscreen mode

This is much easier for other components to consume.

The Polymarket decision engine doesn't need to know anything about Coinbase's raw WebSocket messages.

It only needs:

Momentum State
Enter fullscreen mode Exit fullscreen mode

This is a small architectural decision, but it becomes extremely useful as the system grows.


A Better System Architecture

Instead of putting everything into one function:

Receive Coinbase message
        ↓
Calculate momentum
        ↓
Read Polymarket
        ↓
Calculate probability
        ↓
Place order
Enter fullscreen mode Exit fullscreen mode

we can separate responsibilities:

┌─────────────────────┐
│    Coinbase Feed    │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│  Market Data Layer  │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│   Momentum Engine   │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│    Signal Engine    │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│   Decision Engine   │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│     Risk Engine     │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│  Execution Engine   │
└─────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Each layer has a clear responsibility.

That makes the system easier to test, debug, and modify.


The First Version of the Signal

We can now think about the first version of our momentum signal as:

Short-Term Return
        +
Direction
        +
Strength
        +
Acceleration
        +
Reversal State
        ↓
Momentum Signal
Enter fullscreen mode Exit fullscreen mode

For example:

BTC 10s Return: +0.08%
Direction:       UP
Strength:        HIGH
Momentum:        Increasing

Momentum Signal: +0.82
Enter fullscreen mode Exit fullscreen mode

Or:

BTC 10s Return: -0.06%
Direction:       DOWN
Strength:        MEDIUM

Momentum Signal: -0.61
Enter fullscreen mode Exit fullscreen mode

These numbers are illustrative.

The important point is the transformation:

Raw Price Updates
        ↓
Market Features
        ↓
Normalized Market State
        ↓
Momentum Signal
Enter fullscreen mode Exit fullscreen mode

But We Still Cannot Trade

This is probably the most important lesson in this part of the series.

Suppose our engine produces:

Momentum Signal = +0.82
Enter fullscreen mode Exit fullscreen mode

Does that mean:

BUY YES
Enter fullscreen mode Exit fullscreen mode

?

Not necessarily.

The Coinbase signal is only one input.

The strategy also needs to understand the current Polymarket market.

For example:

Coinbase Momentum
        +
TWAP State
        +
Distance From Strike
        +
Time Remaining
        +
YES/NO Price
        +
Order-Book Depth
        +
Liquidity
        +
Spread
        +
Risk
Enter fullscreen mode Exit fullscreen mode

Only after combining these variables can the decision engine determine whether the current market presents a potentially attractive setup.


The Next Problem: TWAP Context

Imagine the Coinbase engine detects:

Momentum = +0.90
Enter fullscreen mode Exit fullscreen mode

Now consider two different Polymarket situations.

Scenario A

TWAP has already moved significantly
BTC is close to the strike
5 seconds remain
YES = $0.97
Enter fullscreen mode Exit fullscreen mode

Scenario B

TWAP has moved very little
BTC is moving strongly
60 seconds remain
YES = $0.63
Enter fullscreen mode Exit fullscreen mode

The Coinbase momentum is strong in both scenarios.

But the market context is completely different.

This is why the next layer of the system is so important.

We need to connect:

External Momentum
Enter fullscreen mode Exit fullscreen mode

with:

Polymarket + TWAP State
Enter fullscreen mode Exit fullscreen mode

What We Have Built

At the end of this part, the data flow looks like:

Coinbase WebSocket
        ↓
BTC Price Stream
        ↓
Short-Term Price History
        ↓
Returns
        ↓
Direction
        ↓
Momentum Strength
        ↓
Acceleration / Reversal
        ↓
Momentum Signal
Enter fullscreen mode Exit fullscreen mode

We've gone from a raw stream of exchange updates to a structured signal that the rest of the trading system can consume.

But the signal is still missing its most important context.


What's Next?

In Part 3, we'll connect this momentum engine to the Polymarket market state.

We'll start working with:

  • YES and NO prices
  • BTC strike price
  • Current BTC price
  • Chainlink/TWAP-related state
  • Time remaining
  • Distance from strike
  • Order-book depth
  • Liquidity
  • Spread

Then we'll begin building the actual TWAP-aware decision engine.

The goal is to move from:

"BTC is moving."
Enter fullscreen mode Exit fullscreen mode

to:

"BTC is moving,
the TWAP has not fully reflected the movement,
the market has enough time remaining,
the Polymarket price has not fully adjusted,
and the expected edge may justify a position."
Enter fullscreen mode Exit fullscreen mode

That's the point where the system starts becoming a real market-state-driven trading architecture rather than simply a price-feed bot.


Open-Source Research Repository

I've also published a public repository containing research, examples, and educational material related to automated Polymarket trading systems.

GitHub:
https://github.com/Benjam1nCup/Polymarket-trading-bot-python-V2

The repository is intended primarily for educational and research purposes and demonstrates concepts related to automated Polymarket trading, market data, strategy development, and bot architecture.

If you're interested in Polymarket trading-bot development, strategy research, collaboration, or custom automation:

Telegram:
https://t.me/BenjaminCup

Top comments (0)