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
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
Then:
$100,000
↓
$100,040
↓
$100,090
↓
$100,150
If we only request the latest price periodically, we may see:
BTC = $100,150
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
While the settlement relationship is conceptually:
BTC Market
│
▼
Chainlink
│
▼
60-Second TWAP
│
▼
Polymarket Resolution
The trading system therefore has two different types of information:
Fast information
+
Settlement context
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
we want:
Coinbase
│
├── Update
├── Update
├── Update
└── Update
↓
Bot
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())
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
}
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
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
From this small window, we can calculate several features:
Return
Direction
Rate of change
Momentum strength
Acceleration
Reversal
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
The absolute change is:
$100,100 - $100,050 = $50
The percentage return can be calculated as:
return_pct = (
(current_price - previous_price)
/ previous_price
) * 100
Now the system knows more than:
BTC = $100,100
It knows:
BTC changed approximately +0.05%
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
Imagine the engine observes:
1s → +0.012%
5s → +0.038%
10s → +0.071%
30s → +0.052%
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
For example:
if return_10s > threshold:
direction = "UP"
elif return_10s < -threshold:
direction = "DOWN"
else:
direction = "NEUTRAL"
This gives us a basic market state.
For example:
+0.08% → UP
-0.07% → DOWN
+0.01% → NEUTRAL
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
Strong movement
100000
100020
100045
100080
100120
Both are moving upward.
But the second sequence contains significantly stronger movement.
So instead of:
Direction = UP
we want something closer to:
Direction = UP
Strength = HIGH
A normalized signal could eventually look like:
Momentum = +0.82
or:
Momentum = +0.25
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%
The upward movement is accelerating.
Compare it with:
+0.07%
+0.06%
+0.05%
+0.04%
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
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
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"
we can maintain:
momentum_state = {
"direction": direction,
"strength": strength,
"recent_return": recent_return,
"previous_return": previous_return,
"price": current_price,
"timestamp": timestamp
}
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
}
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
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
we can separate responsibilities:
┌─────────────────────┐
│ Coinbase Feed │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Market Data Layer │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Momentum Engine │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Signal Engine │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Decision Engine │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Risk Engine │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Execution Engine │
└─────────────────────┘
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
For example:
BTC 10s Return: +0.08%
Direction: UP
Strength: HIGH
Momentum: Increasing
Momentum Signal: +0.82
Or:
BTC 10s Return: -0.06%
Direction: DOWN
Strength: MEDIUM
Momentum Signal: -0.61
These numbers are illustrative.
The important point is the transformation:
Raw Price Updates
↓
Market Features
↓
Normalized Market State
↓
Momentum Signal
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
Does that mean:
BUY YES
?
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
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
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
Scenario B
TWAP has moved very little
BTC is moving strongly
60 seconds remain
YES = $0.63
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
with:
Polymarket + TWAP State
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
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."
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."
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)