finatic-server-python 0.1.0__py3-none-any.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.
@@ -0,0 +1,142 @@
1
+ """Authentication-related type definitions."""
2
+
3
+ from datetime import datetime
4
+ from typing import Optional, Dict, Any
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class UserToken(BaseModel):
9
+ """User authentication token information."""
10
+
11
+ access_token: str = Field(..., description="Access token")
12
+ refresh_token: str = Field(..., description="Refresh token")
13
+ expires_in: int = Field(..., description="Token expiration time in seconds")
14
+ user_id: str = Field(..., description="User ID")
15
+ token_type: str = Field(default="Bearer", description="Token type")
16
+ scope: str = Field(default="", description="Token scope")
17
+
18
+
19
+ class SessionResponseData(BaseModel):
20
+ """Session response data structure."""
21
+
22
+ session_id: str = Field(..., description="Unique session identifier")
23
+ state: Optional[str] = Field(None, description="Session state (PENDING, ACTIVE, etc.)")
24
+ device_info: Optional[Dict[str, str]] = Field(None, description="Device information")
25
+ company_id: Optional[str] = Field(None, description="Company ID")
26
+ status: Optional[str] = Field(None, description="Session status")
27
+ expires_at: Optional[str] = Field(None, description="Session expiration time")
28
+ user_id: Optional[str] = Field(None, description="User ID")
29
+ auto_login: Optional[bool] = Field(None, description="Auto login flag")
30
+ access_token: Optional[str] = Field(None, description="Access token")
31
+ refresh_token: Optional[str] = Field(None, description="Refresh token")
32
+ expires_in: Optional[int] = Field(None, description="Token expiration time in seconds")
33
+ token_type: Optional[str] = Field(None, description="Token type")
34
+ scope: Optional[str] = Field(None, description="Token scope")
35
+
36
+
37
+ class SessionResponse(BaseModel):
38
+ """Session initialization response."""
39
+
40
+ # Handle both nested data structure and flat structure
41
+ data: Optional[SessionResponseData] = Field(None, description="Session data (nested)")
42
+ message: Optional[str] = Field(None, description="Response message")
43
+
44
+ # Flat structure fields (for direct API responses)
45
+ session_id: Optional[str] = Field(None, description="Session ID (flat structure)")
46
+ auto_login: Optional[bool] = Field(None, description="Auto login flag (flat structure)")
47
+ company_id: Optional[str] = Field(None, description="Company ID (flat structure)")
48
+ state: Optional[str] = Field(None, description="Session state (flat structure)")
49
+
50
+ model_config = {"populate_by_name": True}
51
+
52
+
53
+ class SessionInitResponse(BaseModel):
54
+ """Session initialization response from API key."""
55
+
56
+ success: bool = Field(..., description="Request success status")
57
+ message: str = Field(..., description="Response message")
58
+ data: Dict[str, Any] = Field(..., description="Response data containing one_time_token")
59
+
60
+
61
+ class OtpRequestResponse(BaseModel):
62
+ """OTP request response."""
63
+
64
+ success: bool = Field(..., description="Request success status")
65
+ message: str = Field(..., description="Response message")
66
+ data: Optional[Dict[str, Any]] = Field(None, description="Response data")
67
+
68
+
69
+ class OtpVerifyResponse(BaseModel):
70
+ """OTP verification response."""
71
+
72
+ success: bool = Field(..., description="Request success status")
73
+ message: str = Field(..., description="Response message")
74
+ data: Optional[Dict[str, Any]] = Field(None, description="Response data with tokens")
75
+
76
+
77
+ class SessionAuthenticateResponse(BaseModel):
78
+ """Session authentication response."""
79
+
80
+ success: bool = Field(..., description="Request success status")
81
+ message: str = Field(..., description="Response message")
82
+ data: Optional[Dict[str, Any]] = Field(None, description="Response data with tokens")
83
+
84
+
85
+ class PortalUrlResponse(BaseModel):
86
+ """Portal URL response."""
87
+
88
+ success: bool = Field(..., description="Request success status")
89
+ message: str = Field(..., description="Response message")
90
+ data: Dict[str, str] = Field(..., description="Response data containing portal_url")
91
+
92
+
93
+ class SessionValidationResponse(BaseModel):
94
+ """Session validation response."""
95
+
96
+ valid: bool = Field(..., description="Whether the session is valid")
97
+ company_id: str = Field(..., description="Company ID")
98
+ status: str = Field(..., description="Session status")
99
+
100
+
101
+ class SessionUserResponse(BaseModel):
102
+ """Response when getting user info from completed session."""
103
+
104
+ success: bool = Field(..., description="Request success status")
105
+ message: str = Field(..., description="Response message")
106
+ data: Dict[str, Any] = Field(..., description="Response data containing user info and tokens")
107
+
108
+ def get_user_id(self) -> str:
109
+ """Get user ID from the data object."""
110
+ return self.data['user_id']
111
+
112
+ def get_access_token(self) -> str:
113
+ """Get access token from the data object."""
114
+ return self.data['access_token']
115
+
116
+ def get_refresh_token(self) -> str:
117
+ """Get refresh token from the data object."""
118
+ return self.data['refresh_token']
119
+
120
+ def get_expires_in(self) -> int:
121
+ """Get expires_in from the data object."""
122
+ return self.data['expires_in']
123
+
124
+ def get_token_type(self) -> str:
125
+ """Get token type from the data object."""
126
+ return self.data['token_type']
127
+
128
+ def get_scope(self) -> str:
129
+ """Get scope from the data object."""
130
+ return self.data['scope']
131
+
132
+ def get_company_id(self) -> str:
133
+ """Get company ID from the data object."""
134
+ return self.data['company_id']
135
+
136
+
137
+ class DeviceInfo(BaseModel):
138
+ """Device information for authentication."""
139
+
140
+ ip_address: str = Field(..., description="Device IP address")
141
+ user_agent: str = Field(..., description="User agent string")
142
+ fingerprint: str = Field(..., description="Device fingerprint")
@@ -0,0 +1,147 @@
1
+ """Broker-related type definitions."""
2
+
3
+ from typing import Optional, List, Dict, Any
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class BrokerDataOptions(BaseModel):
8
+ """Options for filtering broker data."""
9
+
10
+ broker_name: Optional[str] = Field(None, description="Filter by broker name")
11
+ account_id: Optional[str] = Field(None, description="Filter by account ID")
12
+ symbol: Optional[str] = Field(None, description="Filter by symbol")
13
+
14
+
15
+ class BrokerInfo(BaseModel):
16
+ """Broker information."""
17
+
18
+ id: str = Field(..., description="Broker ID")
19
+ name: str = Field(..., description="Broker name")
20
+ display_name: str = Field(..., description="Display name")
21
+ description: str = Field(..., description="Broker description")
22
+ website: str = Field(..., description="Broker website")
23
+ features: List[str] = Field(..., description="Available features")
24
+ auth_type: str = Field(..., description="Authentication type (oauth, api_key, username_password, etc.)")
25
+ logo_path: str = Field(..., description="Logo path")
26
+ is_active: bool = Field(..., description="Whether broker is active")
27
+
28
+
29
+ class BrokerAccount(BaseModel):
30
+ """Broker account information."""
31
+
32
+ id: str = Field(..., description="Account ID")
33
+ user_broker_connection_id: str = Field(..., description="User broker connection ID")
34
+ broker_provided_account_id: str = Field(..., description="Broker provided account ID")
35
+ account_name: str = Field(..., description="Account name")
36
+ account_type: Optional[str] = Field(None, description="Account type")
37
+ currency: Optional[str] = Field(None, description="Account currency")
38
+ cash_balance: Optional[float] = Field(None, description="Cash balance")
39
+ buying_power: Optional[float] = Field(None, description="Buying power")
40
+ status: Optional[str] = Field(None, description="Account status")
41
+ created_at: str = Field(..., description="Creation timestamp")
42
+ updated_at: str = Field(..., description="Last update timestamp")
43
+ last_synced_at: str = Field(..., description="Last sync timestamp")
44
+
45
+
46
+ class BrokerOrder(BaseModel):
47
+ """Broker order information."""
48
+
49
+ id: str = Field(..., description="Order ID")
50
+ user_broker_connection_id: str = Field(..., description="User broker connection ID")
51
+ broker_provided_account_id: str = Field(..., description="Broker provided account ID")
52
+ order_id: Optional[str] = Field(None, description="Order ID")
53
+ symbol: str = Field(..., description="Trading symbol")
54
+ order_type: str = Field(..., description="Order type")
55
+ side: str = Field(..., description="Order side (buy/sell)")
56
+ quantity: float = Field(..., description="Order quantity")
57
+ price: Optional[float] = Field(None, description="Order price")
58
+ status: str = Field(..., description="Order status")
59
+ created_at: str = Field(..., description="Creation timestamp")
60
+ updated_at: str = Field(..., description="Last update timestamp")
61
+ filled_at: Optional[str] = Field(None, description="Fill timestamp")
62
+ filled_quantity: Optional[float] = Field(None, description="Filled quantity")
63
+ filled_avg_price: Optional[float] = Field(None, description="Filled average price")
64
+
65
+
66
+ class BrokerPosition(BaseModel):
67
+ """Broker position information."""
68
+
69
+ id: str = Field(..., description="Position ID")
70
+ user_broker_connection_id: str = Field(..., description="User broker connection ID")
71
+ broker_provided_account_id: str = Field(..., description="Broker provided account ID")
72
+ symbol: str = Field(..., description="Trading symbol")
73
+ asset_type: str = Field(..., description="Asset type")
74
+ quantity: float = Field(..., description="Position quantity")
75
+ average_price: Optional[float] = Field(None, description="Average price")
76
+ market_value: float = Field(..., description="Market value")
77
+ cost_basis: float = Field(..., description="Cost basis")
78
+ unrealized_gain_loss: Optional[float] = Field(None, description="Unrealized gain/loss")
79
+ unrealized_gain_loss_percent: Optional[float] = Field(None, description="Unrealized gain/loss percentage")
80
+ current_price: Optional[float] = Field(None, description="Current price")
81
+ last_price: Optional[float] = Field(None, description="Last price")
82
+ last_price_updated_at: Optional[str] = Field(None, description="Last price update timestamp")
83
+ created_at: str = Field(..., description="Creation timestamp")
84
+ updated_at: str = Field(..., description="Last update timestamp")
85
+
86
+
87
+ class BrokerConnection(BaseModel):
88
+ """Broker connection information."""
89
+
90
+ id: str = Field(..., description="Connection ID")
91
+ broker_id: str = Field(..., description="Broker ID")
92
+ user_id: str = Field(..., description="User ID")
93
+ company_id: Optional[str] = Field(None, description="Company ID")
94
+ status: str = Field(..., description="Connection status")
95
+ connected_at: Optional[str] = Field(None, description="Connection timestamp")
96
+ last_synced_at: Optional[str] = Field(None, description="Last sync timestamp")
97
+ permissions: Optional[Dict[str, bool]] = Field(None, description="Connection permissions")
98
+ metadata: Optional[Dict[str, Any]] = Field(None, description="Connection metadata")
99
+ needs_reauth: Optional[bool] = Field(None, description="Whether re-authentication is needed")
100
+
101
+
102
+ # Filter types for pagination
103
+ class OrdersFilter(BaseModel):
104
+ """Filter options for orders pagination."""
105
+
106
+ broker_id: Optional[str] = Field(None, description="Filter by broker ID")
107
+ connection_id: Optional[str] = Field(None, description="Filter by connection ID")
108
+ account_id: Optional[str] = Field(None, description="Filter by account ID")
109
+ symbol: Optional[str] = Field(None, description="Filter by symbol")
110
+ status: Optional[str] = Field(None, description="Filter by status")
111
+ side: Optional[str] = Field(None, description="Filter by side")
112
+ asset_type: Optional[str] = Field(None, description="Filter by asset type")
113
+ limit: Optional[int] = Field(None, description="Result limit")
114
+ offset: Optional[int] = Field(None, description="Result offset")
115
+ created_after: Optional[str] = Field(None, description="Filter by creation date after (ISO 8601)")
116
+ created_before: Optional[str] = Field(None, description="Filter by creation date before (ISO 8601)")
117
+ with_metadata: Optional[bool] = Field(None, description="Include metadata")
118
+
119
+
120
+ class PositionsFilter(BaseModel):
121
+ """Filter options for positions pagination."""
122
+
123
+ broker_id: Optional[str] = Field(None, description="Filter by broker ID")
124
+ connection_id: Optional[str] = Field(None, description="Filter by connection ID")
125
+ account_id: Optional[str] = Field(None, description="Filter by account ID")
126
+ symbol: Optional[str] = Field(None, description="Filter by symbol")
127
+ side: Optional[str] = Field(None, description="Filter by side")
128
+ asset_type: Optional[str] = Field(None, description="Filter by asset type")
129
+ position_status: Optional[str] = Field(None, description="Filter by position status")
130
+ limit: Optional[int] = Field(None, description="Result limit")
131
+ offset: Optional[int] = Field(None, description="Result offset")
132
+ updated_after: Optional[str] = Field(None, description="Filter by update date after (ISO 8601)")
133
+ updated_before: Optional[str] = Field(None, description="Filter by update date before (ISO 8601)")
134
+ with_metadata: Optional[bool] = Field(None, description="Include metadata")
135
+
136
+
137
+ class AccountsFilter(BaseModel):
138
+ """Filter options for accounts pagination."""
139
+
140
+ broker_id: Optional[str] = Field(None, description="Filter by broker ID")
141
+ connection_id: Optional[str] = Field(None, description="Filter by connection ID")
142
+ account_type: Optional[str] = Field(None, description="Filter by account type")
143
+ status: Optional[str] = Field(None, description="Filter by status")
144
+ currency: Optional[str] = Field(None, description="Filter by currency")
145
+ limit: Optional[int] = Field(None, description="Result limit")
146
+ offset: Optional[int] = Field(None, description="Result offset")
147
+ with_metadata: Optional[bool] = Field(None, description="Include metadata")
@@ -0,0 +1,177 @@
1
+ """Common type definitions used across the Finatic Server SDK."""
2
+
3
+ from typing import Optional, List, Dict, Any, Callable, Awaitable
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class DeviceInfo(BaseModel):
8
+ """Device information for authentication and tracking."""
9
+
10
+ ip_address: str = Field(..., description="Device IP address")
11
+ user_agent: str = Field(..., description="User agent string")
12
+ fingerprint: str = Field(..., description="Device fingerprint")
13
+
14
+
15
+ class ApiResponse(BaseModel):
16
+ """Standard API response wrapper."""
17
+
18
+ success: bool = Field(..., description="Request success status")
19
+ message: str = Field(..., description="Response message")
20
+ status_code: Optional[int] = Field(None, description="HTTP status code")
21
+
22
+
23
+ class ApiPaginationInfo(BaseModel):
24
+ """API pagination information."""
25
+
26
+ has_more: bool = Field(..., description="Whether there are more pages")
27
+ next_offset: Optional[int] = Field(None, description="Next page offset")
28
+ current_offset: int = Field(..., description="Current page offset")
29
+ limit: int = Field(..., description="Items per page")
30
+
31
+
32
+ class PaginationMetadata(BaseModel):
33
+ """Pagination metadata for client use."""
34
+
35
+ has_more: bool = Field(..., description="Whether there are more pages")
36
+ next_offset: Optional[int] = Field(None, description="Next page offset")
37
+ current_offset: Optional[int] = Field(None, description="Current page offset")
38
+ limit: Optional[int] = Field(None, description="Items per page")
39
+ current_page: Optional[int] = Field(None, description="Current page number")
40
+ has_next: bool = Field(..., description="Whether there is a next page")
41
+ has_previous: bool = Field(..., description="Whether there is a previous page")
42
+
43
+
44
+ class TradingContext(BaseModel):
45
+ """Trading context for order placement."""
46
+
47
+ broker: Optional[str] = Field(None, description="Selected broker")
48
+ account_number: Optional[str] = Field(None, description="Account number")
49
+ account_id: Optional[str] = Field(None, description="Account ID")
50
+
51
+
52
+ class RequestHeaders(BaseModel):
53
+ """Common request headers."""
54
+
55
+ x_api_key: Optional[str] = Field(None, alias="X-API-Key", description="API key header")
56
+ one_time_token: Optional[str] = Field(None, alias="One-Time-Token", description="One-time token header")
57
+ x_device_info: Optional[str] = Field(None, alias="X-Device-Info", description="Device info header")
58
+ x_session_id: Optional[str] = Field(None, alias="X-Session-ID", description="Session ID header")
59
+ session_id: Optional[str] = Field(None, alias="Session-ID", description="Session ID header")
60
+ x_company_id: Optional[str] = Field(None, alias="X-Company-ID", description="Company ID header")
61
+ authorization: Optional[str] = Field(None, alias="Authorization", description="Authorization header")
62
+
63
+ model_config = {"populate_by_name": True}
64
+
65
+
66
+ class PaginatedResult:
67
+ """Paginated result with navigation capabilities."""
68
+
69
+ def __init__(
70
+ self,
71
+ data: Any,
72
+ pagination_info: ApiPaginationInfo,
73
+ navigation_callback: Optional[Callable[[int, int], Awaitable['PaginatedResult']]] = None
74
+ ):
75
+ self.data = data
76
+ self.navigation_callback = navigation_callback
77
+ # Default to 0 if any are None
78
+ next_offset = pagination_info.next_offset if pagination_info.next_offset is not None else 0
79
+ current_offset = pagination_info.current_offset if pagination_info.current_offset is not None else 0
80
+ limit = pagination_info.limit if pagination_info.limit is not None else 0
81
+ current_page = (current_offset // limit + 1) if limit else 1
82
+ # has_next: only if next_offset is not None and not equal to current_offset
83
+ has_next = (
84
+ pagination_info.next_offset is not None
85
+ and pagination_info.next_offset != pagination_info.current_offset
86
+ )
87
+ # has_previous: only if current_offset is not None and > 0
88
+ has_previous = (
89
+ pagination_info.current_offset is not None
90
+ and pagination_info.current_offset > 0
91
+ )
92
+ self.metadata = PaginationMetadata(
93
+ has_more=pagination_info.has_more,
94
+ next_offset=next_offset,
95
+ current_offset=current_offset,
96
+ limit=limit,
97
+ current_page=current_page,
98
+ has_next=has_next,
99
+ has_previous=has_previous,
100
+ )
101
+
102
+ @property
103
+ def has_next(self) -> bool:
104
+ return self.metadata.has_next
105
+
106
+ @property
107
+ def has_previous(self) -> bool:
108
+ return self.metadata.has_previous
109
+
110
+ @property
111
+ def current_page(self) -> int:
112
+ return self.metadata.current_page or 1
113
+
114
+ async def next_page(self) -> Optional['PaginatedResult']:
115
+ if not self.has_next or not self.navigation_callback:
116
+ return None
117
+ try:
118
+ return await self.navigation_callback(self.metadata.next_offset, self.metadata.limit)
119
+ except Exception as e:
120
+ print(f'Error fetching next page: {e}')
121
+ return None
122
+
123
+ async def previous_page(self) -> Optional['PaginatedResult']:
124
+ if not self.has_previous or not self.navigation_callback:
125
+ return None
126
+ previous_offset = max(0, (self.metadata.current_offset or 0) - (self.metadata.limit or 0))
127
+ try:
128
+ return await self.navigation_callback(previous_offset, self.metadata.limit)
129
+ except Exception as e:
130
+ print(f'Error fetching previous page: {e}')
131
+ return None
132
+
133
+ async def go_to_page(self, page_number: int) -> Optional['PaginatedResult']:
134
+ """Go to a specific page."""
135
+ if not self.navigation_callback or page_number < 1:
136
+ return None
137
+
138
+ offset = (page_number - 1) * self.metadata.limit
139
+ try:
140
+ return await self.navigation_callback(offset, self.metadata.limit)
141
+ except Exception as e:
142
+ print(f'Error fetching page {page_number}: {e}')
143
+ return None
144
+
145
+ async def first_page(self) -> Optional['PaginatedResult']:
146
+ """Get the first page."""
147
+ if not self.navigation_callback:
148
+ return None
149
+
150
+ try:
151
+ return await self.navigation_callback(0, self.metadata.limit)
152
+ except Exception as e:
153
+ print(f'Error fetching first page: {e}')
154
+ return None
155
+
156
+ async def last_page(self) -> Optional['PaginatedResult']:
157
+ """Get the last page by navigating through all pages."""
158
+ if not self.navigation_callback:
159
+ return None
160
+
161
+ async def find_last(page: PaginatedResult) -> PaginatedResult:
162
+ if not page.has_next:
163
+ return page
164
+ next_page = await page.next_page()
165
+ if not next_page:
166
+ return page
167
+ return await find_last(next_page)
168
+
169
+ try:
170
+ return await find_last(self)
171
+ except Exception as e:
172
+ print(f'Error fetching last page: {e}')
173
+ return None
174
+
175
+ def get_pagination_info(self) -> str:
176
+ """Get pagination info as string."""
177
+ return f"Page {self.current_page} ({self.metadata.current_offset + 1}-{self.metadata.current_offset + self.metadata.limit})"
@@ -0,0 +1,74 @@
1
+ """Order-related type definitions."""
2
+
3
+ from typing import Optional, Literal
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class Order(BaseModel):
8
+ """Basic order structure."""
9
+
10
+ symbol: str = Field(..., description="Trading symbol")
11
+ side: Literal["buy", "sell"] = Field(..., description="Order side")
12
+ quantity: float = Field(..., description="Order quantity")
13
+ type_: Literal["market", "limit", "stop", "stop_limit"] = Field(..., alias="type", description="Order type")
14
+ price: Optional[float] = Field(None, description="Order price")
15
+ stop_price: Optional[float] = Field(None, description="Stop price")
16
+ time_in_force: Literal["day", "gtc", "opg", "cls", "ioc", "fok"] = Field(..., description="Time in force")
17
+
18
+ model_config = {"populate_by_name": True}
19
+
20
+
21
+ class OptionsOrder(Order):
22
+ """Options-specific order."""
23
+
24
+ option_type: Literal["call", "put"] = Field(..., description="Option type")
25
+ strike_price: float = Field(..., description="Strike price")
26
+ expiration_date: str = Field(..., description="Expiration date")
27
+
28
+
29
+ class CryptoOrderOptions(BaseModel):
30
+ """Crypto order options."""
31
+
32
+ quantity: Optional[float] = Field(None, description="Quantity")
33
+ notional: Optional[float] = Field(None, description="Notional value")
34
+
35
+
36
+ class OptionsOrderOptions(BaseModel):
37
+ """Options order options."""
38
+
39
+ strike_price: float = Field(..., description="Strike price")
40
+ expiration_date: str = Field(..., description="Expiration date")
41
+ option_type: Literal["call", "put"] = Field(..., description="Option type")
42
+ contract_size: Optional[int] = Field(None, description="Contract size")
43
+
44
+
45
+ class OrderResponse(BaseModel):
46
+ """Order response from API."""
47
+
48
+ success: bool = Field(..., description="Order success status")
49
+ response_data: dict = Field(..., description="Order response data")
50
+ message: str = Field(..., description="Response message")
51
+ status_code: int = Field(..., description="HTTP status code")
52
+
53
+
54
+ class BrokerOrderParams(BaseModel):
55
+ """Broker order parameters."""
56
+
57
+ broker: Literal["robinhood", "tasty_trade", "ninja_trader"] = Field(..., description="Broker name")
58
+ account_number: str = Field(..., description="Account number")
59
+ symbol: str = Field(..., description="Trading symbol")
60
+ order_qty: float = Field(..., description="Order quantity")
61
+ action: Literal["Buy", "Sell"] = Field(..., description="Order action")
62
+ order_type: Literal["Market", "Limit", "Stop", "TrailingStop"] = Field(..., description="Order type")
63
+ asset_type: Literal["Equity", "Equity Option", "Crypto", "Futures", "Futures Option"] = Field(..., description="Asset type")
64
+ time_in_force: Literal["day", "gtc", "gtd", "ioc", "fok"] = Field(..., description="Time in force")
65
+ price: Optional[float] = Field(None, description="Order price")
66
+ stop_price: Optional[float] = Field(None, description="Stop price")
67
+
68
+
69
+ class BrokerExtras(BaseModel):
70
+ """Broker-specific extras for orders."""
71
+
72
+ robinhood: Optional[dict] = Field(None, description="Robinhood-specific options")
73
+ ninja_trader: Optional[dict] = Field(None, description="NinjaTrader-specific options")
74
+ tasty_trade: Optional[dict] = Field(None, description="TastyTrade-specific options")
@@ -0,0 +1,61 @@
1
+ """Portfolio-related type definitions."""
2
+
3
+ from typing import List, Optional
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class Holding(BaseModel):
8
+ """Individual holding/position in portfolio."""
9
+
10
+ symbol: str = Field(..., description="Trading symbol")
11
+ quantity: float = Field(..., description="Quantity held")
12
+ average_price: float = Field(..., description="Average purchase price")
13
+ current_price: float = Field(..., description="Current market price")
14
+ market_value: float = Field(..., description="Current market value")
15
+ unrealized_pnl: float = Field(..., description="Unrealized profit/loss")
16
+ realized_pnl: float = Field(..., description="Realized profit/loss")
17
+ cost_basis: float = Field(..., description="Total cost basis")
18
+ currency: str = Field(..., description="Currency")
19
+
20
+
21
+ class PerformanceMetrics(BaseModel):
22
+ """Portfolio performance metrics."""
23
+
24
+ total_return: float = Field(..., description="Total return percentage")
25
+ daily_return: float = Field(..., description="Daily return percentage")
26
+ weekly_return: float = Field(..., description="Weekly return percentage")
27
+ monthly_return: float = Field(..., description="Monthly return percentage")
28
+ yearly_return: float = Field(..., description="Yearly return percentage")
29
+ max_drawdown: float = Field(..., description="Maximum drawdown")
30
+ sharpe_ratio: float = Field(..., description="Sharpe ratio")
31
+ beta: float = Field(..., description="Beta")
32
+ alpha: float = Field(..., description="Alpha")
33
+
34
+
35
+ class Portfolio(BaseModel):
36
+ """Portfolio information."""
37
+
38
+ id: str = Field(..., description="Portfolio ID")
39
+ name: str = Field(..., description="Portfolio name")
40
+ type: str = Field(..., description="Portfolio type")
41
+ status: str = Field(..., description="Portfolio status")
42
+ cash: float = Field(..., description="Available cash")
43
+ buying_power: float = Field(..., description="Buying power")
44
+ equity: float = Field(..., description="Total equity")
45
+ long_market_value: float = Field(..., description="Long market value")
46
+ short_market_value: float = Field(..., description="Short market value")
47
+ initial_margin: float = Field(..., description="Initial margin requirement")
48
+ maintenance_margin: float = Field(..., description="Maintenance margin requirement")
49
+ last_equity: float = Field(..., description="Last equity value")
50
+ positions: List[Holding] = Field(..., description="Portfolio positions")
51
+ performance: PerformanceMetrics = Field(..., description="Performance metrics")
52
+
53
+
54
+ class PortfolioSnapshot(BaseModel):
55
+ """Portfolio snapshot at a point in time."""
56
+
57
+ timestamp: str = Field(..., description="Snapshot timestamp")
58
+ total_value: float = Field(..., description="Total portfolio value")
59
+ cash: float = Field(..., description="Available cash")
60
+ equity: float = Field(..., description="Total equity")
61
+ positions: List[dict] = Field(..., description="Position data")
@@ -0,0 +1,17 @@
1
+ """Utility functions and classes for the Finatic Server SDK."""
2
+
3
+ from .errors import (
4
+ FinaticError,
5
+ ApiError,
6
+ AuthenticationError,
7
+ ValidationError,
8
+ RateLimitError,
9
+ )
10
+
11
+ __all__ = [
12
+ "FinaticError",
13
+ "ApiError",
14
+ "AuthenticationError",
15
+ "ValidationError",
16
+ "RateLimitError",
17
+ ]