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.
- stonepy/__init__.py +27 -0
- stonepy/_core/__init__.py +1 -0
- stonepy/_core/clock.py +49 -0
- stonepy/_core/codec.py +96 -0
- stonepy/_core/config.py +118 -0
- stonepy/_core/endpoint.py +36 -0
- stonepy/_core/errors.py +169 -0
- stonepy/_core/logging.py +62 -0
- stonepy/_core/models.py +31 -0
- stonepy/_core/pipeline.py +605 -0
- stonepy/_core/plugins.py +144 -0
- stonepy/_core/ratelimit.py +99 -0
- stonepy/_core/resource.py +12 -0
- stonepy/_core/retry.py +30 -0
- stonepy/_core/session.py +155 -0
- stonepy/_core/status.py +80 -0
- stonepy/_core/transport.py +352 -0
- stonepy/_endpoints/__init__.py +36 -0
- stonepy/_endpoints/cfd.py +82 -0
- stonepy/_endpoints/clientapplication.py +45 -0
- stonepy/_endpoints/clientpreference.py +236 -0
- stonepy/_endpoints/margin.py +38 -0
- stonepy/_endpoints/market.py +1295 -0
- stonepy/_endpoints/message.py +284 -0
- stonepy/_endpoints/news.py +319 -0
- stonepy/_endpoints/order.py +367 -0
- stonepy/_endpoints/preference.py +96 -0
- stonepy/_endpoints/price_alert.py +126 -0
- stonepy/_endpoints/session.py +99 -0
- stonepy/_endpoints/spread.py +82 -0
- stonepy/_endpoints/user_account.py +69 -0
- stonepy/_endpoints/watchlist.py +167 -0
- stonepy/_generator/__init__.py +1 -0
- stonepy/_generator/__main__.py +159 -0
- stonepy/_generator/catalog.py +552 -0
- stonepy/_generator/emit_client.py +493 -0
- stonepy/_generator/emit_contract.py +659 -0
- stonepy/_generator/emit_endpoints.py +716 -0
- stonepy/_generator/emit_models.py +203 -0
- stonepy/_generator/render.py +342 -0
- stonepy/_generator/scaffold.py +354 -0
- stonepy/client.py +469 -0
- stonepy/errors.py +23 -0
- stonepy/models/AccountInformationResponseDTOv2.py +18 -0
- stonepy/models/AccountResult.py +40 -0
- stonepy/models/ActiveSignalsResponseDTO.py +18 -0
- stonepy/models/AlertDTO.py +27 -0
- stonepy/models/AllocationProfileDTO.py +24 -0
- stonepy/models/AllocationProfileEntryDTO.py +18 -0
- stonepy/models/Api2FALogonOnResponseDTO.py +15 -0
- stonepy/models/ApiAccountHolderDTO.py +14 -0
- stonepy/models/ApiAccountInformationSaveRequestDTO.py +14 -0
- stonepy/models/ApiAccountInformationSaveResponseDTO.py +11 -0
- stonepy/models/ApiAccountOperatorDTOv2.py +19 -0
- stonepy/models/ApiActiveOrderDTO.py +21 -0
- stonepy/models/ApiActiveStopLimitOrderDTOv2.py +42 -0
- stonepy/models/ApiAdvisoryTradeOrderResponseDTO.py +20 -0
- stonepy/models/ApiAssociatedDTOv2.py +19 -0
- stonepy/models/ApiAssociatedResponseDTO.py +19 -0
- stonepy/models/ApiBandedSpreadsDTO.py +17 -0
- stonepy/models/ApiBasicStopLimitOrderDTO.py +19 -0
- stonepy/models/ApiChangePasswordRequestDTO.py +15 -0
- stonepy/models/ApiChangePasswordResponseDTO.py +13 -0
- stonepy/models/ApiCiConnectMultipleUsersDetailsDTO.py +23 -0
- stonepy/models/ApiClientAccountDTO.py +30 -0
- stonepy/models/ApiClientAccountMarginResponseDTO.py +25 -0
- stonepy/models/ApiClientAccountTokenDTOv2.py +14 -0
- stonepy/models/ApiClientAccountWatchlistDTO.py +21 -0
- stonepy/models/ApiClientAccountWatchlistItemDTO.py +23 -0
- stonepy/models/ApiClientApplicationMessageTranslationDTO.py +14 -0
- stonepy/models/ApiClientApplicationMessageTranslationRequestDTO.py +16 -0
- stonepy/models/ApiClientApplicationMessageTranslationResponseDTO.py +20 -0
- stonepy/models/ApiClientCommunicationResponseDTO.py +20 -0
- stonepy/models/ApiClientCommunicationUpdateRequestDTO.py +15 -0
- stonepy/models/ApiClientCommunicationUpdateResponseDTO.py +13 -0
- stonepy/models/ApiClientPreferencesOverriddenMarginFactorDTO.py +18 -0
- stonepy/models/ApiClientPreferencesOverriddenPriceToleranceDTO.py +15 -0
- stonepy/models/ApiClientPreferencesOverriddenSettingSaveDTO.py +16 -0
- stonepy/models/ApiClientPreferencesOverriddenSettingsDTO.py +28 -0
- stonepy/models/ApiClientPreferencesOverriddenSettingsGetResponseDTO.py +21 -0
- stonepy/models/ApiClientPreferencesOverriddenSettingsSaveDTO.py +25 -0
- stonepy/models/ApiClientPreferencesOverriddenSettingsSaveRequestDTO.py +23 -0
- stonepy/models/ApiClientPreferencesOverriddenSettingsSaveResponseDTO.py +23 -0
- stonepy/models/ApiClientPreferencesOverridenMarginFactorDTO.py +18 -0
- stonepy/models/ApiClientPreferencesOverridenPriceToleranceDTO.py +15 -0
- stonepy/models/ApiClientPreferencesOverridenSettingSaveDTO.py +16 -0
- stonepy/models/ApiClientPreferencesOverridenSettingsDTO.py +28 -0
- stonepy/models/ApiClientPreferencesOverridenSettingsGetResponseDTO.py +21 -0
- stonepy/models/ApiClientPreferencesOverridenSettingsSaveDTO.py +25 -0
- stonepy/models/ApiClientPreferencesOverridenSettingsSaveRequestDTO.py +20 -0
- stonepy/models/ApiClientPreferencesOverridenSettingsSaveResponseDTO.py +23 -0
- stonepy/models/ApiCommunityActionDTO.py +29 -0
- stonepy/models/ApiCommunityActionUserDetailsDTO.py +16 -0
- stonepy/models/ApiConnectUserDetailsDTO.py +28 -0
- stonepy/models/ApiContractDTOv2.py +15 -0
- stonepy/models/ApiCultureLookupDTO.py +13 -0
- stonepy/models/ApiDateTimeOffsetDTO.py +14 -0
- stonepy/models/ApiDeleteWatchlistRequestDTO.py +13 -0
- stonepy/models/ApiDeleteWatchlistResponseDTO.py +13 -0
- stonepy/models/ApiErrorResponseDTO.py +15 -0
- stonepy/models/ApiFollowedDTO.py +14 -0
- stonepy/models/ApiFollowerDTO.py +14 -0
- stonepy/models/ApiFreeCashToTradeResponseDTO.py +16 -0
- stonepy/models/ApiFxFinancingDTO.py +22 -0
- stonepy/models/ApiGetClientPreferenceResponseDTO.py +18 -0
- stonepy/models/ApiGetClientPreferencesResponseDTO.py +20 -0
- stonepy/models/ApiGetKeyListClientPreferenceResponseDTO.py +13 -0
- stonepy/models/ApiGetMarketInformationExtendedResponseDTOv2.py +20 -0
- stonepy/models/ApiGetPreferencesResponseDTO.py +18 -0
- stonepy/models/ApiIfDoneDTOv2.py +19 -0
- stonepy/models/ApiIfDoneResponseDTO.py +19 -0
- stonepy/models/ApiKnockoutDTO.py +20 -0
- stonepy/models/ApiLogOffRequestDTO.py +14 -0
- stonepy/models/ApiLogOffResponseDTO.py +13 -0
- stonepy/models/ApiLogOnRequestDTO.py +17 -0
- stonepy/models/ApiLogOnResponseDTOv2.py +26 -0
- stonepy/models/ApiLookupDTO.py +19 -0
- stonepy/models/ApiLookupResponseDTO.py +24 -0
- stonepy/models/ApiManagedClientAccountMarginResponseDTO.py +13 -0
- stonepy/models/ApiManagedClientAccountsMarginResponseDTO.py +20 -0
- stonepy/models/ApiManagedInstructionResponseDTO.py +21 -0
- stonepy/models/ApiManagedTradeDTO.py +22 -0
- stonepy/models/ApiManagedTradeHistoryDTO.py +27 -0
- stonepy/models/ApiMarketDTO.py +15 -0
- stonepy/models/ApiMarketEodDTO.py +14 -0
- stonepy/models/ApiMarketInformationDTOv2.py +141 -0
- stonepy/models/ApiMarketInformationExtendedDTOv2.py +33 -0
- stonepy/models/ApiMarketInformationSaveDTO.py +19 -0
- stonepy/models/ApiMarketSpreadDTO.py +17 -0
- stonepy/models/ApiMarketTagDTO.py +16 -0
- stonepy/models/ApiOpenPositionDTOv2.py +44 -0
- stonepy/models/ApiOrderActionResponseDTO.py +20 -0
- stonepy/models/ApiOrderDTOv2.py +47 -0
- stonepy/models/ApiOrderResponseDTO.py +34 -0
- stonepy/models/ApiPrimaryMarketTagDTO.py +18 -0
- stonepy/models/ApiProductInformationDTO.py +50 -0
- stonepy/models/ApiQuoteResponseDTO.py +15 -0
- stonepy/models/ApiRestrictionDTOv2.py +15 -0
- stonepy/models/ApiSaveAccountInformationRequestDTO.py +16 -0
- stonepy/models/ApiSaveAccountInformationResponseDTO.py +11 -0
- stonepy/models/ApiSaveClientPreferenceRequestDTO.py +21 -0
- stonepy/models/ApiSaveMarketInformationResponseDTO.py +11 -0
- stonepy/models/ApiSavePreferencesRequestDTO.py +18 -0
- stonepy/models/ApiSaveSignalPreferencesResponseDTO.py +11 -0
- stonepy/models/ApiSaveWatchlistRequestDTO.py +18 -0
- stonepy/models/ApiSaveWatchlistResponseDTO.py +11 -0
- stonepy/models/ApiSimulateOrderResponseDTO.py +14 -0
- stonepy/models/ApiSimulateTradeOrderResponseDTO.py +31 -0
- stonepy/models/ApiStepMarginBandDTO.py +16 -0
- stonepy/models/ApiStepMarginDTO.py +23 -0
- stonepy/models/ApiStopLimitOrderDTOv2.py +27 -0
- stonepy/models/ApiStopLimitOrderHistoryDTO.py +35 -0
- stonepy/models/ApiStopLimitResponseDTO.py +11 -0
- stonepy/models/ApiTradeHistoryDTO.py +43 -0
- stonepy/models/ApiTradeOrderDTOv2.py +22 -0
- stonepy/models/ApiTradeOrderResponseDTO.py +26 -0
- stonepy/models/ApiTraderDetailsDTO.py +25 -0
- stonepy/models/ApiTradingAccountDTOv2.py +18 -0
- stonepy/models/ApiTradingDayTimesDTO.py +20 -0
- stonepy/models/ApiUpdateDeleteClientPreferenceResponseDTO.py +13 -0
- stonepy/models/ApiUserDynamicProfileDTO.py +20 -0
- stonepy/models/ApiUserFollowedUsersDTO.py +19 -0
- stonepy/models/ApiUserFollowersDTO.py +19 -0
- stonepy/models/ApiUserProfileDTO.py +30 -0
- stonepy/models/ApiUserTradingAccountDTO.py +14 -0
- stonepy/models/ApiValidateSessionRequestDTOv2.py +17 -0
- stonepy/models/ApiValidateSessionResponseDTO.py +13 -0
- stonepy/models/ApiWallItemDTO.py +27 -0
- stonepy/models/ApiWallItemsForUsersDTO.py +19 -0
- stonepy/models/CancelOrderRequestDTO.py +16 -0
- stonepy/models/CashEquity.py +20 -0
- stonepy/models/ClientAccountMarginDTO.py +25 -0
- stonepy/models/ClientAccountMarginResponseDTO.py +25 -0
- stonepy/models/ClientCommunicationMessageDTO.py +18 -0
- stonepy/models/ClientPreferenceKeyDTO.py +14 -0
- stonepy/models/ClientPreferenceRequestDTO.py +13 -0
- stonepy/models/ClientPreferencesRequestDTO.py +13 -0
- stonepy/models/CorporateActionsDTO.py +13 -0
- stonepy/models/DeleteAllocationProfileRequestDTO.py +13 -0
- stonepy/models/DeleteAllocationProfileResponseDTO.py +11 -0
- stonepy/models/DeleteWatchlistItemRequestDTO.py +16 -0
- stonepy/models/DeleteWatchlistResponseDTO.py +13 -0
- stonepy/models/EnrichedOrderDTO.py +42 -0
- stonepy/models/ExecutionResponseDTO.py +15 -0
- stonepy/models/ExecutionVenueRequestDTO.py +32 -0
- stonepy/models/FixedMarginOrderResponseDTO.py +27 -0
- stonepy/models/FullMarketInformationSearchWithTagsResponseDTOv2.py +22 -0
- stonepy/models/GetActiveStopLimitOrderResponseDTOv2.py +20 -0
- stonepy/models/GetClientPreferenceResponseDTO.py +18 -0
- stonepy/models/GetClientPreferencesResponseDTO.py +20 -0
- stonepy/models/GetKeyListClientPreferenceResponseDTO.py +13 -0
- stonepy/models/GetListClientPreferenceResponseDTO.py +20 -0
- stonepy/models/GetMarketInformationResponseDTO.py +20 -0
- stonepy/models/GetMessagePopupResponseDTO.py +14 -0
- stonepy/models/GetOpenPositionResponseDTOv2.py +18 -0
- stonepy/models/GetOrderResponseDTOv2.py +20 -0
- stonepy/models/GetOrdersResponseDTOv2.py +22 -0
- stonepy/models/GetPriceBarResponseDTO.py +19 -0
- stonepy/models/GetPriceTickResponseDTO.py +18 -0
- stonepy/models/GetVersionInformationResponseDTO.py +15 -0
- stonepy/models/HistoricalOrderDTO.py +23 -0
- stonepy/models/HistoricalOrdersResponseDTO.py +20 -0
- stonepy/models/IdentifierDTO.py +15 -0
- stonepy/models/InsertWatchlistItemRequestDTO.py +16 -0
- stonepy/models/LegalPartyDTO.py +21 -0
- stonepy/models/LinkedAccountResult.py +36 -0
- stonepy/models/ListActiveOrdersRequestDTO.py +14 -0
- stonepy/models/ListActiveOrdersResponseDTO.py +18 -0
- stonepy/models/ListActiveStopLimitOrderResponseDTO.py +20 -0
- stonepy/models/ListAllocationProfilesResponseDTO.py +20 -0
- stonepy/models/ListCfdMarketsResponseDTO.py +18 -0
- stonepy/models/ListManagedClientsResponseDTO.py +19 -0
- stonepy/models/ListMarketInformationRequestDTO.py +14 -0
- stonepy/models/ListMarketInformationResponseDTO.py +20 -0
- stonepy/models/ListMarketInformationSearchResponseDTO.py +20 -0
- stonepy/models/ListMarketSearchPaginatedResponseDTO.py +13 -0
- stonepy/models/ListMarketSearchResponseDTO.py +18 -0
- stonepy/models/ListOpenPositionsResponseDTO.py +18 -0
- stonepy/models/ListProductInformationDTO.py +14 -0
- stonepy/models/ListProductInformationResponseDTO.py +20 -0
- stonepy/models/ListSpreadMarketsResponseDTO.py +18 -0
- stonepy/models/ListStopLimitOrderHistoryResponseDTO.py +20 -0
- stonepy/models/ListTradeHistoryResponseDTO.py +21 -0
- stonepy/models/ListWatchlistRequestDTO.py +15 -0
- stonepy/models/ListWatchlistResponseDTO.py +21 -0
- stonepy/models/LogonUser.py +13 -0
- stonepy/models/ManagedClientDTO.py +24 -0
- stonepy/models/ManagedTradingAccountDTO.py +16 -0
- stonepy/models/MarketInformationSearchWithTagsResponseDTO.py +20 -0
- stonepy/models/MarketInformationSearchWithoutTagsResponseDTO.py +18 -0
- stonepy/models/MarketInformationTagLookupResponseDTO.py +18 -0
- stonepy/models/MarketPricesDTO.py +19 -0
- stonepy/models/MarketSearchResultDTO.py +122 -0
- stonepy/models/MarketSpreadData.py +21 -0
- stonepy/models/MultipleMarketInformationRequestDTO.py +14 -0
- stonepy/models/NewFixedMarginTradeOrderRequestDTO.py +18 -0
- stonepy/models/NewStopLimitOrderRequestDTO.py +39 -0
- stonepy/models/NewTradeOrderRequestDTO.py +36 -0
- stonepy/models/NewsResponseDTO.py +17 -0
- stonepy/models/OpenOrderDTO.py +13 -0
- stonepy/models/OpenOrdersResponseDTO.py +18 -0
- stonepy/models/OrderDTO.py +34 -0
- stonepy/models/OrderHistoryDTO.py +34 -0
- stonepy/models/OrderRequestDTO.py +47 -0
- stonepy/models/PendingOrderDTO.py +16 -0
- stonepy/models/PendingOrdersResponseDTO.py +18 -0
- stonepy/models/PreferenceDTO.py +14 -0
- stonepy/models/PriceAlertDTO.py +32 -0
- stonepy/models/PriceAlertResponseDTO.py +18 -0
- stonepy/models/PriceBarDTO.py +19 -0
- stonepy/models/PriceDTO.py +29 -0
- stonepy/models/PriceTickDTO.py +16 -0
- stonepy/models/QuoteDTO.py +31 -0
- stonepy/models/SaveAlertRequestDTOv2.py +27 -0
- stonepy/models/SaveAlertResponseDTOv2.py +13 -0
- stonepy/models/SaveAllocationProfileRequestDTO.py +11 -0
- stonepy/models/SaveAllocationProfileResponseDTO.py +11 -0
- stonepy/models/SaveClientPreferenceRequestDTO.py +20 -0
- stonepy/models/SaveMarketInformationRequestDTO.py +21 -0
- stonepy/models/SaveWatchlistRequestDTO.py +19 -0
- stonepy/models/SaveWatchlistResponseDTO.py +13 -0
- stonepy/models/SecureMessageCount.py +16 -0
- stonepy/models/SignalDTO.py +47 -0
- stonepy/models/SignalPerformanceDTO.py +21 -0
- stonepy/models/SignalPerformanceResponseDTO.py +23 -0
- stonepy/models/SingleActiveStopLimitOrderResponseDTO.py +20 -0
- stonepy/models/SingleOpenPositionResponseDTO.py +18 -0
- stonepy/models/StoryResponseDTO.py +14 -0
- stonepy/models/SystemStatusDTO.py +13 -0
- stonepy/models/SystemStatusRequestDTO.py +13 -0
- stonepy/models/Timestamp.py +14 -0
- stonepy/models/TradeMarginDTO.py +41 -0
- stonepy/models/UpdateDeleteClientPreferenceResponseDTO.py +13 -0
- stonepy/models/UpdateFixedMarginTradeOrderRequestDTO.py +20 -0
- stonepy/models/UpdateStopLimitOrderRequestDTO.py +11 -0
- stonepy/models/UpdateTradeOrderRequestDTO.py +14 -0
- stonepy/models/UpdateWatchlistDisplayOrderRequestDTO.py +15 -0
- stonepy/models/__init__.py +781 -0
- stonepy/models/enums.py +665 -0
- stonepy/py.typed +0 -0
- stonepy/resources/__init__.py +48 -0
- stonepy/resources/cfd/__init__.py +21 -0
- stonepy/resources/cfd/_sync/list_cfd_markets.py +37 -0
- stonepy/resources/cfd/list_cfd_markets.py +36 -0
- stonepy/resources/clientapplication/__init__.py +23 -0
- stonepy/resources/clientapplication/_sync/get_version_information.py +19 -0
- stonepy/resources/clientapplication/get_version_information.py +18 -0
- stonepy/resources/clientpreference/__init__.py +57 -0
- stonepy/resources/clientpreference/_sync/delete.py +20 -0
- stonepy/resources/clientpreference/_sync/get.py +18 -0
- stonepy/resources/clientpreference/_sync/get_key_list.py +20 -0
- stonepy/resources/clientpreference/_sync/get_list.py +14 -0
- stonepy/resources/clientpreference/_sync/get_overridden_settings.py +19 -0
- stonepy/resources/clientpreference/_sync/save.py +20 -0
- stonepy/resources/clientpreference/_sync/save_overridden_settings.py +19 -0
- stonepy/resources/clientpreference/delete.py +19 -0
- stonepy/resources/clientpreference/get.py +17 -0
- stonepy/resources/clientpreference/get_key_list.py +19 -0
- stonepy/resources/clientpreference/get_list.py +15 -0
- stonepy/resources/clientpreference/get_overridden_settings.py +18 -0
- stonepy/resources/clientpreference/save.py +19 -0
- stonepy/resources/clientpreference/save_overridden_settings.py +18 -0
- stonepy/resources/margin/__init__.py +25 -0
- stonepy/resources/margin/_sync/get_client_account_margin.py +14 -0
- stonepy/resources/margin/get_client_account_margin.py +15 -0
- stonepy/resources/market/__init__.py +153 -0
- stonepy/resources/market/_sync/full_search_with_tags.py +49 -0
- stonepy/resources/market/_sync/get_full_search_with_tags.py +51 -0
- stonepy/resources/market/_sync/get_latest_price_bars.py +28 -0
- stonepy/resources/market/_sync/get_latest_price_ticks.py +22 -0
- stonepy/resources/market/_sync/get_market_information.py +16 -0
- stonepy/resources/market/_sync/get_market_information_extended.py +19 -0
- stonepy/resources/market/_sync/get_market_spread.py +14 -0
- stonepy/resources/market/_sync/get_price_bars_after_date.py +42 -0
- stonepy/resources/market/_sync/get_price_bars_before_date.py +41 -0
- stonepy/resources/market/_sync/get_price_bars_between_dates.py +44 -0
- stonepy/resources/market/_sync/get_price_ticks_after_date.py +29 -0
- stonepy/resources/market/_sync/get_price_ticks_before_date.py +29 -0
- stonepy/resources/market/_sync/get_price_ticks_between_dates.py +35 -0
- stonepy/resources/market/_sync/list_market_information.py +16 -0
- stonepy/resources/market/_sync/list_market_information_search.py +45 -0
- stonepy/resources/market/_sync/list_market_search.py +46 -0
- stonepy/resources/market/_sync/list_market_search_paginated.py +52 -0
- stonepy/resources/market/_sync/save_market_information.py +19 -0
- stonepy/resources/market/_sync/save_product_information.py +16 -0
- stonepy/resources/market/_sync/search_with_tags.py +51 -0
- stonepy/resources/market/full_search_with_tags.py +48 -0
- stonepy/resources/market/get_full_search_with_tags.py +50 -0
- stonepy/resources/market/get_latest_price_bars.py +27 -0
- stonepy/resources/market/get_latest_price_ticks.py +21 -0
- stonepy/resources/market/get_market_information.py +15 -0
- stonepy/resources/market/get_market_information_extended.py +18 -0
- stonepy/resources/market/get_market_spread.py +13 -0
- stonepy/resources/market/get_price_bars_after_date.py +41 -0
- stonepy/resources/market/get_price_bars_before_date.py +40 -0
- stonepy/resources/market/get_price_bars_between_dates.py +43 -0
- stonepy/resources/market/get_price_ticks_after_date.py +28 -0
- stonepy/resources/market/get_price_ticks_before_date.py +28 -0
- stonepy/resources/market/get_price_ticks_between_dates.py +34 -0
- stonepy/resources/market/list_market_information.py +15 -0
- stonepy/resources/market/list_market_information_search.py +44 -0
- stonepy/resources/market/list_market_search.py +45 -0
- stonepy/resources/market/list_market_search_paginated.py +51 -0
- stonepy/resources/market/save_market_information.py +18 -0
- stonepy/resources/market/save_product_information.py +15 -0
- stonepy/resources/market/search_with_tags.py +50 -0
- stonepy/resources/message/__init__.py +71 -0
- stonepy/resources/message/_sync/client_communication_message_update.py +23 -0
- stonepy/resources/message/_sync/get_client_application_message_translation.py +21 -0
- stonepy/resources/message/_sync/get_client_application_message_translation_with_interesting_items.py +24 -0
- stonepy/resources/message/_sync/get_client_communication_messages.py +18 -0
- stonepy/resources/message/_sync/get_message_popup.py +16 -0
- stonepy/resources/message/_sync/get_system_lookup.py +20 -0
- stonepy/resources/message/_sync/save_client_communication_message_response.py +19 -0
- stonepy/resources/message/client_communication_message_update.py +22 -0
- stonepy/resources/message/get_client_application_message_translation.py +20 -0
- stonepy/resources/message/get_client_application_message_translation_with_interesting_items.py +23 -0
- stonepy/resources/message/get_client_communication_messages.py +17 -0
- stonepy/resources/message/get_message_popup.py +15 -0
- stonepy/resources/message/get_system_lookup.py +21 -0
- stonepy/resources/message/save_client_communication_message_response.py +18 -0
- stonepy/resources/news/__init__.py +55 -0
- stonepy/resources/news/_sync/get_market_report_headlines.py +18 -0
- stonepy/resources/news/_sync/get_market_reports.py +16 -0
- stonepy/resources/news/_sync/get_news.py +16 -0
- stonepy/resources/news/_sync/get_news_headlines.py +16 -0
- stonepy/resources/news/_sync/get_story.py +14 -0
- stonepy/resources/news/_sync/search_market_reports.py +31 -0
- stonepy/resources/news/_sync/search_news.py +31 -0
- stonepy/resources/news/get_market_report_headlines.py +17 -0
- stonepy/resources/news/get_market_reports.py +17 -0
- stonepy/resources/news/get_news.py +15 -0
- stonepy/resources/news/get_news_headlines.py +15 -0
- stonepy/resources/news/get_story.py +13 -0
- stonepy/resources/news/search_market_reports.py +30 -0
- stonepy/resources/news/search_news.py +30 -0
- stonepy/resources/order/__init__.py +83 -0
- stonepy/resources/order/_sync/cancel_order.py +17 -0
- stonepy/resources/order/_sync/get_active_stop_limit_order.py +21 -0
- stonepy/resources/order/_sync/get_open_position.py +21 -0
- stonepy/resources/order/_sync/get_order.py +18 -0
- stonepy/resources/order/_sync/list_active_orders.py +22 -0
- stonepy/resources/order/_sync/list_active_stop_limit_orders.py +22 -0
- stonepy/resources/order/_sync/list_open_positions.py +22 -0
- stonepy/resources/order/_sync/list_stop_limit_order_history.py +31 -0
- stonepy/resources/order/_sync/list_trade_history.py +30 -0
- stonepy/resources/order/_sync/order.py +19 -0
- stonepy/resources/order/_sync/place_order.py +17 -0
- stonepy/resources/order/_sync/simulate_trade.py +18 -0
- stonepy/resources/order/cancel_order.py +16 -0
- stonepy/resources/order/get_active_stop_limit_order.py +22 -0
- stonepy/resources/order/get_open_position.py +20 -0
- stonepy/resources/order/get_order.py +17 -0
- stonepy/resources/order/list_active_orders.py +21 -0
- stonepy/resources/order/list_active_stop_limit_orders.py +23 -0
- stonepy/resources/order/list_open_positions.py +21 -0
- stonepy/resources/order/list_stop_limit_order_history.py +30 -0
- stonepy/resources/order/list_trade_history.py +29 -0
- stonepy/resources/order/order.py +18 -0
- stonepy/resources/order/place_order.py +16 -0
- stonepy/resources/order/simulate_trade.py +19 -0
- stonepy/resources/preference/__init__.py +37 -0
- stonepy/resources/preference/_sync/delete_user_preference.py +14 -0
- stonepy/resources/preference/_sync/get_user_preference.py +16 -0
- stonepy/resources/preference/_sync/save_user_preference.py +15 -0
- stonepy/resources/preference/delete_user_preference.py +15 -0
- stonepy/resources/preference/get_user_preference.py +15 -0
- stonepy/resources/preference/save_user_preference.py +14 -0
- stonepy/resources/price_alert/__init__.py +35 -0
- stonepy/resources/price_alert/_sync/delete_pa.py +14 -0
- stonepy/resources/price_alert/_sync/get_pa.py +19 -0
- stonepy/resources/price_alert/_sync/save_pa.py +19 -0
- stonepy/resources/price_alert/_sync/save_price_alert.py +18 -0
- stonepy/resources/price_alert/delete_pa.py +13 -0
- stonepy/resources/price_alert/get_pa.py +18 -0
- stonepy/resources/price_alert/save_pa.py +18 -0
- stonepy/resources/price_alert/save_price_alert.py +17 -0
- stonepy/resources/session/__init__.py +29 -0
- stonepy/resources/session/_sync/change_password.py +14 -0
- stonepy/resources/session/_sync/delete_session.py +14 -0
- stonepy/resources/session/_sync/log_on.py +21 -0
- stonepy/resources/session/change_password.py +15 -0
- stonepy/resources/session/delete_session.py +13 -0
- stonepy/resources/session/log_on.py +20 -0
- stonepy/resources/spread/__init__.py +21 -0
- stonepy/resources/spread/_sync/list_spread_markets.py +37 -0
- stonepy/resources/spread/list_spread_markets.py +36 -0
- stonepy/resources/user_account/__init__.py +35 -0
- stonepy/resources/user_account/_sync/get_client_and_trading_account.py +19 -0
- stonepy/resources/user_account/_sync/save_account_information.py +19 -0
- stonepy/resources/user_account/get_client_and_trading_account.py +18 -0
- stonepy/resources/user_account/save_account_information.py +18 -0
- stonepy/resources/watchlist/__init__.py +39 -0
- stonepy/resources/watchlist/_sync/delete_watchlist.py +16 -0
- stonepy/resources/watchlist/_sync/get_watchlists.py +14 -0
- stonepy/resources/watchlist/_sync/get_watchlists_list.py +27 -0
- stonepy/resources/watchlist/_sync/save_watchlist.py +14 -0
- stonepy/resources/watchlist/delete_watchlist.py +15 -0
- stonepy/resources/watchlist/get_watchlists.py +13 -0
- stonepy/resources/watchlist/get_watchlists_list.py +26 -0
- stonepy/resources/watchlist/save_watchlist.py +13 -0
- stonepy-0.1.0.dist-info/METADATA +186 -0
- stonepy-0.1.0.dist-info/RECORD +445 -0
- stonepy-0.1.0.dist-info/WHEEL +4 -0
- 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)
|
stonepy/_core/config.py
ADDED
|
@@ -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)
|
stonepy/_core/errors.py
ADDED
|
@@ -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
|
+
)
|
stonepy/_core/logging.py
ADDED
|
@@ -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})"
|
stonepy/_core/models.py
ADDED
|
@@ -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")
|