synpath 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {synpath-0.1.0 → synpath-0.2.0}/.env.example +8 -0
- {synpath-0.1.0 → synpath-0.2.0}/PKG-INFO +30 -28
- {synpath-0.1.0 → synpath-0.2.0}/README.md +28 -26
- {synpath-0.1.0 → synpath-0.2.0}/docs/api.md +1 -1
- {synpath-0.1.0 → synpath-0.2.0}/docs/capabilities.md +38 -29
- {synpath-0.1.0 → synpath-0.2.0}/docs/engine.md +6 -2
- {synpath-0.1.0 → synpath-0.2.0}/docs/streaming.md +15 -0
- {synpath-0.1.0 → synpath-0.2.0}/docs/trading.md +30 -0
- {synpath-0.1.0 → synpath-0.2.0}/pyproject.toml +2 -2
- {synpath-0.1.0 → synpath-0.2.0}/synpath/__init__.py +7 -2
- {synpath-0.1.0 → synpath-0.2.0}/synpath/client.py +6 -1
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/__main__.py +1 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/feeds.py +7 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/orders/routed.py +39 -9
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/router.py +97 -20
- {synpath-0.1.0 → synpath-0.2.0}/synpath/ids.py +4 -4
- {synpath-0.1.0 → synpath-0.2.0}/synpath/matching.py +10 -3
- synpath-0.2.0/synpath/opinion.py +799 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/server/serve.py +1 -1
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/__init__.py +4 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/__main__.py +7 -1
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/credentials.py +45 -1
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/init.py +17 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/instruments.py +5 -0
- synpath-0.2.0/synpath/trading/opinion.py +760 -0
- synpath-0.2.0/synpath/trading/opinion_signing.py +163 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/types.py +7 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/types.py +15 -1
- {synpath-0.1.0 → synpath-0.2.0}/synpath/ws/__init__.py +4 -0
- synpath-0.2.0/synpath/ws/opinion.py +459 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/conftest.py +25 -0
- synpath-0.2.0/tests/samples/opinion_book.json +1 -0
- synpath-0.2.0/tests/samples/opinion_book_no.json +1 -0
- synpath-0.2.0/tests/samples/opinion_categorical.json +1 -0
- synpath-0.2.0/tests/samples/opinion_child.json +1 -0
- synpath-0.2.0/tests/samples/opinion_fee_rate_call.json +1 -0
- synpath-0.2.0/tests/samples/opinion_latest_price.json +1 -0
- synpath-0.2.0/tests/samples/opinion_market.json +1 -0
- synpath-0.2.0/tests/samples/opinion_markets.json +1 -0
- synpath-0.2.0/tests/samples/opinion_not_found.json +1 -0
- synpath-0.2.0/tests/samples/opinion_prices_1d.json +1 -0
- synpath-0.2.0/tests/samples/opinion_prices_1h.json +1 -0
- synpath-0.2.0/tests/samples/opinion_resolved.json +1 -0
- synpath-0.2.0/tests/samples/opinion_signing_vectors.json +96 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_contract.py +24 -1
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_hosted.py +19 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_init.py +27 -5
- synpath-0.2.0/tests/test_opinion.py +430 -0
- synpath-0.2.0/tests/test_opinion_live.py +103 -0
- synpath-0.2.0/tests/test_opinion_trading.py +414 -0
- synpath-0.2.0/tests/test_opinion_ws.py +324 -0
- synpath-0.2.0/tests/test_opinion_ws_live.py +92 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_routed_limit.py +41 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_router.py +62 -0
- {synpath-0.1.0 → synpath-0.2.0}/.github/workflows/publish.yml +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/.github/workflows/tests.yml +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/.gitignore +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/LICENSE +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/assets/kalshi.png +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/assets/polymarket.png +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/assets/synpath-banner.png +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/clients/typescript/.gitignore +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/clients/typescript/example.ts +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/clients/typescript/package.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/clients/typescript/tsconfig.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/docs/server.md +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/examples/track_historical_book.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/__main__.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/base.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/bucket.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/__init__.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/alerts.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/engine.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/eod.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/events.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/fair_values.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/journal.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/ledger.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/orders/__init__.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/orders/base.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/orders/day.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/orders/iceberg.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/orders/manager.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/orders/oco.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/orders/peg.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/orders/stop.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/orders/taker.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/orders/twap.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/paper.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/reconcile.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/engine/risk.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/errors.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/history.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/hosted.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/hosted_auth.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/kalshi.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/polymarket.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/polymarket_us.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/remote.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/server/__init__.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/server/__main__.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/server/api.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/server/errors.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/server/local.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/server/models.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/server/store.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/server/trading.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/base.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/errors.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/kalshi.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/limiter.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/money.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/polymarket.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/polymarket_signing.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/polymarket_us.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/trading/polymarket_us_exchange.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/ws/base.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/ws/grpc.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/ws/kalshi.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/ws/polymarket.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/ws/polymarket_us.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/synpath/ws/polymarket_us_exchange.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_candles.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_events.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_orderbook.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_series.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trades.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_delete_events_orders.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_delete_events_orders_batched.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_delete_events_orders_id.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_delete_order_groups_id.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_get_account_endpoint_costs.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_get_account_limits.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_get_balance.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_get_fills.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_get_markets.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_get_order_groups_id.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_get_orders.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_get_orders_id.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_get_orders_id_queue_position.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_get_positions.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_get_series_id.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_get_settlements.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_post_events_orders.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_post_events_orders_batched.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_post_events_orders_id_amend.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_post_events_orders_id_decrease.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_post_order_groups_create.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_put_order_groups_id_reset.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/kalshi_trading_put_order_groups_id_trigger.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/poly_book.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/poly_events.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/poly_market.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/poly_prices.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/poly_search.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/poly_trades.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/polymarket_signing_vectors.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/polyus_bbo.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/polyus_book.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/polyus_closed_market.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/polyus_events.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/polyus_market.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/polyus_prices.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/polyus_search.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/polyus_series.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/polyus_settlement.json +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/ws/kalshi_demo_transcript.jsonl +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/samples/ws/polymarket_market_transcript.jsonl +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_client.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_engine.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_engine_demo.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_engine_orders.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_engine_orders_demo.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_feeds.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_history_tracker.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_history_types.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_hosted_auth.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_journal_v3.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_kalshi.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_kalshi_demo.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_kalshi_trading.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_live.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_polymarket.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_polymarket_trading.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_polymarket_us.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_polymarket_us_trading.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_remote_client.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_risk_duplicates.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_routed_recovery.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_serve.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_server.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_server_trading.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_trading_foundations.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_venue_errors.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_ws.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_ws_grpc.py +0 -0
- {synpath-0.1.0 → synpath-0.2.0}/tests/test_ws_live.py +0 -0
|
@@ -37,3 +37,11 @@ POLYMARKET_US_PRIVATE_KEY_PATH=
|
|
|
37
37
|
POLYMARKET_US_PARTICIPANT_ID=
|
|
38
38
|
POLYMARKET_US_ACCOUNT=
|
|
39
39
|
POLYMARKET_US_ENV=preprod
|
|
40
|
+
|
|
41
|
+
# Opinion (opinion.trade, BNB Chain): the private key of the wallet connected
|
|
42
|
+
# on opinion.trade, and its Open API key (synpath init creates one by signing
|
|
43
|
+
# with that wallet). The key alone also serves the WebSocket and raises the
|
|
44
|
+
# market data rate limit. The account's Safe is read from the venue if unset.
|
|
45
|
+
OPINION_PRIVATE_KEY=
|
|
46
|
+
OPINION_API_KEY=
|
|
47
|
+
OPINION_MULTISIG_ADDRESS=
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: synpath
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: One API for prediction markets. Kalshi and Polymarket, normalized, with every price labelled by source and time.
|
|
5
5
|
Project-URL: Homepage, https://synpath.dev
|
|
6
6
|
Project-URL: Documentation, https://synpath.dev/docs
|
|
7
7
|
Project-URL: Repository, https://github.com/Synpath-ai/synpath
|
|
8
8
|
Project-URL: Issues, https://github.com/Synpath-ai/synpath/issues
|
|
9
|
-
Project-URL: Discord, https://
|
|
9
|
+
Project-URL: Discord, https://synpath.dev/discord
|
|
10
10
|
Author: Synpath
|
|
11
11
|
License: MIT
|
|
12
12
|
License-File: LICENSE
|
|
@@ -52,19 +52,19 @@ Description-Content-Type: text/markdown
|
|
|
52
52
|
|
|
53
53
|
<img src="https://raw.githubusercontent.com/Synpath-ai/synpath/main/assets/synpath-banner.png" alt="Synpath: one API for every prediction market" width="100%">
|
|
54
54
|
|
|
55
|
-
# Synpath – One API for prediction markets
|
|
55
|
+
# Synpath – One API for prediction markets <a href="https://x.com/Synpath_Dev"><img src="https://img.shields.io/twitter/url?url=https%3A%2F%2Fx.com%2FSynpath_Dev&style=social&label=Follow" alt="Follow @Synpath_Dev on X" height="28"></a>
|
|
56
56
|
|
|
57
57
|
**Build on prediction markets without building against each one.**<br/>
|
|
58
|
-
One open-source API across Kalshi, Polymarket
|
|
58
|
+
One open-source API across Kalshi, Polymarket, Polymarket US and Opinion: market data, order entry, live streams, and an execution engine that remembers what it sent. In-process from Python, over REST and WebSocket from anywhere.
|
|
59
59
|
|
|
60
60
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"></a>
|
|
61
|
-
<a href="https://
|
|
62
|
-
<a href="https://
|
|
61
|
+
<a href="https://pepy.tech/projects/synpath"><img src="https://img.shields.io/pepy/dt/synpath?label=Total%20Downloads&color=blue" alt="Total Downloads"></a>
|
|
62
|
+
<a href="https://synpath.dev/discord"><img src="https://img.shields.io/badge/Discord-join-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
|
|
63
63
|
|
|
64
64
|
</div>
|
|
65
65
|
|
|
66
|
-
**🆕 New:
|
|
67
|
-
|
|
66
|
+
**🆕 New: Smart order routing across Kalshi and Polymarket.**<br/>
|
|
67
|
+
One order, both order books combined, filled from the cheapest price after fees. **[See how it works →](https://www.synpath.dev/docs/concepts/buckets)**
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
@@ -75,10 +75,11 @@ Full-depth books and trades for every past Kalshi trade. Query any moment in und
|
|
|
75
75
|
| <img src="assets/kalshi.png" width="20" height="20" alt="Kalshi"> | [Kalshi](https://kalshi.com) | `kalshi` | ✓ | ✓ | WebSocket | one book per market, both sides read it |
|
|
76
76
|
| <img src="assets/polymarket.png" width="20" height="20" alt="Polymarket"> | [Polymarket](https://polymarket.com) | `polymarket` | ✓ | ✓ | WebSocket | one book per outcome token |
|
|
77
77
|
| <img src="assets/polymarket.png" width="20" height="20" alt="Polymarket US"> | [Polymarket US](https://polymarket.us) | `polymarket_us` | ✓ | ✓ retail and exchange APIs | WebSocket and gRPC | one book per market, both sides read it |
|
|
78
|
+
| | [Opinion](https://opinion.trade) | `opinion` | ✓ | ✓ not yet live-tested | WebSocket (API key) | one book per outcome token |
|
|
78
79
|
|
|
79
80
|
## Why Synpath
|
|
80
81
|
|
|
81
|
-
- **Liquidity is fragmented. Your time shouldn't be.** The same market trades on Kalshi, Polymarket
|
|
82
|
+
- **Liquidity is fragmented. Your time shouldn't be.** The same market trades on Kalshi, Polymarket, Polymarket US and Opinion, each with its own API, units and quirks. Synpath gives you one interface for all of them, and smart order routing buys from whichever book is cheapest. Spend your time on alpha, not plumbing.
|
|
82
83
|
|
|
83
84
|
- **Traders deserve advanced order types.** Stops, trailing stops, icebergs, OCO, brackets, TWAP and pegs, on every venue, even where the exchange has none. Orders are journaled before they're sent, so a crash never places one twice.
|
|
84
85
|
|
|
@@ -124,7 +125,7 @@ fee.estimate(price=0.50, contracts=100) # 1.75
|
|
|
124
125
|
**Same code, every venue**
|
|
125
126
|
|
|
126
127
|
```python
|
|
127
|
-
for venue_id in synpath.exchanges: # ['kalshi', 'polymarket', 'polymarket_us']
|
|
128
|
+
for venue_id in synpath.exchanges: # ['kalshi', 'polymarket', 'polymarket_us', 'opinion']
|
|
128
129
|
with synpath.exchange(venue_id) as venue:
|
|
129
130
|
page = venue.fetch_markets(limit=5)
|
|
130
131
|
|
|
@@ -157,18 +158,26 @@ async with PolymarketMarketStream() as stream:
|
|
|
157
158
|
print(event.market_id, event.side, event.best_bid, event.best_ask)
|
|
158
159
|
```
|
|
159
160
|
|
|
160
|
-
**
|
|
161
|
+
**Smart order routing: one order across Kalshi and Polymarket**
|
|
161
162
|
|
|
162
163
|
```python
|
|
163
|
-
from synpath
|
|
164
|
-
from synpath import
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
from synpath import Bucket, BucketMember, OrderType, PolymarketTrading
|
|
165
|
+
from synpath.engine import Engine, EngineConfig
|
|
166
|
+
|
|
167
|
+
creds = load_credentials()
|
|
168
|
+
async with KalshiTrading(require("kalshi", creds)) as kalshi, \
|
|
169
|
+
PolymarketTrading(require("polymarket", creds)) as poly, \
|
|
170
|
+
Engine({"kalshi": kalshi, "polymarket": poly}, EngineConfig(journal_path="trading.db")) as engine:
|
|
171
|
+
# The same market on both venues; flip=True where a venue asks the question the other way round
|
|
172
|
+
bucket = await engine.save_bucket(Bucket(book="alpha", name="Fed cut in December", members=[
|
|
173
|
+
BucketMember(market_id="kalshi:KXFEDDECISION-26DEC-C25"),
|
|
174
|
+
BucketMember(market_id="polymarket:2252244", flip=True),
|
|
175
|
+
]))
|
|
176
|
+
# Both order books combined, each level net of fees, filled from the cheapest price outward
|
|
168
177
|
await engine.submit(OrderRequest(
|
|
169
|
-
market_id=
|
|
170
|
-
type=OrderType.
|
|
171
|
-
|
|
178
|
+
market_id=bucket.market_id, side=Side.BUY, amount=Decimal("500"),
|
|
179
|
+
type=OrderType.MARKET, price=Decimal("0.45"), # the worst price you accept
|
|
180
|
+
book="alpha",
|
|
172
181
|
))
|
|
173
182
|
```
|
|
174
183
|
|
|
@@ -189,14 +198,7 @@ npx openapi-typescript openapi.json --default-non-nullable false -o src/synpath.
|
|
|
189
198
|
|
|
190
199
|
## Documentation
|
|
191
200
|
|
|
192
|
-
|
|
193
|
-
- [Unified API](https://synpath.dev/docs/sdks/python): methods, data structures, identifiers, paging, status, errors
|
|
194
|
-
- [Capabilities](https://synpath.dev/docs/concepts/exchanges): what each venue can and cannot answer
|
|
195
|
-
- [HTTP server](https://synpath.dev/docs/api-reference): routes, envelopes, typed client generation, trading routes with per-account keys, mounting behind your own auth
|
|
196
|
-
- [Trading](https://synpath.dev/docs/concepts/orders): credentials, `Decimal` money, order entry on Kalshi, Polymarket and Polymarket US
|
|
197
|
-
- [Streaming](https://synpath.dev/docs/ws-api-reference): WebSocket and gRPC books, trades, orders and fills as self-healing event streams
|
|
198
|
-
- [Engine](docs/engine.md): a journal written before anything is sent, pre-trade risk, a ledger, reconciliation, a kill switch, the order types the venues do not hold, buckets that route one order across venues, paper trading
|
|
199
|
-
- [TypeScript client](https://synpath.dev/docs/sdks/typescript): generate a typed client from the OpenAPI document, place through cancel without Python
|
|
201
|
+
See the [API Reference](https://www.synpath.dev/docs) for detailed documentation and more examples.
|
|
200
202
|
|
|
201
203
|
## Development
|
|
202
204
|
|
|
@@ -217,7 +219,7 @@ secret.
|
|
|
217
219
|
|
|
218
220
|
## Prior Art
|
|
219
221
|
|
|
220
|
-
[ccxt](https://github.com/ccxt/ccxt) set the conventions this library follows.
|
|
222
|
+
[ccxt](https://github.com/ccxt/ccxt) set the conventions this library follows.
|
|
221
223
|
|
|
222
224
|
## License
|
|
223
225
|
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
<img src="https://raw.githubusercontent.com/Synpath-ai/synpath/main/assets/synpath-banner.png" alt="Synpath: one API for every prediction market" width="100%">
|
|
4
4
|
|
|
5
|
-
# Synpath – One API for prediction markets
|
|
5
|
+
# Synpath – One API for prediction markets <a href="https://x.com/Synpath_Dev"><img src="https://img.shields.io/twitter/url?url=https%3A%2F%2Fx.com%2FSynpath_Dev&style=social&label=Follow" alt="Follow @Synpath_Dev on X" height="28"></a>
|
|
6
6
|
|
|
7
7
|
**Build on prediction markets without building against each one.**<br/>
|
|
8
|
-
One open-source API across Kalshi, Polymarket
|
|
8
|
+
One open-source API across Kalshi, Polymarket, Polymarket US and Opinion: market data, order entry, live streams, and an execution engine that remembers what it sent. In-process from Python, over REST and WebSocket from anywhere.
|
|
9
9
|
|
|
10
10
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"></a>
|
|
11
|
-
<a href="https://
|
|
12
|
-
<a href="https://
|
|
11
|
+
<a href="https://pepy.tech/projects/synpath"><img src="https://img.shields.io/pepy/dt/synpath?label=Total%20Downloads&color=blue" alt="Total Downloads"></a>
|
|
12
|
+
<a href="https://synpath.dev/discord"><img src="https://img.shields.io/badge/Discord-join-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
|
|
13
13
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
|
-
**🆕 New:
|
|
17
|
-
|
|
16
|
+
**🆕 New: Smart order routing across Kalshi and Polymarket.**<br/>
|
|
17
|
+
One order, both order books combined, filled from the cheapest price after fees. **[See how it works →](https://www.synpath.dev/docs/concepts/buckets)**
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
@@ -25,10 +25,11 @@ Full-depth books and trades for every past Kalshi trade. Query any moment in und
|
|
|
25
25
|
| <img src="assets/kalshi.png" width="20" height="20" alt="Kalshi"> | [Kalshi](https://kalshi.com) | `kalshi` | ✓ | ✓ | WebSocket | one book per market, both sides read it |
|
|
26
26
|
| <img src="assets/polymarket.png" width="20" height="20" alt="Polymarket"> | [Polymarket](https://polymarket.com) | `polymarket` | ✓ | ✓ | WebSocket | one book per outcome token |
|
|
27
27
|
| <img src="assets/polymarket.png" width="20" height="20" alt="Polymarket US"> | [Polymarket US](https://polymarket.us) | `polymarket_us` | ✓ | ✓ retail and exchange APIs | WebSocket and gRPC | one book per market, both sides read it |
|
|
28
|
+
| | [Opinion](https://opinion.trade) | `opinion` | ✓ | ✓ not yet live-tested | WebSocket (API key) | one book per outcome token |
|
|
28
29
|
|
|
29
30
|
## Why Synpath
|
|
30
31
|
|
|
31
|
-
- **Liquidity is fragmented. Your time shouldn't be.** The same market trades on Kalshi, Polymarket
|
|
32
|
+
- **Liquidity is fragmented. Your time shouldn't be.** The same market trades on Kalshi, Polymarket, Polymarket US and Opinion, each with its own API, units and quirks. Synpath gives you one interface for all of them, and smart order routing buys from whichever book is cheapest. Spend your time on alpha, not plumbing.
|
|
32
33
|
|
|
33
34
|
- **Traders deserve advanced order types.** Stops, trailing stops, icebergs, OCO, brackets, TWAP and pegs, on every venue, even where the exchange has none. Orders are journaled before they're sent, so a crash never places one twice.
|
|
34
35
|
|
|
@@ -74,7 +75,7 @@ fee.estimate(price=0.50, contracts=100) # 1.75
|
|
|
74
75
|
**Same code, every venue**
|
|
75
76
|
|
|
76
77
|
```python
|
|
77
|
-
for venue_id in synpath.exchanges: # ['kalshi', 'polymarket', 'polymarket_us']
|
|
78
|
+
for venue_id in synpath.exchanges: # ['kalshi', 'polymarket', 'polymarket_us', 'opinion']
|
|
78
79
|
with synpath.exchange(venue_id) as venue:
|
|
79
80
|
page = venue.fetch_markets(limit=5)
|
|
80
81
|
|
|
@@ -107,18 +108,26 @@ async with PolymarketMarketStream() as stream:
|
|
|
107
108
|
print(event.market_id, event.side, event.best_bid, event.best_ask)
|
|
108
109
|
```
|
|
109
110
|
|
|
110
|
-
**
|
|
111
|
+
**Smart order routing: one order across Kalshi and Polymarket**
|
|
111
112
|
|
|
112
113
|
```python
|
|
113
|
-
from synpath
|
|
114
|
-
from synpath import
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
from synpath import Bucket, BucketMember, OrderType, PolymarketTrading
|
|
115
|
+
from synpath.engine import Engine, EngineConfig
|
|
116
|
+
|
|
117
|
+
creds = load_credentials()
|
|
118
|
+
async with KalshiTrading(require("kalshi", creds)) as kalshi, \
|
|
119
|
+
PolymarketTrading(require("polymarket", creds)) as poly, \
|
|
120
|
+
Engine({"kalshi": kalshi, "polymarket": poly}, EngineConfig(journal_path="trading.db")) as engine:
|
|
121
|
+
# The same market on both venues; flip=True where a venue asks the question the other way round
|
|
122
|
+
bucket = await engine.save_bucket(Bucket(book="alpha", name="Fed cut in December", members=[
|
|
123
|
+
BucketMember(market_id="kalshi:KXFEDDECISION-26DEC-C25"),
|
|
124
|
+
BucketMember(market_id="polymarket:2252244", flip=True),
|
|
125
|
+
]))
|
|
126
|
+
# Both order books combined, each level net of fees, filled from the cheapest price outward
|
|
118
127
|
await engine.submit(OrderRequest(
|
|
119
|
-
market_id=
|
|
120
|
-
type=OrderType.
|
|
121
|
-
|
|
128
|
+
market_id=bucket.market_id, side=Side.BUY, amount=Decimal("500"),
|
|
129
|
+
type=OrderType.MARKET, price=Decimal("0.45"), # the worst price you accept
|
|
130
|
+
book="alpha",
|
|
122
131
|
))
|
|
123
132
|
```
|
|
124
133
|
|
|
@@ -139,14 +148,7 @@ npx openapi-typescript openapi.json --default-non-nullable false -o src/synpath.
|
|
|
139
148
|
|
|
140
149
|
## Documentation
|
|
141
150
|
|
|
142
|
-
|
|
143
|
-
- [Unified API](https://synpath.dev/docs/sdks/python): methods, data structures, identifiers, paging, status, errors
|
|
144
|
-
- [Capabilities](https://synpath.dev/docs/concepts/exchanges): what each venue can and cannot answer
|
|
145
|
-
- [HTTP server](https://synpath.dev/docs/api-reference): routes, envelopes, typed client generation, trading routes with per-account keys, mounting behind your own auth
|
|
146
|
-
- [Trading](https://synpath.dev/docs/concepts/orders): credentials, `Decimal` money, order entry on Kalshi, Polymarket and Polymarket US
|
|
147
|
-
- [Streaming](https://synpath.dev/docs/ws-api-reference): WebSocket and gRPC books, trades, orders and fills as self-healing event streams
|
|
148
|
-
- [Engine](docs/engine.md): a journal written before anything is sent, pre-trade risk, a ledger, reconciliation, a kill switch, the order types the venues do not hold, buckets that route one order across venues, paper trading
|
|
149
|
-
- [TypeScript client](https://synpath.dev/docs/sdks/typescript): generate a typed client from the OpenAPI document, place through cancel without Python
|
|
151
|
+
See the [API Reference](https://www.synpath.dev/docs) for detailed documentation and more examples.
|
|
150
152
|
|
|
151
153
|
## Development
|
|
152
154
|
|
|
@@ -167,7 +169,7 @@ secret.
|
|
|
167
169
|
|
|
168
170
|
## Prior Art
|
|
169
171
|
|
|
170
|
-
[ccxt](https://github.com/ccxt/ccxt) set the conventions this library follows.
|
|
172
|
+
[ccxt](https://github.com/ccxt/ccxt) set the conventions this library follows.
|
|
171
173
|
|
|
172
174
|
## License
|
|
173
175
|
|
|
@@ -68,7 +68,7 @@ and spot exchanges do not.
|
|
|
68
68
|
| Method | Returns | Notes |
|
|
69
69
|
|---|---|---|
|
|
70
70
|
| `fetch_markets(query, limit, cursor, status, sort)` | `Page[Market]` | `limit` up to 100 |
|
|
71
|
-
| `fetch_markets_by_ids(ids)` | `list[Market]` | batched (Kalshi 200, Polymarket 100, Polymarket US 50 ids a request), order preserved, closed and settled markets included |
|
|
71
|
+
| `fetch_markets_by_ids(ids)` | `list[Market]` | batched (Kalshi 200, Polymarket 100, Polymarket US 50 ids a request; Opinion has no batch lookup, one request each), order preserved, closed and settled markets included |
|
|
72
72
|
| `fetch_events(query, limit, cursor, status)` | `Page[Event]` | markets nested |
|
|
73
73
|
| `fetch_market(market_id)` | `Market` | |
|
|
74
74
|
| `fetch_order_book(market_id, side, depth)` | `OrderBook` | `side="yes"` (default) or `"no"` |
|
|
@@ -6,21 +6,21 @@ Read `has` before you call. A capability a venue lacks raises `NotSupported`
|
|
|
6
6
|
rather than returning an empty list, so "cannot" is never mistaken for "there
|
|
7
7
|
is nothing".
|
|
8
8
|
|
|
9
|
-
| Method | `kalshi` | `polymarket` | `polymarket_us` |
|
|
10
|
-
|
|
11
|
-
| `fetch_markets` | yes | yes | yes |
|
|
12
|
-
| `fetch_events` | yes | yes | yes |
|
|
13
|
-
| `fetch_market` | yes | yes | yes |
|
|
14
|
-
| `fetch_order_book` | yes | yes | yes |
|
|
15
|
-
| `fetch_order_books` (batched) | yes, one request per market | yes, one round trip | yes, one request per market |
|
|
16
|
-
| `fetch_trades` | yes | yes | no public tape over REST; the last trade is on the book |
|
|
17
|
-
| `fetch_ohlcv` | yes, the venue's candles | yes, built from the trade tape, both tokens folded into the YES price; the venue's quote samples via `source="quotes"` | partial: quote-derived bid/ask midpoints, no volume; no public tape to build from |
|
|
18
|
-
| `fetch_series` | yes | no series tier | yes, but fees are per market, not per series |
|
|
19
|
-
| `fetch_fee_schedule` | yes | yes | yes |
|
|
20
|
-
| `search` | yes ([undocumented host](api.md#search)) | yes | yes |
|
|
21
|
-
| `fetch_markets_by_ids` | yes | yes | yes |
|
|
22
|
-
| `sort` | yes, the page is ordered after it is read | yes, at the venue | yes, the page is ordered after it is read; `volume` and `liquidity` cost one read per market |
|
|
23
|
-
| `match_market` / `match_event` | no | no | no |
|
|
9
|
+
| Method | `kalshi` | `polymarket` | `polymarket_us` | `opinion` |
|
|
10
|
+
|---|---|---|---|---|
|
|
11
|
+
| `fetch_markets` | yes | yes | yes | yes, categorical topics flattened into their options |
|
|
12
|
+
| `fetch_events` | yes | yes | yes | yes, one per topic |
|
|
13
|
+
| `fetch_market` | yes | yes | yes | yes; a categorical option costs a second read for its topic |
|
|
14
|
+
| `fetch_order_book` | yes | yes | yes | yes |
|
|
15
|
+
| `fetch_order_books` (batched) | yes, one request per market | yes, one round trip | yes, one request per market | yes, one request per market |
|
|
16
|
+
| `fetch_trades` | yes | yes | no public tape over REST; the last trade is on the book | no public tape; the last trade is on `refresh_quotes` |
|
|
17
|
+
| `fetch_ohlcv` | yes, the venue's candles | yes, built from the trade tape, both tokens folded into the YES price; the venue's quote samples via `source="quotes"` | partial: quote-derived bid/ask midpoints, no volume; no public tape to build from | partial: last-trade samples, hourly or daily, no volume |
|
|
18
|
+
| `fetch_series` | yes | no series tier | yes, but fees are per market, not per series | no series tier |
|
|
19
|
+
| `fetch_fee_schedule` | yes | yes | yes | yes, read from the venue's fee contract on BNB Chain |
|
|
20
|
+
| `search` | yes ([undocumented host](api.md#search)) | yes | yes | partial: no venue search; titles matched here over 25 catalog pages |
|
|
21
|
+
| `fetch_markets_by_ids` | yes | yes | yes | yes, one request per market |
|
|
22
|
+
| `sort` | yes, the page is ordered after it is read | yes, at the venue | yes, the page is ordered after it is read; `volume` and `liquidity` cost one read per market | partial: `volume` and `newest` at the venue; no liquidity figure exists |
|
|
23
|
+
| `match_market` / `match_event` | no | no | no | no |
|
|
24
24
|
|
|
25
25
|
Polymarket US is the CFTC-regulated exchange, not the on-chain CLOB the
|
|
26
26
|
`polymarket` adapter reads; the two share a brand and nothing else. It has
|
|
@@ -29,6 +29,13 @@ marked `derived=True` as on Kalshi. Its catalog payloads carry no volume, no
|
|
|
29
29
|
sizes and no last trade; `refresh_quotes(market)` reads the book once for all
|
|
30
30
|
of them, and for the market's live state.
|
|
31
31
|
|
|
32
|
+
Opinion lists *topics*: a binary topic is one market, a categorical topic
|
|
33
|
+
holds one binary market per option, and only those trade. Each topic is an
|
|
34
|
+
event here. Its catalog carries no prices at all, so `refresh_quotes(market)`
|
|
35
|
+
reads both books and the last trade. Statuses `open`, `settled` and `all`
|
|
36
|
+
filter at the venue; `closed` cannot and raises. An API key is optional and
|
|
37
|
+
only raises the rate limit (`Opinion(api_key=...)`).
|
|
38
|
+
|
|
32
39
|
Matching spans venues, so no adapter claims it. `synpath.match_market(id)`
|
|
33
40
|
and `synpath.match_event(id)` are answered by Synpath's hosted matching service
|
|
34
41
|
at `api.synpath.dev`, with a Synpath API key (the one `synpath keys create` saved, `SYNPATH_API_KEY`, or `api_key=`). See
|
|
@@ -56,20 +63,20 @@ install. Polymarket US has two, because the venue has
|
|
|
56
63
|
two: a retail API any verified account uses, and an exchange API for
|
|
57
64
|
onboarded firms.
|
|
58
65
|
|
|
59
|
-
| Method | `kalshi` | `polymarket` | `polymarket_us` (retail) | `polymarket_us` (exchange) |
|
|
60
|
-
|
|
61
|
-
| `create_order`, `create_orders` | yes | yes | yes | yes |
|
|
62
|
-
| `cancel_order`, `cancel_orders`, `cancel_all_orders` | yes | yes | yes | yes |
|
|
63
|
-
| `edit_order` | yes, amend or decrease | no; an edit is a cancel and a new order | yes, price, quantity and time in force | yes |
|
|
64
|
-
| `fetch_order`, `fetch_open_orders` | yes | yes | yes | yes |
|
|
65
|
-
| `fetch_orders` (by status or time) | yes | no | no | yes, rationed to 12 requests a minute |
|
|
66
|
-
| `fetch_my_trades` | yes | yes | no; the activity feed carries no order id | yes |
|
|
67
|
-
| `fetch_positions`, `fetch_balance` | yes | yes | yes | yes |
|
|
68
|
-
| `fetch_settlements` | yes | yes, from the redemption activity | yes | no |
|
|
69
|
-
| `fetch_queue_position` | yes | no | no | no |
|
|
70
|
-
| `fetch_fee_estimate` | yes | yes | yes | yes |
|
|
71
|
-
| `rfq` | yes | no | no | no |
|
|
72
|
-
| `split_merge` | no; contracts are not tokens | partial: Deposit Wallets and EOAs | no | no |
|
|
66
|
+
| Method | `kalshi` | `polymarket` | `polymarket_us` (retail) | `polymarket_us` (exchange) | `opinion` (not yet live-tested) |
|
|
67
|
+
|---|---|---|---|---|---|
|
|
68
|
+
| `create_order`, `create_orders` | yes | yes | yes | yes | one at a time; `market` and `ioc` are limits with the rest cancelled |
|
|
69
|
+
| `cancel_order`, `cancel_orders`, `cancel_all_orders` | yes | yes | yes | yes | one at a time; `cancel_all_orders` lists then cancels |
|
|
70
|
+
| `edit_order` | yes, amend or decrease | no; an edit is a cancel and a new order | yes, price, quantity and time in force | yes | no |
|
|
71
|
+
| `fetch_order`, `fetch_open_orders` | yes | yes | yes | yes | yes |
|
|
72
|
+
| `fetch_orders` (by status or time) | yes | no | no | yes, rationed to 12 requests a minute | yes, by status |
|
|
73
|
+
| `fetch_my_trades` | yes | yes | no; the activity feed carries no order id | yes | yes, confirmed fills |
|
|
74
|
+
| `fetch_positions`, `fetch_balance` | yes | yes | yes | yes | yes |
|
|
75
|
+
| `fetch_settlements` | yes | yes, from the redemption activity | yes | no | no |
|
|
76
|
+
| `fetch_queue_position` | yes | no | no | no | no |
|
|
77
|
+
| `fetch_fee_estimate` | yes | yes | yes | yes | yes, from the fee contract, 0.25 USDT minimum |
|
|
78
|
+
| `rfq` | yes | no | no | no | no |
|
|
79
|
+
| `split_merge` | no; contracts are not tokens | partial: Deposit Wallets and EOAs | no | no | no |
|
|
73
80
|
|
|
74
81
|
Order types are a separate question. A venue holds `limit` and `market`;
|
|
75
82
|
everything else -- stops, icebergs, brackets, TWAP, pegs -- is held by the
|
|
@@ -93,6 +100,8 @@ Every stream answers the `watch_*` keys the same way
|
|
|
93
100
|
| `PolymarketUSPrivateStream` | no | no | no | no | yes | yes | yes | yes |
|
|
94
101
|
| `PolymarketUSExchangeMarketDataStream` | yes | yes | no | no | no | no | no | no |
|
|
95
102
|
| `PolymarketUSExchangeOrderStream` | no | no | no | no | yes | yes | no | no |
|
|
103
|
+
| `OpinionMarketStream` | yes | partial | yes | no | no | no | no | no |
|
|
104
|
+
| `OpinionUserStream` | no | no | no | no | yes | yes | no | no |
|
|
96
105
|
|
|
97
106
|
The exchange API's other gRPC streams carry one thing each and say so:
|
|
98
107
|
drop copy and trade capture carry fills, position and position-change carry
|
|
@@ -159,8 +159,12 @@ bound would walk each book to its end; a limit order on a bucket is refused. The
|
|
|
159
159
|
lays the members' books side by side in bucket terms, a flipped member's
|
|
160
160
|
YES book read as its NO book, prices every level net of the taker fee, and
|
|
161
161
|
walks from the best net price to that worst price. A leg under its venue's minimum
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
-- in contracts, or in value where the venue counts value (Opinion: 5 USDT
|
|
163
|
+
of the token bought) -- is dropped and its size moves to the next venue. A
|
|
164
|
+
venue that charges a minimum fee per order (Opinion: 0.25 USDT) has the
|
|
165
|
+
floor added to each leg's cost: a leg it pushes past the worst price is
|
|
166
|
+
dropped, and one whose size fills as fully for less elsewhere is moved. The
|
|
167
|
+
legs never sum to more than was asked.
|
|
164
168
|
|
|
165
169
|
The parent re-plans whenever a leg fills, a book moves or a leg is pulled.
|
|
166
170
|
A leg still at a price the plan wants is kept, so it holds its place in the
|
|
@@ -31,6 +31,8 @@ asyncio.run(main())
|
|
|
31
31
|
| `PolymarketUserStream` | CLOB user channel | CLOB API credentials, or a `PolymarketTrading` to derive them | orders; fills with settlement state |
|
|
32
32
|
| `PolymarketUSMarketStream` | `api.polymarket.us/v1/ws/markets` | retail API key | full books, top of book, trades, market state |
|
|
33
33
|
| `PolymarketUSPrivateStream` | `api.polymarket.us/v1/ws/private` | retail API key | orders, fills, positions, balances |
|
|
34
|
+
| `OpinionMarketStream` | `ws.opinion.trade` | Opinion API key (every channel needs one) | books, trades, last prices |
|
|
35
|
+
| `OpinionUserStream` | `ws.opinion.trade` | Opinion API key | orders; fills once the chain confirms them |
|
|
34
36
|
|
|
35
37
|
Each stream's `has` answers the `watch_*` capability keys (`watch_order_book`,
|
|
36
38
|
`watch_ticker`, `watch_trades`, `watch_market_status`, `watch_orders`,
|
|
@@ -85,6 +87,15 @@ jittered exponential backoff between attempts.
|
|
|
85
87
|
a fresh snapshot.
|
|
86
88
|
- *Polymarket US* sends the whole visible book every time, so books cannot
|
|
87
89
|
drift.
|
|
90
|
+
- *Opinion* sends one changed level per message, with no snapshot and no
|
|
91
|
+
sequence number. The stream reads each book over REST once the
|
|
92
|
+
subscription is live and replays the changes that arrived during the read.
|
|
93
|
+
A change is read as the level's whole size (the venue documents `size` as
|
|
94
|
+
the level's shares), so replaying one the snapshot already holds is
|
|
95
|
+
harmless; `tests/test_opinion_ws_live.py` checks this against the venue.
|
|
96
|
+
Nothing in the feed reveals a missed message, so every book is read again
|
|
97
|
+
on a timer (`resync_interval`, 60 s by default): a book that disagrees
|
|
98
|
+
while no change was in flight is a `gap`, and is replaced.
|
|
88
99
|
|
|
89
100
|
**A book is right or marked not ready.** After a gap or a disconnect the
|
|
90
101
|
book's `ready` is false until a snapshot arrives; deltas are not applied to
|
|
@@ -214,6 +225,10 @@ documented heartbeat and rely on gRPC keepalive.
|
|
|
214
225
|
covers.
|
|
215
226
|
- **Polymarket US:** built to the venue's documentation and SDK, with the
|
|
216
227
|
envelope spellings both of them accept.
|
|
228
|
+
- **Opinion:** built to the venue's documentation and SDK samples, with the
|
|
229
|
+
REST books recorded for the read adapter. `tests/test_opinion_ws_live.py`
|
|
230
|
+
checks streamed books against the venue's own over a live minute; it needs
|
|
231
|
+
`OPINION_API_KEY`.
|
|
217
232
|
- **Polymarket US exchange API (gRPC):** a real local gRPC server speaking
|
|
218
233
|
the venue's own compiled messages. It covers a refused token, a snapshot,
|
|
219
234
|
an execution report and a drop, and a drop-copy resume token surviving a
|
|
@@ -53,6 +53,7 @@ they point at and the public half of the identity. It prints no secret.
|
|
|
53
53
|
| Polymarket | `POLYMARKET_PRIVATE_KEY`, `POLYMARKET_SIGNATURE_TYPE` (3 Deposit Wallet, used by every account created since May 2026 and what `synpath init` suggests; 1 proxy, 2 Safe, 0 allowlisted EOA, which is also what an unset value means), `POLYMARKET_FUNDER` (the wallet address, required for 1-3); optional `POLYMARKET_API_KEY` / `_API_SECRET` / `_API_PASSPHRASE` (derived when absent), `POLYMARKET_BUILDER_CODE`, `POLYMARKET_RELAYER_API_KEY` / `_RELAYER_API_KEY_ADDRESS` (gasless wallet transactions), `POLYMARKET_RPC_URL` (an EOA's own transactions) |
|
|
54
54
|
| Polymarket US, retail API | `POLYMARKET_US_KEY_ID`, `POLYMARKET_US_SECRET_KEY` (from polymarket.us/developer) |
|
|
55
55
|
| Polymarket US, exchange API | `POLYMARKET_US_CLIENT_ID`, `POLYMARKET_US_PRIVATE_KEY_PATH`, `POLYMARKET_US_PARTICIPANT_ID`, `POLYMARKET_US_ACCOUNT` (optional), `POLYMARKET_US_ENV` = `preprod` or `prod` |
|
|
56
|
+
| Opinion | `OPINION_PRIVATE_KEY` (the wallet connected on opinion.trade), `OPINION_API_KEY` (`synpath init` creates it by signing with that wallet), `OPINION_MULTISIG_ADDRESS` (optional; the account's Safe, read from the venue when absent) |
|
|
56
57
|
|
|
57
58
|
## Through your own server
|
|
58
59
|
|
|
@@ -316,3 +317,32 @@ five-second latency stopgap, not a rate limit, and maps to `OrderRejected`
|
|
|
316
317
|
with `reason="latency_stopgap"`: safe to resend. Venue `day` orders do not
|
|
317
318
|
cancel at the session roll, so `day` is refused here as on the other venues.
|
|
318
319
|
|
|
320
|
+
## Opinion
|
|
321
|
+
|
|
322
|
+
`OpinionTrading`. **Not yet tested against the live venue**: it is built to
|
|
323
|
+
Opinion's documentation and its own SDK, with signing and amounts checked
|
|
324
|
+
byte for byte against the SDK's code. Start small.
|
|
325
|
+
|
|
326
|
+
Orders are the CTF exchange's EIP-712 `Order` on BNB Chain, signed by the
|
|
327
|
+
wallet for the account's Safe, which holds the USDT and the tokens; the
|
|
328
|
+
Safe's address is read from the venue when not configured. YES and NO are
|
|
329
|
+
separate tokens, as on Polymarket: `buy` buys YES, `sell` buys NO at
|
|
330
|
+
`1 - price`, and with `reduce_only` they sell the tokens held instead.
|
|
331
|
+
|
|
332
|
+
The venue rests limit orders until cancelled and has nothing else: no time
|
|
333
|
+
in force, no post-only, and a market buy that spends a USDT amount rather
|
|
334
|
+
than taking a number of contracts. So `market` and `ioc` are sent as a limit
|
|
335
|
+
at the price given and the unmatched rest is cancelled at once; `fok`, `gtd`
|
|
336
|
+
and `post_only` are refused. The two amounts of an order must state its
|
|
337
|
+
price exactly, which trims the size to four significant digits of its USDT
|
|
338
|
+
value: the order's `amount` is what was actually sent. The minimum order is
|
|
339
|
+
5 USDT. Fees are taker only, `rate * notional * p * (1 - p)` with the rate
|
|
340
|
+
read from the venue's fee contract, and at least 0.25 USDT an order.
|
|
341
|
+
|
|
342
|
+
A match moves an order's filled figures, and produces a trade, only once the
|
|
343
|
+
chain confirms it. `OpinionUserStream` carries both
|
|
344
|
+
([Streaming](streaming.md)). The one-time approvals ("enable trading") are
|
|
345
|
+
made on opinion.trade; split, merge and redeem are not built here.
|
|
346
|
+
|
|
347
|
+
Opinion's terms bar residents and citizens of several countries, the UK and
|
|
348
|
+
US among them: check them before trading.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "synpath"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "One API for prediction markets. Kalshi and Polymarket, normalized, with every price labelled by source and time."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -57,7 +57,7 @@ Homepage = "https://synpath.dev"
|
|
|
57
57
|
Documentation = "https://synpath.dev/docs"
|
|
58
58
|
Repository = "https://github.com/Synpath-ai/synpath"
|
|
59
59
|
Issues = "https://github.com/Synpath-ai/synpath/issues"
|
|
60
|
-
Discord = "https://
|
|
60
|
+
Discord = "https://synpath.dev/discord"
|
|
61
61
|
|
|
62
62
|
[tool.hatch.build.targets.wheel]
|
|
63
63
|
packages = ["synpath"]
|
|
@@ -42,6 +42,7 @@ from .errors import (
|
|
|
42
42
|
from .kalshi import Kalshi
|
|
43
43
|
from .polymarket import Polymarket
|
|
44
44
|
from .polymarket_us import PolymarketUS
|
|
45
|
+
from .opinion import Opinion
|
|
45
46
|
from .matching import match_event, match_market
|
|
46
47
|
from .history import fetch_order_book_at, fetch_order_book_range, fetch_trades_range
|
|
47
48
|
from .bucket import Bucket, BucketMember, BucketOrderReport, BucketPosition
|
|
@@ -75,12 +76,13 @@ from .types import (
|
|
|
75
76
|
TradesRangeResponse,
|
|
76
77
|
)
|
|
77
78
|
|
|
78
|
-
__version__ = "0.
|
|
79
|
+
__version__ = "0.2.0"
|
|
79
80
|
|
|
80
81
|
exchanges: dict[str, type[Exchange]] = {
|
|
81
82
|
"kalshi": Kalshi,
|
|
82
83
|
"polymarket": Polymarket,
|
|
83
84
|
"polymarket_us": PolymarketUS,
|
|
85
|
+
"opinion": Opinion,
|
|
84
86
|
}
|
|
85
87
|
"""Every venue this library speaks, by id."""
|
|
86
88
|
|
|
@@ -101,6 +103,7 @@ def exchange(venue: str, **kwargs) -> Exchange:
|
|
|
101
103
|
# keeps working. Imported last, after the read API it builds on.
|
|
102
104
|
from .trading import (
|
|
103
105
|
TradingExchange, KalshiTrading, PolymarketTrading, PolymarketUSTrading, PolymarketUSExchangeTrading,
|
|
106
|
+
OpinionTrading,
|
|
104
107
|
Account, Balance, EditRequest, FeeEstimate, Fill, HeldBy, Liquidity,
|
|
105
108
|
Order, OrderRequest, OrderStatus, OrderType, Position, PositionSide,
|
|
106
109
|
Precision, Settlement, SettlementState, Side, TimeInForce,
|
|
@@ -128,6 +131,7 @@ _STREAM_CLASSES = (
|
|
|
128
131
|
"PolymarketUSExchangeTradeCaptureStream", "PolymarketUSExchangePositionChangeStream",
|
|
129
132
|
"PolymarketUSExchangeInstrumentStream", "PolymarketUSExchangePositionStream",
|
|
130
133
|
"PolymarketUSExchangeMarketDataStream", "PolymarketUSExchangeBalanceLedgerStream",
|
|
134
|
+
"OpinionMarketStream", "OpinionUserStream",
|
|
131
135
|
)
|
|
132
136
|
_SERVER_NAMES = ("create_app", "create_trading_app", "VenueRegistry", "ControlStore", "Principal")
|
|
133
137
|
|
|
@@ -149,7 +153,7 @@ def __getattr__(name: str):
|
|
|
149
153
|
|
|
150
154
|
|
|
151
155
|
__all__ = [
|
|
152
|
-
"Kalshi", "Polymarket", "PolymarketUS", "Exchange", "exchange", "exchanges",
|
|
156
|
+
"Kalshi", "Polymarket", "PolymarketUS", "Opinion", "Exchange", "exchange", "exchanges",
|
|
153
157
|
"Capability", "HttpClient", "RateLimiter",
|
|
154
158
|
"Market", "Event", "Outcome", "Quote", "MarketStats", "OrderBook",
|
|
155
159
|
"OrderLevel", "Trade", "Candle", "FeeSchedule", "Series", "Page",
|
|
@@ -166,6 +170,7 @@ __all__ = [
|
|
|
166
170
|
"MarketNotFound", "NotSupported", "AuthenticationError",
|
|
167
171
|
# order entry
|
|
168
172
|
"TradingExchange", "KalshiTrading", "PolymarketTrading", "PolymarketUSTrading", "PolymarketUSExchangeTrading",
|
|
173
|
+
"OpinionTrading",
|
|
169
174
|
"Account", "Balance", "EditRequest", "FeeEstimate", "Fill", "HeldBy", "Liquidity",
|
|
170
175
|
"Order", "OrderRequest", "OrderStatus", "OrderType", "Position", "PositionSide",
|
|
171
176
|
"Precision", "Settlement", "SettlementState", "Side", "TimeInForce",
|
|
@@ -353,4 +353,9 @@ def _polymarket_us(creds: Credentials) -> TradingExchange:
|
|
|
353
353
|
return PolymarketUSTrading(creds) # type: ignore[arg-type]
|
|
354
354
|
|
|
355
355
|
|
|
356
|
-
|
|
356
|
+
def _opinion(creds: Credentials) -> TradingExchange:
|
|
357
|
+
from .trading.opinion import OpinionTrading
|
|
358
|
+
return OpinionTrading(creds) # type: ignore[arg-type]
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
_TRADING = {"kalshi": _kalshi, "polymarket": _polymarket, "polymarket_us": _polymarket_us, "opinion": _opinion}
|
|
@@ -60,6 +60,7 @@ ADAPTERS = {
|
|
|
60
60
|
"polymarket": ("..trading.polymarket", "PolymarketTrading"),
|
|
61
61
|
"polymarket_us": ("..trading.polymarket_us", "PolymarketUSTrading"),
|
|
62
62
|
"polymarket_us_exchange": ("..trading.polymarket_us_exchange", "PolymarketUSExchangeTrading"),
|
|
63
|
+
"opinion": ("..trading.opinion", "OpinionTrading"),
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
|
|
@@ -71,6 +71,13 @@ def default_streams(adapters: Mapping[str, Any], credentials: Mapping[str, Any])
|
|
|
71
71
|
elif venue == "polymarket_us" and creds is not None:
|
|
72
72
|
from ..ws.polymarket_us import PolymarketUSMarketStream, PolymarketUSPrivateStream
|
|
73
73
|
out[venue] = VenueStreams(market=PolymarketUSMarketStream(creds), private=PolymarketUSPrivateStream(creds))
|
|
74
|
+
elif venue == "opinion" and creds is not None:
|
|
75
|
+
from ..ws.opinion import OpinionMarketStream, OpinionUserStream
|
|
76
|
+
catalog = getattr(adapter, "catalog", None)
|
|
77
|
+
out[venue] = VenueStreams(
|
|
78
|
+
market=OpinionMarketStream(api_key=creds.api_key, catalog=catalog),
|
|
79
|
+
private=OpinionUserStream(api_key=creds.api_key, catalog=catalog),
|
|
80
|
+
)
|
|
74
81
|
else:
|
|
75
82
|
log.info("synpath.engine.feeds: no stream for %s; its orders reach the engine by polling", venue)
|
|
76
83
|
return out
|