Skip to content

Commit 9e3b43e

Browse files
committed
Adds Futures support (alpha)
1 parent a7c00b8 commit 9e3b43e

File tree

8 files changed

+699
-39
lines changed

8 files changed

+699
-39
lines changed

polygon/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .rest import RESTClient
22
from .rest.base import version
33
from .websocket import WebSocketClient
4+
from .rest.futures import FuturesClient
45
from .exceptions import *
56

67
__version__ = version

polygon/rest/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .snapshot import SnapshotClient
55
from .indicators import IndicatorsClient
66
from .summaries import SummariesClient
7+
from .futures import FuturesClient
78
from .reference import (
89
MarketsClient,
910
TickersClient,
@@ -35,6 +36,7 @@ class RESTClient(
3536
ContractsClient,
3637
IndicatorsClient,
3738
SummariesClient,
39+
FuturesClient,
3840
):
3941
def __init__(
4042
self,

polygon/rest/futures.py

Lines changed: 409 additions & 0 deletions
Large diffs are not rendered by default.

polygon/rest/models/futures.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
from typing import Optional, List
2+
from ...modelclass import modelclass
3+
4+
@modelclass
5+
class MarketExchanges:
6+
"Contains exchange market status data."
7+
nasdaq: Optional[str] = None
8+
nyse: Optional[str] = None
9+
otc: Optional[str] = None
10+
11+
@modelclass
12+
class FuturesAggregate:
13+
"""Represents an aggregate for a futures contract."""
14+
close: Optional[float] = None
15+
dollar_volume: Optional[float] = None
16+
high: Optional[float] = None
17+
low: Optional[float] = None
18+
open: Optional[float] = None
19+
ticker: Optional[str] = None
20+
transaction_count: Optional[int] = None
21+
underlying_asset: Optional[str] = None
22+
volume: Optional[int] = None
23+
window_end: Optional[int] = None
24+
window_start: Optional[int] = None
25+
26+
@staticmethod
27+
def from_dict(d):
28+
return FuturesAggregate(**{k: v for k, v in d.items() if k in FuturesAggregate.__annotations__})
29+
30+
@modelclass
31+
class FuturesContract:
32+
"""Represents a futures contract."""
33+
active: Optional[bool] = None
34+
as_of: Optional[str] = None
35+
days_to_maturity: Optional[int] = None
36+
exchange_code: Optional[str] = None
37+
first_trade_date: Optional[str] = None
38+
last_trade_date: Optional[str] = None
39+
max_order_quantity: Optional[int] = None
40+
min_order_quantity: Optional[int] = None
41+
name: Optional[str] = None
42+
product_code: Optional[str] = None
43+
settlement_date: Optional[str] = None
44+
settlement_tick_size: Optional[float] = None
45+
spread_tick_size: Optional[float] = None
46+
ticker: Optional[str] = None
47+
trade_tick_size: Optional[float] = None
48+
type: Optional[str] = None
49+
50+
@staticmethod
51+
def from_dict(d):
52+
return FuturesContract(**{k: v for k, v in d.items() if k in FuturesContract.__annotations__})
53+
54+
@modelclass
55+
class FuturesMarketStatus:
56+
"""Represents the market status for a futures product."""
57+
exchange_code: Optional[str] = None
58+
market_status: Optional[str] = None
59+
product_code: Optional[str] = None
60+
61+
@staticmethod
62+
def from_dict(d):
63+
return FuturesMarketStatus(**{k: v for k, v in d.items() if k in FuturesMarketStatus.__annotations__})
64+
65+
@modelclass
66+
class FuturesProduct:
67+
"""Represents a futures product."""
68+
as_of: Optional[str] = None
69+
asset_class: Optional[str] = None
70+
asset_sub_class: Optional[str] = None
71+
exchange_code: Optional[str] = None
72+
last_updated: Optional[str] = None
73+
name: Optional[str] = None
74+
otc_eligible: Optional[bool] = None
75+
price_quotation: Optional[str] = None
76+
product_code: Optional[str] = None
77+
sector: Optional[str] = None
78+
settlement_currency_code: Optional[str] = None
79+
settlement_method: Optional[str] = None
80+
settlement_type: Optional[str] = None
81+
sub_sector: Optional[str] = None
82+
trade_currency_code: Optional[str] = None
83+
type: Optional[str] = None
84+
unit_of_measure: Optional[str] = None
85+
unit_of_measure_quantity: Optional[float] = None
86+
87+
@staticmethod
88+
def from_dict(d):
89+
return FuturesProduct(**{k: v for k, v in d.items() if k in FuturesProduct.__annotations__})
90+
91+
@modelclass
92+
class FuturesScheduleEvent:
93+
"""Represents a single event in a futures schedule."""
94+
event: Optional[str] = None
95+
timestamp: Optional[str] = None
96+
97+
@staticmethod
98+
def from_dict(d):
99+
return FuturesScheduleEvent(**{k: v for k, v in d.items() if k in FuturesScheduleEvent.__annotations__})
100+
101+
@modelclass
102+
class FuturesSchedule:
103+
"""Represents a futures trading schedule."""
104+
market_identifier_code: Optional[str] = None
105+
product_code: Optional[str] = None
106+
product_name: Optional[str] = None
107+
schedule: Optional[List[FuturesScheduleEvent]] = None
108+
session_end_date: Optional[str] = None
109+
110+
@staticmethod
111+
def from_dict(d):
112+
schedule = [FuturesScheduleEvent.from_dict(e) for e in d.get("schedule", [])] if d.get("schedule") else None
113+
return FuturesSchedule(
114+
market_identifier_code=d.get("market_identifier_code"),
115+
product_code=d.get("product_code"),
116+
product_name=d.get("product_name"),
117+
schedule=schedule,
118+
session_end_date=d.get("session_end_date"),
119+
)
120+
121+
@modelclass
122+
class FuturesQuote:
123+
"""Represents a quote for a futures contract."""
124+
ask_price: Optional[float] = None
125+
ask_size: Optional[float] = None
126+
ask_timestamp: Optional[int] = None
127+
bid_price: Optional[float] = None
128+
bid_size: Optional[float] = None
129+
bid_timestamp: Optional[int] = None
130+
session_start_date: Optional[str] = None
131+
ticker: Optional[str] = None
132+
timestamp: Optional[int] = None
133+
134+
@staticmethod
135+
def from_dict(d):
136+
return FuturesQuote(**{k: v for k, v in d.items() if k in FuturesQuote.__annotations__})
137+
138+
@modelclass
139+
class FuturesTrade:
140+
"""Represents a trade for a futures contract."""
141+
price: Optional[float] = None
142+
session_start_date: Optional[str] = None
143+
size: Optional[float] = None
144+
ticker: Optional[str] = None
145+
timestamp: Optional[int] = None
146+
147+
@staticmethod
148+
def from_dict(d):
149+
return FuturesTrade(**{k: v for k, v in d.items() if k in FuturesTrade.__annotations__})

polygon/websocket/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ async def connect(
140140
if m["ev"] == "status":
141141
logger.debug("status: %s", m["message"])
142142
continue
143-
cmsg = parse(msgJson, logger)
143+
cmsg = parse(msgJson, self.market, logger)
144144

145145
if len(cmsg) > 0:
146146
await processor(cmsg) # type: ignore

polygon/websocket/models/__init__.py

Lines changed: 67 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,81 @@
1-
from typing import Dict, Any, List
1+
from typing import Dict, Any, List, Optional
22
from .common import *
33
from .models import *
44
import logging
55

6+
logger = logging.getLogger(__name__)
67

7-
def parse_single(data: Dict[str, Any]):
8-
event_type = data["ev"]
9-
if event_type in [EventType.EquityAgg.value, EventType.EquityAggMin.value]:
10-
return EquityAgg.from_dict(data)
11-
elif event_type in [
12-
EventType.CryptoAgg.value,
13-
EventType.CryptoAggSec.value,
14-
EventType.ForexAgg.value,
15-
EventType.ForexAggSec.value,
16-
]:
17-
return CurrencyAgg.from_dict(data)
18-
elif event_type == EventType.EquityTrade.value:
19-
return EquityTrade.from_dict(data)
20-
elif event_type == EventType.CryptoTrade.value:
21-
return CryptoTrade.from_dict(data)
22-
elif event_type == EventType.EquityQuote.value:
23-
return EquityQuote.from_dict(data)
24-
elif event_type == EventType.ForexQuote.value:
25-
return ForexQuote.from_dict(data)
26-
elif event_type == EventType.CryptoQuote.value:
27-
return CryptoQuote.from_dict(data)
28-
elif event_type == EventType.Imbalances.value:
29-
return Imbalance.from_dict(data)
30-
elif event_type == EventType.LimitUpLimitDown.value:
31-
return LimitUpLimitDown.from_dict(data)
32-
elif event_type == EventType.CryptoL2.value:
33-
return Level2Book.from_dict(data)
34-
elif event_type == EventType.Value.value:
35-
return IndexValue.from_dict(data)
36-
elif event_type == EventType.LaunchpadValue.value:
37-
return LaunchpadValue.from_dict(data)
38-
elif event_type == EventType.BusinessFairMarketValue.value:
39-
return FairMarketValue.from_dict(data)
8+
def parse_single(data: Dict[str, Any], market: Market) -> Optional[WebSocketMessage]:
9+
event_type = data.get("ev")
10+
if not event_type:
11+
logger.warning("No event type ('ev') found in message data")
12+
return None
13+
14+
if market == Market.Stocks:
15+
if event_type == "T":
16+
return EquityTrade.from_dict(data)
17+
elif event_type == "Q":
18+
return EquityQuote.from_dict(data)
19+
elif event_type in ["A", "AM"]:
20+
return EquityAgg.from_dict(data)
21+
# Add more stock-specific events as needed (e.g., "LULD", "NOI")
22+
23+
elif market == Market.Options:
24+
if event_type == "T":
25+
return OptionTrade.from_dict(data)
26+
elif event_type == "Q":
27+
return OptionQuote.from_dict(data)
28+
elif event_type in ["A", "AM"]:
29+
return OptionAggregate.from_dict(data)
30+
31+
elif market == Market.Forex:
32+
if event_type == "C":
33+
return ForexQuote.from_dict(data)
34+
elif event_type in ["CA", "CAS"]:
35+
return ForexAggregate.from_dict(data)
36+
37+
elif market == Market.Crypto:
38+
if event_type == "XT":
39+
return CryptoTrade.from_dict(data)
40+
elif event_type == "XQ":
41+
return CryptoQuote.from_dict(data)
42+
elif event_type in ["XA", "XAS"]:
43+
return CryptoAggregate.from_dict(data)
44+
elif event_type == "XL2":
45+
return CryptoL2Book.from_dict(data)
46+
47+
elif market == Market.Indices:
48+
if event_type in ["A", "AM"]:
49+
return IndicesAggregate.from_dict(data)
50+
elif event_type == "V":
51+
return IndicesValue.from_dict(data)
52+
53+
elif market == Market.Futures:
54+
if event_type == "T":
55+
return FuturesTrade.from_dict(data)
56+
elif event_type == "Q":
57+
return FuturesQuote.from_dict(data)
58+
elif event_type in ["A", "AM"]:
59+
return FuturesAggregate.from_dict(data)
60+
61+
# Handle unknown markets
62+
else:
63+
logger.warning(f"Unknown market: {market}")
64+
return None
65+
66+
# If event type is unrecognized within a known market
67+
logger.warning(f"Unknown event type '{event_type}' for market '{market}'")
4068
return None
4169

4270

43-
def parse(msg: List[Dict[str, Any]], logger: logging.Logger) -> List[WebSocketMessage]:
71+
def parse(
72+
msg: List[Dict[str, Any]], market: Market, logger: logging.Logger
73+
) -> List[WebSocketMessage]:
4474
res = []
4575
for m in msg:
46-
parsed = parse_single(m)
76+
parsed = parse_single(m, market)
4777
if parsed is None:
48-
if m["ev"] != "status":
78+
if m.get("ev") != "status":
4979
logger.warning("could not parse message %s", m)
5080
else:
5181
res.append(parsed)

polygon/websocket/models/common.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
class Feed(Enum):
55
Delayed = "delayed.polygon.io"
6-
RealTime = "socket.polygon.io"
6+
RealTime = "socket.ny5.polygon.io" # "socket.polygon.io"
77
Nasdaq = "nasdaqfeed.polygon.io"
88
PolyFeed = "polyfeed.polygon.io"
99
PolyFeedPlus = "polyfeedplus.polygon.io"
@@ -28,6 +28,11 @@ class Market(Enum):
2828
Forex = "forex"
2929
Crypto = "crypto"
3030
Indices = "indices"
31+
Futures = "futures"
32+
FuturesCME = "futures/cme"
33+
FuturesCBOT = "futures/cbot"
34+
FuturesNYMEX = "futures/nymex"
35+
FuturesCOMEX = "futures/comex"
3136

3237

3338
class EventType(Enum):
@@ -42,6 +47,10 @@ class EventType(Enum):
4247
EquityQuote = "Q"
4348
ForexQuote = "C"
4449
CryptoQuote = "XQ"
50+
FuturesTrade = "T"
51+
FuturesQuote = "Q"
52+
FuturesAggregateSecond = "A"
53+
FuturesAggregateMinute = "AM"
4554
Imbalances = "NOI"
4655
LimitUpLimitDown = "LULD"
4756
CryptoL2 = "XL2"

0 commit comments

Comments
 (0)