finatic-server-python 0.1.6__tar.gz → 0.2.1__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.
Files changed (22) hide show
  1. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/PKG-INFO +18 -1
  2. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/README.md +17 -0
  3. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/pyproject.toml +2 -2
  4. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server/core/api_client.py +114 -0
  5. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server/core/client.py +16 -0
  6. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server/types/__init__.py +20 -0
  7. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server/types/broker.py +159 -0
  8. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server_python.egg-info/PKG-INFO +18 -1
  9. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/setup.cfg +0 -0
  10. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server/__init__.py +0 -0
  11. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server/core/__init__.py +0 -0
  12. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server/types/auth.py +0 -0
  13. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server/types/common.py +0 -0
  14. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server/types/orders.py +0 -0
  15. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server/types/portfolio.py +0 -0
  16. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server/types/webhook.py +0 -0
  17. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server/utils/__init__.py +0 -0
  18. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server/utils/errors.py +0 -0
  19. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server_python.egg-info/SOURCES.txt +0 -0
  20. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server_python.egg-info/dependency_links.txt +0 -0
  21. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server_python.egg-info/requires.txt +0 -0
  22. {finatic_server_python-0.1.6 → finatic_server_python-0.2.1}/src/finatic_server_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: finatic-server-python
3
- Version: 0.1.6
3
+ Version: 0.2.1
4
4
  Summary: Python SDK for Finatic Server API
5
5
  Author-email: Finatic <support@finatic.dev>
6
6
  License: MIT
@@ -103,6 +103,23 @@ print(f"User ID: {client.get_user_id()}")
103
103
  brokers = await client.get_broker_list()
104
104
  ```
105
105
 
106
+ ### Server: Get one-time token for Client SDK (additive helper)
107
+
108
+ ```python
109
+ async with FinaticServerClient("your-api-key") as client:
110
+ # Fetch a fresh one-time token without modifying the current server session
111
+ one_time_token = await client.get_token()
112
+
113
+ # Pass this token to the Client SDK on the frontend to start its session
114
+ # e.g., FinaticClient.init({ "token": one_time_token })
115
+ ```
116
+
117
+ Notes:
118
+
119
+ - Requires the client to be initialized (use the async context manager or call **aenter**()).
120
+ - Does not call `/session/start` and does not change `session_id`/`company_id` state.
121
+ - Safe to call multiple times; each call returns a new short-lived token.
122
+
106
123
  ### 2. Direct Authentication (Server-side with known user ID)
107
124
 
108
125
  ```python
@@ -65,6 +65,23 @@ print(f"User ID: {client.get_user_id()}")
65
65
  brokers = await client.get_broker_list()
66
66
  ```
67
67
 
68
+ ### Server: Get one-time token for Client SDK (additive helper)
69
+
70
+ ```python
71
+ async with FinaticServerClient("your-api-key") as client:
72
+ # Fetch a fresh one-time token without modifying the current server session
73
+ one_time_token = await client.get_token()
74
+
75
+ # Pass this token to the Client SDK on the frontend to start its session
76
+ # e.g., FinaticClient.init({ "token": one_time_token })
77
+ ```
78
+
79
+ Notes:
80
+
81
+ - Requires the client to be initialized (use the async context manager or call **aenter**()).
82
+ - Does not call `/session/start` and does not change `session_id`/`company_id` state.
83
+ - Safe to call multiple times; each call returns a new short-lived token.
84
+
68
85
  ### 2. Direct Authentication (Server-side with known user ID)
69
86
 
70
87
  ```python
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "finatic-server-python"
7
- version = "0.1.6"
7
+ version = "0.2.1"
8
8
  description = "Python SDK for Finatic Server API"
9
9
  authors = [{ name = "Finatic", email = "support@finatic.dev" }]
10
10
  license = { text = "MIT" }
@@ -63,7 +63,7 @@ multi_line_output = 3
63
63
  line_length = 88
64
64
 
65
65
  [tool.mypy]
66
- python_version = "0.1.5"
66
+ python_version = "0.2.1"
67
67
  warn_return_any = true
68
68
  warn_unused_configs = true
69
69
  disallow_untyped_defs = true
@@ -38,6 +38,16 @@ from ..types import (
38
38
  TradingContext,
39
39
  ApiPaginationInfo,
40
40
  PaginatedResult,
41
+ OrderFill,
42
+ OrderEvent,
43
+ OrderGroup,
44
+ PositionLot,
45
+ PositionLotFill,
46
+ OrderFillsFilter,
47
+ OrderEventsFilter,
48
+ OrderGroupsFilter,
49
+ PositionLotsFilter,
50
+ PositionLotFillsFilter,
41
51
  )
42
52
  from ..utils.errors import (
43
53
  ApiError,
@@ -981,6 +991,110 @@ class ApiClient:
981
991
  )
982
992
  return response
983
993
 
994
+ async def get_order_fills(
995
+ self, order_id: str, filters: Optional[OrderFillsFilter] = None
996
+ ) -> List[OrderFill]:
997
+ """Get order fills for a specific order using session-based authentication."""
998
+ params = {}
999
+ if filters:
1000
+ if filters.connection_id:
1001
+ params["connection_id"] = filters.connection_id
1002
+ if filters.limit:
1003
+ params["limit"] = str(filters.limit)
1004
+ if filters.offset:
1005
+ params["offset"] = str(filters.offset)
1006
+
1007
+ response = await self._request(
1008
+ method="GET", path=f"/brokers/data/orders/{order_id}/fills", params=params
1009
+ )
1010
+ return [OrderFill(**fill) for fill in response.get("response_data", [])]
1011
+
1012
+ async def get_order_events(
1013
+ self, order_id: str, filters: Optional[OrderEventsFilter] = None
1014
+ ) -> List[OrderEvent]:
1015
+ """Get order events for a specific order using session-based authentication."""
1016
+ params = {}
1017
+ if filters:
1018
+ if filters.connection_id:
1019
+ params["connection_id"] = filters.connection_id
1020
+ if filters.limit:
1021
+ params["limit"] = str(filters.limit)
1022
+ if filters.offset:
1023
+ params["offset"] = str(filters.offset)
1024
+
1025
+ response = await self._request(
1026
+ method="GET", path=f"/brokers/data/orders/{order_id}/events", params=params
1027
+ )
1028
+ return [OrderEvent(**event) for event in response.get("response_data", [])]
1029
+
1030
+ async def get_order_groups(
1031
+ self, filters: Optional[OrderGroupsFilter] = None
1032
+ ) -> List[OrderGroup]:
1033
+ """Get order groups using session-based authentication."""
1034
+ params = {}
1035
+ if filters:
1036
+ if filters.broker_id:
1037
+ params["broker_id"] = filters.broker_id
1038
+ if filters.connection_id:
1039
+ params["connection_id"] = filters.connection_id
1040
+ if filters.limit:
1041
+ params["limit"] = str(filters.limit)
1042
+ if filters.offset:
1043
+ params["offset"] = str(filters.offset)
1044
+ if filters.created_after:
1045
+ params["created_after"] = filters.created_after
1046
+ if filters.created_before:
1047
+ params["created_before"] = filters.created_before
1048
+
1049
+ response = await self._request(
1050
+ method="GET", path="/brokers/data/orders/groups", params=params
1051
+ )
1052
+ return [OrderGroup(**group) for group in response.get("response_data", [])]
1053
+
1054
+ async def get_position_lots(
1055
+ self, filters: Optional[PositionLotsFilter] = None
1056
+ ) -> List[PositionLot]:
1057
+ """Get position lots (tax lots) using session-based authentication."""
1058
+ params = {}
1059
+ if filters:
1060
+ if filters.broker_id:
1061
+ params["broker_id"] = filters.broker_id
1062
+ if filters.connection_id:
1063
+ params["connection_id"] = filters.connection_id
1064
+ if filters.account_id:
1065
+ params["account_id"] = filters.account_id
1066
+ if filters.symbol:
1067
+ params["symbol"] = filters.symbol
1068
+ if filters.position_id:
1069
+ params["position_id"] = filters.position_id
1070
+ if filters.limit:
1071
+ params["limit"] = str(filters.limit)
1072
+ if filters.offset:
1073
+ params["offset"] = str(filters.offset)
1074
+
1075
+ response = await self._request(
1076
+ method="GET", path="/brokers/data/positions/lots", params=params
1077
+ )
1078
+ return [PositionLot(**lot) for lot in response.get("response_data", [])]
1079
+
1080
+ async def get_position_lot_fills(
1081
+ self, lot_id: str, filters: Optional[PositionLotFillsFilter] = None
1082
+ ) -> List[PositionLotFill]:
1083
+ """Get position lot fills for a specific lot using session-based authentication."""
1084
+ params = {}
1085
+ if filters:
1086
+ if filters.connection_id:
1087
+ params["connection_id"] = filters.connection_id
1088
+ if filters.limit:
1089
+ params["limit"] = str(filters.limit)
1090
+ if filters.offset:
1091
+ params["offset"] = str(filters.offset)
1092
+
1093
+ response = await self._request(
1094
+ method="GET", path=f"/brokers/data/positions/lots/{lot_id}/fills", params=params
1095
+ )
1096
+ return [PositionLotFill(**fill) for fill in response.get("response_data", [])]
1097
+
984
1098
  # Trading context methods
985
1099
  def set_broker(self, broker: str):
986
1100
  """Set the current broker."""
@@ -200,6 +200,22 @@ class FinaticServerClient:
200
200
 
201
201
  return session_response
202
202
 
203
+ async def get_token(self) -> str:
204
+ """Get a fresh one-time token for client SDK, requiring initialized client.
205
+
206
+ This does not modify the current server SDK session; it only returns a token
207
+ from /session/init that can be passed to the Client SDK.
208
+ """
209
+ # Ensure client is initialized (HTTP session exists)
210
+ if not hasattr(self._api_client, "_session") or self._api_client._session is None:
211
+ raise AuthenticationError(
212
+ "Client not initialized. Use 'async with' or call __aenter__() first."
213
+ )
214
+
215
+ # Call existing init logic and return the token
216
+ token = await self._initialize_session()
217
+ return token
218
+
203
219
  async def get_portal_url(
204
220
  self,
205
221
  theme: Optional[Dict[str, Any]] = None,
@@ -55,6 +55,16 @@ from .broker import (
55
55
  PositionsFilter,
56
56
  AccountsFilter,
57
57
  BalancesFilter,
58
+ OrderFill,
59
+ OrderEvent,
60
+ OrderGroup,
61
+ PositionLot,
62
+ PositionLotFill,
63
+ OrderFillsFilter,
64
+ OrderEventsFilter,
65
+ OrderGroupsFilter,
66
+ PositionLotsFilter,
67
+ PositionLotFillsFilter,
58
68
  )
59
69
 
60
70
  # Webhook types
@@ -110,6 +120,16 @@ __all__ = [
110
120
  "PositionsFilter",
111
121
  "AccountsFilter",
112
122
  "BalancesFilter",
123
+ "OrderFill",
124
+ "OrderEvent",
125
+ "OrderGroup",
126
+ "PositionLot",
127
+ "PositionLotFill",
128
+ "OrderFillsFilter",
129
+ "OrderEventsFilter",
130
+ "OrderGroupsFilter",
131
+ "PositionLotsFilter",
132
+ "PositionLotFillsFilter",
113
133
 
114
134
  # Webhook
115
135
  "TestWebhookRequest",
@@ -220,3 +220,162 @@ class BalancesFilter(BaseModel):
220
220
  None, description="Filter by balance creation date before (ISO 8601)"
221
221
  )
222
222
  with_metadata: Optional[bool] = Field(None, description="Include metadata")
223
+
224
+
225
+ class OrderFill(BaseModel):
226
+ """Order fill information."""
227
+
228
+ id: str = Field(..., description="Fill ID")
229
+ order_id: str = Field(..., description="Order ID")
230
+ leg_id: Optional[str] = Field(None, description="Order leg ID")
231
+ price: float = Field(..., description="Fill price")
232
+ quantity: float = Field(..., description="Fill quantity")
233
+ executed_at: str = Field(..., description="Execution timestamp")
234
+ execution_id: Optional[str] = Field(None, description="Execution ID")
235
+ trade_id: Optional[str] = Field(None, description="Trade ID")
236
+ venue: Optional[str] = Field(None, description="Execution venue")
237
+ commission_fee: Optional[float] = Field(None, description="Commission fee")
238
+ created_at: str = Field(..., description="Creation timestamp")
239
+ updated_at: str = Field(..., description="Last update timestamp")
240
+
241
+
242
+ class OrderEvent(BaseModel):
243
+ """Order event information."""
244
+
245
+ id: str = Field(..., description="Event ID")
246
+ order_id: str = Field(..., description="Order ID")
247
+ order_group_id: Optional[str] = Field(None, description="Order group ID")
248
+ event_type: Optional[str] = Field(None, description="Event type")
249
+ event_time: str = Field(..., description="Event timestamp")
250
+ event_id: Optional[str] = Field(None, description="Event ID")
251
+ order_status: Optional[str] = Field(None, description="Order status")
252
+ inferred: bool = Field(..., description="Whether event was inferred")
253
+ confidence: Optional[float] = Field(None, description="Confidence score")
254
+ reason_code: Optional[str] = Field(None, description="Reason code")
255
+ recorded_at: Optional[str] = Field(None, description="Recorded timestamp")
256
+
257
+
258
+ class OrderLeg(BaseModel):
259
+ """Order leg information."""
260
+
261
+ id: str = Field(..., description="Leg ID")
262
+ order_id: str = Field(..., description="Order ID")
263
+ leg_index: int = Field(..., description="Leg index")
264
+ asset_type: str = Field(..., description="Asset type")
265
+ broker_provided_symbol: Optional[str] = Field(None, description="Broker provided symbol")
266
+ quantity: float = Field(..., description="Quantity")
267
+ filled_quantity: Optional[float] = Field(None, description="Filled quantity")
268
+ avg_fill_price: Optional[float] = Field(None, description="Average fill price")
269
+ created_at: Optional[str] = Field(None, description="Creation timestamp")
270
+ updated_at: Optional[str] = Field(None, description="Last update timestamp")
271
+
272
+
273
+ class OrderGroupOrder(BrokerOrder):
274
+ """Order within a group with legs."""
275
+
276
+ legs: List[OrderLeg] = Field(default_factory=list, description="Order legs")
277
+
278
+
279
+ class OrderGroup(BaseModel):
280
+ """Order group information with nested orders and legs."""
281
+
282
+ id: str = Field(..., description="Group ID")
283
+ user_broker_connection_id: Optional[str] = Field(None, description="User broker connection ID")
284
+ created_at: str = Field(..., description="Creation timestamp")
285
+ updated_at: str = Field(..., description="Last update timestamp")
286
+ orders: List[OrderGroupOrder] = Field(
287
+ default_factory=list, description="Orders in group with their legs"
288
+ )
289
+
290
+
291
+ class PositionLot(BaseModel):
292
+ """Position lot (tax lot) information."""
293
+
294
+ id: str = Field(..., description="Lot ID")
295
+ position_id: Optional[str] = Field(None, description="Position ID")
296
+ user_broker_connection_id: str = Field(..., description="User broker connection ID")
297
+ broker_provided_account_id: str = Field(..., description="Broker provided account ID")
298
+ instrument_key: str = Field(..., description="Instrument key")
299
+ asset_type: Optional[str] = Field(None, description="Asset type")
300
+ side: Optional[str] = Field(None, description="Position side")
301
+ open_quantity: float = Field(..., description="Open quantity")
302
+ closed_quantity: float = Field(..., description="Closed quantity")
303
+ remaining_quantity: float = Field(..., description="Remaining quantity")
304
+ open_price: float = Field(..., description="Open price")
305
+ close_price_avg: Optional[float] = Field(None, description="Average close price")
306
+ cost_basis: float = Field(..., description="Cost basis")
307
+ cost_basis_w_commission: float = Field(..., description="Cost basis with commission")
308
+ realized_pl: float = Field(..., description="Realized P&L")
309
+ realized_pl_w_commission: float = Field(..., description="Realized P&L with commission")
310
+ lot_opened_at: str = Field(..., description="Lot opened timestamp")
311
+ lot_closed_at: Optional[str] = Field(None, description="Lot closed timestamp")
312
+ position_group_id: Optional[str] = Field(None, description="Position group ID")
313
+ created_at: str = Field(..., description="Creation timestamp")
314
+ updated_at: str = Field(..., description="Last update timestamp")
315
+ position_lot_fills: Optional[List["PositionLotFill"]] = Field(None, description="Lot fills")
316
+
317
+
318
+ class PositionLotFill(BaseModel):
319
+ """Position lot fill information."""
320
+
321
+ id: str = Field(..., description="Fill ID")
322
+ lot_id: str = Field(..., description="Lot ID")
323
+ order_fill_id: str = Field(..., description="Order fill ID")
324
+ fill_price: float = Field(..., description="Fill price")
325
+ fill_quantity: float = Field(..., description="Fill quantity")
326
+ executed_at: str = Field(..., description="Execution timestamp")
327
+ commission_share: Optional[float] = Field(None, description="Commission share")
328
+ created_at: str = Field(..., description="Creation timestamp")
329
+ updated_at: str = Field(..., description="Last update timestamp")
330
+
331
+
332
+ # Filter types for detail endpoints
333
+ class OrderFillsFilter(BaseModel):
334
+ """Filter options for order fills."""
335
+
336
+ connection_id: Optional[str] = Field(None, description="Filter by connection ID")
337
+ limit: Optional[int] = Field(None, description="Result limit")
338
+ offset: Optional[int] = Field(None, description="Result offset")
339
+
340
+
341
+ class OrderEventsFilter(BaseModel):
342
+ """Filter options for order events."""
343
+
344
+ connection_id: Optional[str] = Field(None, description="Filter by connection ID")
345
+ limit: Optional[int] = Field(None, description="Result limit")
346
+ offset: Optional[int] = Field(None, description="Result offset")
347
+
348
+
349
+ class OrderGroupsFilter(BaseModel):
350
+ """Filter options for order groups."""
351
+
352
+ broker_id: Optional[str] = Field(None, description="Filter by broker ID")
353
+ connection_id: Optional[str] = Field(None, description="Filter by connection ID")
354
+ limit: Optional[int] = Field(None, description="Result limit")
355
+ offset: Optional[int] = Field(None, description="Result offset")
356
+ created_after: Optional[str] = Field(
357
+ None, description="Filter by creation date after (ISO 8601)"
358
+ )
359
+ created_before: Optional[str] = Field(
360
+ None, description="Filter by creation date before (ISO 8601)"
361
+ )
362
+
363
+
364
+ class PositionLotsFilter(BaseModel):
365
+ """Filter options for position lots."""
366
+
367
+ broker_id: Optional[str] = Field(None, description="Filter by broker ID")
368
+ connection_id: Optional[str] = Field(None, description="Filter by connection ID")
369
+ account_id: Optional[str] = Field(None, description="Filter by account ID")
370
+ symbol: Optional[str] = Field(None, description="Filter by symbol")
371
+ position_id: Optional[str] = Field(None, description="Filter by position ID")
372
+ limit: Optional[int] = Field(None, description="Result limit")
373
+ offset: Optional[int] = Field(None, description="Result offset")
374
+
375
+
376
+ class PositionLotFillsFilter(BaseModel):
377
+ """Filter options for position lot fills."""
378
+
379
+ connection_id: Optional[str] = Field(None, description="Filter by connection ID")
380
+ limit: Optional[int] = Field(None, description="Result limit")
381
+ offset: Optional[int] = Field(None, description="Result offset")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: finatic-server-python
3
- Version: 0.1.6
3
+ Version: 0.2.1
4
4
  Summary: Python SDK for Finatic Server API
5
5
  Author-email: Finatic <support@finatic.dev>
6
6
  License: MIT
@@ -103,6 +103,23 @@ print(f"User ID: {client.get_user_id()}")
103
103
  brokers = await client.get_broker_list()
104
104
  ```
105
105
 
106
+ ### Server: Get one-time token for Client SDK (additive helper)
107
+
108
+ ```python
109
+ async with FinaticServerClient("your-api-key") as client:
110
+ # Fetch a fresh one-time token without modifying the current server session
111
+ one_time_token = await client.get_token()
112
+
113
+ # Pass this token to the Client SDK on the frontend to start its session
114
+ # e.g., FinaticClient.init({ "token": one_time_token })
115
+ ```
116
+
117
+ Notes:
118
+
119
+ - Requires the client to be initialized (use the async context manager or call **aenter**()).
120
+ - Does not call `/session/start` and does not change `session_id`/`company_id` state.
121
+ - Safe to call multiple times; each call returns a new short-lived token.
122
+
106
123
  ### 2. Direct Authentication (Server-side with known user ID)
107
124
 
108
125
  ```python