A market-data request can look successful while quietly failing the system that depends on it.
At 09:29:59, a trading process receives a quote, updates its order-flow estimate, and places an order. Thirty milliseconds later, another feed reports a different best bid. The strategy did not crash. It acted on a stale or differently conditioned view of the market.
Two providers can return valid JSON for the same request and still produce materially different systems because they differ in historical coverage, real-time granularity, entitlement rules, timestamp semantics, rate limits, and failure behavior.
This comparison evaluates TickDB and Polygon across five engineering dimensions: data coverage, market-depth support, real-time delivery, operational resilience, and pricing. It also provides a reproducible benchmark harness rather than treating vendor latency claims as universal measurements.
1. Data Architecture Determines the Product You Can Build
The first architectural distinction is the separation between historical aggregation and real-time delivery.
TickDB uses two explicit REST endpoints for common market-data workflows:
GET /v1/market/klineretrieves completed historical bars.GET /v1/market/kline/latestretrieves the current real-time candle.
Polygon provides a broader set of market-data products, including historical aggregates, grouped daily bars, trades, quotes, and Level 2 order-book data. Coverage varies by product and subscription tier.
This distinction matters because a historical research workflow and a live execution workflow have different requirements. Historical research emphasizes deterministic access to long time series, stable symbols, and reproducible queries. Live execution emphasizes event delivery, update frequency, connection recovery, and consistent sequencing.
| Architectural concern | TickDB | Polygon | Practical implication |
|---|---|---|---|
| Historical market data | Cleaned and aligned US equity OHLCV covering 10+ years | Aggregates and grouped daily data across supported market products | The longer and more consistent history can reduce preprocessing before a long-horizon backtest |
| Current candle | Dedicated /kline/latest endpoint |
Latest aggregate and real-time aggregate APIs | Separate live and historical semantics prevent accidentally mixing an incomplete bar with a finalized one |
| Real-time channels | Includes a depth channel with market-specific coverage |
Trades, quotes, Level 2, and other event streams under applicable entitlements | Polygon offers a wider event-stream portfolio; TickDB provides a more focused depth-oriented interface |
| Authentication | REST uses X-API-Key; WebSocket uses an api_key URL parameter |
REST commonly uses an API-key query parameter; WebSocket authentication is connection-specific | Secrets must be handled differently across REST and streaming implementations |
| Error model | Documented application codes, including 3001 for rate limiting |
HTTP and transport-level errors, including 429 for HTTP rate limiting |
Retry logic cannot be copied unchanged from one provider to the other |
The capabilities should not be compared as interchangeable labels. In particular, a historical bar is not tick data, and an order-book snapshot is not equivalent to a maintained Level 2 stream.
2. Coverage: What Each Provider Actually Exposes
2.1 Historical Equity Data
Historical coverage is more useful when measured by the questions a research process needs to answer.
TickDB supports cleaned and aligned US equity OHLCV history spanning more than 10 years. That range supports tests that cross multiple volatility regimes and market cycles. It also reduces the amount of raw-data normalization required before an OHLCV strategy begins.
Polygon offers historical aggregates and grouped daily data. Its exact retention and entitlement depend on the selected product and plan. Polygon also provides richer event-oriented products for users who need trades, quotes, or order-book history rather than bars alone.
| Historical requirement | TickDB | Polygon | Selection guidance |
|---|---|---|---|
| Multi-cycle US equity OHLCV | Explicitly supported, covering 10+ years | Available through aggregates and grouped data products | Prefer TickDB when the primary requirement is long, cleaned OHLCV history |
| Custom bar aggregation | Available through supported kline intervals | Strong aggregation APIs and grouped daily bars | Polygon is compelling for teams already standardized on aggregate schemas |
| US equity trade history | Not supported by the TickDB trades endpoint |
Available as a Polygon market-data product when entitled | TickDB alone does not cover this requirement |
| Grouped daily historical bars | Not the primary historical model described here | Native product category | Evaluate Polygon when grouped daily bars are central to the workflow |
| Cross-asset experimentation | Multi-asset API coverage | Broad product portfolio | Test symbol, timestamp, and entitlement consistency rather than relying on asset count alone |
A crucial boundary is that TickDB's trades endpoint does not support US equities or A-shares. A strategy that requires individual US equity trades therefore needs another source or a different analytical design.
2.2 Order-Book Depth
Order-book support is the clearest capability boundary in this comparison.
TickDB supports the depth channel with the following documented coverage:
| Market | TickDB depth coverage |
|---|---|
| US equities | Level 1 |
| Hong Kong equities | Level 1 through Level 10 |
| Crypto | Level 1 through Level 10 |
| Forex | Not supported |
| Precious metals | Not supported |
| Indices | Not supported |
Polygon exposes Level 2 market data for US equities when the relevant product and entitlement are included. Level 2 contains deeper bid and ask information than a Level 1 quote, but depth alone does not guarantee the same signal quality for every strategy.
The comparison therefore depends on three separate questions:
- How many price levels are available?
- Does the feed represent incremental updates, snapshots, or both?
- Does the provider normalize corrections, sequence numbers, and crossed books?
Polygon provides the stronger choice when the requirement is a deeper US equity order-book feed. TickDB provides stronger documented multi-level coverage for Hong Kong equities and crypto, where levels 1 through 10 are available.
2.3 Asset Coverage Is Not Capability Uniformity
Polygon covers a broad portfolio of market products, including stocks, options, forex, crypto, and indices through its individual data services. TickDB also provides multi-asset access, but its endpoint and channel coverage varies by asset class.
The correct comparison is not a count of supported instruments. It is a matrix of the exact fields required by the application:
| Application requirement | TickDB | Polygon |
|---|---|---|
| US equity OHLCV over multiple cycles | Native fit | Available through aggregate products |
| US equity real-time quotes | Verify the applicable real-time channel and entitlement | Native through the quotes stream when entitled |
| US equity Level 2 depth | Not available beyond documented L1 coverage | Available through the Level 2 product when entitled |
| Hong Kong L1–L10 depth | Native fit | Verify product availability and entitlement |
| Crypto L1–L10 depth | Native fit | Verify the corresponding product and schema |
| Historical US equity trades | Not supported | Available when entitled |
| Forex and index depth | Not supported | Verify product-specific availability; breadth alone does not imply Level 2 support |
3. Real-Time Architecture and Engineering Robustness
The following diagram separates the common layers without assuming access to either vendor's internal implementation.
Historical research path
────────────────────────
REST client
│
├── TickDB: /v1/market/kline ───────► Historical US equity OHLCV
└── Polygon: aggregate endpoints ───► Aggregates and grouped bars
Real-time monitoring path
─────────────────────────
WebSocket connection
│
├── Authentication and subscription
├── Heartbeat and timeout policy
├── Reconnection with exponential backoff + jitter
├── Rate-limit and server-error handling
│
▼
Event normalization
│
├── Symbol mapping
├── Timestamp and sequence checks
├── Deduplication
├── Gap detection
└── Derived metrics
│
▼
Strategy, monitoring, or alert service
A production client should treat every network connection as temporary. The design must survive several distinct failure modes:
- The process starts before credentials are available.
- The DNS lookup or TLS handshake fails.
- The server closes the connection during a subscription.
- Messages arrive slowly but are still valid.
- The server applies a rate limit.
- A sequence gap appears after reconnection.
- The process receives duplicate events after recovery.
3.1 Retry and Rate-Limit Semantics
TickDB uses an application-level rate-limit code of 3001 and expects the client to respect the Retry-After header. Polygon commonly communicates HTTP rate limiting through status code 429.
A provider-neutral research client therefore needs two handling paths:
def retry_delay(response, attempt: int) -> float:
"""Return a bounded exponential delay with jitter."""
base = min(2.0 ** attempt, 30.0)
retry_after = response.headers.get("Retry-After")
if retry_after:
try:
return min(float(retry_after), 60.0)
except ValueError:
pass
return base + random.uniform(0, base * 0.1)
This function does not decide whether a response is retryable. That decision belongs in the provider-specific request layer.
3.2 Reconnection and Heartbeats
WebSocket connections should use:
- A bounded exponential backoff.
- Random jitter to avoid synchronized reconnects.
- A connection timeout.
- A read timeout that detects silent connections.
- Heartbeat or protocol-level ping support.
- Fresh authentication after reconnecting.
- Resubscription and state reconciliation after reconnecting.
- Explicit gap detection before resuming signal generation.
The critical control is state reconciliation. Re-establishing a socket does not prove that no events were missed. A production strategy should compare the last received sequence number with the provider's current state and rebuild its order book when required.
4. Reproducible API and Latency Benchmark
Vendor latency claims rarely answer the operational question that matters: how long does the complete request take from the same machine, in the same region, under the same plan?
The following Python harness compares completed US equity daily-bar retrieval. It measures DNS, connection, TLS, server-processing, and response-download time as a total request duration. It does not attempt to compare fundamentally different products as if they were interchangeable.
Install the dependency with:
pip install requests
Set the credentials and optional benchmark settings:
export TICKDB_API_KEY="your_tickdb_key"
export POLYGON_API_KEY="your_polygon_key"
export BENCHMARK_RUNS="20"
Then run the script:
python benchmark_tickdb_vs_polygon.py
#!/usr/bin/env python3
"""
Compare completed US equity daily-bar retrieval performance.
Engineering warning:
- Results depend on endpoint, region, plan, network, cache state, and load.
- Do not publish them as provider-independent latency guarantees.
- Do not print API keys or full authenticated URLs.
"""
import json
import math
import os
import random
import statistics
import time
from typing import Any
import requests
TICKDB_BASE_URL = "https://api.tickdb.ai"
POLYGON_BASE_URL = "https://api.polygon.io"
RUNS = max(3, int(os.getenv("BENCHMARK_RUNS", "20")))
TIMEOUT = (3.05, 15)
USER_AGENT = "market-data-benchmark/1.0"
def percentile(values: list[float], percentile_value: float) -> float:
"""Calculate a nearest-rank percentile without a statistics dependency."""
if not values:
raise ValueError("Cannot calculate a percentile for an empty sample")
ordered = sorted(values)
index = max(0, math.ceil((percentile_value / 100) * len(ordered)) - 1)
return ordered[index]
def retry_delay(response: requests.Response, attempt: int) -> float:
"""Return Retry-After when available; otherwise use exponential backoff."""
base = min(2.0 ** attempt, 30.0)
retry_after = response.headers.get("Retry-After")
if retry_after:
try:
return min(max(float(retry_after), 0.0), 60.0)
except ValueError:
pass
return base + random.uniform(0, base * 0.1)
def request_with_retry(
session: requests.Session,
method: str,
url: str,
*,
headers: dict[str, str] | None = None,
params: dict[str, Any] | None = None,
max_attempts: int = 6,
) -> requests.Response:
"""Send an authenticated request with provider-aware retry behavior."""
for attempt in range(max_attempts):
response = session.request(
method,
url,
headers=headers,
params=params,
timeout=TIMEOUT,
)
if response.status_code == 429 or response.status_code >= 500:
if attempt == max_attempts - 1:
response.raise_for_status()
time.sleep(retry_delay(response, attempt))
continue
# TickDB can return an application-level 3001 code in a successful
# HTTP response. Inspect it before treating the payload as data.
if "json" in response.headers.get("Content-Type", "").lower():
try:
payload = response.json()
except requests.JSONDecodeError:
payload = None
if isinstance(payload, dict) and payload.get("code") == 3001:
if attempt == max_attempts - 1:
raise RuntimeError("TickDB rate limit persisted after retries")
time.sleep(retry_delay(response, attempt))
continue
response.raise_for_status()
return response
raise RuntimeError("Request loop exited unexpectedly")
def benchmark(
name: str,
url: str,
*,
headers: dict[str, str] | None,
params: dict[str, Any],
) -> dict[str, Any]:
"""Measure end-to-end request duration over a controlled sample."""
durations_ms: list[float] = []
payload_sizes: list[int] = []
statuses: set[int] = set()
with requests.Session() as session:
session.headers.update({"User-Agent": USER_AGENT})
# One untimed warm-up reduces the effect of initial DNS and TLS setup.
request_with_retry(
session,
"GET",
url,
headers=headers,
params=params,
)
for _ in range(RUNS):
started = time.perf_counter()
response = request_with_retry(
session,
"GET",
url,
headers=headers,
params=params,
)
elapsed_ms = (time.perf_counter() - started) * 1000
durations_ms.append(elapsed_ms)
payload_sizes.append(len(response.content))
statuses.add(response.status_code)
# Avoid triggering provider rate limits with a benchmark loop.
time.sleep(random.uniform(0.10, 0.25))
return {
"provider": name,
"runs": RUNS,
"status_codes": sorted(statuses),
"min_ms": round(min(durations_ms), 2),
"p50_ms": round(percentile(durations_ms, 50), 2),
"p95_ms": round(percentile(durations_ms, 95), 2),
"max_ms": round(max(durations_ms), 2),
"mean_ms": round(statistics.mean(durations_ms), 2),
"response_bytes": sorted(set(payload_sizes)),
}
def main() -> None:
tickdb_key = os.getenv("TICKDB_API_KEY")
polygon_key = os.getenv("POLYGON_API_KEY")
if not tickdb_key or not polygon_key:
raise SystemExit(
"Set TICKDB_API_KEY and POLYGON_API_KEY before running the benchmark."
)
tickdb_result = benchmark(
"TickDB",
f"{TICKDB_BASE_URL}/v1/market/kline",
headers={"X-API-Key": tickdb_key},
params={
"symbol": "AAPL.US",
"interval": "1d",
"limit": 500,
},
)
polygon_result = benchmark(
"Polygon",
f"{POLYGON_BASE_URL}/v2/aggs/ticker/AAPL/range/1/day/2020-01-01/2026-04-01",
headers=None,
params={
"adjusted": "true",
"sort": "asc",
"limit": 50000,
"apiKey": polygon_key,
},
)
report = {
"environment": {
"runs": RUNS,
"timeout_seconds": TIMEOUT,
"note": "Network-dependent; rerun from each deployment region.",
},
"results": [tickdb_result, polygon_result],
}
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()
4.1 How to Interpret the Results
Use the output as evidence for one environment, not as a provider ranking.
| Metric | Interpretation | Required follow-up |
|---|---|---|
| p50 | Typical request duration under sampled conditions | Compare from the same region and network |
| p95 | Behavior near the upper end of ordinary latency | Investigate cache misses and connection reuse |
| Maximum | Most extreme observed sample | Inspect for DNS, TLS, gateway, or rate-limit events |
| Response size | Payload transfer cost | Normalize if the endpoints return different bar counts |
| Status codes | Authentication and entitlement correctness | Investigate any non-2xx result before drawing a performance conclusion |
A fair historical comparison should also request overlapping dates and verify bar-for-bar equality after accounting for adjustment rules. Comparing one provider's completed bars with another provider's partially formed bars creates a meaningless latency result.
5. Production WebSocket Resilience
A robust WebSocket client must distinguish a healthy connection from a connection that has merely remained open.
The following Polygon example implements connection timeout, protocol-level keepalive, bounded exponential backoff, jitter, rate-limit handling, and resubscription. It processes trades, quotes, and Level 2 events while checking for provider status and error messages.
#!/usr/bin/env python3
"""
Production-oriented Polygon WebSocket consumer.
Engineering warning:
- Validate subscription parameters against the entitlements attached to
your Polygon account.
- A Level 2 consumer must reconcile sequence gaps after reconnection.
- This example logs messages; add durable queueing before driving orders.
"""
import asyncio
import json
import os
import random
import urllib.parse
import websockets
from websockets.exceptions import ConnectionClosed
API_KEY = os.getenv("POLYGON_API_KEY")
WS_URL = "wss://socket.polygon.io/stocks"
HEARTBEAT_INTERVAL = 20
READ_TIMEOUT = 45
MAX_BACKOFF = 30
class PolygonStreamConsumer:
async def run(self) -> None:
if not API_KEY:
raise RuntimeError("POLYGON_API_KEY is not set")
attempt = 0
while True:
query = urllib.parse.urlencode({"apiKey": API_KEY})
uri = f"{WS_URL}?{query}"
try:
async with websockets.connect(
uri,
open_timeout=10,
close_timeout=5,
ping_interval=HEARTBEAT_INTERVAL,
ping_timeout=10,
max_queue=32,
) as websocket:
attempt = 0
subscription = {
"action": "subscribe",
"params": "trades,quotes,level2",
}
await websocket.send(json.dumps(subscription))
async for raw_message in websocket:
message = json.loads(raw_message)
message_type = message.get("type")
if message_type == "error":
error = message.get("message", "Unknown WebSocket error")
if "rate limit" in error.lower():
await asyncio.sleep(5 + random.uniform(0, 2))
break
raise RuntimeError(error)
if message_type == "status":
print(f"Status event: {message.get('status')}")
continue
# Replace this handler with normalization, persistence,
# sequence validation, and strategy-specific logic.
print(json.dumps(message))
except (ConnectionClosed, OSError, TimeoutError) as exc:
attempt += 1
base = min(2 ** (attempt - 1), MAX_BACKOFF)
delay = base + random.uniform(0, base * 0.1)
print(
f"Stream disconnected: {exc}. "
f"Reconnecting in {delay:.2f}s"
)
await asyncio.sleep(delay)
if __name__ == "__main__":
asyncio.run(PolygonStreamConsumer().run())
The same lifecycle must be applied to a TickDB depth subscription. The exact payload schema should come from the API contract attached to the account, but the control requirements remain unchanged:
- Authenticate with
?api_key=when connecting. - Subscribe to the
depthchannel for the required symbol. - Send the documented heartbeat command at the required interval.
- Track sequence or timestamp continuity.
- Rebuild state after a detected gap.
- Reconnect with exponential backoff and jitter.
- Respect application-level rate-limit responses.
- Never resume signal generation until the order book is synchronized.
6. Derived Metrics and Signal Quality
Depth data becomes useful only after the provider's schema has been normalized.
For example, a simple top-10 pressure ratio can be defined as:
[
\text{Pressure Ratio}N =
\frac{\sum{i=1}^{N}\text{Bid Size}i}
{\sum{i=1}^{N}\text{Ask Size}_i}
]
The metric is straightforward, but its interpretation depends on the input:
| Input property | Why it matters |
|---|---|
| Price-level ordering | A stale or unsorted book produces incorrect top-N aggregates |
| Snapshot versus increment | Applying an increment directly to a snapshot corrupts the book |
| Sequence continuity | Missing updates leave incorrect quantities on existing levels |
| Timestamp precision | Low-resolution timestamps reduce event-ordering accuracy |
| Exchange treatment | Venue-specific rules can affect displayed depth |
| Update frequency | Sparse updates may miss rapid liquidity withdrawal |
A raw pressure ratio should therefore travel with data-quality fields:
from dataclasses import dataclass
from datetime import datetime
@dataclass(frozen=True)
class DepthQuality:
received_at: datetime
source_timestamp: datetime | None
sequence: int | None
is_snapshot: bool
gaps_detected: int = 0
def pressure_ratio(bid_sizes: list[int], ask_sizes: list[int]) -> float | None:
"""Calculate top-N pressure while avoiding division by zero."""
bid_depth = sum(bid_sizes)
ask_depth = sum(ask_sizes)
if bid_depth <= 0 and ask_depth <= 0:
return None
if ask_depth == 0:
return float("inf")
return bid_depth / ask_depth
Neither provider can make an order-flow strategy robust merely by supplying a depth feed. The strategy must distinguish a genuine liquidity change from a missing update, a schema mismatch, or a sequence discontinuity.
7. Pricing and Total Cost of Ownership
Pricing for both providers depends on product, region, billing period, and entitlement. Public plan names and dollar amounts can change, so procurement should verify the current pricing pages and contract rather than relying on an old article or cached comparison.
| Cost dimension | TickDB | Polygon | Evaluation method |
|---|---|---|---|
| Entry access | Free API-key access is available | Free or trial-style access varies by product | Verify current account terms |
| Paid architecture | Professional and Enterprise positioning | Product-specific developer, business, and enterprise tiers | Compare equivalent entitlements |
| Historical depth | 10+ years of cleaned US equity OHLCV is a core capability | Historical aggregates are available through product-specific access | Compare usable history, not advertised sample size |
| Real-time depth | Market-specific depth coverage, including HK and crypto L1–L10 |
US equity Level 2 is available through the applicable product | Confirm market, level, venue, and exchange coverage |
| Entitlement complexity | Smaller product surface | Broader portfolio can create product-specific access rules | Produce a signed entitlement matrix before launch |
| Engineering cost | Potentially lower for focused OHLCV and multi-level depth workflows | Potentially lower for teams already standardized on Polygon schemas | Include integration and maintenance hours in the model |
| Commercial scale | Enterprise contact for institutional requirements | Enterprise terms available | Compare service levels, support, and contract protections |
| Exit cost | Schema and metric definitions must be reproduced | Schema and metric definitions must be reproduced | Maintain a provider-neutral internal data model |
7.1 Normalized Cost Model
Compare monthly cost using a common formula:
[
\begin{aligned}
C_{monthly} ={}& C_{subscription}
- C_{market_entitlements}
- C_{history}\
&+ C_{support} - C_{infrastructure}
- C_{engineering_amortization}
\end{aligned}
]
The engineering component should include:
- Data-model normalization.
- Authentication and entitlement management.
- WebSocket recovery.
- Sequence-gap reconciliation.
- Historical/live symbol mapping.
- Monitoring and alerting.
- Backfill procedures.
- Testing against provider schema changes.
A lower listed subscription price can produce a higher total cost if the team must maintain multiple vendor-specific schemas or replace unavailable market data with another feed.
7.2 Scenario-Based Selection
| Scenario | Better initial fit | Reason |
|---|---|---|
| Multi-cycle US equity OHLCV research | TickDB | Long, cleaned, aligned history directly matches the requirement |
| US equity Level 2 microstructure research | Polygon | Level 2 depth is the central requirement |
| Hong Kong or crypto L1–L10 depth | TickDB | Documented depth coverage extends through ten levels |
| Multi-product US market-data stack | Polygon | Broad event and aggregate product portfolio |
| Small team seeking a focused API and historical backtests | TickDB | Lower integration surface for the defined workflows |
| Team already operating a Polygon data architecture | Polygon | Existing schemas and operational tooling reduce migration cost |
| Enterprise requiring negotiated service terms | Evaluate both commercially | Compare SLA, support, retention, legal terms, and exit provisions |
No selection should depend on a total market count alone. Obtain a written entitlement matrix covering the exact exchanges, symbols, data fields, history periods, and real-time channels required in production.
8. Decision Framework and Closing Checklist
The strongest architecture is the one that matches the data semantics, operating model, and failure budget of the intended system.
A US equity Level 2 strategy places Polygon’s deeper order-book product at the center of the decision. A long-horizon US equity OHLCV backtest places TickDB’s 10+ years of cleaned and aligned history at the center. A Hong Kong or crypto multi-level depth strategy also gives TickDB a direct capability advantage because the documented depth coverage extends from Level 1 through Level 10.
Before signing, run the following checks:
- Verify coverage against exact symbols and venues.
- Confirm whether historical and live data use the same normalization.
- Test authentication failures and missing entitlements.
- Trigger rate limits and verify
Retry-Afterhandling. - Kill and restore the network during a live session.
- Detect and repair sequence gaps after reconnecting.
- Benchmark from every production region.
- Reconcile overlapping historical bars.
- Model the total cost of engineering and entitlement management.
- Keep a provider-neutral internal schema to reduce exit cost.
Next Steps
If you are an investor looking for deeper market insight, subscribe to the TickDB newsletter for weekly supply-chain and microstructure analysis.
If you want to run the comparison yourself:
- Sign up at tickdb.ai for API access.
- Generate an API key in the dashboard.
- Set the
TICKDB_API_KEYenvironment variable. - Run the benchmark harness from each intended production region.
- Record p50, p95, maximum latency, entitlement behavior, and reconnect recovery.
If you need 10+ years of historical OHLCV data for institutional strategy backtesting, contact [email protected] for Enterprise options.
If you use AI coding assistants, search for and install the tickdb-market-data SKILL in your AI tool’s marketplace.
Reference Sources
Verify current plan terms and endpoint behavior against the providers’ official documentation before procurement or deployment.
https://tickdb.ai
https://api.tickdb.ai/v1/symbols/available
https://polygon.io/pricing
https://polygon.io/docs
https://polygon.io/docs/rest
https://polygon.io/docs/websocket
This article does not constitute investment advice. Markets involve risk; past performance does not guarantee future results.