stonepy 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.
Files changed (445) hide show
  1. stonepy/__init__.py +27 -0
  2. stonepy/_core/__init__.py +1 -0
  3. stonepy/_core/clock.py +49 -0
  4. stonepy/_core/codec.py +96 -0
  5. stonepy/_core/config.py +118 -0
  6. stonepy/_core/endpoint.py +36 -0
  7. stonepy/_core/errors.py +169 -0
  8. stonepy/_core/logging.py +62 -0
  9. stonepy/_core/models.py +31 -0
  10. stonepy/_core/pipeline.py +605 -0
  11. stonepy/_core/plugins.py +144 -0
  12. stonepy/_core/ratelimit.py +99 -0
  13. stonepy/_core/resource.py +12 -0
  14. stonepy/_core/retry.py +30 -0
  15. stonepy/_core/session.py +155 -0
  16. stonepy/_core/status.py +80 -0
  17. stonepy/_core/transport.py +352 -0
  18. stonepy/_endpoints/__init__.py +36 -0
  19. stonepy/_endpoints/cfd.py +82 -0
  20. stonepy/_endpoints/clientapplication.py +45 -0
  21. stonepy/_endpoints/clientpreference.py +236 -0
  22. stonepy/_endpoints/margin.py +38 -0
  23. stonepy/_endpoints/market.py +1295 -0
  24. stonepy/_endpoints/message.py +284 -0
  25. stonepy/_endpoints/news.py +319 -0
  26. stonepy/_endpoints/order.py +367 -0
  27. stonepy/_endpoints/preference.py +96 -0
  28. stonepy/_endpoints/price_alert.py +126 -0
  29. stonepy/_endpoints/session.py +99 -0
  30. stonepy/_endpoints/spread.py +82 -0
  31. stonepy/_endpoints/user_account.py +69 -0
  32. stonepy/_endpoints/watchlist.py +167 -0
  33. stonepy/_generator/__init__.py +1 -0
  34. stonepy/_generator/__main__.py +159 -0
  35. stonepy/_generator/catalog.py +552 -0
  36. stonepy/_generator/emit_client.py +493 -0
  37. stonepy/_generator/emit_contract.py +659 -0
  38. stonepy/_generator/emit_endpoints.py +716 -0
  39. stonepy/_generator/emit_models.py +203 -0
  40. stonepy/_generator/render.py +342 -0
  41. stonepy/_generator/scaffold.py +354 -0
  42. stonepy/client.py +469 -0
  43. stonepy/errors.py +23 -0
  44. stonepy/models/AccountInformationResponseDTOv2.py +18 -0
  45. stonepy/models/AccountResult.py +40 -0
  46. stonepy/models/ActiveSignalsResponseDTO.py +18 -0
  47. stonepy/models/AlertDTO.py +27 -0
  48. stonepy/models/AllocationProfileDTO.py +24 -0
  49. stonepy/models/AllocationProfileEntryDTO.py +18 -0
  50. stonepy/models/Api2FALogonOnResponseDTO.py +15 -0
  51. stonepy/models/ApiAccountHolderDTO.py +14 -0
  52. stonepy/models/ApiAccountInformationSaveRequestDTO.py +14 -0
  53. stonepy/models/ApiAccountInformationSaveResponseDTO.py +11 -0
  54. stonepy/models/ApiAccountOperatorDTOv2.py +19 -0
  55. stonepy/models/ApiActiveOrderDTO.py +21 -0
  56. stonepy/models/ApiActiveStopLimitOrderDTOv2.py +42 -0
  57. stonepy/models/ApiAdvisoryTradeOrderResponseDTO.py +20 -0
  58. stonepy/models/ApiAssociatedDTOv2.py +19 -0
  59. stonepy/models/ApiAssociatedResponseDTO.py +19 -0
  60. stonepy/models/ApiBandedSpreadsDTO.py +17 -0
  61. stonepy/models/ApiBasicStopLimitOrderDTO.py +19 -0
  62. stonepy/models/ApiChangePasswordRequestDTO.py +15 -0
  63. stonepy/models/ApiChangePasswordResponseDTO.py +13 -0
  64. stonepy/models/ApiCiConnectMultipleUsersDetailsDTO.py +23 -0
  65. stonepy/models/ApiClientAccountDTO.py +30 -0
  66. stonepy/models/ApiClientAccountMarginResponseDTO.py +25 -0
  67. stonepy/models/ApiClientAccountTokenDTOv2.py +14 -0
  68. stonepy/models/ApiClientAccountWatchlistDTO.py +21 -0
  69. stonepy/models/ApiClientAccountWatchlistItemDTO.py +23 -0
  70. stonepy/models/ApiClientApplicationMessageTranslationDTO.py +14 -0
  71. stonepy/models/ApiClientApplicationMessageTranslationRequestDTO.py +16 -0
  72. stonepy/models/ApiClientApplicationMessageTranslationResponseDTO.py +20 -0
  73. stonepy/models/ApiClientCommunicationResponseDTO.py +20 -0
  74. stonepy/models/ApiClientCommunicationUpdateRequestDTO.py +15 -0
  75. stonepy/models/ApiClientCommunicationUpdateResponseDTO.py +13 -0
  76. stonepy/models/ApiClientPreferencesOverriddenMarginFactorDTO.py +18 -0
  77. stonepy/models/ApiClientPreferencesOverriddenPriceToleranceDTO.py +15 -0
  78. stonepy/models/ApiClientPreferencesOverriddenSettingSaveDTO.py +16 -0
  79. stonepy/models/ApiClientPreferencesOverriddenSettingsDTO.py +28 -0
  80. stonepy/models/ApiClientPreferencesOverriddenSettingsGetResponseDTO.py +21 -0
  81. stonepy/models/ApiClientPreferencesOverriddenSettingsSaveDTO.py +25 -0
  82. stonepy/models/ApiClientPreferencesOverriddenSettingsSaveRequestDTO.py +23 -0
  83. stonepy/models/ApiClientPreferencesOverriddenSettingsSaveResponseDTO.py +23 -0
  84. stonepy/models/ApiClientPreferencesOverridenMarginFactorDTO.py +18 -0
  85. stonepy/models/ApiClientPreferencesOverridenPriceToleranceDTO.py +15 -0
  86. stonepy/models/ApiClientPreferencesOverridenSettingSaveDTO.py +16 -0
  87. stonepy/models/ApiClientPreferencesOverridenSettingsDTO.py +28 -0
  88. stonepy/models/ApiClientPreferencesOverridenSettingsGetResponseDTO.py +21 -0
  89. stonepy/models/ApiClientPreferencesOverridenSettingsSaveDTO.py +25 -0
  90. stonepy/models/ApiClientPreferencesOverridenSettingsSaveRequestDTO.py +20 -0
  91. stonepy/models/ApiClientPreferencesOverridenSettingsSaveResponseDTO.py +23 -0
  92. stonepy/models/ApiCommunityActionDTO.py +29 -0
  93. stonepy/models/ApiCommunityActionUserDetailsDTO.py +16 -0
  94. stonepy/models/ApiConnectUserDetailsDTO.py +28 -0
  95. stonepy/models/ApiContractDTOv2.py +15 -0
  96. stonepy/models/ApiCultureLookupDTO.py +13 -0
  97. stonepy/models/ApiDateTimeOffsetDTO.py +14 -0
  98. stonepy/models/ApiDeleteWatchlistRequestDTO.py +13 -0
  99. stonepy/models/ApiDeleteWatchlistResponseDTO.py +13 -0
  100. stonepy/models/ApiErrorResponseDTO.py +15 -0
  101. stonepy/models/ApiFollowedDTO.py +14 -0
  102. stonepy/models/ApiFollowerDTO.py +14 -0
  103. stonepy/models/ApiFreeCashToTradeResponseDTO.py +16 -0
  104. stonepy/models/ApiFxFinancingDTO.py +22 -0
  105. stonepy/models/ApiGetClientPreferenceResponseDTO.py +18 -0
  106. stonepy/models/ApiGetClientPreferencesResponseDTO.py +20 -0
  107. stonepy/models/ApiGetKeyListClientPreferenceResponseDTO.py +13 -0
  108. stonepy/models/ApiGetMarketInformationExtendedResponseDTOv2.py +20 -0
  109. stonepy/models/ApiGetPreferencesResponseDTO.py +18 -0
  110. stonepy/models/ApiIfDoneDTOv2.py +19 -0
  111. stonepy/models/ApiIfDoneResponseDTO.py +19 -0
  112. stonepy/models/ApiKnockoutDTO.py +20 -0
  113. stonepy/models/ApiLogOffRequestDTO.py +14 -0
  114. stonepy/models/ApiLogOffResponseDTO.py +13 -0
  115. stonepy/models/ApiLogOnRequestDTO.py +17 -0
  116. stonepy/models/ApiLogOnResponseDTOv2.py +26 -0
  117. stonepy/models/ApiLookupDTO.py +19 -0
  118. stonepy/models/ApiLookupResponseDTO.py +24 -0
  119. stonepy/models/ApiManagedClientAccountMarginResponseDTO.py +13 -0
  120. stonepy/models/ApiManagedClientAccountsMarginResponseDTO.py +20 -0
  121. stonepy/models/ApiManagedInstructionResponseDTO.py +21 -0
  122. stonepy/models/ApiManagedTradeDTO.py +22 -0
  123. stonepy/models/ApiManagedTradeHistoryDTO.py +27 -0
  124. stonepy/models/ApiMarketDTO.py +15 -0
  125. stonepy/models/ApiMarketEodDTO.py +14 -0
  126. stonepy/models/ApiMarketInformationDTOv2.py +141 -0
  127. stonepy/models/ApiMarketInformationExtendedDTOv2.py +33 -0
  128. stonepy/models/ApiMarketInformationSaveDTO.py +19 -0
  129. stonepy/models/ApiMarketSpreadDTO.py +17 -0
  130. stonepy/models/ApiMarketTagDTO.py +16 -0
  131. stonepy/models/ApiOpenPositionDTOv2.py +44 -0
  132. stonepy/models/ApiOrderActionResponseDTO.py +20 -0
  133. stonepy/models/ApiOrderDTOv2.py +47 -0
  134. stonepy/models/ApiOrderResponseDTO.py +34 -0
  135. stonepy/models/ApiPrimaryMarketTagDTO.py +18 -0
  136. stonepy/models/ApiProductInformationDTO.py +50 -0
  137. stonepy/models/ApiQuoteResponseDTO.py +15 -0
  138. stonepy/models/ApiRestrictionDTOv2.py +15 -0
  139. stonepy/models/ApiSaveAccountInformationRequestDTO.py +16 -0
  140. stonepy/models/ApiSaveAccountInformationResponseDTO.py +11 -0
  141. stonepy/models/ApiSaveClientPreferenceRequestDTO.py +21 -0
  142. stonepy/models/ApiSaveMarketInformationResponseDTO.py +11 -0
  143. stonepy/models/ApiSavePreferencesRequestDTO.py +18 -0
  144. stonepy/models/ApiSaveSignalPreferencesResponseDTO.py +11 -0
  145. stonepy/models/ApiSaveWatchlistRequestDTO.py +18 -0
  146. stonepy/models/ApiSaveWatchlistResponseDTO.py +11 -0
  147. stonepy/models/ApiSimulateOrderResponseDTO.py +14 -0
  148. stonepy/models/ApiSimulateTradeOrderResponseDTO.py +31 -0
  149. stonepy/models/ApiStepMarginBandDTO.py +16 -0
  150. stonepy/models/ApiStepMarginDTO.py +23 -0
  151. stonepy/models/ApiStopLimitOrderDTOv2.py +27 -0
  152. stonepy/models/ApiStopLimitOrderHistoryDTO.py +35 -0
  153. stonepy/models/ApiStopLimitResponseDTO.py +11 -0
  154. stonepy/models/ApiTradeHistoryDTO.py +43 -0
  155. stonepy/models/ApiTradeOrderDTOv2.py +22 -0
  156. stonepy/models/ApiTradeOrderResponseDTO.py +26 -0
  157. stonepy/models/ApiTraderDetailsDTO.py +25 -0
  158. stonepy/models/ApiTradingAccountDTOv2.py +18 -0
  159. stonepy/models/ApiTradingDayTimesDTO.py +20 -0
  160. stonepy/models/ApiUpdateDeleteClientPreferenceResponseDTO.py +13 -0
  161. stonepy/models/ApiUserDynamicProfileDTO.py +20 -0
  162. stonepy/models/ApiUserFollowedUsersDTO.py +19 -0
  163. stonepy/models/ApiUserFollowersDTO.py +19 -0
  164. stonepy/models/ApiUserProfileDTO.py +30 -0
  165. stonepy/models/ApiUserTradingAccountDTO.py +14 -0
  166. stonepy/models/ApiValidateSessionRequestDTOv2.py +17 -0
  167. stonepy/models/ApiValidateSessionResponseDTO.py +13 -0
  168. stonepy/models/ApiWallItemDTO.py +27 -0
  169. stonepy/models/ApiWallItemsForUsersDTO.py +19 -0
  170. stonepy/models/CancelOrderRequestDTO.py +16 -0
  171. stonepy/models/CashEquity.py +20 -0
  172. stonepy/models/ClientAccountMarginDTO.py +25 -0
  173. stonepy/models/ClientAccountMarginResponseDTO.py +25 -0
  174. stonepy/models/ClientCommunicationMessageDTO.py +18 -0
  175. stonepy/models/ClientPreferenceKeyDTO.py +14 -0
  176. stonepy/models/ClientPreferenceRequestDTO.py +13 -0
  177. stonepy/models/ClientPreferencesRequestDTO.py +13 -0
  178. stonepy/models/CorporateActionsDTO.py +13 -0
  179. stonepy/models/DeleteAllocationProfileRequestDTO.py +13 -0
  180. stonepy/models/DeleteAllocationProfileResponseDTO.py +11 -0
  181. stonepy/models/DeleteWatchlistItemRequestDTO.py +16 -0
  182. stonepy/models/DeleteWatchlistResponseDTO.py +13 -0
  183. stonepy/models/EnrichedOrderDTO.py +42 -0
  184. stonepy/models/ExecutionResponseDTO.py +15 -0
  185. stonepy/models/ExecutionVenueRequestDTO.py +32 -0
  186. stonepy/models/FixedMarginOrderResponseDTO.py +27 -0
  187. stonepy/models/FullMarketInformationSearchWithTagsResponseDTOv2.py +22 -0
  188. stonepy/models/GetActiveStopLimitOrderResponseDTOv2.py +20 -0
  189. stonepy/models/GetClientPreferenceResponseDTO.py +18 -0
  190. stonepy/models/GetClientPreferencesResponseDTO.py +20 -0
  191. stonepy/models/GetKeyListClientPreferenceResponseDTO.py +13 -0
  192. stonepy/models/GetListClientPreferenceResponseDTO.py +20 -0
  193. stonepy/models/GetMarketInformationResponseDTO.py +20 -0
  194. stonepy/models/GetMessagePopupResponseDTO.py +14 -0
  195. stonepy/models/GetOpenPositionResponseDTOv2.py +18 -0
  196. stonepy/models/GetOrderResponseDTOv2.py +20 -0
  197. stonepy/models/GetOrdersResponseDTOv2.py +22 -0
  198. stonepy/models/GetPriceBarResponseDTO.py +19 -0
  199. stonepy/models/GetPriceTickResponseDTO.py +18 -0
  200. stonepy/models/GetVersionInformationResponseDTO.py +15 -0
  201. stonepy/models/HistoricalOrderDTO.py +23 -0
  202. stonepy/models/HistoricalOrdersResponseDTO.py +20 -0
  203. stonepy/models/IdentifierDTO.py +15 -0
  204. stonepy/models/InsertWatchlistItemRequestDTO.py +16 -0
  205. stonepy/models/LegalPartyDTO.py +21 -0
  206. stonepy/models/LinkedAccountResult.py +36 -0
  207. stonepy/models/ListActiveOrdersRequestDTO.py +14 -0
  208. stonepy/models/ListActiveOrdersResponseDTO.py +18 -0
  209. stonepy/models/ListActiveStopLimitOrderResponseDTO.py +20 -0
  210. stonepy/models/ListAllocationProfilesResponseDTO.py +20 -0
  211. stonepy/models/ListCfdMarketsResponseDTO.py +18 -0
  212. stonepy/models/ListManagedClientsResponseDTO.py +19 -0
  213. stonepy/models/ListMarketInformationRequestDTO.py +14 -0
  214. stonepy/models/ListMarketInformationResponseDTO.py +20 -0
  215. stonepy/models/ListMarketInformationSearchResponseDTO.py +20 -0
  216. stonepy/models/ListMarketSearchPaginatedResponseDTO.py +13 -0
  217. stonepy/models/ListMarketSearchResponseDTO.py +18 -0
  218. stonepy/models/ListOpenPositionsResponseDTO.py +18 -0
  219. stonepy/models/ListProductInformationDTO.py +14 -0
  220. stonepy/models/ListProductInformationResponseDTO.py +20 -0
  221. stonepy/models/ListSpreadMarketsResponseDTO.py +18 -0
  222. stonepy/models/ListStopLimitOrderHistoryResponseDTO.py +20 -0
  223. stonepy/models/ListTradeHistoryResponseDTO.py +21 -0
  224. stonepy/models/ListWatchlistRequestDTO.py +15 -0
  225. stonepy/models/ListWatchlistResponseDTO.py +21 -0
  226. stonepy/models/LogonUser.py +13 -0
  227. stonepy/models/ManagedClientDTO.py +24 -0
  228. stonepy/models/ManagedTradingAccountDTO.py +16 -0
  229. stonepy/models/MarketInformationSearchWithTagsResponseDTO.py +20 -0
  230. stonepy/models/MarketInformationSearchWithoutTagsResponseDTO.py +18 -0
  231. stonepy/models/MarketInformationTagLookupResponseDTO.py +18 -0
  232. stonepy/models/MarketPricesDTO.py +19 -0
  233. stonepy/models/MarketSearchResultDTO.py +122 -0
  234. stonepy/models/MarketSpreadData.py +21 -0
  235. stonepy/models/MultipleMarketInformationRequestDTO.py +14 -0
  236. stonepy/models/NewFixedMarginTradeOrderRequestDTO.py +18 -0
  237. stonepy/models/NewStopLimitOrderRequestDTO.py +39 -0
  238. stonepy/models/NewTradeOrderRequestDTO.py +36 -0
  239. stonepy/models/NewsResponseDTO.py +17 -0
  240. stonepy/models/OpenOrderDTO.py +13 -0
  241. stonepy/models/OpenOrdersResponseDTO.py +18 -0
  242. stonepy/models/OrderDTO.py +34 -0
  243. stonepy/models/OrderHistoryDTO.py +34 -0
  244. stonepy/models/OrderRequestDTO.py +47 -0
  245. stonepy/models/PendingOrderDTO.py +16 -0
  246. stonepy/models/PendingOrdersResponseDTO.py +18 -0
  247. stonepy/models/PreferenceDTO.py +14 -0
  248. stonepy/models/PriceAlertDTO.py +32 -0
  249. stonepy/models/PriceAlertResponseDTO.py +18 -0
  250. stonepy/models/PriceBarDTO.py +19 -0
  251. stonepy/models/PriceDTO.py +29 -0
  252. stonepy/models/PriceTickDTO.py +16 -0
  253. stonepy/models/QuoteDTO.py +31 -0
  254. stonepy/models/SaveAlertRequestDTOv2.py +27 -0
  255. stonepy/models/SaveAlertResponseDTOv2.py +13 -0
  256. stonepy/models/SaveAllocationProfileRequestDTO.py +11 -0
  257. stonepy/models/SaveAllocationProfileResponseDTO.py +11 -0
  258. stonepy/models/SaveClientPreferenceRequestDTO.py +20 -0
  259. stonepy/models/SaveMarketInformationRequestDTO.py +21 -0
  260. stonepy/models/SaveWatchlistRequestDTO.py +19 -0
  261. stonepy/models/SaveWatchlistResponseDTO.py +13 -0
  262. stonepy/models/SecureMessageCount.py +16 -0
  263. stonepy/models/SignalDTO.py +47 -0
  264. stonepy/models/SignalPerformanceDTO.py +21 -0
  265. stonepy/models/SignalPerformanceResponseDTO.py +23 -0
  266. stonepy/models/SingleActiveStopLimitOrderResponseDTO.py +20 -0
  267. stonepy/models/SingleOpenPositionResponseDTO.py +18 -0
  268. stonepy/models/StoryResponseDTO.py +14 -0
  269. stonepy/models/SystemStatusDTO.py +13 -0
  270. stonepy/models/SystemStatusRequestDTO.py +13 -0
  271. stonepy/models/Timestamp.py +14 -0
  272. stonepy/models/TradeMarginDTO.py +41 -0
  273. stonepy/models/UpdateDeleteClientPreferenceResponseDTO.py +13 -0
  274. stonepy/models/UpdateFixedMarginTradeOrderRequestDTO.py +20 -0
  275. stonepy/models/UpdateStopLimitOrderRequestDTO.py +11 -0
  276. stonepy/models/UpdateTradeOrderRequestDTO.py +14 -0
  277. stonepy/models/UpdateWatchlistDisplayOrderRequestDTO.py +15 -0
  278. stonepy/models/__init__.py +781 -0
  279. stonepy/models/enums.py +665 -0
  280. stonepy/py.typed +0 -0
  281. stonepy/resources/__init__.py +48 -0
  282. stonepy/resources/cfd/__init__.py +21 -0
  283. stonepy/resources/cfd/_sync/list_cfd_markets.py +37 -0
  284. stonepy/resources/cfd/list_cfd_markets.py +36 -0
  285. stonepy/resources/clientapplication/__init__.py +23 -0
  286. stonepy/resources/clientapplication/_sync/get_version_information.py +19 -0
  287. stonepy/resources/clientapplication/get_version_information.py +18 -0
  288. stonepy/resources/clientpreference/__init__.py +57 -0
  289. stonepy/resources/clientpreference/_sync/delete.py +20 -0
  290. stonepy/resources/clientpreference/_sync/get.py +18 -0
  291. stonepy/resources/clientpreference/_sync/get_key_list.py +20 -0
  292. stonepy/resources/clientpreference/_sync/get_list.py +14 -0
  293. stonepy/resources/clientpreference/_sync/get_overridden_settings.py +19 -0
  294. stonepy/resources/clientpreference/_sync/save.py +20 -0
  295. stonepy/resources/clientpreference/_sync/save_overridden_settings.py +19 -0
  296. stonepy/resources/clientpreference/delete.py +19 -0
  297. stonepy/resources/clientpreference/get.py +17 -0
  298. stonepy/resources/clientpreference/get_key_list.py +19 -0
  299. stonepy/resources/clientpreference/get_list.py +15 -0
  300. stonepy/resources/clientpreference/get_overridden_settings.py +18 -0
  301. stonepy/resources/clientpreference/save.py +19 -0
  302. stonepy/resources/clientpreference/save_overridden_settings.py +18 -0
  303. stonepy/resources/margin/__init__.py +25 -0
  304. stonepy/resources/margin/_sync/get_client_account_margin.py +14 -0
  305. stonepy/resources/margin/get_client_account_margin.py +15 -0
  306. stonepy/resources/market/__init__.py +153 -0
  307. stonepy/resources/market/_sync/full_search_with_tags.py +49 -0
  308. stonepy/resources/market/_sync/get_full_search_with_tags.py +51 -0
  309. stonepy/resources/market/_sync/get_latest_price_bars.py +28 -0
  310. stonepy/resources/market/_sync/get_latest_price_ticks.py +22 -0
  311. stonepy/resources/market/_sync/get_market_information.py +16 -0
  312. stonepy/resources/market/_sync/get_market_information_extended.py +19 -0
  313. stonepy/resources/market/_sync/get_market_spread.py +14 -0
  314. stonepy/resources/market/_sync/get_price_bars_after_date.py +42 -0
  315. stonepy/resources/market/_sync/get_price_bars_before_date.py +41 -0
  316. stonepy/resources/market/_sync/get_price_bars_between_dates.py +44 -0
  317. stonepy/resources/market/_sync/get_price_ticks_after_date.py +29 -0
  318. stonepy/resources/market/_sync/get_price_ticks_before_date.py +29 -0
  319. stonepy/resources/market/_sync/get_price_ticks_between_dates.py +35 -0
  320. stonepy/resources/market/_sync/list_market_information.py +16 -0
  321. stonepy/resources/market/_sync/list_market_information_search.py +45 -0
  322. stonepy/resources/market/_sync/list_market_search.py +46 -0
  323. stonepy/resources/market/_sync/list_market_search_paginated.py +52 -0
  324. stonepy/resources/market/_sync/save_market_information.py +19 -0
  325. stonepy/resources/market/_sync/save_product_information.py +16 -0
  326. stonepy/resources/market/_sync/search_with_tags.py +51 -0
  327. stonepy/resources/market/full_search_with_tags.py +48 -0
  328. stonepy/resources/market/get_full_search_with_tags.py +50 -0
  329. stonepy/resources/market/get_latest_price_bars.py +27 -0
  330. stonepy/resources/market/get_latest_price_ticks.py +21 -0
  331. stonepy/resources/market/get_market_information.py +15 -0
  332. stonepy/resources/market/get_market_information_extended.py +18 -0
  333. stonepy/resources/market/get_market_spread.py +13 -0
  334. stonepy/resources/market/get_price_bars_after_date.py +41 -0
  335. stonepy/resources/market/get_price_bars_before_date.py +40 -0
  336. stonepy/resources/market/get_price_bars_between_dates.py +43 -0
  337. stonepy/resources/market/get_price_ticks_after_date.py +28 -0
  338. stonepy/resources/market/get_price_ticks_before_date.py +28 -0
  339. stonepy/resources/market/get_price_ticks_between_dates.py +34 -0
  340. stonepy/resources/market/list_market_information.py +15 -0
  341. stonepy/resources/market/list_market_information_search.py +44 -0
  342. stonepy/resources/market/list_market_search.py +45 -0
  343. stonepy/resources/market/list_market_search_paginated.py +51 -0
  344. stonepy/resources/market/save_market_information.py +18 -0
  345. stonepy/resources/market/save_product_information.py +15 -0
  346. stonepy/resources/market/search_with_tags.py +50 -0
  347. stonepy/resources/message/__init__.py +71 -0
  348. stonepy/resources/message/_sync/client_communication_message_update.py +23 -0
  349. stonepy/resources/message/_sync/get_client_application_message_translation.py +21 -0
  350. stonepy/resources/message/_sync/get_client_application_message_translation_with_interesting_items.py +24 -0
  351. stonepy/resources/message/_sync/get_client_communication_messages.py +18 -0
  352. stonepy/resources/message/_sync/get_message_popup.py +16 -0
  353. stonepy/resources/message/_sync/get_system_lookup.py +20 -0
  354. stonepy/resources/message/_sync/save_client_communication_message_response.py +19 -0
  355. stonepy/resources/message/client_communication_message_update.py +22 -0
  356. stonepy/resources/message/get_client_application_message_translation.py +20 -0
  357. stonepy/resources/message/get_client_application_message_translation_with_interesting_items.py +23 -0
  358. stonepy/resources/message/get_client_communication_messages.py +17 -0
  359. stonepy/resources/message/get_message_popup.py +15 -0
  360. stonepy/resources/message/get_system_lookup.py +21 -0
  361. stonepy/resources/message/save_client_communication_message_response.py +18 -0
  362. stonepy/resources/news/__init__.py +55 -0
  363. stonepy/resources/news/_sync/get_market_report_headlines.py +18 -0
  364. stonepy/resources/news/_sync/get_market_reports.py +16 -0
  365. stonepy/resources/news/_sync/get_news.py +16 -0
  366. stonepy/resources/news/_sync/get_news_headlines.py +16 -0
  367. stonepy/resources/news/_sync/get_story.py +14 -0
  368. stonepy/resources/news/_sync/search_market_reports.py +31 -0
  369. stonepy/resources/news/_sync/search_news.py +31 -0
  370. stonepy/resources/news/get_market_report_headlines.py +17 -0
  371. stonepy/resources/news/get_market_reports.py +17 -0
  372. stonepy/resources/news/get_news.py +15 -0
  373. stonepy/resources/news/get_news_headlines.py +15 -0
  374. stonepy/resources/news/get_story.py +13 -0
  375. stonepy/resources/news/search_market_reports.py +30 -0
  376. stonepy/resources/news/search_news.py +30 -0
  377. stonepy/resources/order/__init__.py +83 -0
  378. stonepy/resources/order/_sync/cancel_order.py +17 -0
  379. stonepy/resources/order/_sync/get_active_stop_limit_order.py +21 -0
  380. stonepy/resources/order/_sync/get_open_position.py +21 -0
  381. stonepy/resources/order/_sync/get_order.py +18 -0
  382. stonepy/resources/order/_sync/list_active_orders.py +22 -0
  383. stonepy/resources/order/_sync/list_active_stop_limit_orders.py +22 -0
  384. stonepy/resources/order/_sync/list_open_positions.py +22 -0
  385. stonepy/resources/order/_sync/list_stop_limit_order_history.py +31 -0
  386. stonepy/resources/order/_sync/list_trade_history.py +30 -0
  387. stonepy/resources/order/_sync/order.py +19 -0
  388. stonepy/resources/order/_sync/place_order.py +17 -0
  389. stonepy/resources/order/_sync/simulate_trade.py +18 -0
  390. stonepy/resources/order/cancel_order.py +16 -0
  391. stonepy/resources/order/get_active_stop_limit_order.py +22 -0
  392. stonepy/resources/order/get_open_position.py +20 -0
  393. stonepy/resources/order/get_order.py +17 -0
  394. stonepy/resources/order/list_active_orders.py +21 -0
  395. stonepy/resources/order/list_active_stop_limit_orders.py +23 -0
  396. stonepy/resources/order/list_open_positions.py +21 -0
  397. stonepy/resources/order/list_stop_limit_order_history.py +30 -0
  398. stonepy/resources/order/list_trade_history.py +29 -0
  399. stonepy/resources/order/order.py +18 -0
  400. stonepy/resources/order/place_order.py +16 -0
  401. stonepy/resources/order/simulate_trade.py +19 -0
  402. stonepy/resources/preference/__init__.py +37 -0
  403. stonepy/resources/preference/_sync/delete_user_preference.py +14 -0
  404. stonepy/resources/preference/_sync/get_user_preference.py +16 -0
  405. stonepy/resources/preference/_sync/save_user_preference.py +15 -0
  406. stonepy/resources/preference/delete_user_preference.py +15 -0
  407. stonepy/resources/preference/get_user_preference.py +15 -0
  408. stonepy/resources/preference/save_user_preference.py +14 -0
  409. stonepy/resources/price_alert/__init__.py +35 -0
  410. stonepy/resources/price_alert/_sync/delete_pa.py +14 -0
  411. stonepy/resources/price_alert/_sync/get_pa.py +19 -0
  412. stonepy/resources/price_alert/_sync/save_pa.py +19 -0
  413. stonepy/resources/price_alert/_sync/save_price_alert.py +18 -0
  414. stonepy/resources/price_alert/delete_pa.py +13 -0
  415. stonepy/resources/price_alert/get_pa.py +18 -0
  416. stonepy/resources/price_alert/save_pa.py +18 -0
  417. stonepy/resources/price_alert/save_price_alert.py +17 -0
  418. stonepy/resources/session/__init__.py +29 -0
  419. stonepy/resources/session/_sync/change_password.py +14 -0
  420. stonepy/resources/session/_sync/delete_session.py +14 -0
  421. stonepy/resources/session/_sync/log_on.py +21 -0
  422. stonepy/resources/session/change_password.py +15 -0
  423. stonepy/resources/session/delete_session.py +13 -0
  424. stonepy/resources/session/log_on.py +20 -0
  425. stonepy/resources/spread/__init__.py +21 -0
  426. stonepy/resources/spread/_sync/list_spread_markets.py +37 -0
  427. stonepy/resources/spread/list_spread_markets.py +36 -0
  428. stonepy/resources/user_account/__init__.py +35 -0
  429. stonepy/resources/user_account/_sync/get_client_and_trading_account.py +19 -0
  430. stonepy/resources/user_account/_sync/save_account_information.py +19 -0
  431. stonepy/resources/user_account/get_client_and_trading_account.py +18 -0
  432. stonepy/resources/user_account/save_account_information.py +18 -0
  433. stonepy/resources/watchlist/__init__.py +39 -0
  434. stonepy/resources/watchlist/_sync/delete_watchlist.py +16 -0
  435. stonepy/resources/watchlist/_sync/get_watchlists.py +14 -0
  436. stonepy/resources/watchlist/_sync/get_watchlists_list.py +27 -0
  437. stonepy/resources/watchlist/_sync/save_watchlist.py +14 -0
  438. stonepy/resources/watchlist/delete_watchlist.py +15 -0
  439. stonepy/resources/watchlist/get_watchlists.py +13 -0
  440. stonepy/resources/watchlist/get_watchlists_list.py +26 -0
  441. stonepy/resources/watchlist/save_watchlist.py +13 -0
  442. stonepy-0.1.0.dist-info/METADATA +186 -0
  443. stonepy-0.1.0.dist-info/RECORD +445 -0
  444. stonepy-0.1.0.dist-info/WHEEL +4 -0
  445. stonepy-0.1.0.dist-info/licenses/LICENSE +21 -0
stonepy/__init__.py ADDED
@@ -0,0 +1,27 @@
1
+ from stonepy._core.config import ClientConfig
2
+ from stonepy._core.errors import (
3
+ AuthenticationError,
4
+ OrderRejectedError,
5
+ RateLimitError,
6
+ ResponseParseError,
7
+ StoneXAPIError,
8
+ StoneXError,
9
+ TransportError,
10
+ )
11
+ from stonepy.client import AsyncStoneXClient, StoneXClient
12
+
13
+ __version__ = "0.1.0"
14
+
15
+ __all__ = [
16
+ "AsyncStoneXClient",
17
+ "AuthenticationError",
18
+ "ClientConfig",
19
+ "OrderRejectedError",
20
+ "RateLimitError",
21
+ "ResponseParseError",
22
+ "StoneXAPIError",
23
+ "StoneXClient",
24
+ "StoneXError",
25
+ "TransportError",
26
+ "__version__",
27
+ ]
@@ -0,0 +1 @@
1
+
stonepy/_core/clock.py ADDED
@@ -0,0 +1,49 @@
1
+ """Clock abstraction so timing logic is testable with a fake clock."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import time
7
+ from typing import Protocol, runtime_checkable
8
+
9
+
10
+ class Clock(Protocol):
11
+ def now(self) -> float: ...
12
+ def sleep(self, seconds: float) -> None: ...
13
+
14
+
15
+ @runtime_checkable
16
+ class AsyncClock(Clock, Protocol):
17
+ async def asleep(self, seconds: float) -> None: ...
18
+
19
+
20
+ class SystemClock:
21
+ def now(self) -> float:
22
+ return time.monotonic()
23
+
24
+ def sleep(self, seconds: float) -> None:
25
+ if seconds > 0:
26
+ time.sleep(seconds)
27
+
28
+ async def asleep(self, seconds: float) -> None:
29
+ if seconds > 0:
30
+ await asyncio.sleep(seconds)
31
+
32
+
33
+ class FakeClock:
34
+ def __init__(self) -> None:
35
+ self._t = 0.0
36
+
37
+ def now(self) -> float:
38
+ return self._t
39
+
40
+ def sleep(self, seconds: float) -> None:
41
+ self._t += max(0.0, seconds)
42
+
43
+ async def asleep(self, seconds: float) -> None:
44
+ self.sleep(seconds)
45
+
46
+ def advance(self, seconds: float) -> None:
47
+ if seconds < 0:
48
+ raise ValueError("seconds must be non-negative")
49
+ self._t += seconds
stonepy/_core/codec.py ADDED
@@ -0,0 +1,96 @@
1
+ """Decimal-safe JSON and WCF (`/Date(ms)/`) date handling."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ from datetime import UTC, datetime, timedelta
8
+ from decimal import Decimal
9
+ from typing import Annotated, Any, NoReturn, TypeAlias
10
+
11
+ import simplejson
12
+ from pydantic import BeforeValidator, PlainSerializer
13
+
14
+ WCF_MINVALUE_MS = -62135596800000
15
+ _WCF_RE = re.compile(r"\\?/Date\((-?\d+)(?:[+-]\d{4})?\)\\?/")
16
+ _UTC_EPOCH = datetime(1970, 1, 1, tzinfo=UTC)
17
+ _MS_PER_DAY = 86_400_000
18
+ _MS_PER_SECOND = 1_000
19
+
20
+
21
+ def _ensure_timezone_aware(value: datetime) -> datetime:
22
+ if value.utcoffset() is None:
23
+ raise ValueError("datetime must be timezone-aware")
24
+ return value
25
+
26
+
27
+ def parse_wcf_date(value: Any) -> datetime | None:
28
+ if value is None:
29
+ return None
30
+ if isinstance(value, datetime):
31
+ return _ensure_timezone_aware(value)
32
+ if isinstance(value, bool):
33
+ raise ValueError(f"not a WCF date: {value!r}")
34
+ if isinstance(value, int):
35
+ ms = value
36
+ elif isinstance(value, str):
37
+ m = _WCF_RE.fullmatch(value)
38
+ if not m:
39
+ raise ValueError(f"not a WCF date: {value!r}")
40
+ ms = int(m.group(1))
41
+ else:
42
+ raise ValueError(f"not a WCF date: {value!r}")
43
+ if ms == WCF_MINVALUE_MS:
44
+ return None
45
+ try:
46
+ return _UTC_EPOCH + timedelta(milliseconds=ms)
47
+ except OverflowError as exc:
48
+ raise ValueError(f"WCF date out of range: {value!r}") from exc
49
+
50
+
51
+ def format_wcf_date(value: datetime | None) -> str | None:
52
+ if value is None:
53
+ return None
54
+ aware_value = _ensure_timezone_aware(value)
55
+ delta = aware_value.astimezone(UTC) - _UTC_EPOCH
56
+ ms = (
57
+ delta.days * _MS_PER_DAY
58
+ + delta.seconds * _MS_PER_SECOND
59
+ + delta.microseconds // _MS_PER_SECOND
60
+ )
61
+ return f"/Date({ms})/"
62
+
63
+
64
+ StoneXDateTime: TypeAlias = Annotated[
65
+ datetime | None,
66
+ BeforeValidator(parse_wcf_date),
67
+ PlainSerializer(format_wcf_date, when_used="always"),
68
+ ]
69
+
70
+
71
+ def _reject_nonfinite_decimals(value: Any) -> None:
72
+ if isinstance(value, Decimal):
73
+ if not value.is_finite():
74
+ raise ValueError("non-finite Decimal values are not valid JSON")
75
+ elif isinstance(value, dict):
76
+ for key, item in value.items():
77
+ _reject_nonfinite_decimals(key)
78
+ _reject_nonfinite_decimals(item)
79
+ elif isinstance(value, (list, tuple)):
80
+ for item in value:
81
+ _reject_nonfinite_decimals(item)
82
+
83
+
84
+ def _reject_json_constant(value: str) -> NoReturn:
85
+ raise ValueError(f"non-finite JSON constant is not allowed: {value}")
86
+
87
+
88
+ def dumps(obj: Any) -> str:
89
+ _reject_nonfinite_decimals(obj)
90
+ return simplejson.dumps(obj, use_decimal=True, allow_nan=False, separators=(",", ":"))
91
+
92
+
93
+ def loads(text: str | bytes) -> Any:
94
+ if isinstance(text, bytes):
95
+ text = text.decode("utf-8")
96
+ return json.loads(text, parse_float=Decimal, parse_constant=_reject_json_constant)
@@ -0,0 +1,118 @@
1
+ """Client configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from dataclasses import dataclass, fields
7
+ from importlib.metadata import PackageNotFoundError, version
8
+ from typing import Any, TypeVar, cast
9
+
10
+ from stonepy._core.logging import safe_repr
11
+ from stonepy._core.status import StatusDecoder, default_status_decoder
12
+
13
+ _T = TypeVar("_T")
14
+ _FALLBACK_VERSION = "0.1.0"
15
+
16
+
17
+ def _default_user_agent() -> str:
18
+ try:
19
+ package_version = version("stonepy")
20
+ except PackageNotFoundError:
21
+ package_version = _FALLBACK_VERSION
22
+ return f"stonepy/{package_version}"
23
+
24
+
25
+ _DEFAULT_USER_AGENT = _default_user_agent()
26
+
27
+
28
+ @dataclass
29
+ class ClientConfig:
30
+ """Configuration for StoneX clients.
31
+
32
+ `base_url` is required and points at the CIAPI root. `app_key`, `username`, and
33
+ `password` enable automatic session refresh. Timeout, retry, rate-limit, TLS, proxy,
34
+ plugin, and status-decoder fields tune transport behavior. Use `from_env()` to read the
35
+ `STONEX_*` environment variables with optional keyword overrides.
36
+ """
37
+
38
+ base_url: str
39
+ app_key: str = ""
40
+ username: str = ""
41
+ password: str = ""
42
+ app_version: str = "stonepy"
43
+ connect_timeout: float = 10.0
44
+ read_timeout: float = 30.0
45
+ write_timeout: float = 30.0
46
+ pool_timeout: float = 5.0
47
+ max_connections: int = 20
48
+ verify_tls: bool = True
49
+ proxy: str | None = None
50
+ user_agent: str = _DEFAULT_USER_AGENT
51
+ max_retries: int = 3
52
+ retry_budget_seconds: float = 30.0
53
+ rate_limit_max: int = 500
54
+ rate_limit_window_seconds: float = 5.0
55
+ proactive_refresh_seconds: float = 1080.0
56
+ status_decoder: StatusDecoder | None = default_status_decoder
57
+ enable_plugins: bool = False
58
+ allow_overrides: tuple[str, ...] = ()
59
+
60
+ @classmethod
61
+ def from_env(cls, **overrides: Any) -> ClientConfig:
62
+ known_fields = {field.name for field in fields(cls)}
63
+ unknown_fields = set(overrides) - known_fields
64
+ if unknown_fields:
65
+ unknown = sorted(unknown_fields)[0]
66
+ raise TypeError(f"unexpected ClientConfig override: {unknown}")
67
+
68
+ base_url = _override(
69
+ overrides,
70
+ "base_url",
71
+ os.environ.get("STONEX_BASE_URL", ""),
72
+ )
73
+ if not base_url.strip():
74
+ raise ValueError("base_url is required: set STONEX_BASE_URL or pass base_url=...")
75
+
76
+ return cls(
77
+ base_url=base_url,
78
+ app_key=_override(overrides, "app_key", os.environ.get("STONEX_APP_KEY", "")),
79
+ username=_override(overrides, "username", os.environ.get("STONEX_USERNAME", "")),
80
+ password=_override(overrides, "password", os.environ.get("STONEX_PASSWORD", "")),
81
+ app_version=_override(overrides, "app_version", "stonepy"),
82
+ connect_timeout=_override(overrides, "connect_timeout", 10.0),
83
+ read_timeout=_override(overrides, "read_timeout", 30.0),
84
+ write_timeout=_override(overrides, "write_timeout", 30.0),
85
+ pool_timeout=_override(overrides, "pool_timeout", 5.0),
86
+ max_connections=_override(overrides, "max_connections", 20),
87
+ verify_tls=_override(overrides, "verify_tls", True),
88
+ proxy=_override(overrides, "proxy", None),
89
+ user_agent=_override(overrides, "user_agent", _DEFAULT_USER_AGENT),
90
+ max_retries=_override(overrides, "max_retries", 3),
91
+ retry_budget_seconds=_override(overrides, "retry_budget_seconds", 30.0),
92
+ rate_limit_max=_override(overrides, "rate_limit_max", 500),
93
+ rate_limit_window_seconds=_override(overrides, "rate_limit_window_seconds", 5.0),
94
+ proactive_refresh_seconds=_override(overrides, "proactive_refresh_seconds", 1080.0),
95
+ status_decoder=_override_nullable(
96
+ overrides,
97
+ "status_decoder",
98
+ default_status_decoder,
99
+ ),
100
+ enable_plugins=_override(overrides, "enable_plugins", False),
101
+ allow_overrides=_override(overrides, "allow_overrides", ()),
102
+ )
103
+
104
+ def __repr__(self) -> str:
105
+ return safe_repr(self)
106
+
107
+
108
+ def _override(overrides: dict[str, Any], name: str, default: _T) -> _T:
109
+ value = overrides.get(name, default)
110
+ if value is None:
111
+ return default
112
+ return cast(_T, value)
113
+
114
+
115
+ def _override_nullable(overrides: dict[str, Any], name: str, default: _T) -> _T | None:
116
+ if name in overrides:
117
+ return cast(_T | None, overrides[name])
118
+ return default
@@ -0,0 +1,36 @@
1
+ """Endpoint metadata emitted by the generator and consumed by the pipeline."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import enum
6
+ from dataclasses import dataclass, field
7
+ from typing import Generic, Literal, TypeVar
8
+
9
+ from pydantic import BaseModel
10
+
11
+ ResponseT = TypeVar("ResponseT", bound=BaseModel)
12
+
13
+
14
+ class AuthPolicy(enum.Enum):
15
+ NONE = "none"
16
+ SESSION = "session"
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class Param:
21
+ name: str
22
+ location: Literal["path", "query", "body"]
23
+ python_name: str
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class EndpointSpec(Generic[ResponseT]):
28
+ name: str
29
+ method: str
30
+ path: str
31
+ idempotent: bool
32
+ auth_policy: AuthPolicy
33
+ rate_limit_bucket: str
34
+ response_model: type[ResponseT]
35
+ request_model: type | None = None
36
+ params: tuple[Param, ...] = field(default_factory=tuple)
@@ -0,0 +1,169 @@
1
+ """Exception hierarchy for stonepy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+
7
+ _REDACT = {"session", "password", "appkey", "authorization"}
8
+
9
+
10
+ def _safe_headers(headers: Mapping[str, str]) -> dict[str, str]:
11
+ return {k: ("***" if k.lower() in _REDACT else v) for k, v in headers.items()}
12
+
13
+
14
+ class StoneXError(Exception):
15
+ """Base class for all stonepy errors."""
16
+
17
+
18
+ class StoneXAPIError(StoneXError):
19
+ """HTTP or API business-error response with endpoint context.
20
+
21
+ `raw_body` is retained for diagnostics and may contain sensitive response data.
22
+ It is intentionally omitted from `str()` and redacted from `repr()`.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ *,
28
+ http_status: int,
29
+ error_code: int | None,
30
+ error_message: str | None,
31
+ method: str,
32
+ path: str,
33
+ raw_body: bytes | None,
34
+ headers: Mapping[str, str],
35
+ ) -> None:
36
+ self.http_status = http_status
37
+ self.error_code = error_code
38
+ self.error_message = error_message
39
+ self.method = method
40
+ self.path = path
41
+ self.raw_body = raw_body
42
+ self.headers = dict(headers)
43
+ super().__init__(
44
+ f"{method} {path} -> HTTP {http_status} (ErrorCode={error_code}): {error_message}"
45
+ )
46
+
47
+ def __repr__(self) -> str:
48
+ return (
49
+ f"{type(self).__name__}(http_status={self.http_status}, "
50
+ f"error_code={self.error_code}, method={self.method!r}, "
51
+ f"path={self.path!r}, headers={_safe_headers(self.headers)!r})"
52
+ )
53
+
54
+
55
+ class AuthenticationError(StoneXAPIError):
56
+ """Invalid credentials (ErrorCode 4010) or unrecoverable 401."""
57
+
58
+
59
+ class ResponseParseError(StoneXError):
60
+ """Malformed or schema-invalid success response body.
61
+
62
+ `raw_body` is retained for diagnostics and may contain sensitive response data.
63
+ It is intentionally omitted from `str()` and redacted from `repr()`.
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ *,
69
+ phase: str,
70
+ http_status: int,
71
+ method: str,
72
+ path: str,
73
+ raw_body: bytes,
74
+ message: str,
75
+ ) -> None:
76
+ self.phase = phase
77
+ self.http_status = http_status
78
+ self.method = method
79
+ self.path = path
80
+ self.raw_body = raw_body
81
+ super().__init__(
82
+ f"{method} {path} -> HTTP {http_status} response {phase} failed: {message}"
83
+ )
84
+
85
+ def __repr__(self) -> str:
86
+ return (
87
+ f"{type(self).__name__}(phase={self.phase!r}, "
88
+ f"http_status={self.http_status}, method={self.method!r}, path={self.path!r}, "
89
+ f"raw_body=<redacted {len(self.raw_body)} bytes>)"
90
+ )
91
+
92
+
93
+ class RateLimitError(StoneXAPIError):
94
+ """Rate-limit API error with an optional parsed retry_after delay."""
95
+
96
+ def __init__(
97
+ self,
98
+ *,
99
+ http_status: int,
100
+ error_code: int | None,
101
+ error_message: str | None,
102
+ method: str,
103
+ path: str,
104
+ raw_body: bytes | None,
105
+ headers: Mapping[str, str],
106
+ retry_after: float | None = None,
107
+ ) -> None:
108
+ self.retry_after = retry_after
109
+ super().__init__(
110
+ http_status=http_status,
111
+ error_code=error_code,
112
+ error_message=error_message,
113
+ method=method,
114
+ path=path,
115
+ raw_body=raw_body,
116
+ headers=headers,
117
+ )
118
+
119
+
120
+ class OrderRejectedError(StoneXError):
121
+ """Order business-status rejection with optional endpoint context.
122
+
123
+ `response` is retained for diagnostics and may contain sensitive response data.
124
+ It is intentionally omitted from `repr()`.
125
+ """
126
+
127
+ def __init__(
128
+ self,
129
+ *,
130
+ status: int,
131
+ status_reason: int | None,
132
+ reason: str | None,
133
+ response: object,
134
+ method: str | None = None,
135
+ path: str | None = None,
136
+ http_status: int | None = None,
137
+ ) -> None:
138
+ self.status = status
139
+ self.status_reason = status_reason
140
+ self.reason = reason
141
+ self.response = response
142
+ self.method = method
143
+ self.path = path
144
+ self.http_status = http_status
145
+ endpoint = f"{method} {path} -> HTTP {http_status}: " if method and path else ""
146
+ super().__init__(f"{endpoint}Order rejected: status={status} reason={reason!r}")
147
+
148
+ def __repr__(self) -> str:
149
+ return (
150
+ f"{type(self).__name__}(status={self.status}, "
151
+ f"status_reason={self.status_reason}, reason={self.reason!r}, "
152
+ f"method={self.method!r}, path={self.path!r}, http_status={self.http_status})"
153
+ )
154
+
155
+
156
+ class TransportError(StoneXError):
157
+ """Network-level failure (connect/read/timeout)."""
158
+
159
+ def __init__(self, message: str, *, method: str, path: str, attempt: int) -> None:
160
+ self.method = method
161
+ self.path = path
162
+ self.attempt = attempt
163
+ super().__init__(f"{method} {path} transport failed on attempt {attempt}: {message}")
164
+
165
+ def __repr__(self) -> str:
166
+ return (
167
+ f"{type(self).__name__}(method={self.method!r}, path={self.path!r}, "
168
+ f"attempt={self.attempt})"
169
+ )
@@ -0,0 +1,62 @@
1
+ """Logging helpers with secret redaction."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from dataclasses import is_dataclass
7
+ from typing import Protocol, TypeGuard
8
+
9
+ _DEFAULT_SECRET_KEYS = {"app_key", "appkey", "authorization", "password", "proxy", "session"}
10
+
11
+
12
+ class _DataclassInstance(Protocol):
13
+ __dataclass_fields__: dict[str, object]
14
+
15
+
16
+ def redact(value: str) -> str:
17
+ if not value:
18
+ return value
19
+ return "***"
20
+
21
+
22
+ def safe_repr(obj: object, secret_keys: set[str] | None = None) -> str:
23
+ keys = _secret_keys(secret_keys)
24
+
25
+ if isinstance(obj, Mapping):
26
+ return repr(_redacted_mapping(obj, keys))
27
+
28
+ if _is_dataclass_instance(obj):
29
+ return _redacted_dataclass_repr(obj, keys)
30
+
31
+ return repr(obj)
32
+
33
+
34
+ def _secret_keys(secret_keys: set[str] | None) -> set[str]:
35
+ keys = set(_DEFAULT_SECRET_KEYS)
36
+ if secret_keys is not None:
37
+ keys.update(key.lower() for key in secret_keys)
38
+ return keys
39
+
40
+
41
+ def _redacted_mapping(
42
+ mapping: Mapping[object, object], secret_keys: set[str]
43
+ ) -> dict[object, object]:
44
+ return {k: _redacted_value(k, v, secret_keys) for k, v in mapping.items()}
45
+
46
+
47
+ def _redacted_value(key: object, value: object, secret_keys: set[str]) -> object:
48
+ if isinstance(key, str) and key.lower() in secret_keys:
49
+ return "***"
50
+ return value
51
+
52
+
53
+ def _is_dataclass_instance(obj: object) -> TypeGuard[_DataclassInstance]:
54
+ return is_dataclass(obj) and not isinstance(obj, type)
55
+
56
+
57
+ def _redacted_dataclass_repr(obj: _DataclassInstance, secret_keys: set[str]) -> str:
58
+ rendered = ", ".join(
59
+ f"{name}={_redacted_value(name, getattr(obj, name), secret_keys)!r}"
60
+ for name in obj.__dataclass_fields__
61
+ )
62
+ return f"{type(obj).__qualname__}({rendered})"
@@ -0,0 +1,31 @@
1
+ """Base Pydantic models for requests and responses."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pydantic import BaseModel, ConfigDict
6
+
7
+ from stonepy._core.codec import StoneXDateTime
8
+
9
+ __all__ = [
10
+ "PassthroughResponseModel",
11
+ "RequestModel",
12
+ "ResponseModel",
13
+ "StoneXDateTime",
14
+ "StoneXModel",
15
+ ]
16
+
17
+
18
+ class StoneXModel(BaseModel):
19
+ model_config = ConfigDict(populate_by_name=True)
20
+
21
+
22
+ class RequestModel(StoneXModel):
23
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
24
+
25
+
26
+ class ResponseModel(StoneXModel):
27
+ model_config = ConfigDict(populate_by_name=True, extra="ignore")
28
+
29
+
30
+ class PassthroughResponseModel(StoneXModel):
31
+ model_config = ConfigDict(populate_by_name=True, extra="allow")