synpath 0.3.0__cp310-abi3-win_amd64.whl
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/__init__.py +188 -0
- synpath/__main__.py +66 -0
- synpath/_core.pyd +0 -0
- synpath/_native.py +32 -0
- synpath/base.py +723 -0
- synpath/bucket.py +154 -0
- synpath/client.py +361 -0
- synpath/engine/__init__.py +37 -0
- synpath/engine/__main__.py +355 -0
- synpath/engine/alerts.py +170 -0
- synpath/engine/engine.py +888 -0
- synpath/engine/eod.py +154 -0
- synpath/engine/events.py +140 -0
- synpath/engine/fair_values.py +117 -0
- synpath/engine/feeds.py +227 -0
- synpath/engine/journal.py +907 -0
- synpath/engine/ledger.py +353 -0
- synpath/engine/orders/__init__.py +42 -0
- synpath/engine/orders/base.py +441 -0
- synpath/engine/orders/day.py +72 -0
- synpath/engine/orders/iceberg.py +121 -0
- synpath/engine/orders/manager.py +223 -0
- synpath/engine/orders/oco.py +255 -0
- synpath/engine/orders/peg.py +168 -0
- synpath/engine/orders/routed.py +526 -0
- synpath/engine/orders/stop.py +240 -0
- synpath/engine/orders/taker.py +187 -0
- synpath/engine/orders/twap.py +190 -0
- synpath/engine/paper.py +532 -0
- synpath/engine/reconcile.py +279 -0
- synpath/engine/risk.py +403 -0
- synpath/engine/router.py +406 -0
- synpath/errors.py +98 -0
- synpath/history.py +71 -0
- synpath/hosted.py +86 -0
- synpath/hosted_auth.py +201 -0
- synpath/ids.py +61 -0
- synpath/kalshi.py +1378 -0
- synpath/matching.py +93 -0
- synpath/opinion.py +799 -0
- synpath/polymarket.py +1004 -0
- synpath/polymarket_us.py +989 -0
- synpath/remote.py +195 -0
- synpath/server/__init__.py +98 -0
- synpath/server/__main__.py +118 -0
- synpath/server/api.py +439 -0
- synpath/server/errors.py +87 -0
- synpath/server/local.py +96 -0
- synpath/server/models.py +75 -0
- synpath/server/serve.py +236 -0
- synpath/server/store.py +363 -0
- synpath/server/trading.py +764 -0
- synpath/trading/__init__.py +83 -0
- synpath/trading/__main__.py +75 -0
- synpath/trading/base.py +126 -0
- synpath/trading/credentials.py +444 -0
- synpath/trading/errors.py +94 -0
- synpath/trading/init.py +250 -0
- synpath/trading/instruments.py +167 -0
- synpath/trading/kalshi.py +957 -0
- synpath/trading/limiter.py +177 -0
- synpath/trading/money.py +172 -0
- synpath/trading/opinion.py +760 -0
- synpath/trading/opinion_signing.py +174 -0
- synpath/trading/polymarket.py +1362 -0
- synpath/trading/polymarket_signing.py +523 -0
- synpath/trading/polymarket_us.py +705 -0
- synpath/trading/polymarket_us_exchange.py +825 -0
- synpath/trading/types.py +421 -0
- synpath/types.py +622 -0
- synpath/ws/__init__.py +59 -0
- synpath/ws/base.py +564 -0
- synpath/ws/grpc.py +578 -0
- synpath/ws/kalshi.py +451 -0
- synpath/ws/opinion.py +486 -0
- synpath/ws/polymarket.py +457 -0
- synpath/ws/polymarket_us.py +317 -0
- synpath/ws/polymarket_us_exchange.py +754 -0
- synpath-0.3.0.dist-info/METADATA +233 -0
- synpath-0.3.0.dist-info/RECORD +84 -0
- synpath-0.3.0.dist-info/WHEEL +4 -0
- synpath-0.3.0.dist-info/entry_points.txt +2 -0
- synpath-0.3.0.dist-info/licenses/LICENSE +21 -0
- synpath-0.3.0.dist-info/sboms/synpath-python.cyclonedx.json +3117 -0
synpath/__init__.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""synpath — one API for prediction markets.
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
import synpath
|
|
5
|
+
|
|
6
|
+
kalshi = synpath.Kalshi()
|
|
7
|
+
markets = kalshi.fetch_markets(limit=5)
|
|
8
|
+
|
|
9
|
+
market = markets[0]
|
|
10
|
+
yes = market.yes
|
|
11
|
+
print(market.title, yes.quote.bid, yes.quote.ask, yes.quote.last)
|
|
12
|
+
|
|
13
|
+
book = kalshi.fetch_order_book(yes.id)
|
|
14
|
+
print(book.best_bid, book.best_ask)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Every venue exposes the same methods and returns the same types. What it will
|
|
18
|
+
not do is make two venues look more alike than they are: the unit a volume is
|
|
19
|
+
counted in, where a candle's prices came from, and whether a price is missing
|
|
20
|
+
or merely zero are all carried in the response instead of being smoothed away.
|
|
21
|
+
|
|
22
|
+
Check `exchange.has` before calling — a capability a venue lacks raises
|
|
23
|
+
`NotSupported` rather than returning an empty list.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from . import ids
|
|
28
|
+
from .base import Capability, Exchange, HttpClient, RateLimiter
|
|
29
|
+
from .errors import (
|
|
30
|
+
AuthenticationError,
|
|
31
|
+
BadRequest,
|
|
32
|
+
BadSymbol,
|
|
33
|
+
ExchangeError,
|
|
34
|
+
ExchangeNotAvailable,
|
|
35
|
+
MarketNotFound,
|
|
36
|
+
NetworkError,
|
|
37
|
+
NotSupported,
|
|
38
|
+
RateLimitExceeded,
|
|
39
|
+
RequestTimeout,
|
|
40
|
+
SynpathError,
|
|
41
|
+
)
|
|
42
|
+
from .kalshi import Kalshi
|
|
43
|
+
from .polymarket import Polymarket
|
|
44
|
+
from .polymarket_us import PolymarketUS
|
|
45
|
+
from .opinion import Opinion
|
|
46
|
+
from .matching import match_event, match_market
|
|
47
|
+
from .history import fetch_order_book_at, fetch_order_book_range, fetch_trades_range
|
|
48
|
+
from .bucket import Bucket, BucketMember, BucketOrderReport, BucketPosition
|
|
49
|
+
from .types import (
|
|
50
|
+
BookModel,
|
|
51
|
+
Candle,
|
|
52
|
+
Event,
|
|
53
|
+
EventMatch,
|
|
54
|
+
FeeSchedule,
|
|
55
|
+
Outcome,
|
|
56
|
+
Market,
|
|
57
|
+
MarketLink,
|
|
58
|
+
MarketMatch,
|
|
59
|
+
MarketStats,
|
|
60
|
+
MarketStatus,
|
|
61
|
+
OrderBook,
|
|
62
|
+
OrderLevel,
|
|
63
|
+
Page,
|
|
64
|
+
PriceSource,
|
|
65
|
+
Quote,
|
|
66
|
+
Series,
|
|
67
|
+
Trade,
|
|
68
|
+
HistoryMetadata,
|
|
69
|
+
HistoryCoverage,
|
|
70
|
+
HistoricalTrade,
|
|
71
|
+
HistoricalOrderBook,
|
|
72
|
+
HistoricalBookChange,
|
|
73
|
+
HistoricalBookSegment,
|
|
74
|
+
OrderBookAtResponse,
|
|
75
|
+
OrderBookRangeResponse,
|
|
76
|
+
TradesRangeResponse,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
__version__ = "0.3.0"
|
|
80
|
+
|
|
81
|
+
exchanges: dict[str, type[Exchange]] = {
|
|
82
|
+
"kalshi": Kalshi,
|
|
83
|
+
"polymarket": Polymarket,
|
|
84
|
+
"polymarket_us": PolymarketUS,
|
|
85
|
+
"opinion": Opinion,
|
|
86
|
+
}
|
|
87
|
+
"""Every venue this library speaks, by id."""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def exchange(venue: str, **kwargs) -> Exchange:
|
|
91
|
+
"""Construct a venue adapter by id: `synpath.exchange("kalshi")`."""
|
|
92
|
+
try:
|
|
93
|
+
return exchanges[venue](**kwargs)
|
|
94
|
+
except KeyError:
|
|
95
|
+
raise BadRequest(
|
|
96
|
+
f"unknown venue {venue!r}; available: {', '.join(sorted(exchanges))}"
|
|
97
|
+
) from None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# Order entry lives in `synpath.trading`, but it is part of the same install
|
|
101
|
+
# and the same API, so its public names are available here too:
|
|
102
|
+
# `from synpath import KalshiTrading, OrderRequest`. The subpackage import
|
|
103
|
+
# keeps working. Imported last, after the read API it builds on.
|
|
104
|
+
from .trading import (
|
|
105
|
+
TradingExchange, KalshiTrading, PolymarketTrading, PolymarketUSTrading, PolymarketUSExchangeTrading,
|
|
106
|
+
OpinionTrading,
|
|
107
|
+
Account, Balance, EditRequest, FeeEstimate, Fill, HeldBy, Liquidity,
|
|
108
|
+
Order, OrderRequest, OrderStatus, OrderType, Position, PositionSide,
|
|
109
|
+
Precision, Settlement, SettlementState, Side, TimeInForce,
|
|
110
|
+
CredentialsMissing, DuplicateClientOrderId, InsufficientFunds, InvalidOrder,
|
|
111
|
+
MarketHalted, OrderNotFound, OrderRejected, PermissionDenied,
|
|
112
|
+
RateBudgetExceeded, RiskRejected,
|
|
113
|
+
)
|
|
114
|
+
from .trading.credentials import load_credentials, require
|
|
115
|
+
from .client import Client
|
|
116
|
+
|
|
117
|
+
# Live streams live in `synpath.ws` and are exported here too. The event types
|
|
118
|
+
# come in eagerly; the stream classes are resolved on first use through
|
|
119
|
+
# `__getattr__` below, the same way `synpath.ws` itself defers them. The
|
|
120
|
+
# streams' base event class is `synpath.ws.Event`; here it is `StreamEvent`,
|
|
121
|
+
# because `synpath.Event` is already a venue event (a group of markets).
|
|
122
|
+
from .ws import (
|
|
123
|
+
Stream, StreamStats, BookEvent, BookLevel, QuoteEvent, TradeEvent, OrderEvent, FillEvent, PositionEvent,
|
|
124
|
+
BalanceEvent, MarketStatusEvent, VenueEvent, StreamStatusEvent, LocalBook,
|
|
125
|
+
)
|
|
126
|
+
from .ws import Event as StreamEvent
|
|
127
|
+
|
|
128
|
+
_STREAM_CLASSES = (
|
|
129
|
+
"KalshiStream", "PolymarketMarketStream", "PolymarketUserStream", "PolymarketUSMarketStream",
|
|
130
|
+
"PolymarketUSPrivateStream", "PolymarketUSExchangeOrderStream", "PolymarketUSExchangeDropCopyStream",
|
|
131
|
+
"PolymarketUSExchangeTradeCaptureStream", "PolymarketUSExchangePositionChangeStream",
|
|
132
|
+
"PolymarketUSExchangeInstrumentStream", "PolymarketUSExchangePositionStream",
|
|
133
|
+
"PolymarketUSExchangeMarketDataStream", "PolymarketUSExchangeBalanceLedgerStream",
|
|
134
|
+
"OpinionMarketStream", "OpinionUserStream",
|
|
135
|
+
)
|
|
136
|
+
_SERVER_NAMES = ("create_app", "create_trading_app", "VenueRegistry", "ControlStore", "Principal")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def __getattr__(name: str):
|
|
140
|
+
"""Stream classes and the HTTP server, resolved on first use.
|
|
141
|
+
|
|
142
|
+
Both are part of the package, but neither is imported by `import synpath`:
|
|
143
|
+
a stream module pulls in a venue's WebSocket client, and the server pulls
|
|
144
|
+
in FastAPI, and a notebook reading quotes needs neither.
|
|
145
|
+
"""
|
|
146
|
+
if name in _STREAM_CLASSES:
|
|
147
|
+
from . import ws
|
|
148
|
+
return getattr(ws, name)
|
|
149
|
+
if name in _SERVER_NAMES:
|
|
150
|
+
from . import server
|
|
151
|
+
return getattr(server, name)
|
|
152
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
__all__ = [
|
|
156
|
+
"Kalshi", "Polymarket", "PolymarketUS", "Opinion", "Exchange", "exchange", "exchanges",
|
|
157
|
+
"Capability", "HttpClient", "RateLimiter",
|
|
158
|
+
"Market", "Event", "Outcome", "Quote", "MarketStats", "OrderBook",
|
|
159
|
+
"OrderLevel", "Trade", "Candle", "FeeSchedule", "Series", "Page",
|
|
160
|
+
"HistoryMetadata", "HistoryCoverage", "HistoricalTrade", "HistoricalOrderBook",
|
|
161
|
+
"HistoricalBookChange", "HistoricalBookSegment", "OrderBookAtResponse",
|
|
162
|
+
"OrderBookRangeResponse", "TradesRangeResponse",
|
|
163
|
+
"match_market", "match_event", "ids",
|
|
164
|
+
"fetch_order_book_at", "fetch_order_book_range", "fetch_trades_range",
|
|
165
|
+
"MarketMatch", "EventMatch", "MarketLink",
|
|
166
|
+
"Bucket", "BucketMember", "BucketOrderReport", "BucketPosition",
|
|
167
|
+
"MarketStatus", "BookModel", "PriceSource",
|
|
168
|
+
"SynpathError", "NetworkError", "ExchangeError", "ExchangeNotAvailable",
|
|
169
|
+
"RequestTimeout", "RateLimitExceeded", "BadRequest", "BadSymbol",
|
|
170
|
+
"MarketNotFound", "NotSupported", "AuthenticationError",
|
|
171
|
+
# order entry
|
|
172
|
+
"TradingExchange", "KalshiTrading", "PolymarketTrading", "PolymarketUSTrading", "PolymarketUSExchangeTrading",
|
|
173
|
+
"OpinionTrading",
|
|
174
|
+
"Account", "Balance", "EditRequest", "FeeEstimate", "Fill", "HeldBy", "Liquidity",
|
|
175
|
+
"Order", "OrderRequest", "OrderStatus", "OrderType", "Position", "PositionSide",
|
|
176
|
+
"Precision", "Settlement", "SettlementState", "Side", "TimeInForce",
|
|
177
|
+
"CredentialsMissing", "DuplicateClientOrderId", "InsufficientFunds", "InvalidOrder",
|
|
178
|
+
"MarketHalted", "OrderNotFound", "OrderRejected", "PermissionDenied",
|
|
179
|
+
"RateBudgetExceeded", "RiskRejected",
|
|
180
|
+
"load_credentials", "require", "Client",
|
|
181
|
+
# live streams
|
|
182
|
+
"Stream", "StreamStats", "StreamEvent", "BookEvent", "BookLevel", "QuoteEvent", "TradeEvent", "OrderEvent",
|
|
183
|
+
"FillEvent", "PositionEvent", "BalanceEvent", "MarketStatusEvent", "VenueEvent", "StreamStatusEvent",
|
|
184
|
+
"LocalBook", *_STREAM_CLASSES,
|
|
185
|
+
# HTTP server
|
|
186
|
+
*_SERVER_NAMES,
|
|
187
|
+
"__version__",
|
|
188
|
+
]
|
synpath/__main__.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""`synpath`: one command for everything the package runs.
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
synpath init # asks for your venue keys and writes .env (readable by you only)
|
|
5
|
+
synpath serve # engine + streams + HTTP API, in one process
|
|
6
|
+
synpath doctor # which venue credentials load (prints no secret)
|
|
7
|
+
synpath status | halt | resume | eod # ask or tell a running engine, through its journal
|
|
8
|
+
synpath run --config engine.toml # the engine alone, no HTTP
|
|
9
|
+
synpath schema [--trading] --out openapi.json
|
|
10
|
+
synpath bootstrap # the first key for a control database
|
|
11
|
+
synpath login # Google sign-in in your browser; return to CLI
|
|
12
|
+
synpath keys create|list|revoke # manage hosted history API keys
|
|
13
|
+
synpath logout # end the key-management session
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`synpath engine ...`, `synpath server ...` and `synpath trading ...` reach
|
|
17
|
+
the underlying `python -m synpath.<module>` commands with every option.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import importlib
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
MODULES = {
|
|
25
|
+
"serve": "synpath.server.serve",
|
|
26
|
+
"engine": "synpath.engine.__main__",
|
|
27
|
+
"server": "synpath.server.__main__",
|
|
28
|
+
"trading": "synpath.trading.__main__",
|
|
29
|
+
"init": "synpath.trading.init",
|
|
30
|
+
"login": "synpath.hosted_auth",
|
|
31
|
+
"keys": "synpath.hosted_auth",
|
|
32
|
+
"logout": "synpath.hosted_auth",
|
|
33
|
+
}
|
|
34
|
+
SHORTCUTS = {
|
|
35
|
+
"run": ("engine", ["run"]),
|
|
36
|
+
"status": ("engine", ["status"]),
|
|
37
|
+
"halt": ("engine", ["halt"]),
|
|
38
|
+
"resume": ("engine", ["resume"]),
|
|
39
|
+
"eod": ("engine", ["eod"]),
|
|
40
|
+
"schema": ("server", ["schema"]),
|
|
41
|
+
"bootstrap": ("server", ["bootstrap"]),
|
|
42
|
+
"doctor": ("trading", ["doctor"]),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
USAGE = __doc__.split("```bash", 1)[1].split("```", 1)[0].strip()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def main(argv: list[str] | None = None) -> int:
|
|
49
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
50
|
+
if not argv or argv[0] in ("-h", "--help", "help"):
|
|
51
|
+
print("usage: synpath <command> [options]\n\n" + USAGE)
|
|
52
|
+
return 0
|
|
53
|
+
command, rest = argv[0], argv[1:]
|
|
54
|
+
if command in SHORTCUTS:
|
|
55
|
+
command, prefix = SHORTCUTS[command]
|
|
56
|
+
rest = prefix + rest
|
|
57
|
+
if command not in MODULES:
|
|
58
|
+
print(f"synpath: unknown command {command!r}\n\nusage: synpath <command> [options]\n\n" + USAGE, file=sys.stderr)
|
|
59
|
+
return 2
|
|
60
|
+
module = importlib.import_module(MODULES[command])
|
|
61
|
+
result = getattr(module, command if command in ("login", "keys", "logout") else "main")(rest)
|
|
62
|
+
return int(result or 0)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
raise SystemExit(main())
|
synpath/_core.pyd
ADDED
|
Binary file
|
synpath/_native.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Which implementation of the hot paths is in use: the Rust core or Python.
|
|
2
|
+
|
|
3
|
+
The Rust core ships inside the wheel as `synpath._core`. Every class it
|
|
4
|
+
provides has a pure-Python twin with the same interface, so the library works
|
|
5
|
+
the same either way and the choice is invisible to callers:
|
|
6
|
+
|
|
7
|
+
* the Rust one, when the extension is installed (every published wheel);
|
|
8
|
+
* the Python one, when it is not (a source checkout without Rust), or when
|
|
9
|
+
`SYNPATH_PURE_PYTHON=1` is set -- which the test suite uses to run every
|
|
10
|
+
test against both.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
FORCED_PURE = os.environ.get("SYNPATH_PURE_PYTHON", "").strip().lower() in ("1", "true", "yes")
|
|
18
|
+
|
|
19
|
+
core: Any = None
|
|
20
|
+
if not FORCED_PURE:
|
|
21
|
+
try:
|
|
22
|
+
from . import _core as core # type: ignore[no-redef]
|
|
23
|
+
except ImportError: # pragma: no cover - only without a built extension
|
|
24
|
+
core = None
|
|
25
|
+
|
|
26
|
+
ENABLED = core is not None
|
|
27
|
+
"""True when the Rust core is in use."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def pick(name: str, fallback: Any) -> Any:
|
|
31
|
+
"""`synpath._core.<name>` when the Rust core is in use, else `fallback`."""
|
|
32
|
+
return getattr(core, name) if core is not None else fallback
|