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.
Files changed (84) hide show
  1. synpath/__init__.py +188 -0
  2. synpath/__main__.py +66 -0
  3. synpath/_core.pyd +0 -0
  4. synpath/_native.py +32 -0
  5. synpath/base.py +723 -0
  6. synpath/bucket.py +154 -0
  7. synpath/client.py +361 -0
  8. synpath/engine/__init__.py +37 -0
  9. synpath/engine/__main__.py +355 -0
  10. synpath/engine/alerts.py +170 -0
  11. synpath/engine/engine.py +888 -0
  12. synpath/engine/eod.py +154 -0
  13. synpath/engine/events.py +140 -0
  14. synpath/engine/fair_values.py +117 -0
  15. synpath/engine/feeds.py +227 -0
  16. synpath/engine/journal.py +907 -0
  17. synpath/engine/ledger.py +353 -0
  18. synpath/engine/orders/__init__.py +42 -0
  19. synpath/engine/orders/base.py +441 -0
  20. synpath/engine/orders/day.py +72 -0
  21. synpath/engine/orders/iceberg.py +121 -0
  22. synpath/engine/orders/manager.py +223 -0
  23. synpath/engine/orders/oco.py +255 -0
  24. synpath/engine/orders/peg.py +168 -0
  25. synpath/engine/orders/routed.py +526 -0
  26. synpath/engine/orders/stop.py +240 -0
  27. synpath/engine/orders/taker.py +187 -0
  28. synpath/engine/orders/twap.py +190 -0
  29. synpath/engine/paper.py +532 -0
  30. synpath/engine/reconcile.py +279 -0
  31. synpath/engine/risk.py +403 -0
  32. synpath/engine/router.py +406 -0
  33. synpath/errors.py +98 -0
  34. synpath/history.py +71 -0
  35. synpath/hosted.py +86 -0
  36. synpath/hosted_auth.py +201 -0
  37. synpath/ids.py +61 -0
  38. synpath/kalshi.py +1378 -0
  39. synpath/matching.py +93 -0
  40. synpath/opinion.py +799 -0
  41. synpath/polymarket.py +1004 -0
  42. synpath/polymarket_us.py +989 -0
  43. synpath/remote.py +195 -0
  44. synpath/server/__init__.py +98 -0
  45. synpath/server/__main__.py +118 -0
  46. synpath/server/api.py +439 -0
  47. synpath/server/errors.py +87 -0
  48. synpath/server/local.py +96 -0
  49. synpath/server/models.py +75 -0
  50. synpath/server/serve.py +236 -0
  51. synpath/server/store.py +363 -0
  52. synpath/server/trading.py +764 -0
  53. synpath/trading/__init__.py +83 -0
  54. synpath/trading/__main__.py +75 -0
  55. synpath/trading/base.py +126 -0
  56. synpath/trading/credentials.py +444 -0
  57. synpath/trading/errors.py +94 -0
  58. synpath/trading/init.py +250 -0
  59. synpath/trading/instruments.py +167 -0
  60. synpath/trading/kalshi.py +957 -0
  61. synpath/trading/limiter.py +177 -0
  62. synpath/trading/money.py +172 -0
  63. synpath/trading/opinion.py +760 -0
  64. synpath/trading/opinion_signing.py +174 -0
  65. synpath/trading/polymarket.py +1362 -0
  66. synpath/trading/polymarket_signing.py +523 -0
  67. synpath/trading/polymarket_us.py +705 -0
  68. synpath/trading/polymarket_us_exchange.py +825 -0
  69. synpath/trading/types.py +421 -0
  70. synpath/types.py +622 -0
  71. synpath/ws/__init__.py +59 -0
  72. synpath/ws/base.py +564 -0
  73. synpath/ws/grpc.py +578 -0
  74. synpath/ws/kalshi.py +451 -0
  75. synpath/ws/opinion.py +486 -0
  76. synpath/ws/polymarket.py +457 -0
  77. synpath/ws/polymarket_us.py +317 -0
  78. synpath/ws/polymarket_us_exchange.py +754 -0
  79. synpath-0.3.0.dist-info/METADATA +233 -0
  80. synpath-0.3.0.dist-info/RECORD +84 -0
  81. synpath-0.3.0.dist-info/WHEEL +4 -0
  82. synpath-0.3.0.dist-info/entry_points.txt +2 -0
  83. synpath-0.3.0.dist-info/licenses/LICENSE +21 -0
  84. 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