euclid-clob-sdk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- euclid_clob_sdk-0.1.0/LICENSE +21 -0
- euclid_clob_sdk-0.1.0/PKG-INFO +567 -0
- euclid_clob_sdk-0.1.0/README.md +529 -0
- euclid_clob_sdk-0.1.0/pyproject.toml +111 -0
- euclid_clob_sdk-0.1.0/setup.cfg +4 -0
- euclid_clob_sdk-0.1.0/src/euclid/__init__.py +56 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/__init__.py +11 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/__init__.py +1 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/__init__.py +1 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/get_account_balance.py +178 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/get_account_hold.py +178 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/get_account_transaction_by_hash.py +180 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/get_account_withdrawal.py +192 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/get_explorer_block.py +159 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/get_explorer_transaction.py +159 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/get_explorer_transaction_by_hash.py +161 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/list_account_balances.py +215 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/list_account_deposits.py +245 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/list_account_fees.py +290 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/list_account_holds.py +245 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/list_account_transactions.py +250 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/list_account_transfers.py +235 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/list_account_withdrawals.py +275 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/list_explorer_block_transactions.py +212 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/list_explorer_blocks.py +209 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/account_indexer/list_explorer_transactions.py +261 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/__init__.py +1 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/confirm_totp.py +169 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/create_api_key.py +169 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/create_delegated_session.py +173 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/disable_totp.py +169 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/get_mfa_status.py +137 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/get_my_account_profile.py +137 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/get_public_account_profile.py +161 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/issue_nonce.py +164 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/list_api_keys.py +199 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/list_sessions.py +219 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/logout.py +164 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/recover_totp.py +169 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/refresh_session.py +164 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/regenerate_recovery_codes.py +169 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/revoke_api_key.py +169 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/revoke_session.py +185 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/setup_totp.py +169 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/verify_privy.py +164 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/auth/verify_wallet_signature.py +164 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/__init__.py +1 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/cancel_all_orders.py +169 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/cancel_order.py +173 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/cancel_replace.py +169 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/create_transfer.py +185 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/engine_get_depth.py +180 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/get_account.py +199 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/get_balance.py +178 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/get_effective_pair_fee.py +169 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/get_exchange_info.py +198 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/get_leaderboard.py +279 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/get_my_fee_tier.py +137 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/get_server_time.py +132 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/issue_account_transactions_ticket.py +177 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/list_assets.py +194 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/list_balances.py +200 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/list_fee_tiers.py +194 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/list_pair_fee_overrides.py +194 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/list_pairs.py +179 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/list_user_trades.py +215 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/place_order.py +169 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/place_signed_order.py +164 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/engine/request_withdrawal.py +185 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/market_data/__init__.py +1 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/market_data/get_global_stats.py +132 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/market_data/get_symbol.py +159 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/market_data/get_symbol_stats.py +159 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/market_data/get_ticker.py +159 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/market_data/list_klines.py +210 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/market_data/list_symbol_stats.py +179 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/market_data/list_symbols.py +179 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/market_data/list_tickers.py +198 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/market_data/list_trades.py +210 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/market_data/market_data_get_depth.py +180 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/oms/__init__.py +1 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/oms/get_order.py +164 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/oms/list_open_orders.py +215 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/api/oms/list_order_history.py +235 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/client.py +268 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/errors.py +16 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/__init__.py +361 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/accepted_order.py +89 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/account_balance.py +80 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/account_profile.py +115 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/api_key.py +196 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/asset.py +80 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/balance.py +116 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/book_ticker.py +107 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/cancel_all_orders_request.py +62 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/cancel_all_orders_response.py +116 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/cancel_order_request.py +71 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/cancel_order_response.py +80 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/cancel_replace_cancel_response.py +80 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/cancel_replace_mode.py +10 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/cancel_replace_request.py +153 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/cancel_replace_response.py +140 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/cancelled_order.py +71 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/command_kind.py +31 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/commission_rates.py +89 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/confirm_totp_request.py +70 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/confirm_totp_response.py +72 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/create_api_key_request.py +173 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/create_api_key_response.py +188 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/create_delegated_session_request.py +71 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/create_delegated_session_response.py +131 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/create_transfer_request_type_0.py +61 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/create_transfer_request_type_1.py +61 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/create_transfer_response.py +120 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/deposit.py +161 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/depth_diff.py +137 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/depth_snapshot.py +119 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/disable_totp_request.py +61 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/disable_totp_response.py +61 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/engine_get_depth_response.py +119 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/engine_price_level.py +71 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/engine_trade.py +134 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/exchange_symbol.py +208 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/explorer_block.py +305 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/explorer_block_transaction.py +98 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/explorer_transaction.py +291 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/fee_configuration.py +71 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/fee_posting.py +134 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/fee_tier.py +134 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/fee_tier_assignment.py +107 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/fill.py +107 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_account_balance_response.py +93 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_account_hold_response.py +93 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_account_response.py +226 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_account_transaction_by_hash_response.py +93 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_account_withdrawal_response.py +93 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_balance_response.py +75 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_effective_pair_fee_response.py +157 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_exchange_info_response.py +97 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_explorer_block_response.py +92 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_explorer_transaction_by_hash_response.py +92 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_explorer_transaction_response.py +92 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_global_stats_response.py +97 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_leaderboard_response.py +116 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_mfa_status_response.py +61 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_my_account_profile_response.py +74 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_my_fee_tier_response.py +119 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_order_response.py +75 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_public_account_profile_response.py +74 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_server_time_response.py +62 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_symbol_response.py +74 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_symbol_stats_response.py +74 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/get_ticker_response.py +74 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/google_rpc_error_info.py +93 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/google_rpc_error_info_metadata.py +47 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/google_rpc_status.py +98 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/issue_account_transactions_ticket_request.py +47 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/issue_account_transactions_ticket_response.py +71 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/issue_nonce_request.py +61 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/issue_nonce_response.py +78 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/kline.py +134 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/kline_update.py +111 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/leaderboard_cursor.py +71 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/leaderboard_facts.py +188 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/leaderboard_item.py +102 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/leaderboard_metric.py +12 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/leaderboard_profile.py +89 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/leaderboard_window.py +10 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ledger_balance.py +80 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_account_balances_response.py +107 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_account_deposits_response.py +107 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_account_fees_response.py +107 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_account_holds_response.py +107 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_account_transactions_response.py +107 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_account_transfers_response.py +107 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_account_withdrawals_response.py +107 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_api_keys_response.py +88 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_assets_response.py +89 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_balances_response.py +89 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_explorer_block_transactions_response.py +106 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_explorer_blocks_response.py +106 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_explorer_transactions_response.py +106 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_fee_tiers_response.py +89 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_klines_response.py +106 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_open_orders_response.py +89 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_order_history_response.py +89 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_pair_fee_overrides_response.py +98 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_pairs_response.py +89 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_sessions_response.py +89 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_symbol_stats_response.py +88 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_symbols_response.py +88 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_tickers_response.py +88 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_trades_response.py +97 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/list_user_trades_response.py +98 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/logout_request.py +61 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/logout_response.py +45 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/market_data_get_depth_response.py +118 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/market_data_price_level.py +71 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/market_data_trade.py +116 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/order.py +187 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/order_details.py +142 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/order_hold.py +134 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/order_status.py +13 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/order_type.py +12 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/pair.py +161 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/pair_fee_override.py +84 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/place_order_request.py +127 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/place_order_response.py +148 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/place_signed_order_request.py +163 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/place_signed_order_response.py +148 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/projection_freshness.py +94 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/recover_totp_request.py +61 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/recover_totp_response.py +78 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/refresh_session_request.py +61 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/refresh_session_response.py +121 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/regenerate_recovery_codes_request.py +61 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/regenerate_recovery_codes_response.py +72 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/request_withdrawal_request.py +108 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/request_withdrawal_response.py +166 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/revoke_api_key_request.py +61 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/revoke_api_key_response.py +45 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/revoke_session_request.py +62 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/revoke_session_response.py +47 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/session.py +141 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/session_token_type.py +10 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/setup_totp_request.py +45 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/setup_totp_response.py +79 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/symbol.py +98 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/symbol_stats.py +134 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ticker_24h.py +224 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/trade_fee.py +91 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/transaction_order.py +124 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/transaction_trade.py +152 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/transfer.py +126 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/transfer_direction.py +10 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/transfer_policy.py +10 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/user_balance.py +84 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/user_trade.py +143 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/verify_privy_request.py +61 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/verify_privy_response.py +157 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/verify_wallet_signature_request.py +70 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/verify_wallet_signature_response.py +121 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/wallet.py +174 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/withdraw_destination.py +71 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/withdraw_policy.py +10 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/withdrawal.py +286 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ws_ack.py +81 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ws_channel.py +13 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ws_client_message_type_0.py +67 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ws_client_message_type_1.py +67 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ws_error.py +80 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ws_server_message_type_0.py +67 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ws_server_message_type_1.py +67 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ws_server_message_type_2.py +67 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ws_server_message_type_3.py +67 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ws_server_message_type_4.py +67 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ws_server_message_type_5.py +67 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ws_server_message_type_6.py +67 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ws_subscribe.py +115 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/models/ws_unsubscribe.py +62 -0
- euclid_clob_sdk-0.1.0/src/euclid/_generated/types.py +54 -0
- euclid_clob_sdk-0.1.0/src/euclid/_numbers.py +85 -0
- euclid_clob_sdk-0.1.0/src/euclid/_ops.py +1347 -0
- euclid_clob_sdk-0.1.0/src/euclid/_version.py +1 -0
- euclid_clob_sdk-0.1.0/src/euclid/client.py +954 -0
- euclid_clob_sdk-0.1.0/src/euclid/config.py +139 -0
- euclid_clob_sdk-0.1.0/src/euclid/enums.py +145 -0
- euclid_clob_sdk-0.1.0/src/euclid/errors.py +252 -0
- euclid_clob_sdk-0.1.0/src/euclid/models.py +784 -0
- euclid_clob_sdk-0.1.0/src/euclid/py.typed +0 -0
- euclid_clob_sdk-0.1.0/src/euclid/streams/__init__.py +20 -0
- euclid_clob_sdk-0.1.0/src/euclid/streams/_market_frames.py +153 -0
- euclid_clob_sdk-0.1.0/src/euclid/streams/_retry.py +122 -0
- euclid_clob_sdk-0.1.0/src/euclid/streams/_transport.py +103 -0
- euclid_clob_sdk-0.1.0/src/euclid/streams/account.py +321 -0
- euclid_clob_sdk-0.1.0/src/euclid/streams/blocking.py +210 -0
- euclid_clob_sdk-0.1.0/src/euclid/streams/market_data.py +404 -0
- euclid_clob_sdk-0.1.0/src/euclid/streams/order_book.py +246 -0
- euclid_clob_sdk-0.1.0/src/euclid_clob_sdk.egg-info/PKG-INFO +567 -0
- euclid_clob_sdk-0.1.0/src/euclid_clob_sdk.egg-info/SOURCES.txt +301 -0
- euclid_clob_sdk-0.1.0/src/euclid_clob_sdk.egg-info/dependency_links.txt +1 -0
- euclid_clob_sdk-0.1.0/src/euclid_clob_sdk.egg-info/requires.txt +14 -0
- euclid_clob_sdk-0.1.0/src/euclid_clob_sdk.egg-info/top_level.txt +1 -0
- euclid_clob_sdk-0.1.0/tests/test_account.py +179 -0
- euclid_clob_sdk-0.1.0/tests/test_account_history.py +615 -0
- euclid_clob_sdk-0.1.0/tests/test_build_spec.py +206 -0
- euclid_clob_sdk-0.1.0/tests/test_check_distribution.py +123 -0
- euclid_clob_sdk-0.1.0/tests/test_check_release.py +147 -0
- euclid_clob_sdk-0.1.0/tests/test_config.py +192 -0
- euclid_clob_sdk-0.1.0/tests/test_edge_cases.py +162 -0
- euclid_clob_sdk-0.1.0/tests/test_errors.py +172 -0
- euclid_clob_sdk-0.1.0/tests/test_examples.py +897 -0
- euclid_clob_sdk-0.1.0/tests/test_market_data.py +317 -0
- euclid_clob_sdk-0.1.0/tests/test_orders.py +508 -0
- euclid_clob_sdk-0.1.0/tests/test_pair_rules.py +165 -0
- euclid_clob_sdk-0.1.0/tests/test_parity.py +151 -0
- euclid_clob_sdk-0.1.0/tests/test_route_coverage.py +138 -0
- euclid_clob_sdk-0.1.0/tests/test_stream_account.py +814 -0
- euclid_clob_sdk-0.1.0/tests/test_stream_blocking.py +222 -0
- euclid_clob_sdk-0.1.0/tests/test_stream_market_data.py +716 -0
- euclid_clob_sdk-0.1.0/tests/test_stream_order_book.py +384 -0
- euclid_clob_sdk-0.1.0/tests/test_stream_transport.py +105 -0
- euclid_clob_sdk-0.1.0/tests/test_sync_specs.py +259 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Euclid Protocol
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: euclid-clob-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the Euclid exchange API
|
|
5
|
+
Author: Euclid Protocol
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/EuclidProtocol/euclid-python-sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/EuclidProtocol/euclid-python-sdk
|
|
9
|
+
Project-URL: Issues, https://github.com/EuclidProtocol/euclid-python-sdk/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/EuclidProtocol/euclid-python-sdk/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: euclid,exchange,trading,orderbook,clob,market-data,websocket,sdk
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
15
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
16
|
+
Classifier: Framework :: AsyncIO
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: httpx2<3,>=2.13.1
|
|
27
|
+
Requires-Dist: attrs>=22.2.0
|
|
28
|
+
Requires-Dist: websockets<18,>=15
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-cov>=5; extra == "test"
|
|
32
|
+
Provides-Extra: codegen
|
|
33
|
+
Requires-Dist: openapi-python-client==0.30.0; extra == "codegen"
|
|
34
|
+
Requires-Dist: ruff==0.17.4; extra == "codegen"
|
|
35
|
+
Provides-Extra: typecheck
|
|
36
|
+
Requires-Dist: mypy==2.4.1; extra == "typecheck"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# Euclid Python SDK
|
|
40
|
+
|
|
41
|
+
A Python client for the Euclid exchange API: market data, account and balances, and order
|
|
42
|
+
placement, with a blocking client and an asyncio client that share one implementation, plus
|
|
43
|
+
asyncio WebSocket streams for live market data, a local order book, and your own account events.
|
|
44
|
+
|
|
45
|
+
> **Status: pre-release (`0.x`).** The API of this package may change between minor versions;
|
|
46
|
+
> [`CHANGELOG.md`](CHANGELOG.md) says what each release changed.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
Python 3.11 or newer.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install euclid-clob-sdk
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The distribution is named `euclid-clob-sdk`; the package you import is `euclid`.
|
|
57
|
+
|
|
58
|
+
## Getting started
|
|
59
|
+
|
|
60
|
+
The example scripts are in this repository, not in the installed package, so start from a
|
|
61
|
+
checkout:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git clone https://github.com/EuclidProtocol/euclid-python-sdk
|
|
65
|
+
cd euclid-python-sdk
|
|
66
|
+
python -m venv .venv
|
|
67
|
+
source .venv/bin/activate
|
|
68
|
+
pip install -e .
|
|
69
|
+
export EUCLID_BASE_URL=https://your-euclid-host.example
|
|
70
|
+
export EUCLID_API_KEY="ek1.<key-id>.<secret>" # optional: leave it out for public data only
|
|
71
|
+
python examples/check_setup.py
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`check_setup.py` prints one line per check: the configuration, the server, market data, the
|
|
75
|
+
WebSocket and, when a key is set, one read per kind of private data, with the scopes the key is
|
|
76
|
+
missing. It changes nothing and never prints the key. From there,
|
|
77
|
+
[`examples/`](examples/README.md) is a path from reading an order book to a small quoting loop
|
|
78
|
+
that only trades when you pass `--live`.
|
|
79
|
+
|
|
80
|
+
The SDK and the examples read real environment variables. Nothing loads a `.env` file for you.
|
|
81
|
+
[`.env.example`](.env.example) lists the variable names; to use a copy of it from your shell:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
cp .env.example .env # then edit .env; it is ignored by git
|
|
85
|
+
set -a; source .env; set +a
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Quick start
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from decimal import Decimal
|
|
92
|
+
from euclid import EuclidClient, OrderType, Side
|
|
93
|
+
|
|
94
|
+
with EuclidClient("ek1.<key-id>.<secret>", base_url="https://<your-euclid-host>") as client:
|
|
95
|
+
book = client.get_order_book("ETH-USDC", limit=5)
|
|
96
|
+
print(book.bids[0].price, book.asks[0].price)
|
|
97
|
+
|
|
98
|
+
order = client.place_order(
|
|
99
|
+
pair_id="ETH-USDC",
|
|
100
|
+
side=Side.BUY,
|
|
101
|
+
order_type=OrderType.POST_ONLY,
|
|
102
|
+
price=Decimal("1950.25"),
|
|
103
|
+
base_size=Decimal("0.5"),
|
|
104
|
+
)
|
|
105
|
+
client.cancel_order(pair_id="ETH-USDC", order_id=order.order_id)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`AsyncEuclidClient` has the same methods with the same arguments; `await` them and use
|
|
109
|
+
`async with`. Live data is under [WebSocket streams](#websocket-streams). Runnable scripts are
|
|
110
|
+
in [`examples/`](examples/README.md).
|
|
111
|
+
|
|
112
|
+
## Configuration
|
|
113
|
+
|
|
114
|
+
The API is five services that may be on different hosts. This package contains no hostnames;
|
|
115
|
+
you supply them.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from euclid import Environment, EuclidClient
|
|
119
|
+
|
|
120
|
+
# One origin for everything:
|
|
121
|
+
client = EuclidClient(api_key, base_url="https://gateway.example")
|
|
122
|
+
|
|
123
|
+
# Or one URL per service:
|
|
124
|
+
client = EuclidClient(
|
|
125
|
+
api_key,
|
|
126
|
+
environment=Environment(
|
|
127
|
+
engine="https://...", # orders, balances, pairs, assets
|
|
128
|
+
market_data="https://...", # order book, klines, tickers, trades
|
|
129
|
+
order_history="https://...", # open orders and order history
|
|
130
|
+
account_indexer="https://...", # account history: transactions, deposits, withdrawals, transfers
|
|
131
|
+
auth="https://...", # not used by any wrapped method yet
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Or from the environment: EUCLID_API_KEY, EUCLID_BASE_URL, and optionally
|
|
136
|
+
# EUCLID_ENGINE_URL, EUCLID_MARKET_DATA_URL, EUCLID_ORDER_HISTORY_URL,
|
|
137
|
+
# EUCLID_ACCOUNT_INDEXER_URL, EUCLID_AUTH_URL.
|
|
138
|
+
client = EuclidClient.from_env()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The WebSocket URLs are derived from these: the market-data stream is `/ws` on the market-data
|
|
142
|
+
URL and the account stream is `/ws/account/transactions` on the engine URL, with `https`
|
|
143
|
+
becoming `wss`. If your deployment serves them elsewhere, set
|
|
144
|
+
`Environment(market_data_stream="wss://...", account_stream="wss://...")` or
|
|
145
|
+
`EUCLID_MARKET_DATA_WS_URL` and `EUCLID_ACCOUNT_WS_URL`.
|
|
146
|
+
|
|
147
|
+
- The API key is optional. Without one, the public methods work and the others raise
|
|
148
|
+
`ConfigurationError` before sending anything.
|
|
149
|
+
- The key is sent only on routes that require it, as `Authorization: Bearer <key>`. It is never
|
|
150
|
+
sent to the market-data service.
|
|
151
|
+
- Plain `http://` and `ws://` are refused except for localhost, unless you pass
|
|
152
|
+
`allow_insecure_http=True`.
|
|
153
|
+
- `client.has_api_key` says whether the client has a credential, and
|
|
154
|
+
`client.environment.configured` lists the URLs that are set.
|
|
155
|
+
|
|
156
|
+
## Amounts are `Decimal`
|
|
157
|
+
|
|
158
|
+
Every price, size and balance is a `decimal.Decimal` in human units, in both directions:
|
|
159
|
+
`Decimal("0.5")` ETH, never a scaled integer and never a float. Arguments accept a `Decimal`,
|
|
160
|
+
an `int` or a `str`. A `float` raises `TypeError`, because `0.1 + 0.2` is not `0.3`.
|
|
161
|
+
|
|
162
|
+
The API itself returns most amounts as integers scaled by a per-pair or per-asset number of
|
|
163
|
+
decimal places. The SDK reads those scales from the exchange and converts for you. It caches
|
|
164
|
+
them for `metadata_ttl` seconds (default 60); `client.forget_metadata()` drops the cache.
|
|
165
|
+
|
|
166
|
+
`str()` of a very small `Decimal` uses scientific notation (`1E-8`). Print with the `f` format
|
|
167
|
+
to avoid it: `f"{balance.available:f}"`.
|
|
168
|
+
|
|
169
|
+
An order's price must be a whole number of the pair's `tick_size` and its size a whole number
|
|
170
|
+
of `lot_size`. A `Pair` rounds for you, exactly:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
pair = next(p for p in client.list_pairs() if p.pair_id == "ETH-USDC")
|
|
174
|
+
bid = pair.round_price(mid - offset) # down to the tick
|
|
175
|
+
ask = pair.round_price(mid + offset, up=True) # up to the tick
|
|
176
|
+
size = pair.round_size("0.12345") # down to the lot
|
|
177
|
+
smallest = pair.min_size_at(bid) # smallest order the pair accepts at that price
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Do not use `Decimal.quantize` for this. It fixes the number of decimal places, which is the
|
|
181
|
+
same thing only when the tick is a power of ten: with a tick of `0.05`,
|
|
182
|
+
`Decimal("100.02").quantize(Decimal("0.05"))` is still `100.02`.
|
|
183
|
+
|
|
184
|
+
## Errors
|
|
185
|
+
|
|
186
|
+
A failed call raises; nothing returns an error object. Everything derives from `EuclidError`.
|
|
187
|
+
|
|
188
|
+
| Exception | When |
|
|
189
|
+
| --- | --- |
|
|
190
|
+
| `ValidationError` | 400: malformed request or a trading rule was broken |
|
|
191
|
+
| `AuthenticationError` | 401: missing, invalid or expired credential |
|
|
192
|
+
| `PermissionDeniedError` | 403: valid credential, not allowed |
|
|
193
|
+
| `InsufficientScopeError` | 403 with reason `INSUFFICIENT_SCOPE` (a `PermissionDeniedError`) |
|
|
194
|
+
| `NotFoundError` | 404: unknown order, pair or symbol |
|
|
195
|
+
| `ConflictError` | 409 |
|
|
196
|
+
| `RateLimitError` | 429; `retry_after` holds the `Retry-After` seconds when sent |
|
|
197
|
+
| `ServerError` | 5xx |
|
|
198
|
+
| `NetworkError`, `RequestTimeoutError` | no HTTP response arrived |
|
|
199
|
+
| `UnexpectedResponseError` | a success response the SDK could not read |
|
|
200
|
+
| `ConfigurationError`, `UnknownPairError` | raised locally, before or without a request |
|
|
201
|
+
| `SubscriptionError` | a stream subscription the server refused for good (`UNKNOWN_SYMBOL`, ...) |
|
|
202
|
+
| `StreamError` | a stream gave up reconnecting, or could not be brought back in sync |
|
|
203
|
+
| `StreamTimeoutError` | nothing arrived within the `timeout` given to `receive` (also a built-in `TimeoutError`) |
|
|
204
|
+
|
|
205
|
+
Server errors are `APIError` subclasses and carry `status`, `reason` (such as
|
|
206
|
+
`VALIDATION_FAILED`), `domain` (such as `engine.euclid`), `message`, `error_code` (the stable
|
|
207
|
+
business code, such as `POST_ONLY_WOULD_CROSS`), `metadata` and `body`.
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from euclid import RateLimitError, ValidationError
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
client.place_order(pair_id="ETH-USDC", side="buy", price="1950.25", base_size="0.5")
|
|
214
|
+
except ValidationError as error:
|
|
215
|
+
print(error.error_code, error.message)
|
|
216
|
+
except RateLimitError as error:
|
|
217
|
+
print("retry in", error.retry_after)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
After a `NetworkError` on a call that changes state, the SDK cannot know whether the server
|
|
221
|
+
applied it. Check `list_open_orders()` before retrying an order. The SDK never retries by itself.
|
|
222
|
+
|
|
223
|
+
## Account history
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
page = client.list_account_transactions(page_size=50) # newest first
|
|
227
|
+
for tx in page.items:
|
|
228
|
+
print(tx.engine_seq, tx.kind, [t.base_amount for t in tx.trades])
|
|
229
|
+
|
|
230
|
+
# Everything after a known point, oldest first, across all pages:
|
|
231
|
+
for tx in client.paginate(client.list_account_transactions, after_engine_seq=1200):
|
|
232
|
+
...
|
|
233
|
+
|
|
234
|
+
# At most 250 items, asking for no page that is not needed:
|
|
235
|
+
recent = list(client.paginate(client.list_my_trades, limit=250))
|
|
236
|
+
|
|
237
|
+
# Page by page, when you want what a page carries besides its items:
|
|
238
|
+
for page in client.pages(client.list_deposits, page_size=100):
|
|
239
|
+
print(len(page.items), page.freshness)
|
|
240
|
+
|
|
241
|
+
found = client.get_account_transaction(transaction_hash="...")
|
|
242
|
+
deposits = client.list_deposits(asset="USDC")
|
|
243
|
+
withdrawals = client.list_withdrawals(status="reserved")
|
|
244
|
+
transfers = client.list_transfers(direction="sent")
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
With `AsyncEuclidClient`, `paginate` is an async iterator: `async for tx in client.paginate(...)`.
|
|
248
|
+
A `page_token` remembers the position and direction of a walk, so `after_engine_seq` and
|
|
249
|
+
`before_engine_seq` are ignored when a token is given.
|
|
250
|
+
|
|
251
|
+
**This data is a projection and can trail the engine.** It is served by the account indexer,
|
|
252
|
+
which stores what the engine commits a moment after the engine commits it. Three consequences:
|
|
253
|
+
|
|
254
|
+
- Every page and lookup carries a `freshness`: `freshness.is_current`, `freshness.state`, and
|
|
255
|
+
`freshness.lag` (how many engine sequences behind, when known).
|
|
256
|
+
- A lookup returns a `Lookup`. `result.item is None` means the indexer has no such record.
|
|
257
|
+
Treat that as "does not exist" only when `result.known_absent` is true; otherwise the
|
|
258
|
+
indexer may not have caught up yet and the lookup is worth repeating.
|
|
259
|
+
- For anything you act on, ask the authoritative service: balances from `get_balances`
|
|
260
|
+
(the engine ledger), open orders from `list_open_orders`. The account indexer's own copies of
|
|
261
|
+
balances and order holds are deliberately not wrapped.
|
|
262
|
+
|
|
263
|
+
Scopes an API key needs: `funds:read` for transactions, `deposits:read`, `withdrawals:read` and
|
|
264
|
+
`transfers:read` for the others.
|
|
265
|
+
|
|
266
|
+
## WebSocket streams
|
|
267
|
+
|
|
268
|
+
The streams are asyncio and are built on an `AsyncEuclidClient`, which they use for the URLs
|
|
269
|
+
and for the REST calls they need. Each one is an async iterator. Blocking code can read its
|
|
270
|
+
account's events with [`BlockingAccountStream`](#from-blocking-code); the market-data streams
|
|
271
|
+
have no blocking form.
|
|
272
|
+
|
|
273
|
+
`websockets` is the WebSocket library: it is the most widely used asyncio implementation, is
|
|
274
|
+
pure Python with no dependencies of its own, and answers pings and detects dead connections
|
|
275
|
+
without help.
|
|
276
|
+
|
|
277
|
+
### Market data
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
from euclid import AsyncEuclidClient, MarketDataStream
|
|
281
|
+
|
|
282
|
+
async with AsyncEuclidClient(base_url="https://<your-euclid-host>") as client:
|
|
283
|
+
async with MarketDataStream(client) as stream:
|
|
284
|
+
async for trade in stream.trades("ETH-USDC"):
|
|
285
|
+
print(trade.price, trade.size)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
| Method | Yields |
|
|
289
|
+
| --- | --- |
|
|
290
|
+
| `stream.depth(symbol, depth=20, speed_ms=100)` | `OrderBook`: a full snapshot that replaces the last |
|
|
291
|
+
| `stream.depth_diff(symbol)` | `DepthDiff`: changed levels; size zero removes a level |
|
|
292
|
+
| `stream.trades(symbol)` | `PublicTrade` |
|
|
293
|
+
| `stream.book_ticker(symbol)` | `BookTicker`: best bid and ask |
|
|
294
|
+
| `stream.klines(symbol, interval="1m")` | `KlineUpdate` |
|
|
295
|
+
| `stream.order_book(symbol)` | `BookUpdate`, from a `LocalOrderBook` |
|
|
296
|
+
|
|
297
|
+
The server allows one subscription per connection, so every subscription has its own
|
|
298
|
+
connection; `MarketDataStream` keeps track of them and closes them together. Iterate several
|
|
299
|
+
subscriptions from separate tasks. Closing a subscription (`await subscription.aclose()`, or
|
|
300
|
+
leaving its `async with`) sends an unsubscribe, which ends that connection for good. It waits
|
|
301
|
+
at most `close_timeout` seconds (default 5, set on `MarketDataStream`) for the server to
|
|
302
|
+
acknowledge, then closes anyway. No API key is sent.
|
|
303
|
+
|
|
304
|
+
To wait for one event with a time limit, instead of looping:
|
|
305
|
+
|
|
306
|
+
```python
|
|
307
|
+
book = await stream.depth("ETH-USDC").receive(timeout=10)
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
`receive` raises `StreamTimeoutError` if nothing arrives in time, and the message says why
|
|
311
|
+
connecting failed if it did. The subscription can still be read afterwards.
|
|
312
|
+
|
|
313
|
+
### A local order book
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
async with stream.order_book("ETH-USDC", depth=1000) as book:
|
|
317
|
+
async for update in book:
|
|
318
|
+
print(book.best_bid, book.best_ask, book.bids(5), update.resynced)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
`LocalOrderBook` subscribes to the depth-diff channel, loads a snapshot over REST and applies
|
|
322
|
+
each diff on top. Every diff names the range of book updates it covers. A diff that is already
|
|
323
|
+
included is ignored. A diff that skips an update is a gap: the book stops, loads a fresh
|
|
324
|
+
snapshot and continues from there, and it does the same after every reconnect. It never applies
|
|
325
|
+
a diff out of sequence. `book.gaps` and `book.resyncs` count what happened.
|
|
326
|
+
|
|
327
|
+
The server sends the latest change roughly every 200 ms, not every change, so on a busy pair
|
|
328
|
+
gaps are normal and the book is rebuilt from snapshots often (at most once per
|
|
329
|
+
`min_resync_interval`, default one second). If you only need the top of a busy book,
|
|
330
|
+
`stream.depth(symbol, depth=20, speed_ms=100)` is simpler and just as fresh.
|
|
331
|
+
|
|
332
|
+
### Your account's events
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
from euclid import AccountStream
|
|
336
|
+
|
|
337
|
+
async with AsyncEuclidClient(api_key, base_url="https://<your-euclid-host>") as client:
|
|
338
|
+
async with AccountStream(client) as stream:
|
|
339
|
+
async for event in stream:
|
|
340
|
+
print(event.kind, event.transaction_hash, event.orders, event.trades)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Each event is one committed transaction that touched your account, as the same
|
|
344
|
+
`AccountTransaction` record that [`list_account_transactions`](#account-history) returns: `kind`
|
|
345
|
+
(`"place_order"`, `"cancel_order"`, ...), `engine_seq`, the order changes it made and the trades
|
|
346
|
+
it executed, with amounts as `Decimal`. `event.raw` is the transaction exactly as the server
|
|
347
|
+
sent it, and `event.unparsed` names any amount the SDK could not convert because its pair or
|
|
348
|
+
asset is not in the exchange's lists. The stream authenticates with a 60-second ticket that it
|
|
349
|
+
fetches itself; the API key needs the `funds:read` scope.
|
|
350
|
+
|
|
351
|
+
To continue where an earlier run stopped, save `stream.cursor` and pass
|
|
352
|
+
`AccountStream(client, cursor=saved)`. Without a cursor the stream starts at the present;
|
|
353
|
+
`from_start=True` replays the account's history first.
|
|
354
|
+
|
|
355
|
+
After a disconnect the stream reads what it missed over HTTP, from the account history, and
|
|
356
|
+
yields it in order before it reconnects, so events keep arriving even while the socket cannot
|
|
357
|
+
be re-established. You can also ask for this yourself:
|
|
358
|
+
`await stream.backfill(after_engine_seq=n)` queues everything after sequence `n`, to be yielded
|
|
359
|
+
next. History is read by `engine_seq`. The stream's `cursor` and the history's `page_token` are
|
|
360
|
+
different tokens and are never exchanged for one another. If the history cannot be read, the
|
|
361
|
+
stream reconnects anyway and the server replays from the cursor; the reason is kept in
|
|
362
|
+
`stream.last_backfill_error`. Pass `backfill_on_reconnect=False` to switch this off.
|
|
363
|
+
|
|
364
|
+
### From blocking code
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
from euclid import BlockingAccountStream, EuclidClient
|
|
368
|
+
|
|
369
|
+
with EuclidClient(api_key, base_url="https://<your-euclid-host>") as client:
|
|
370
|
+
with BlockingAccountStream(client) as events:
|
|
371
|
+
events.wait_until_connected(timeout=10)
|
|
372
|
+
order = client.place_order(...)
|
|
373
|
+
for event in events.drain(): # what has arrived so far; never waits
|
|
374
|
+
...
|
|
375
|
+
event = events.get(timeout=5) # wait for one; None if none came
|
|
376
|
+
for event in events: # or block until each arrives
|
|
377
|
+
...
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
`BlockingAccountStream` is an `AccountStream` running on a background thread, with its own
|
|
381
|
+
connection built from the client's key and URLs. It takes the same options (`cursor`,
|
|
382
|
+
`max_reconnect_attempts`, ...), delivers the same events, and reconnects and catches up the
|
|
383
|
+
same way. Events wait in memory until you read them. If the stream fails for good, the next
|
|
384
|
+
read raises the error it failed with. Call `wait_until_connected` before doing something whose
|
|
385
|
+
result you want to see: a stream started without a cursor delivers what happens after it
|
|
386
|
+
connects.
|
|
387
|
+
|
|
388
|
+
### Reconnecting
|
|
389
|
+
|
|
390
|
+
All streams reconnect by themselves. The wait starts at half a second and doubles up to 30
|
|
391
|
+
seconds, with random jitter, and starts over once a connection works; pass `backoff=Backoff(...)`
|
|
392
|
+
to change it. A refused connection that names a `Retry-After` waits at least that long. By
|
|
393
|
+
default a stream retries for ever; `max_reconnect_attempts=n` makes it raise `StreamError` after
|
|
394
|
+
`n` failures in a row. Failures that retrying cannot fix are raised straight away:
|
|
395
|
+
`SubscriptionError` for an unknown symbol or a rejected subscription, `AuthenticationError` or
|
|
396
|
+
`InsufficientScopeError` when a ticket is refused.
|
|
397
|
+
|
|
398
|
+
After a reconnect:
|
|
399
|
+
|
|
400
|
+
- a market-data subscription subscribes again, and `trades` first fetches the trades it missed
|
|
401
|
+
over REST and yields them in order;
|
|
402
|
+
- a `LocalOrderBook` rebuilds from a snapshot;
|
|
403
|
+
- an `AccountStream` first yields what it missed from the HTTP history, then gets a new ticket
|
|
404
|
+
and resumes after the last cursor it received, dropping any transaction it has already
|
|
405
|
+
delivered (by `transaction_hash`).
|
|
406
|
+
|
|
407
|
+
### What the streams do not guarantee
|
|
408
|
+
|
|
409
|
+
The server describes its WebSockets as best-effort, and so is this layer.
|
|
410
|
+
|
|
411
|
+
- **The stream alone is not a durable record.** A process that restarts has to catch up itself:
|
|
412
|
+
save `event.engine_seq` as you process events and, on start, walk
|
|
413
|
+
`list_account_transactions(after_engine_seq=saved)` before or alongside the stream.
|
|
414
|
+
- **The history the backfill reads can itself be behind.** It comes from the account indexer,
|
|
415
|
+
which trails the engine by a moment; `stream.last_backfill_freshness` says by how much. A
|
|
416
|
+
transaction too new for the indexer arrives later, over the socket or the next backfill.
|
|
417
|
+
- **An account stream started without a cursor can miss events** if it disconnects before the
|
|
418
|
+
server has told it a position (the server sends one at most 20 seconds after connecting).
|
|
419
|
+
- **Duplicates are dropped within one `AccountStream` object only**, across the last 10,000
|
|
420
|
+
transactions it saw (`dedupe_window`).
|
|
421
|
+
- **Depth, book-ticker and kline streams are samples.** After a reconnect you get the current
|
|
422
|
+
state, not what changed in between.
|
|
423
|
+
- **A local order book is only as deep as its last snapshot** (`depth` levels per side, at most
|
|
424
|
+
5000), and between a gap and the next snapshot it is briefly behind.
|
|
425
|
+
- **Do not act on money from a stream event alone.** Confirm an order's state with `get_order`
|
|
426
|
+
or `list_open_orders` first.
|
|
427
|
+
- Prices and sizes are converted with the pair's scales as read when the subscription
|
|
428
|
+
connected. If a pair is reconfigured, close and reopen its subscriptions.
|
|
429
|
+
|
|
430
|
+
## What is wrapped
|
|
431
|
+
|
|
432
|
+
| Area | Methods |
|
|
433
|
+
| --- | --- |
|
|
434
|
+
| Reference data | `get_server_time`, `list_pairs`, `list_assets`, `get_exchange_info` |
|
|
435
|
+
| Market data | `get_order_book`, `get_klines`, `get_symbol_stats`, `get_ticker`, `list_public_trades` |
|
|
436
|
+
| Account | `get_account`, `get_balances`, `get_balance`, `get_fee_tier`, `create_account_stream_ticket` |
|
|
437
|
+
| Trading | `place_order`, `cancel_order`, `cancel_all_orders`, `cancel_replace` |
|
|
438
|
+
| Orders and trades | `list_open_orders`, `list_order_history`, `get_order`, `list_my_trades` |
|
|
439
|
+
| Account history | `list_account_transactions`, `get_account_transaction`, `list_deposits`, `list_withdrawals`, `get_withdrawal`, `list_transfers` |
|
|
440
|
+
| Paging | `paginate` walks the items of every page of any method above that returns a `Page`, up to an optional `limit`; `pages` yields the pages themselves |
|
|
441
|
+
| Trading rules | `Pair.round_price`, `Pair.round_size`, `Pair.min_size_at` |
|
|
442
|
+
| Streams (asyncio) | `MarketDataStream`, `LocalOrderBook`, `AccountStream` |
|
|
443
|
+
| Streams (blocking) | `BlockingAccountStream` |
|
|
444
|
+
|
|
445
|
+
All arguments of the trading, order and history methods are keyword-only. You never pass a user
|
|
446
|
+
id: the SDK reads it once from the account route and reuses it.
|
|
447
|
+
|
|
448
|
+
Not wrapped yet: making a transfer or a withdrawal, wallet-signed orders, API-key and session
|
|
449
|
+
management (the whole auth service), leaderboards, fee-schedule discovery, the multi-symbol
|
|
450
|
+
ticker and stats lists, and from the account indexer its copies of balances and order holds
|
|
451
|
+
(the engine's `get_balances` and `list_open_orders` are the authority for those), raw fee
|
|
452
|
+
postings, and the public block explorer. The exact list, with a reason per route, is
|
|
453
|
+
`NOT_YET_WRAPPED` in [`src/euclid/_ops.py`](src/euclid/_ops.py); a test fails if the API gains a
|
|
454
|
+
route that is neither wrapped nor on that list.
|
|
455
|
+
|
|
456
|
+
## How the package is built
|
|
457
|
+
|
|
458
|
+
```
|
|
459
|
+
specs/ the five public OpenAPI documents, one per service, pruned (see below)
|
|
460
|
+
scripts/sync_specs.py refreshes specs/ from the server's public documents
|
|
461
|
+
scripts/build_spec.py merges them into one document with short schema names
|
|
462
|
+
src/euclid/_generated/ low-level client generated from the merged document (private)
|
|
463
|
+
src/euclid/ the hand-written public API on top of it
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**The generated code is checked in.** Installing the package needs no generator and no build
|
|
467
|
+
step, the exact code that ships is visible in review, and a diff of `_generated/` shows what an
|
|
468
|
+
API change does to the client. Nobody edits it by hand.
|
|
469
|
+
|
|
470
|
+
**The documents in `specs/` are pruned copies, not byte-identical ones.** The server's public
|
|
471
|
+
OpenAPI documents define more schemas than their routes use. Each file here is the server's
|
|
472
|
+
document with those removed: a schema is kept only if a route in the same document reaches it,
|
|
473
|
+
directly or through another schema, or if it is one of the market-data WebSocket messages
|
|
474
|
+
(`WsClientMessage`, `WsServerMessage` and what they reference). Paths, operations, descriptions,
|
|
475
|
+
security schemes and the kept schemas are exactly as the server publishes them. A test fails if
|
|
476
|
+
`specs/` ever holds a schema that is not reachable this way.
|
|
477
|
+
|
|
478
|
+
Never copy a document into `specs/` by hand. To refresh them, put the server's public documents
|
|
479
|
+
in a directory of their own and run:
|
|
480
|
+
|
|
481
|
+
```bash
|
|
482
|
+
python scripts/sync_specs.py path/to/public/documents
|
|
483
|
+
pip install -e ".[codegen]"
|
|
484
|
+
python scripts/regenerate.py
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
`sync_specs.py` uses the same rule as the merge to decide what to keep, needs only the standard
|
|
488
|
+
library, and writes nothing unless every file passes. It refuses a file not named
|
|
489
|
+
`euclid.<service>.v1.openapi.json` for one of the five services, and a document with an
|
|
490
|
+
`internal` path.
|
|
491
|
+
|
|
492
|
+
`python scripts/regenerate.py --check` exits non-zero if `_generated/` does not match `specs/`;
|
|
493
|
+
the test suite runs the same check when the generator is installed. The generator
|
|
494
|
+
(`openapi-python-client`) and the formatter (`ruff`) are pinned in `pyproject.toml` so the output
|
|
495
|
+
is reproducible.
|
|
496
|
+
|
|
497
|
+
Runtime dependencies: [`httpx2`](https://github.com/pydantic/httpx2) and `attrs`, which the
|
|
498
|
+
generated client uses for HTTP and models, and
|
|
499
|
+
[`websockets`](https://github.com/python-websockets/websockets) for the streams.
|
|
500
|
+
|
|
501
|
+
## Development
|
|
502
|
+
|
|
503
|
+
```bash
|
|
504
|
+
pip install -e ".[test,codegen,typecheck]"
|
|
505
|
+
pytest --cov # the tests, with coverage of the hand-written code
|
|
506
|
+
ruff check . && ruff format --check . # lint and formatting
|
|
507
|
+
mypy # types: strict for the package, default for examples and scripts
|
|
508
|
+
python scripts/regenerate.py --check # the generated client matches specs/
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
The tests use a mocked HTTP transport and a scripted in-process WebSocket, with an injected
|
|
512
|
+
clock and sleep. They never touch the network and never wait. Coverage of the hand-written code
|
|
513
|
+
must stay at 95% or more; the generated client is left out of that number, because
|
|
514
|
+
`regenerate.py --check` is what guards it.
|
|
515
|
+
|
|
516
|
+
Every push to `main` and every pull request runs the same commands in
|
|
517
|
+
[GitHub Actions](.github/workflows/ci.yml):
|
|
518
|
+
|
|
519
|
+
- the tests on Python 3.11, 3.12, 3.13 and 3.14 on Linux, and on macOS;
|
|
520
|
+
- the tests again with the oldest version of each dependency that `pyproject.toml` allows;
|
|
521
|
+
- lint, formatting, types and the generated-client check;
|
|
522
|
+
- a build of the wheel and source archive, a check of what they contain
|
|
523
|
+
(`scripts/check_distribution.py`), and an install of the wheel into a clean environment.
|
|
524
|
+
|
|
525
|
+
Windows is not tested.
|
|
526
|
+
|
|
527
|
+
### Releasing
|
|
528
|
+
|
|
529
|
+
A release is a git tag. Pushing a tag such as `v0.2.0` runs
|
|
530
|
+
[the release workflow](.github/workflows/release.yml), which checks, builds, publishes to PyPI
|
|
531
|
+
and creates the GitHub release with that version's changelog entry as its notes.
|
|
532
|
+
|
|
533
|
+
1. On a branch: set the version in `src/euclid/_version.py`, add a `## <version>` section to
|
|
534
|
+
[`CHANGELOG.md`](CHANGELOG.md), and merge it to `main`.
|
|
535
|
+
`python scripts/check_release.py` tells you whether the two agree.
|
|
536
|
+
2. Optional rehearsal: run the Release workflow by hand from the Actions tab. That builds the
|
|
537
|
+
same way and uploads to TestPyPI.
|
|
538
|
+
3. Tag the merged commit and push the tag:
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
git switch main && git pull
|
|
542
|
+
git tag v0.2.0
|
|
543
|
+
git push origin v0.2.0
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
The workflow refuses to publish if the tag is not on `main`, if the tag and the version in the
|
|
547
|
+
code differ, if the changelog has no entry for the version, or if the tests or the package
|
|
548
|
+
checks fail. A version on PyPI is permanent: it can be hidden afterwards but never replaced, so
|
|
549
|
+
a mistake is fixed by releasing the next patch version.
|
|
550
|
+
|
|
551
|
+
Publishing uses PyPI's trusted publishing, so no token is stored in this repository. It needs a
|
|
552
|
+
one-time setup by an owner of the PyPI project: on pypi.org, under the project's publishing
|
|
553
|
+
settings (or "pending publisher" before the first release), add a GitHub publisher with
|
|
554
|
+
|
|
555
|
+
| Field | Value |
|
|
556
|
+
| --- | --- |
|
|
557
|
+
| Owner | `EuclidProtocol` |
|
|
558
|
+
| Repository | `euclid-python-sdk` |
|
|
559
|
+
| Workflow | `release.yml` |
|
|
560
|
+
| Environment | `pypi` |
|
|
561
|
+
|
|
562
|
+
and the same on test.pypi.org with the environment `testpypi`. To require a person to approve
|
|
563
|
+
each publish, add required reviewers to the `pypi` environment in this repository's settings.
|
|
564
|
+
|
|
565
|
+
## Licence
|
|
566
|
+
|
|
567
|
+
[MIT](LICENSE).
|