Skip to content

Commit 74612c7

Browse files
committed
Chore: update twap price
1 parent fbc4418 commit 74612c7

7 files changed

Lines changed: 78 additions & 143 deletions

File tree

src/client/chainlink_client/chainlink_client.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,9 @@ def last_update_time(self) -> Optional[int]:
149149

150150
def get_price_at_timestamp(self, timestamp: int | float | str) -> float:
151151
"""
152-
Get the Chainlink Data Streams benchmark price for this feed at a specific Unix timestamp (seconds).
152+
Get the Chainlink Data Streams price for this feed at a Unix timestamp (seconds).
153153
154-
Uses the authenticated Data Streams API to fetch the report at the given time
155-
and returns the decoded benchmark price.
154+
For 5m markets use the TWAP-30 feed (``CHAINLINK_TWAP_30S_FEED_ID``).
156155
"""
157156
if not self.feed_id:
158157
raise ValueError("feed_id is not set; cannot get report")

src/client/coinbase_client/coinbase_client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ def _dispatch_loop(self) -> None:
339339
if not isinstance(price, (int, float)):
340340
continue
341341
price_f = float(price)
342+
print(f"Coinbase price: {price_f}")
342343
side = msg.get("side")
343344
# Order matters: write the timestamp last so a reader
344345
# that observes a fresh COIN_BASE_LAST_UPDATE_MS is
Lines changed: 30 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""
2-
Polymarket Chainlink price streams via Polymarket RTDS (no Chainlink API keys).
2+
Polymarket Chainlink TWAP-30 stream via Polymarket RTDS (no Chainlink API keys).
33
4-
Subscribes to:
5-
- ``prices.crypto.chainlink`` → benchmark → ``config.CURRENT_PRICE``
6-
- ``prices.crypto.chainlink.twap`` (30s) → ``config.CURRENT_TWAP_PRICE`` (settlement only)
4+
For 5-minute BTC UP/DOWN markets, Polymarket resolves using Chainlink TWAP-30
5+
(beat at open, settlement at end). This client subscribes to
6+
``prices.crypto.chainlink.twap`` (30s) and mirrors values into
7+
``config.CURRENT_TWAP_PRICE`` and ``config.CURRENT_PRICE`` (legacy alias).
78
"""
89
from __future__ import annotations
910

@@ -26,13 +27,12 @@
2627

2728
try:
2829
from polymarket import AsyncPublicClient
29-
from polymarket.streams import CryptoPricesChainlinkTwapSpec, CryptoPricesSpec
30+
from polymarket.streams import CryptoPricesChainlinkTwapSpec
3031

3132
POLYMARKET_CHAINLINK_AVAILABLE = True
3233
except ImportError:
3334
AsyncPublicClient = None # type: ignore[assignment,misc]
3435
CryptoPricesChainlinkTwapSpec = None # type: ignore[assignment,misc]
35-
CryptoPricesSpec = None # type: ignore[assignment,misc]
3636
POLYMARKET_CHAINLINK_AVAILABLE = False
3737

3838
# Back-compat alias used elsewhere in the repo.
@@ -45,7 +45,7 @@ def _asset_to_symbol(asset: str) -> str:
4545

4646

4747
class PolymarketTwapClient:
48-
"""Polymarket RTDS: Chainlink benchmark + TWAP-30 background streams."""
48+
"""Polymarket RTDS: Chainlink TWAP-30 background stream."""
4949

5050
def __init__(
5151
self,
@@ -61,14 +61,12 @@ def __init__(
6161
self._stop = threading.Event()
6262
self._running = False
6363

64-
self._last_benchmark: Optional[float] = None
6564
self._last_twap: Optional[float] = None
66-
self._last_benchmark_ts_ms: Optional[int] = None
6765
self._last_twap_ts_ms: Optional[int] = None
6866

6967
@property
7068
def last_price(self) -> Optional[float]:
71-
"""Last TWAP price (legacy alias)."""
69+
"""Last TWAP-30 price."""
7270
return self._last_twap
7371

7472
@property
@@ -80,15 +78,15 @@ def start(self) -> None:
8078
return
8179
if not POLYMARKET_CHAINLINK_AVAILABLE:
8280
logger.warning(
83-
"polymarket-client not installed; Chainlink streams disabled. "
81+
"polymarket-client not installed; Chainlink TWAP stream disabled. "
8482
"pip install polymarket-client"
8583
)
8684
return
8785
self._running = True
8886
self._stop.clear()
8987
self._thread = threading.Thread(
9088
target=self._run_thread,
91-
name="polymarket-chainlink",
89+
name="polymarket-chainlink-twap",
9290
daemon=True,
9391
)
9492
self._thread.start()
@@ -103,18 +101,8 @@ def stop(self) -> None:
103101
pass
104102
self._thread = None
105103

106-
def wait_for_benchmark(self, timeout_sec: float = 15.0) -> Optional[float]:
107-
"""Block until a positive Chainlink benchmark price arrives."""
108-
deadline = time.time() + timeout_sec
109-
while time.time() < deadline:
110-
px = config.CURRENT_PRICE
111-
if px and px > 0:
112-
return float(px)
113-
time.sleep(0.2)
114-
return None
115-
116104
def wait_for_twap(self, timeout_sec: float = 15.0) -> Optional[float]:
117-
"""Block until a positive TWAP price arrives (use after market end)."""
105+
"""Block until a positive Chainlink TWAP-30 price arrives."""
118106
deadline = time.time() + timeout_sec
119107
while time.time() < deadline:
120108
px = config.CURRENT_TWAP_PRICE
@@ -123,28 +111,40 @@ def wait_for_twap(self, timeout_sec: float = 15.0) -> Optional[float]:
123111
time.sleep(0.2)
124112
return None
125113

114+
def wait_for_benchmark(self, timeout_sec: float = 15.0) -> Optional[float]:
115+
"""Legacy alias → ``wait_for_twap`` (beat/settlement use TWAP-30)."""
116+
return self.wait_for_twap(timeout_sec)
117+
126118
def wait_for_price(self, timeout_sec: float = 15.0) -> Optional[float]:
127119
"""Legacy alias → ``wait_for_twap``."""
128120
return self.wait_for_twap(timeout_sec)
129121

122+
def _set_twap(self, price: float, obs_ms: object) -> None:
123+
config.CURRENT_TWAP_PRICE = price
124+
config.CURRENT_PRICE = price # legacy alias
125+
self._last_twap = price
126+
if obs_ms is not None:
127+
ts = float(obs_ms)
128+
config.CURRENT_TWAP_TS_MS = ts
129+
config.CURRENT_PRICE_TS_MS = ts # legacy alias
130+
self._last_twap_ts_ms = int(obs_ms)
131+
130132
def _run_thread(self) -> None:
131133
while self._running and not self._stop.is_set():
132134
try:
133135
asyncio.run(self._stream_loop())
134136
except Exception:
135-
logger.exception("Polymarket Chainlink stream error")
137+
logger.exception("Polymarket Chainlink TWAP stream error")
136138
if self._stop.is_set() or not self._running:
137139
break
138-
logger.warning("Polymarket Chainlink stream ended; reconnecting in 5s")
140+
logger.warning("Polymarket Chainlink TWAP stream ended; reconnecting in 5s")
139141
time.sleep(5.0)
140142

141143
async def _stream_loop(self) -> None:
142144
assert AsyncPublicClient is not None
143-
assert CryptoPricesSpec is not None
144145
assert CryptoPricesChainlinkTwapSpec is not None
145146

146147
specs = [
147-
CryptoPricesSpec(topic="prices.crypto.chainlink", symbols=[self.symbol]),
148148
CryptoPricesChainlinkTwapSpec(
149149
window_seconds=self.window_seconds,
150150
symbols=[self.symbol],
@@ -154,31 +154,17 @@ async def _stream_loop(self) -> None:
154154
async with await client.subscribe(specs) as stream:
155155
if self._debug:
156156
print(
157-
f"✅ Polymarket Chainlink streams connected "
158-
f"({self.symbol}, benchmark + TWAP {self.window_seconds}s)"
157+
f"✅ Polymarket Chainlink TWAP stream connected "
158+
f"({self.symbol}, {self.window_seconds}s)"
159159
)
160160
async for event in stream:
161161
if self._stop.is_set():
162162
break
163-
topic = getattr(event, "topic", "")
164163
payload = event.payload
165164
try:
166165
price = float(payload.value)
167166
except (TypeError, ValueError):
168167
continue
169168
if price <= 0:
170169
continue
171-
obs_ms = getattr(payload, "timestamp", None)
172-
173-
if topic == "prices.crypto.chainlink":
174-
config.CURRENT_PRICE = price
175-
self._last_benchmark = price
176-
if obs_ms is not None:
177-
config.CURRENT_PRICE_TS_MS = float(obs_ms)
178-
self._last_benchmark_ts_ms = int(obs_ms)
179-
elif topic == "prices.crypto.chainlink.twap":
180-
config.CURRENT_TWAP_PRICE = price
181-
self._last_twap = price
182-
if obs_ms is not None:
183-
config.CURRENT_TWAP_TS_MS = float(obs_ms)
184-
self._last_twap_ts_ms = int(obs_ms)
170+
self._set_twap(price, getattr(payload, "timestamp", None))

src/config/config.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,11 @@ def round_usdc(value: float) -> float:
9393
DIFF: float = 0.0
9494
PREV_MOMENTUM_PRICE: float = 0.0
9595

96-
CURRENT_PRICE: float = 0.0
97-
# Chainlink benchmark observation time (ms) from Polymarket RTDS.
98-
CURRENT_PRICE_TS_MS: float = 0.0
96+
# Chainlink TWAP-30 (5m market beat/settlement). CURRENT_* are legacy aliases.
9997
CURRENT_TWAP_PRICE: float = 0.0
100-
# Chainlink observation time (ms) from the latest Polymarket TWAP event payload.
10198
CURRENT_TWAP_TS_MS: float = 0.0
99+
CURRENT_PRICE: float = 0.0
100+
CURRENT_PRICE_TS_MS: float = 0.0
102101
# Settlement TWAP captured closest to market end (set by arb bots).
103102
END_TWAP_PRICE: float = 0.0
104103

src/script/arb_bot_paper.py

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def _sanitize_slug(slug: str) -> str:
5555

5656

5757
def _save_chainlink_market_log(slug: str | None, record: dict) -> str | None:
58-
"""Write beat + final-30s benchmark samples + end TWAP to logs/chainlink/."""
58+
"""Write beat + final-30s TWAP samples + end TWAP to logs/chainlink/."""
5959
safe_slug = _sanitize_slug(slug or record.get("slug") or "unknown-market")
6060
record = {**record, "slug": safe_slug}
6161
path = os.path.join(CHAINLINK_LOG_DIR, f"{safe_slug}.json")
@@ -76,23 +76,23 @@ def _save_chainlink_market_log(slug: str | None, record: dict) -> str | None:
7676
return path
7777

7878

79-
# The end TWAP is the arithmetic mean of the per-second benchmark prices over
80-
# the final TWAP_WINDOW_SECONDS. Once N samples are known, N/30 of the result is
81-
# already fixed; the rest is estimated by holding the current benchmark flat.
79+
# End settlement TWAP mean of per-second TWAP-30 samples over the final window.
80+
# Once N samples are known, N/30 of the result is fixed; the rest is estimated
81+
# by holding the current TWAP flat.
8282
TWAP_WINDOW_SECONDS = 30
8383
PREDICT_AT_SECONDS_LEFT = 5
8484
# Calls closer than this to the beat price sit inside the estimator's noise band.
8585
MIN_PREDICT_MARGIN = 1.0
86-
# A benchmark this old means the Chainlink stream stalled; the estimate is unusable.
87-
MAX_BENCHMARK_AGE_MS = 3000.0
86+
# A TWAP update this old means the Chainlink stream stalled; estimate is unusable.
87+
MAX_TWAP_AGE_MS = 3000.0
8888

8989
_pred_total = 0
9090
_pred_correct = 0
9191
_pred_abs_err_sum = 0.0
9292

9393

9494
def _predict_end_twap(prices: list[float], current: float) -> float | None:
95-
"""Estimate the final TWAP from the benchmark samples seen so far.
95+
"""Estimate the final TWAP from TWAP-30 samples seen so far.
9696
9797
Unknown seconds are filled with ``current`` (flat-hold assumption).
9898
"""
@@ -638,14 +638,14 @@ def _main_impl():
638638
end_ts = bot.current_market_end_timestamp
639639
config.END_TWAP_PRICE = 0.0
640640

641-
# Beat = Chainlink benchmark at market open.
642-
beat = bot.polymarket_twap_client.wait_for_benchmark(timeout_sec=10.0)
641+
# Beat = Chainlink TWAP-30 at market open.
642+
beat = bot.polymarket_twap_client.wait_for_twap(timeout_sec=10.0)
643643
if beat is not None and beat > 0:
644644
config.PRICE_TO_BEAT = float(beat)
645645
config.PRICE_TO_BEAT_COINBASE = float(beat)
646-
print(f"PRICE TO BEAT (benchmark): {config.PRICE_TO_BEAT:.2f}")
646+
print(f"PRICE TO BEAT (TWAP-30): {config.PRICE_TO_BEAT:.2f}")
647647
else:
648-
print("⚠️ Could not get opening benchmark from Polymarket stream")
648+
print("⚠️ Could not get opening TWAP-30 from Polymarket stream")
649649

650650
# Wait until the final 30 seconds of this market.
651651
if end_ts:
@@ -654,39 +654,39 @@ def _main_impl():
654654
time.sleep(0.5)
655655

656656
beat = config.PRICE_TO_BEAT
657-
benchmark_final_30s: list[dict] = []
657+
twap_final_30s: list[dict] = []
658658
prediction: dict | None = None
659659

660-
# Final 30s: Chainlink benchmark prices (T-30 → T-0), one sample per second.
660+
# Final 30s: Chainlink TWAP-30 (T-30 → T-0), one sample per second.
661661
while end_ts and time.time() < end_ts + 1:
662662
_ensure_websocket_and_subscribe(bot, ws_url, prev_token_ids)
663663

664664
now = time.time()
665665
seconds_left = end_ts - now
666-
benchmark = config.CURRENT_PRICE
667-
if benchmark > 0 and end_ts - 30 <= now <= end_ts:
668-
side = "UP" if beat > 0 and benchmark > beat else ("DOWN" if beat > 0 else "?")
666+
twap = config.CURRENT_TWAP_PRICE
667+
if twap > 0 and end_ts - 30 <= now <= end_ts:
668+
side = "UP" if beat > 0 and twap > beat else ("DOWN" if beat > 0 else "?")
669669
dt = datetime.fromtimestamp(now, timezone.utc)
670-
benchmark_final_30s.append({
670+
twap_final_30s.append({
671671
"ts": round(now, 3),
672672
"iso": dt.isoformat(),
673673
"seconds_left": round(seconds_left, 1),
674-
"benchmark_price": round(benchmark, 2),
674+
"twap_price": round(twap, 2),
675675
"vs_beat": side,
676676
})
677677
print(
678678
f"{dt.strftime('%d/%m/%Y, %-H:%M:%S %Z')} | "
679-
f"T-{seconds_left:.0f}s | benchmark={benchmark:.2f} | beat={beat:.2f} | {side}"
679+
f"T-{seconds_left:.0f}s | twap={twap:.2f} | beat={beat:.2f} | {side}"
680680
)
681681

682682
if prediction is None and seconds_left <= PREDICT_AT_SECONDS_LEFT:
683-
prices = [s["benchmark_price"] for s in benchmark_final_30s]
684-
est = _predict_end_twap(prices, benchmark)
683+
prices = [s["twap_price"] for s in twap_final_30s]
684+
est = _predict_end_twap(prices, twap)
685685
if est is not None:
686-
age_ms = (now * 1000.0) - config.CURRENT_PRICE_TS_MS
686+
age_ms = (now * 1000.0) - config.CURRENT_TWAP_TS_MS
687687
stale = (
688-
config.CURRENT_PRICE_TS_MS <= 0
689-
or age_ms > MAX_BENCHMARK_AGE_MS
688+
config.CURRENT_TWAP_TS_MS <= 0
689+
or age_ms > MAX_TWAP_AGE_MS
690690
or len(set(prices)) < 2
691691
)
692692
margin = est - beat if beat > 0 else 0.0
@@ -698,11 +698,11 @@ def _main_impl():
698698
"seconds_left": round(seconds_left, 1),
699699
"samples_known": len(prices),
700700
"locked_fraction": round(len(prices) / TWAP_WINDOW_SECONDS, 3),
701-
"current_benchmark": round(benchmark, 2),
701+
"current_twap": round(twap, 2),
702702
"predicted_twap": round(est, 2),
703703
"margin_vs_beat": round(margin, 2),
704704
"predicted_side": pred_side,
705-
"benchmark_age_ms": round(max(age_ms, 0.0), 1),
705+
"twap_age_ms": round(max(age_ms, 0.0), 1),
706706
"stale_feed": stale,
707707
}
708708
print(
@@ -721,7 +721,7 @@ def _main_impl():
721721

722722
result = None
723723
print(f"END TWAP: {end_twap}")
724-
print(f"PRICE TO BEAT (benchmark): {config.PRICE_TO_BEAT}")
724+
print(f"PRICE TO BEAT (TWAP-30): {config.PRICE_TO_BEAT}")
725725

726726
if end_twap is not None and config.PRICE_TO_BEAT > 0:
727727
if end_twap > config.PRICE_TO_BEAT:
@@ -734,7 +734,7 @@ def _main_impl():
734734
print("⚠️ Settlement skipped — missing end TWAP or beat price")
735735

736736
if prediction is None:
737-
print("🔮 PREDICTION: none made this market (no benchmark samples in final 5s)")
737+
print("🔮 PREDICTION: none made this market (no TWAP samples in final 5s)")
738738
elif end_twap is None:
739739
print("🔮 PREDICTION: cannot verify — no end TWAP")
740740
else:
@@ -773,7 +773,7 @@ def _main_impl():
773773
"market_start_ts": int(start_ts) if start_ts else None,
774774
"market_end_ts": int(end_ts) if end_ts else None,
775775
"beat_price": round(config.PRICE_TO_BEAT, 2) if config.PRICE_TO_BEAT else None,
776-
"benchmark_prices_final_30s": benchmark_final_30s,
776+
"twap_prices_final_30s": twap_final_30s,
777777
"end_twap_price": round(end_twap, 2) if end_twap is not None else None,
778778
"result": result,
779779
"prediction": prediction,

0 commit comments

Comments
 (0)