├── .gitattributes ├── .github └── workflows │ ├── check.yml │ └── review.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── check.sh ├── examples ├── go.mod ├── go.sum ├── main.go ├── ohlcv.go ├── order.go ├── ticker.go ├── wallet.go └── watcher.go ├── typos.toml └── v2 ├── account_commission_rate.go ├── account_service.go ├── account_service_test.go ├── asset_detail_service.go ├── asset_detail_service_test.go ├── asset_dividend_service.go ├── asset_dividend_service_test.go ├── bnb_burn_service.go ├── bnb_burn_service_test.go ├── c2c_service.go ├── c2c_service_test.go ├── client.go ├── client_integration_test.go ├── client_test.go ├── common ├── errors.go ├── helpers.go ├── helpers_test.go ├── priceLevel.go ├── sign.go └── websocket │ ├── client.go │ ├── client_test.go │ ├── mock │ └── client.go │ └── types.go ├── convert_trade.go ├── convert_trade_test.go ├── delivery ├── account_service.go ├── account_service_test.go ├── client.go ├── client_test.go ├── depth_service.go ├── exchange_info_service.go ├── exchange_info_service_test.go ├── funding_info_service.go ├── funding_info_service_test.go ├── funding_rate_service.go ├── funding_rate_service_test.go ├── kline_service.go ├── kline_service_test.go ├── order_service.go ├── order_service_test.go ├── position_risk.go ├── position_risk_test.go ├── position_service.go ├── position_service_test.go ├── request.go ├── server_service.go ├── server_service_test.go ├── ticker_service.go ├── ticker_service_test.go ├── user_stream_service.go ├── user_stream_service_test.go ├── websocket.go ├── websocket_service.go └── websocket_service_test.go ├── deposit_service.go ├── deposit_service_test.go ├── depth_service.go ├── depth_service_test.go ├── doc.go ├── dual_investment_service.go ├── dual_investment_service_test.go ├── dust_log_service.go ├── dust_log_service_test.go ├── exchange_info_service.go ├── exchange_info_service_test.go ├── fiat_service.go ├── fiat_service_test.go ├── futures ├── account_config_service.go ├── account_config_service_test.go ├── account_service.go ├── account_service_test.go ├── api_trading_status_service.go ├── api_trading_status_service_test.go ├── asset_index_service.go ├── asset_index_service_test.go ├── client.go ├── client_test.go ├── commission_rate_service.go ├── commission_rate_service_test.go ├── constituents_service.go ├── constituents_service_test.go ├── continuous_kline_service.go ├── continuous_kline_service_test.go ├── convert_service.go ├── convert_service_test.go ├── data_service.go ├── data_service_test.go ├── depth_service.go ├── depth_service_test.go ├── exchange_info_service.go ├── exchange_info_service_test.go ├── fee_burn_service.go ├── fee_burn_service_test.go ├── funding_rate_info.go ├── funding_rate_info_test.go ├── income_history.go ├── income_history_test.go ├── index_info_service.go ├── index_info_service_test.go ├── index_price_kline_service.go ├── index_price_kline_service_test.go ├── kline_service.go ├── kline_service_test.go ├── longshort_ratio_service.go ├── longshort_ratio_service_test.go ├── lvtKlines_service.go ├── lvtKlines_service_test.go ├── mark_price.go ├── mark_price_kline_service.go ├── mark_price_kline_service_test.go ├── mark_price_test.go ├── mock │ └── client_ws.go ├── openinterest_service.go ├── openinterest_service_test.go ├── order_cancel_service_ws.go ├── order_cancel_service_ws_test.go ├── order_place_service_ws.go ├── order_place_service_ws_test.go ├── order_service.go ├── order_service_test.go ├── position_margin_history.go ├── position_margin_history_test.go ├── position_risk.go ├── position_risk_test.go ├── position_service.go ├── position_service_test.go ├── premium_index_kline_service.go ├── premium_index_kline_service_test.go ├── rebate_newuser.go ├── rebate_newuser_test.go ├── request.go ├── request_test.go ├── server_service.go ├── server_service_test.go ├── symbol_config_service.go ├── symbol_config_service_test.go ├── ticker_service.go ├── ticker_service_test.go ├── trade_service.go ├── trade_service_test.go ├── user_stream_service.go ├── user_stream_service_test.go ├── websocket.go ├── websocket_service.go └── websocket_service_test.go ├── futures_algo_service.go ├── futures_algo_service_test.go ├── futures_service.go ├── futures_service_test.go ├── go.mod ├── go.sum ├── interest_history_service.go ├── interest_history_service_test.go ├── internal_universal_transfer_service.go ├── internal_universal_transfer_service_test.go ├── kline_service.go ├── kline_service_test.go ├── liquidity_pool_service.go ├── liquidity_pool_service_test.go ├── margin_interest_history_service.go ├── margin_interest_history_service_integration_test.go ├── margin_interest_history_service_test.go ├── margin_interest_rate_history_service.go ├── margin_interest_rate_history_service_integration_test.go ├── margin_interest_rate_history_service_test.go ├── margin_next_hourly_interest_rate_service.go ├── margin_next_hourly_interest_rate_service_integration_test.go ├── margin_next_hourly_interest_rate_service_test.go ├── margin_order_service.go ├── margin_order_service_test.go ├── margin_service.go ├── margin_service_test.go ├── options ├── account_service.go ├── account_service_test.go ├── client.go ├── client_test.go ├── depth_service.go ├── depth_service_test.go ├── exchange_info_service.go ├── exchange_info_service_test.go ├── exercise_history_service.go ├── exercise_history_service_test.go ├── index_service.go ├── index_service_test.go ├── kline_service.go ├── kline_service_test.go ├── mark_service.go ├── mark_service_test.go ├── open_interest_service.go ├── open_interest_service_test.go ├── order_service.go ├── order_service_test.go ├── request.go ├── server_service.go ├── server_service_test.go ├── ticker_service.go ├── ticker_service_test.go ├── trade_service_test.go ├── trades_service.go ├── user_stream_service.go ├── user_stream_service_test.go ├── websocket.go ├── websocket_service.go └── websocket_service_test.go ├── order_service.go ├── order_service_test.go ├── order_service_ws_create.go ├── order_service_ws_create_test.go ├── pay_service.go ├── pay_service_test.go ├── portfolio ├── account_service.go ├── account_service_integration_test.go ├── account_service_test.go ├── auto_repay_futures_status_service.go ├── auto_repay_futures_status_service_integration_test.go ├── auto_repay_futures_status_service_test.go ├── auto_repay_futures_switch_service.go ├── auto_repay_futures_switch_service_integration_test.go ├── auto_repay_futures_switch_service_test.go ├── balance_integration_test.go ├── balance_service.go ├── balance_service_test.go ├── bnb_transfer_service.go ├── bnb_transfer_service_integration_test.go ├── bnb_transfer_service_test.go ├── client.go ├── client_integration_test.go ├── client_test.go ├── cm_account_detail_service.go ├── cm_account_detail_service_integration_test.go ├── cm_account_detail_service_test.go ├── cm_account_trade_service.go ├── cm_account_trade_service_integration_test.go ├── cm_account_trade_service_test.go ├── cm_account_trades_service.go ├── cm_account_trades_service_integration_test.go ├── cm_account_trades_service_test.go ├── cm_adl_quantile_service.go ├── cm_adl_quantile_service_integration_test.go ├── cm_adl_quantile_service_test.go ├── cm_all_orders_service.go ├── cm_all_orders_service_integration_test.go ├── cm_all_orders_service_test.go ├── cm_cancel_all_conditional_orders_service.go ├── cm_cancel_all_conditional_orders_service_integration_test.go ├── cm_cancel_all_conditional_orders_service_test.go ├── cm_cancel_all_orders_service.go ├── cm_cancel_all_orders_service_integration_test.go ├── cm_cancel_all_orders_service_test.go ├── cm_cancel_conditional_order_service.go ├── cm_cancel_conditional_order_service_integration_test.go ├── cm_cancel_conditional_order_service_test.go ├── cm_cancel_order_service.go ├── cm_cancel_order_service_integration_test.go ├── cm_cancel_order_service_test.go ├── cm_commission_rate_service.go ├── cm_commission_rate_service_integration_test.go ├── cm_commission_rate_service_test.go ├── cm_conditional_order_history_service.go ├── cm_conditional_order_history_service_integration_test.go ├── cm_conditional_order_history_service_test.go ├── cm_conditional_order_service.go ├── cm_conditional_order_service_integration_test.go ├── cm_conditional_order_service_test.go ├── cm_conditional_orders_service.go ├── cm_conditional_orders_service_integration_test.go ├── cm_conditional_orders_service_test.go ├── cm_force_orders_service.go ├── cm_force_orders_service_integration_test.go ├── cm_force_orders_service_test.go ├── cm_income_history_service.go ├── cm_income_history_service_integration_test.go ├── cm_income_history_service_test.go ├── cm_leverage_bracket_service.go ├── cm_leverage_bracket_service_integration_test.go ├── cm_leverage_bracket_service_test.go ├── cm_leverage_service.go ├── cm_leverage_service_integration_test.go ├── cm_leverage_service_test.go ├── cm_modify_order_history_service.go ├── cm_modify_order_history_service_integration_test.go ├── cm_modify_order_history_service_test.go ├── cm_modify_order_service.go ├── cm_modify_order_service_integration_test.go ├── cm_modify_order_service_test.go ├── cm_open_conditional_order_service.go ├── cm_open_conditional_order_service_integration_test.go ├── cm_open_conditional_order_service_test.go ├── cm_open_conditional_orders_service.go ├── cm_open_conditional_orders_service_integration_test.go ├── cm_open_conditional_orders_service_test.go ├── cm_open_order_service.go ├── cm_open_order_service_integration_test.go ├── cm_open_order_service_test.go ├── cm_open_orders_service.go ├── cm_open_orders_service_integration_test.go ├── cm_open_orders_service_test.go ├── cm_order_service.go ├── cm_order_service_integration_test.go ├── cm_order_service_test.go ├── cm_position_mode_get_service.go ├── cm_position_mode_get_service_integration_test.go ├── cm_position_mode_get_service_test.go ├── cm_position_mode_service.go ├── cm_position_mode_service_integration_test.go ├── cm_position_mode_service_test.go ├── cm_position_service.go ├── cm_position_service_integration_test.go ├── cm_position_service_test.go ├── cm_query_order_service.go ├── cm_query_order_service_integration_test.go ├── cm_query_order_service_test.go ├── errors.go ├── fund_auto_collection_service.go ├── fund_auto_collection_service_integration_test.go ├── fund_auto_collection_service_test.go ├── fund_collection_by_asset_service.go ├── fund_collection_by_asset_service_integration_test.go ├── fund_collection_by_asset_service_test.go ├── get_margin_all_orders_service.go ├── get_margin_all_orders_service_integration_test.go ├── get_margin_all_orders_service_test.go ├── get_margin_force_orders_service.go ├── get_margin_force_orders_service_integration_test.go ├── get_margin_force_orders_service_test.go ├── get_margin_loan_service.go ├── get_margin_loan_service_integration_test.go ├── get_margin_loan_service_test.go ├── get_margin_open_orders_service.go ├── get_margin_open_orders_service_integration_test.go ├── get_margin_open_orders_service_test.go ├── get_margin_repay_service.go ├── get_margin_repay_service_integration_test.go ├── get_margin_repay_service_test.go ├── margin_account_trades_service.go ├── margin_account_trades_service_integration_test.go ├── margin_account_trades_service_test.go ├── margin_all_oco_service.go ├── margin_all_oco_service_integration_test.go ├── margin_all_oco_service_test.go ├── margin_borrow_service.go ├── margin_borrow_service_integration_test.go ├── margin_borrow_service_test.go ├── margin_cancel_all_orders_service.go ├── margin_cancel_all_orders_service_integration_test.go ├── margin_cancel_all_orders_service_test.go ├── margin_cancel_oco_service.go ├── margin_cancel_oco_service_integration_test.go ├── margin_cancel_oco_service_test.go ├── margin_cancel_order_service.go ├── margin_cancel_order_service_integration_test.go ├── margin_cancel_order_service_test.go ├── margin_force_orders_service.go ├── margin_force_orders_service_integration_test.go ├── margin_force_orders_service_test.go ├── margin_interest_history_service.go ├── margin_interest_history_service_integration_test.go ├── margin_interest_history_service_test.go ├── margin_loan_service.go ├── margin_loan_service_integration_test.go ├── margin_loan_service_test.go ├── margin_oco_query_service.go ├── margin_oco_query_service_integration_test.go ├── margin_oco_query_service_test.go ├── margin_oco_service.go ├── margin_oco_service_integration_test.go ├── margin_oco_service_test.go ├── margin_open_oco_service.go ├── margin_open_oco_service_integration_test.go ├── margin_open_oco_service_test.go ├── margin_order_service.go ├── margin_order_service_integration_test.go ├── margin_order_service_test.go ├── margin_repay_debt_service.go ├── margin_repay_debt_service_integration_test.go ├── margin_repay_debt_service_test.go ├── margin_repay_service.go ├── margin_repay_service_integration_test.go ├── margin_repay_service_test.go ├── margin_withdraw_service.go ├── margin_withdraw_service_integration_test.go ├── margin_withdraw_service_test.go ├── mock │ └── client_ws.go ├── negative_balance_interest_history_service.go ├── negative_balance_interest_history_service_integration_test.go ├── negative_balance_interest_history_service_test.go ├── negative_balance_service.go ├── negative_balance_service_integration_test.go ├── negative_balance_service_test.go ├── rate_limit_service.go ├── rate_limit_service_integration_test.go ├── rate_limit_service_test.go ├── repay_futures_negative_balance_service.go ├── repay_futures_negative_balance_service_integration_test.go ├── repay_futures_negative_balance_service_test.go ├── request.go ├── request_test.go ├── server_service.go ├── server_service_integration_test.go ├── server_service_test.go ├── um_account_config_service.go ├── um_account_config_service_integration_test.go ├── um_account_config_service_test.go ├── um_account_detail_service.go ├── um_account_detail_service_integration_test.go ├── um_account_detail_service_test.go ├── um_account_detail_v2_service.go ├── um_account_detail_v2_service_integration_test.go ├── um_account_detail_v2_service_test.go ├── um_account_trade_service.go ├── um_account_trade_service_integration_test.go ├── um_account_trade_service_test.go ├── um_account_trades_service.go ├── um_account_trades_service_integration_test.go ├── um_account_trades_service_test.go ├── um_adl_quantile_service.go ├── um_adl_quantile_service_integration_test.go ├── um_adl_quantile_service_test.go ├── um_all_conditional_orders_service.go ├── um_all_conditional_orders_service_integration_test.go ├── um_all_conditional_orders_service_test.go ├── um_all_orders_service.go ├── um_all_orders_service_integration_test.go ├── um_all_orders_service_test.go ├── um_cancel_all_conditional_orders_service.go ├── um_cancel_all_conditional_orders_service_integration_test.go ├── um_cancel_all_conditional_orders_service_test.go ├── um_cancel_all_orders_service.go ├── um_cancel_all_orders_service_integration_test.go ├── um_cancel_all_orders_service_test.go ├── um_cancel_conditional_order_service.go ├── um_cancel_conditional_order_service_integration_test.go ├── um_cancel_conditional_order_service_test.go ├── um_cancel_order_service.go ├── um_cancel_order_service_integration_test.go ├── um_cancel_order_service_test.go ├── um_commission_rate_service.go ├── um_commission_rate_service_integration_test.go ├── um_commission_rate_service_test.go ├── um_conditional_order_history_service.go ├── um_conditional_order_history_service_integration_test.go ├── um_conditional_order_history_service_test.go ├── um_conditional_order_service.go ├── um_conditional_order_service_integration_test.go ├── um_conditional_order_service_test.go ├── um_fee_burn_service.go ├── um_fee_burn_service_integration_test.go ├── um_fee_burn_service_test.go ├── um_fee_burn_status_service.go ├── um_fee_burn_status_service_integration_test.go ├── um_fee_burn_status_service_test.go ├── um_force_orders_service.go ├── um_force_orders_service_integration_test.go ├── um_force_orders_service_test.go ├── um_get_adl_quantile_service.go ├── um_get_adl_quantile_service_integration_test.go ├── um_get_adl_quantile_service_test.go ├── um_income_history_service.go ├── um_income_history_service_integration_test.go ├── um_income_history_service_test.go ├── um_leverage_bracket_service.go ├── um_leverage_bracket_service_integration_test.go ├── um_leverage_bracket_service_test.go ├── um_leverage_service.go ├── um_leverage_service_integration_test.go ├── um_leverage_service_test.go ├── um_modify_order_history_service.go ├── um_modify_order_history_service_integration_test.go ├── um_modify_order_history_service_test.go ├── um_modify_order_service.go ├── um_modify_order_service_integration_test.go ├── um_modify_order_service_test.go ├── um_open_conditional_order_service.go ├── um_open_conditional_order_service_integration_test.go ├── um_open_conditional_order_service_test.go ├── um_open_conditional_orders_service.go ├── um_open_conditional_orders_service_integration_test.go ├── um_open_conditional_orders_service_test.go ├── um_open_order_service.go ├── um_open_order_service_integration_test.go ├── um_open_order_service_test.go ├── um_open_orders_service.go ├── um_open_orders_service_integration_test.go ├── um_open_orders_service_test.go ├── um_order_download_link_service.go ├── um_order_download_link_service_integration_test.go ├── um_order_download_link_service_test.go ├── um_order_history_service.go ├── um_order_history_service_integration_test.go ├── um_order_history_service_test.go ├── um_order_service.go ├── um_order_service_integration_test.go ├── um_order_service_test.go ├── um_position_mode_get_service.go ├── um_position_mode_get_service_integration_test.go ├── um_position_mode_get_service_test.go ├── um_position_mode_service.go ├── um_position_mode_service_integration_test.go ├── um_position_mode_service_test.go ├── um_position_service.go ├── um_position_service_integration_test.go ├── um_position_service_test.go ├── um_query_order_service.go ├── um_query_order_service_integration_test.go ├── um_query_order_service_test.go ├── um_symbol_config_service.go ├── um_symbol_config_service_integration_test.go ├── um_symbol_config_service_test.go ├── um_trade_download_link_service.go ├── um_trade_download_link_service_integration_test.go ├── um_trade_download_link_service_test.go ├── um_trade_history_service.go ├── um_trade_history_service_integration_test.go ├── um_trade_history_service_test.go ├── um_trading_status_service.go ├── um_trading_status_service_integration_test.go ├── um_trading_status_service_test.go ├── um_transaction_download_link_service.go ├── um_transaction_download_link_service_integration_test.go ├── um_transaction_download_link_service_test.go ├── um_transaction_history_service.go ├── um_transaction_history_service_integration_test.go ├── um_transaction_history_service_test.go ├── user_stream_service.go ├── user_stream_service_integration_test.go ├── user_stream_service_test.go ├── websocket.go ├── websocket_service.go ├── websocket_service_integration_test.go └── websocket_service_test.go ├── rate_limit_service.go ├── rate_limit_service_test.go ├── rebate.go ├── rebate_test.go ├── request.go ├── savings_service.go ├── savings_service_test.go ├── server_service.go ├── server_service_test.go ├── simple_earn_service.go ├── simple_earn_service_test.go ├── staking_service.go ├── staking_service_test.go ├── subaccount_service.go ├── subaccount_service_test.go ├── ticker_service.go ├── ticker_service_test.go ├── trade_fee_service.go ├── trade_fee_service_test.go ├── trade_service.go ├── trade_service_test.go ├── trading_day_ticker.go ├── trading_day_ticker_test.go ├── uiKlines_service.go ├── uiKlines_service_test.go ├── user_stream_service.go ├── user_stream_service_test.go ├── user_universal_transfer.go ├── user_universal_transfer_test.go ├── wallet_balance_service.go ├── wallet_balance_service_test.go ├── websocket.go ├── websocket_service.go ├── websocket_service_test.go ├── withdraw_service.go └── withdraw_service_test.go /.gitattributes: -------------------------------------------------------------------------------- 1 | # Force LF for all text files. 2 | **/* text eol=lf -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | 12 | jobs: 13 | TyposCheck: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: crate-ci/typos@v1.22.7 18 | with: 19 | config: ./typos.toml 20 | 21 | UnitTest: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Setup Go 26 | uses: actions/setup-go@v3 27 | with: 28 | go-version-file: './v2/go.mod' 29 | cache: true 30 | cache-dependency-path: './v2/go.sum' 31 | - name: Format 32 | run: ./check.sh format 33 | - name: Vet 34 | run: ./check.sh vet 35 | - name: UnitTest 36 | run: ./check.sh unittest 37 | # - name: IntegrationTest 38 | # run: ./check.sh integration 39 | -------------------------------------------------------------------------------- /.github/workflows/review.yml: -------------------------------------------------------------------------------- 1 | name: Code Review 2 | 3 | on: 4 | issue_comment: 5 | types: [created, edited] 6 | 7 | jobs: 8 | code-review: 9 | if: | 10 | github.event_name == 'pull_request' || 11 | (github.event.comment.user.login == 'adshao' && 12 | startsWith(github.event.comment.body, 'chatgpt')) 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: OpenAI ChatGPT Code Review 16 | uses: adshao/chatgpt-code-review-action@v0.2.5 17 | with: 18 | PROGRAMMING_LANGUAGE: 'Go' 19 | REVIEW_COMMENT_PREFIX: 'chatgpt:' 20 | FULL_REVIEW_COMMENT: 'chatgpt' 21 | OPENAI_TOKEN: ${{ secrets.OPENAI_TOKEN }} 22 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/ 2 | .idea 3 | *.iml 4 | coverage.txt 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "ocos" 4 | ] 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 adshao 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. 22 | -------------------------------------------------------------------------------- /check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ACTION=$1 6 | 7 | function format() { 8 | echo "Running gofmt ..." 9 | if [[ $1 == "-w" ]]; then 10 | gofmt -w $(find . -type f -name '*.go') 11 | elif [[ $1 == "-l" ]]; then 12 | gofmt -l $(find . -type f -name '*.go') 13 | elif [[ $1 == "-d" ]]; then 14 | gofmt -d $(find . -type f -name '*.go') 15 | else 16 | UNFORMATTED=$(gofmt -l $(find . -type f -name '*.go')) 17 | if [[ ! -z "$UNFORMATTED" ]]; then 18 | echo "The following files are not properly formatted:" 19 | echo "$UNFORMATTED" 20 | exit 1 21 | fi 22 | fi 23 | } 24 | 25 | function lint() { 26 | echo "Running golint ..." 27 | go install golang.org/x/lint/golint 28 | golint -set_exit_status ./... 29 | } 30 | 31 | function vet() { 32 | echo "Running go vet ..." 33 | ( 34 | cd v2 35 | go vet ./... 36 | ) 37 | } 38 | 39 | function unittest() { 40 | echo "Running go test ..." 41 | ( 42 | cd v2 43 | go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... 44 | ) 45 | } 46 | 47 | function integration() { 48 | echo "Running integration test ..." 49 | cd v2 50 | go test -v -tags=integration -race -coverprofile=coverage.txt -covermode=atomic ./... 51 | } 52 | 53 | if [[ -z $ACTION ]]; then 54 | format 55 | # lint 56 | vet 57 | unittest 58 | else 59 | shift 60 | $ACTION "$@" 61 | fi 62 | -------------------------------------------------------------------------------- /examples/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.23.4 4 | 5 | // require v2.8.0 6 | 7 | replace github.com/adshao/go-binance/v2 => ../v2 8 | 9 | replace github.com/adshao/go-binance/v2/futures => ../v2/futures 10 | 11 | require github.com/adshao/go-binance/v2 v2.0.0-00010101000000-000000000000 12 | 13 | require ( 14 | github.com/bitly/go-simplejson v0.5.0 // indirect 15 | github.com/google/uuid v1.6.0 // indirect 16 | github.com/gorilla/websocket v1.5.3 // indirect 17 | github.com/jpillora/backoff v1.0.0 // indirect 18 | github.com/kr/text v0.2.0 // indirect 19 | github.com/shopspring/decimal v1.4.0 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /examples/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | // Ticker() 5 | // Ohlcv() 6 | // SpotOrder() 7 | // FuturesOrder() 8 | // WalletBalance() 9 | WatchMiniMarketsStat() 10 | } 11 | -------------------------------------------------------------------------------- /examples/ohlcv.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/adshao/go-binance/v2" 8 | ) 9 | 10 | func Ohlcv() { 11 | apiKey := "" 12 | secret := "" 13 | client := binance.NewClient(apiKey, secret) 14 | 15 | // spot ohlcv 16 | ohlcv, err := client.NewKlinesService().Symbol("BTCUSDT").Interval("1m").Limit(5).Do(context.Background()) 17 | 18 | if err != nil { 19 | fmt.Println(err) 20 | return 21 | } 22 | 23 | for _, kline := range ohlcv { 24 | fmt.Println(kline.OpenTime, kline.Open, kline.High, kline.Low, kline.Close, kline.Volume) 25 | } 26 | 27 | // futures ohlcv 28 | futuresClient := binance.NewFuturesClient(apiKey, secret) 29 | futuresOHLCV, err2 := futuresClient.NewKlinesService().Symbol("BTCUSDT").Interval("1m").Limit(5).Do(context.Background()) 30 | if err2 != nil { 31 | fmt.Println(err2) 32 | return 33 | } 34 | 35 | for _, kline := range futuresOHLCV { 36 | fmt.Println(kline.OpenTime, kline.Open, kline.High, kline.Low, kline.Close, kline.Volume) 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /examples/order.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/adshao/go-binance/v2" 8 | "github.com/adshao/go-binance/v2/futures" 9 | ) 10 | 11 | func SpotOrder() { 12 | binance.UseTestnet = true 13 | apiKey := "" 14 | secret := "" 15 | client := binance.NewClient(apiKey, secret) 16 | 17 | symbol := "BTCUSDT" 18 | side := binance.SideTypeSell 19 | orderType := binance.OrderTypeMarket 20 | quantity := "0.0001" 21 | 22 | res, err := client.NewCreateOrderService().Symbol(symbol).Side(side). 23 | Type(orderType).Quantity(quantity).Do(context.Background()) 24 | 25 | if err != nil { 26 | fmt.Println(err) 27 | return 28 | } 29 | 30 | fmt.Println(res) 31 | } 32 | 33 | func FuturesOrder() { 34 | futures.UseTestnet = true 35 | apiKey := "" 36 | secret := "" 37 | client := binance.NewFuturesClient(apiKey, secret) 38 | 39 | symbol := "LTCUSDT" 40 | side := futures.SideTypeSell 41 | orderType := futures.OrderTypeMarket 42 | quantity := "0.1" 43 | 44 | res, err := client.NewCreateOrderService().Symbol(symbol).Side(side). 45 | Type(orderType).Quantity(quantity).PositionSide(futures.PositionSideTypeLong).Do(context.Background()) 46 | 47 | if err != nil { 48 | fmt.Println(err) 49 | return 50 | } 51 | 52 | fmt.Println(res) 53 | 54 | } 55 | -------------------------------------------------------------------------------- /examples/ticker.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/adshao/go-binance/v2" 8 | ) 9 | 10 | func Ticker() { 11 | apiKey := "" 12 | secret := "" 13 | client := binance.NewClient(apiKey, secret) 14 | 15 | // spot ticker 16 | ticker, err := client.NewTradingDayTickerService().Symbol("BTCUSDT").Do(context.Background()) 17 | 18 | if err != nil { 19 | fmt.Println(err) 20 | return 21 | } 22 | 23 | for _, ticker := range ticker { 24 | fmt.Println(ticker) 25 | } 26 | 27 | // futures ticker 28 | futuresClient := binance.NewFuturesClient(apiKey, secret) 29 | futuresTicker, err2 := futuresClient.NewListBookTickersService().Symbol("BTCUSDT").Do(context.Background()) 30 | if err2 != nil { 31 | fmt.Println(err2) 32 | return 33 | } 34 | 35 | for _, ticker := range futuresTicker { 36 | fmt.Println(ticker) 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /examples/wallet.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/adshao/go-binance/v2" 8 | ) 9 | 10 | func WalletBalance() { 11 | apiKey := "" 12 | secret := "" 13 | client := binance.NewClient(apiKey, secret) 14 | 15 | quoteAsset := "USDT" 16 | 17 | res, err := client.NewWalletBalanceService(). 18 | QuoteAsset(quoteAsset). 19 | Do(context.Background()) 20 | 21 | if err != nil { 22 | fmt.Println(err) 23 | return 24 | } 25 | 26 | for _, w := range res { 27 | fmt.Printf("%s: %s\n", w.WalletName, w.Balance) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /examples/watcher.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | 8 | "github.com/adshao/go-binance/v2" 9 | ) 10 | 11 | func WatchMiniMarketsStat() { 12 | binance.UseTestnet = true 13 | 14 | doneC, stopC, err := binance.WsAllMiniMarketsStatServe(func(event binance.WsAllMiniMarketsStatEvent) { 15 | fmt.Printf("got %d events\n", len(event)) 16 | }, func(err error) { 17 | fmt.Printf("%v", err) 18 | }) 19 | if err != nil { 20 | fmt.Printf("%v", err) 21 | } 22 | c := make(chan os.Signal, 1) 23 | signal.Notify(c, os.Interrupt) 24 | select { 25 | case <-c: 26 | stopC <- struct{}{} 27 | } 28 | <-doneC 29 | 30 | } 31 | -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | ignore-files = true 3 | ignore-hidden = false 4 | extend-exclude = [ 5 | ".git/", 6 | "v2/go.mod", 7 | "v2/go.sum", 8 | "v2/go.work.sum", 9 | ] 10 | 11 | 12 | [default] 13 | extend-ignore-re=[ 14 | "ios_54d9b18d8e7a4caf9d149573e16480ba", 15 | "Pn", 16 | "SIZ9", 17 | "alo", 18 | "ot", 19 | "[Cc]ummulative", 20 | "OTU", 21 | "[Tt]ransfered", 22 | "DELIVERED_SETTELMENT", 23 | "SETTELMENT", 24 | "[Aa]ccured" 25 | ] 26 | check-filename = true 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /v2/client_integration_test.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/suite" 8 | ) 9 | 10 | type baseIntegrationTestSuite struct { 11 | suite.Suite 12 | client *Client 13 | } 14 | 15 | func SetupTest(t *testing.T) *baseIntegrationTestSuite { 16 | apiKey := os.Getenv("BINANCE_API_KEY") 17 | secretKey := os.Getenv("BINANCE_SECRET_KEY") 18 | proxyURL := os.Getenv("BINANCE_PROXY_URL") 19 | useTestnet := true 20 | if os.Getenv("BINANCE_USE_TESTNET") == "false" { 21 | useTestnet = false 22 | } 23 | 24 | if apiKey == "" || secretKey == "" { 25 | t.Skip("API key and secret are required for integration tests") 26 | } 27 | 28 | var client *Client 29 | if proxyURL != "" { 30 | client = NewProxiedClient(apiKey, secretKey, proxyURL) 31 | } else { 32 | client = NewClient(apiKey, secretKey) 33 | } 34 | 35 | client.Debug = true 36 | UseTestnet = useTestnet // Set the global testnet flag 37 | 38 | return &baseIntegrationTestSuite{ 39 | client: client, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /v2/common/errors.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // APIError define API error when response status is 4xx or 5xx 8 | type APIError struct { 9 | Code int64 `json:"code"` 10 | Message string `json:"msg"` 11 | Response []byte `json:"-"` // Assign the body value when the Code and Message fields are invalid. 12 | } 13 | 14 | // Error return error code and message 15 | func (e APIError) Error() string { 16 | if e.IsValid() { 17 | return fmt.Sprintf(" code=%d, msg=%s", e.Code, e.Message) 18 | } 19 | return fmt.Sprintf(" rsp=%s", string(e.Response)) 20 | } 21 | 22 | func (e APIError) IsValid() bool { 23 | return e.Code != 0 || e.Message != "" 24 | } 25 | 26 | // IsAPIError check if e is an API error 27 | func IsAPIError(e error) bool { 28 | _, ok := e.(*APIError) 29 | return ok 30 | } 31 | -------------------------------------------------------------------------------- /v2/common/priceLevel.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "strconv" 4 | 5 | // PriceLevel is a common structure for bids and asks in the 6 | // order book. 7 | type PriceLevel struct { 8 | Price string 9 | Quantity string 10 | } 11 | 12 | // Parse parses this PriceLevel's Price and Quantity and 13 | // returns them both. It also returns an error if either 14 | // fails to parse. 15 | func (p *PriceLevel) Parse() (float64, float64, error) { 16 | price, err := strconv.ParseFloat(p.Price, 64) 17 | if err != nil { 18 | return 0, 0, err 19 | } 20 | quantity, err := strconv.ParseFloat(p.Quantity, 64) 21 | if err != nil { 22 | return price, 0, err 23 | } 24 | return price, quantity, nil 25 | } 26 | -------------------------------------------------------------------------------- /v2/delivery/depth_service.go: -------------------------------------------------------------------------------- 1 | package delivery 2 | 3 | import "github.com/adshao/go-binance/v2/common" 4 | 5 | // Ask is a type alias for PriceLevel. 6 | type Ask = common.PriceLevel 7 | 8 | // Bid is a type alias for PriceLevel. 9 | type Bid = common.PriceLevel 10 | -------------------------------------------------------------------------------- /v2/delivery/funding_info_service.go: -------------------------------------------------------------------------------- 1 | package delivery 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | type GetFundingInfoService struct { 10 | c *Client 11 | } 12 | 13 | type FundingInfo struct { 14 | Symbol string `json:"symbol"` 15 | AdjustedFundingRateCap string `json:"adjustedFundingRateCap"` 16 | AdjustedFundingRateFloor string `json:"adjustedFundingRateFloor"` 17 | FundingIntervalHours int `json:"fundingIntervalHours"` 18 | Disclaimer bool `json:"disclaimer"` 19 | } 20 | 21 | func (s *GetFundingInfoService) Do(ctx context.Context, opts ...RequestOption) (fundingInfo []*FundingInfo, err error) { 22 | r := &request{ 23 | method: http.MethodGet, 24 | endpoint: "/dapi/v1/fundingInfo", 25 | secType: secTypeNone, 26 | } 27 | data, err := s.c.callAPI(ctx, r, opts...) 28 | if err != nil { 29 | return nil, err 30 | } 31 | fundingInfo = make([]*FundingInfo, 0) 32 | err = json.Unmarshal(data, &fundingInfo) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return fundingInfo, nil 37 | } 38 | -------------------------------------------------------------------------------- /v2/delivery/server_service.go: -------------------------------------------------------------------------------- 1 | package delivery 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | // PingService ping server 9 | type PingService struct { 10 | c *Client 11 | } 12 | 13 | // Do send request 14 | func (s *PingService) Do(ctx context.Context, opts ...RequestOption) (err error) { 15 | r := &request{ 16 | method: http.MethodGet, 17 | endpoint: "/dapi/v1/ping", 18 | } 19 | _, err = s.c.callAPI(ctx, r, opts...) 20 | return err 21 | } 22 | 23 | // ServerTimeService get server time 24 | type ServerTimeService struct { 25 | c *Client 26 | } 27 | 28 | // Do send request 29 | func (s *ServerTimeService) Do(ctx context.Context, opts ...RequestOption) (serverTime int64, err error) { 30 | r := &request{ 31 | method: http.MethodGet, 32 | endpoint: "/dapi/v1/time", 33 | } 34 | data, err := s.c.callAPI(ctx, r, opts...) 35 | if err != nil { 36 | return 0, err 37 | } 38 | j, err := newJSON(data) 39 | if err != nil { 40 | return 0, err 41 | } 42 | serverTime = j.Get("serverTime").MustInt64() 43 | return serverTime, nil 44 | } 45 | 46 | // SetServerTimeService set server time 47 | type SetServerTimeService struct { 48 | c *Client 49 | } 50 | 51 | // Do send request 52 | func (s *SetServerTimeService) Do(ctx context.Context, opts ...RequestOption) (timeOffset int64, err error) { 53 | serverTime, err := s.c.NewServerTimeService().Do(ctx) 54 | if err != nil { 55 | return 0, err 56 | } 57 | timeOffset = currentTimestamp() - serverTime 58 | s.c.TimeOffset = timeOffset 59 | return timeOffset, nil 60 | } 61 | -------------------------------------------------------------------------------- /v2/doc.go: -------------------------------------------------------------------------------- 1 | // Package binance is a Golang SDK for binance APIs. 2 | package binance 3 | -------------------------------------------------------------------------------- /v2/futures/account_config_service.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | ) 7 | 8 | // AccountConfigService get futures account configuration 9 | type AccountConfigService struct { 10 | c *Client 11 | } 12 | 13 | // AccountConfig define futures account configuration 14 | type AccountConfig struct { 15 | FeeTier int `json:"feeTier"` // Account commission tier 16 | CanTrade bool `json:"canTrade"` // If can trade 17 | CanDeposit bool `json:"canDeposit"` // If can transfer in asset 18 | CanWithdraw bool `json:"canWithdraw"` // If can transfer out asset 19 | DualSidePosition bool `json:"dualSidePosition"` // If dual side position is enabled 20 | UpdateTime int64 `json:"updateTime"` // Reserved property 21 | MultiAssetsMargin bool `json:"multiAssetsMargin"` 22 | TradeGroupId int `json:"tradeGroupId"` 23 | } 24 | 25 | // Do send request 26 | func (s *AccountConfigService) Do(ctx context.Context, opts ...RequestOption) (*AccountConfig, error) { 27 | r := &request{ 28 | method: "GET", 29 | endpoint: "/fapi/v1/accountConfig", 30 | secType: secTypeSigned, 31 | } 32 | data, _, err := s.c.callAPI(ctx, r, opts...) 33 | if err != nil { 34 | return nil, err 35 | } 36 | res := new(AccountConfig) 37 | err = json.Unmarshal(data, res) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return res, nil 42 | } 43 | -------------------------------------------------------------------------------- /v2/futures/account_config_service_test.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type AccountConfigServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestAccountConfigService(t *testing.T) { 14 | suite.Run(t, new(AccountConfigServiceTestSuite)) 15 | } 16 | 17 | func (s *AccountConfigServiceTestSuite) TestGetAccountConfig() { 18 | data := []byte(`{ 19 | "feeTier": 0, 20 | "canTrade": true, 21 | "canDeposit": true, 22 | "canWithdraw": true, 23 | "dualSidePosition": true, 24 | "updateTime": 1724416653850, 25 | "multiAssetsMargin": false, 26 | "tradeGroupId": -1 27 | }`) 28 | 29 | s.mockDo(data, nil) 30 | defer s.assertDo() 31 | 32 | expected := &AccountConfig{ 33 | FeeTier: 0, 34 | CanTrade: true, 35 | CanDeposit: true, 36 | CanWithdraw: true, 37 | DualSidePosition: true, 38 | UpdateTime: 1724416653850, 39 | MultiAssetsMargin: false, 40 | TradeGroupId: -1, 41 | } 42 | 43 | s.assertReq(func(r *request) { 44 | e := newSignedRequest() 45 | s.assertRequestEqual(e, r) 46 | }) 47 | 48 | config, err := s.client.NewGetAccountConfigService().Do(newContext()) 49 | s.r().NoError(err) 50 | s.r().Equal(expected, config) 51 | } 52 | -------------------------------------------------------------------------------- /v2/futures/asset_index_service.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | type AssetIndexService struct { 10 | c *Client 11 | symbol *string // for example, BTCUSD 12 | } 13 | 14 | type AssetIndex struct { 15 | Symbol string `json:"symbol"` 16 | Time uint64 `json:"time"` 17 | Index string `json:"index"` 18 | BidBuffer string `json:"bidBuffer"` 19 | AskBuffer string `json:"askBuffer"` 20 | BidRate string `json:"bidRate"` 21 | AskRate string `json:"askRate"` 22 | AutoExchangeBidBuffer string `json:"autoExchangeBidBuffer"` 23 | AutoExchangeAskBuffer string `json:"autoExchangeAskBuffer"` 24 | AutoExchangeBidRate string `json:"autoExchangeBidRate"` 25 | AutoExchangeAskRate string `json:"autoExchangeAskRate"` 26 | } 27 | 28 | func (s *AssetIndexService) Symbol(symbol string) *AssetIndexService { 29 | s.symbol = &symbol 30 | return s 31 | } 32 | 33 | func (s *AssetIndexService) Do(ctx context.Context, opts ...RequestOption) (res []*AssetIndex, err error) { 34 | r := &request{ 35 | method: http.MethodGet, 36 | endpoint: "/fapi/v1/assetIndex", 37 | } 38 | if s.symbol != nil { 39 | r.setParam("symbol", *s.symbol) 40 | } 41 | 42 | data, _, err := s.c.callAPI(ctx, r, opts...) 43 | if err != nil { 44 | return nil, err 45 | } 46 | res = make([]*AssetIndex, 0) 47 | err = json.Unmarshal(data, &res) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return res, nil 53 | } 54 | -------------------------------------------------------------------------------- /v2/futures/commission_rate_service.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | type CommissionRateService struct { 10 | c *Client 11 | symbol string 12 | } 13 | 14 | // Symbol set symbol 15 | func (service *CommissionRateService) Symbol(symbol string) *CommissionRateService { 16 | service.symbol = symbol 17 | return service 18 | } 19 | 20 | // Do send request 21 | func (s *CommissionRateService) Do(ctx context.Context, opts ...RequestOption) (res *CommissionRate, err error) { 22 | r := &request{ 23 | method: http.MethodGet, 24 | endpoint: "/fapi/v1/commissionRate", 25 | secType: secTypeSigned, 26 | } 27 | if s.symbol != "" { 28 | r.setParam("symbol", s.symbol) 29 | } 30 | data, _, err := s.c.callAPI(ctx, r, opts...) 31 | if err != nil { 32 | return nil, err 33 | } 34 | res = new(CommissionRate) 35 | err = json.Unmarshal(data, &res) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return res, nil 40 | } 41 | 42 | // Commission Rate 43 | type CommissionRate struct { 44 | Symbol string `json:"symbol"` 45 | MakerCommissionRate string `json:"makerCommissionRate"` 46 | TakerCommissionRate string `json:"takerCommissionRate"` 47 | } 48 | -------------------------------------------------------------------------------- /v2/futures/constituents_service.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | type ConstituentsService struct { 10 | c *Client 11 | symbol string // for example, BTCUSDT 12 | } 13 | 14 | type ConstituentsServiceRsp struct { 15 | Symbol string `json:"symbol"` 16 | Time uint64 `json:"time"` 17 | Constituents []*Constituents `json:"constituents"` 18 | } 19 | 20 | type Constituents struct { 21 | Exchange string `json:"exchange"` 22 | Symbol string `json:"symbol"` 23 | } 24 | 25 | func (s *ConstituentsService) Symbol(symbol string) *ConstituentsService { 26 | s.symbol = symbol 27 | return s 28 | } 29 | 30 | func (s *ConstituentsService) Do(ctx context.Context, opts ...RequestOption) (res *ConstituentsServiceRsp, err error) { 31 | r := &request{ 32 | method: http.MethodGet, 33 | endpoint: "/fapi/v1/constituents", 34 | } 35 | r.setParam("symbol", s.symbol) 36 | 37 | data, _, err := s.c.callAPI(ctx, r, opts...) 38 | if err != nil { 39 | return nil, err 40 | } 41 | res = new(ConstituentsServiceRsp) 42 | err = json.Unmarshal(data, res) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | return res, nil 48 | } 49 | -------------------------------------------------------------------------------- /v2/futures/fee_burn_service_test.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type feeburnServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestFeeBurnService(t *testing.T) { 14 | suite.Run(t, new(feeburnServiceTestSuite)) 15 | } 16 | 17 | func (s *feeburnServiceTestSuite) TestGetFeeBurn() { 18 | data := []byte(`{"feeBurn": true}`) 19 | s.mockDo(data, nil) 20 | defer s.assertDo() 21 | s.assertReq(func(r *request) { 22 | e := newSignedRequest() 23 | s.assertRequestEqual(e, r) 24 | }) 25 | 26 | res, err := s.client.NewGetFeeBurnService().Do(newContext()) 27 | s.r().NoError(err) 28 | s.r().True(res.FeeBurn) 29 | } 30 | 31 | func (s *feeburnServiceTestSuite) TestFeeBurnEnable() { 32 | data := []byte(`{"msg": "success"}`) 33 | s.mockDo(data, nil) 34 | defer s.assertDo() 35 | s.assertReq(func(r *request) { 36 | e := newSignedRequest().setFormParam("feeBurn", "true") 37 | s.assertRequestEqual(e, r) 38 | }) 39 | 40 | err := s.client.NewFeeBurnService().Enable().Do(newContext()) 41 | s.r().NoError(err) 42 | } 43 | 44 | func (s *feeburnServiceTestSuite) TestFeeBurnDisable() { 45 | data := []byte(`{"msg": "success"}`) 46 | s.mockDo(data, nil) 47 | defer s.assertDo() 48 | s.assertReq(func(r *request) { 49 | e := newSignedRequest().setFormParam("feeBurn", "false") 50 | s.assertRequestEqual(e, r) 51 | }) 52 | 53 | err := s.client.NewFeeBurnService().Disable().Do(newContext()) 54 | s.r().NoError(err) 55 | } 56 | -------------------------------------------------------------------------------- /v2/futures/funding_rate_info.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | 8 | "github.com/adshao/go-binance/v2/common" 9 | ) 10 | 11 | // FundingRateInfoService gets funding rate info 12 | type FundingRateInfoService struct { 13 | c *Client 14 | } 15 | 16 | // Do sends request 17 | func (s *FundingRateInfoService) Do(ctx context.Context, opts ...RequestOption) (res []*FundingRateInfo, err error) { 18 | r := &request{ 19 | method: http.MethodGet, 20 | endpoint: "/fapi/v1/fundingInfo", 21 | secType: secTypeNone, 22 | } 23 | data, _, err := s.c.callAPI(ctx, r, opts...) 24 | data = common.ToJSONList(data) 25 | if err != nil { 26 | return []*FundingRateInfo{}, err 27 | } 28 | res = make([]*FundingRateInfo, 0) 29 | err = json.Unmarshal(data, &res) 30 | if err != nil { 31 | return []*FundingRateInfo{}, err 32 | } 33 | return res, nil 34 | } 35 | 36 | // FundingRateInfo defines funding rate info for symbols 37 | type FundingRateInfo struct { 38 | Symbol string `json:"symbol"` 39 | AdjustedFundingRateCap string `json:"adjustedFundingRateCap"` 40 | AdjustedFundingRateFloor string `json:"adjustedFundingRateFloor"` 41 | FundingIntervalHours int64 `json:"fundingIntervalHours"` 42 | } 43 | -------------------------------------------------------------------------------- /v2/futures/funding_rate_info_test.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "github.com/stretchr/testify/suite" 5 | "testing" 6 | ) 7 | 8 | type fundingRateInfoServiceTestSuite struct { 9 | baseTestSuite 10 | } 11 | 12 | func TestFundingRateInfoService(t *testing.T) { 13 | suite.Run(t, new(fundingRateInfoServiceTestSuite)) 14 | } 15 | 16 | func (s *fundingRateInfoServiceTestSuite) TestGetFundingRateInfo() { 17 | data := []byte(`[{ 18 | "symbol": "BTCUSDT", 19 | "adjustedFundingRateCap": "0.02500000", 20 | "adjustedFundingRateFloor": "-0.02500000", 21 | "fundingIntervalHours": 8 22 | }]`) 23 | s.mockDo(data, nil) 24 | defer s.assertDo() 25 | 26 | s.assertReq(func(r *request) { 27 | e := newRequest() 28 | s.assertRequestEqual(e, r) 29 | }) 30 | 31 | res, err := s.client.NewFundingRateInfoService().Do(newContext()) 32 | s.r().NoError(err) 33 | e := []*FundingRateInfo{ 34 | { 35 | Symbol: "BTCUSDT", 36 | AdjustedFundingRateCap: "0.02500000", 37 | AdjustedFundingRateFloor: "-0.02500000", 38 | FundingIntervalHours: 8, 39 | }, 40 | } 41 | s.assertFundingRateInfoEqual(e, res) 42 | } 43 | 44 | func (s *fundingRateInfoServiceTestSuite) assertFundingRateInfoEqual(e, a []*FundingRateInfo) { 45 | r := s.r() 46 | r.Equal(e[0].Symbol, a[0].Symbol, "Symbol") 47 | r.Equal(e[0].AdjustedFundingRateCap, a[0].AdjustedFundingRateCap, "AdjustedFundingRateCap") 48 | r.Equal(e[0].AdjustedFundingRateFloor, a[0].AdjustedFundingRateFloor, "AdjustedFundingRateFloor") 49 | r.Equal(e[0].FundingIntervalHours, a[0].FundingIntervalHours, "FundingIntervalHours") 50 | } 51 | -------------------------------------------------------------------------------- /v2/futures/index_info_service.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | type IndexInfoService struct { 10 | c *Client 11 | symbol *string // for example, FOOTBALLUSDT 12 | } 13 | 14 | type IndexInfo struct { 15 | Symbol string `json:"symbol"` 16 | Time uint64 `json:"time"` 17 | Component string `json:"component"` 18 | BaseAssetList []*BaseAssetList `json:"baseAssetList"` 19 | } 20 | 21 | type BaseAssetList struct { 22 | BaseAsset string `json:"baseAsset"` 23 | QuoteAsset string `json:"quoteAsset"` 24 | WeightInQuantity string `json:"weightInQuantity"` 25 | WeightInPercentage string `json:"weightInPercentage"` 26 | } 27 | 28 | func (s *IndexInfoService) Symbol(symbol string) *IndexInfoService { 29 | s.symbol = &symbol 30 | return s 31 | } 32 | 33 | func (s *IndexInfoService) Do(ctx context.Context, opts ...RequestOption) (res []*IndexInfo, err error) { 34 | r := &request{ 35 | method: http.MethodGet, 36 | endpoint: "/futures/v1/indexInfo", 37 | } 38 | if s.symbol != nil { 39 | r.setParam("symbol", *s.symbol) 40 | } 41 | 42 | data, _, err := s.c.callAPI(ctx, r, opts...) 43 | if err != nil { 44 | return nil, err 45 | } 46 | res = make([]*IndexInfo, 0) 47 | err = json.Unmarshal(data, &res) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return res, nil 53 | } 54 | -------------------------------------------------------------------------------- /v2/futures/rebate_newuser.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // GetRebateNewUserService 10 | type GetRebateNewUserService struct { 11 | c *Client 12 | brokerageID string 13 | type_future int 14 | } 15 | 16 | // BrokerageID setting 17 | func (s *GetRebateNewUserService) BrokerageID(brokerageID string) *GetRebateNewUserService { 18 | s.brokerageID = brokerageID 19 | return s 20 | } 21 | 22 | // Type future setting 23 | func (s *GetRebateNewUserService) Type(type_future int) *GetRebateNewUserService { 24 | s.type_future = type_future 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *GetRebateNewUserService) Do(ctx context.Context, opts ...RequestOption) (res *RebateNewUser, err error) { 30 | r := &request{ 31 | method: http.MethodGet, 32 | endpoint: "/fapi/v1/apiReferral/ifNewUser", 33 | secType: secTypeSigned, 34 | } 35 | 36 | if s.brokerageID != "" { 37 | r.setParam("brokerId", s.brokerageID) 38 | } 39 | if s.type_future != 0 { 40 | r.setParam("type", s.type_future) 41 | } 42 | 43 | data, _, err := s.c.callAPI(ctx, r, opts...) 44 | if err != nil { 45 | return &RebateNewUser{}, err 46 | } 47 | 48 | err = json.Unmarshal(data, &res) 49 | if err != nil { 50 | return &RebateNewUser{}, err 51 | } 52 | return res, nil 53 | } 54 | 55 | type RebateNewUser struct { 56 | BrokerId string `json:"brokerId"` 57 | RebateWorking bool `json:"rebateWorking"` 58 | IfNewUser bool `json:"ifNewUser"` 59 | } 60 | -------------------------------------------------------------------------------- /v2/futures/rebate_newuser_test.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/suite" 8 | ) 9 | 10 | type baseRebateNewUserTestSuite struct { 11 | baseTestSuite 12 | } 13 | 14 | func TestRebateNewUserTestService(t *testing.T) { 15 | suite.Run(t, new(baseRebateNewUserServiceTestSuite)) 16 | } 17 | 18 | type baseRebateNewUserServiceTestSuite struct { 19 | baseRebateNewUserTestSuite 20 | } 21 | 22 | func (s *baseRebateNewUserServiceTestSuite) TestRebateNewUser() { 23 | data := []byte(` 24 | { 25 | "brokerId": "123456", 26 | "rebateWorking": true, 27 | "ifNewUser": true 28 | } 29 | `) 30 | s.mockDo(data, nil) 31 | defer s.assertDo() 32 | 33 | brokerageID := "123456" 34 | recvWindow := int64(1000) 35 | s.assertReq(func(r *request) { 36 | e := newSignedRequest().setParams(params{ 37 | "brokerId": brokerageID, 38 | "recvWindow": recvWindow, 39 | }) 40 | s.assertRequestEqual(e, r) 41 | }) 42 | check, err := s.client.NewGetRebateNewUserService().BrokerageID(brokerageID). 43 | Do(newContext(), WithRecvWindow(recvWindow)) 44 | fmt.Println(check) 45 | r := s.r() 46 | r.NoError(err) 47 | e := &RebateNewUser{ 48 | BrokerId: "123456", 49 | RebateWorking: true, 50 | IfNewUser: true, 51 | } 52 | s.assertOrderEqual(e, check) 53 | } 54 | 55 | func (s *baseRebateNewUserServiceTestSuite) assertOrderEqual(e, a *RebateNewUser) { 56 | r := s.r() 57 | r.Equal(e.BrokerId, a.BrokerId, "BrokerageId") 58 | r.Equal(e.IfNewUser, a.IfNewUser, "New User check") 59 | r.Equal(e.RebateWorking, a.RebateWorking, "Rebate Working check") 60 | } 61 | -------------------------------------------------------------------------------- /v2/futures/request_test.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestWithExtraForm(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | r *request 12 | m map[string]any 13 | wantRequest *request 14 | }{ 15 | { 16 | name: "place order use extra priceMatch and goodTillDate", 17 | r: &request{ 18 | form: map[string][]string{ 19 | "symbol": {"BTCUSDT"}, 20 | "orderId": {"1"}, 21 | }, 22 | }, 23 | m: map[string]any{ 24 | "priceMatch": "QUEUE", 25 | "goodTillDate": 1697796587000, 26 | }, 27 | wantRequest: &request{ 28 | form: map[string][]string{ 29 | "symbol": {"BTCUSDT"}, 30 | "orderId": {"1"}, 31 | "priceMatch": {"QUEUE"}, 32 | "goodTillDate": {"1697796587000"}, 33 | }, 34 | }, 35 | }, 36 | } 37 | for _, tt := range tests { 38 | t.Run(tt.name, func(t *testing.T) { 39 | 40 | opt := WithExtraForm(tt.m) 41 | opt(tt.r) 42 | 43 | assert.Equal(t, tt.wantRequest, tt.r) 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /v2/futures/server_service.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | // PingService ping server 9 | type PingService struct { 10 | c *Client 11 | } 12 | 13 | // Do send request 14 | func (s *PingService) Do(ctx context.Context, opts ...RequestOption) (err error) { 15 | r := &request{ 16 | method: http.MethodGet, 17 | endpoint: "/fapi/v1/ping", 18 | } 19 | _, _, err = s.c.callAPI(ctx, r, opts...) 20 | return err 21 | } 22 | 23 | // ServerTimeService get server time 24 | type ServerTimeService struct { 25 | c *Client 26 | } 27 | 28 | // Do send request 29 | func (s *ServerTimeService) Do(ctx context.Context, opts ...RequestOption) (serverTime int64, err error) { 30 | r := &request{ 31 | method: http.MethodGet, 32 | endpoint: "/fapi/v1/time", 33 | } 34 | data, _, err := s.c.callAPI(ctx, r, opts...) 35 | if err != nil { 36 | return 0, err 37 | } 38 | j, err := newJSON(data) 39 | if err != nil { 40 | return 0, err 41 | } 42 | serverTime = j.Get("serverTime").MustInt64() 43 | return serverTime, nil 44 | } 45 | 46 | // SetServerTimeService set server time 47 | type SetServerTimeService struct { 48 | c *Client 49 | } 50 | 51 | // Do send request 52 | func (s *SetServerTimeService) Do(ctx context.Context, opts ...RequestOption) (timeOffset int64, err error) { 53 | serverTime, err := s.c.NewServerTimeService().Do(ctx) 54 | if err != nil { 55 | return 0, err 56 | } 57 | timeOffset = currentTimestamp() - serverTime 58 | s.c.TimeOffset = timeOffset 59 | return timeOffset, nil 60 | } 61 | -------------------------------------------------------------------------------- /v2/futures/symbol_config_service.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | ) 7 | 8 | // SymbolConfigService get futures symbol configuration 9 | type SymbolConfigService struct { 10 | c *Client 11 | symbol *string 12 | } 13 | 14 | // SymbolConfig define futures symbol configuration 15 | type SymbolConfig struct { 16 | Symbol string `json:"symbol"` 17 | MarginType string `json:"marginType"` 18 | IsAutoAddMargin bool `json:"isAutoAddMargin"` 19 | Leverage int `json:"leverage"` 20 | MaxNotionalValue string `json:"maxNotionalValue"` 21 | } 22 | 23 | // Symbol set symbol 24 | func (s *SymbolConfigService) Symbol(symbol string) *SymbolConfigService { 25 | s.symbol = &symbol 26 | return s 27 | } 28 | 29 | // Do send request 30 | func (s *SymbolConfigService) Do(ctx context.Context, opts ...RequestOption) ([]*SymbolConfig, error) { 31 | r := &request{ 32 | method: "GET", 33 | endpoint: "/fapi/v1/symbolConfig", 34 | secType: secTypeSigned, 35 | } 36 | if s.symbol != nil { 37 | r.setParam("symbol", *s.symbol) 38 | } 39 | data, _, err := s.c.callAPI(ctx, r, opts...) 40 | if err != nil { 41 | return nil, err 42 | } 43 | var res []*SymbolConfig 44 | err = json.Unmarshal(data, &res) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return res, nil 49 | } 50 | -------------------------------------------------------------------------------- /v2/futures/symbol_config_service_test.go: -------------------------------------------------------------------------------- 1 | package futures 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type SymbolConfigServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestSymbolConfigService(t *testing.T) { 14 | suite.Run(t, new(SymbolConfigServiceTestSuite)) 15 | } 16 | 17 | func (s *SymbolConfigServiceTestSuite) TestGetSymbolConfig() { 18 | data := []byte(`[ 19 | { 20 | "symbol": "BTCUSDT", 21 | "marginType": "CROSSED", 22 | "isAutoAddMargin": false, 23 | "leverage": 21, 24 | "maxNotionalValue": "1000000" 25 | } 26 | ]`) 27 | 28 | s.mockDo(data, nil) 29 | defer s.assertDo() 30 | 31 | symbol := "BTCUSDT" 32 | s.assertReq(func(r *request) { 33 | e := newSignedRequest() 34 | e.setParam("symbol", symbol) 35 | s.assertRequestEqual(e, r) 36 | }) 37 | 38 | configs, err := s.client.NewGetSymbolConfigService().Symbol(symbol).Do(newContext()) 39 | s.r().NoError(err) 40 | s.r().Len(configs, 1) 41 | s.r().Equal("BTCUSDT", configs[0].Symbol) 42 | s.r().Equal("CROSSED", configs[0].MarginType) 43 | s.r().Equal(false, configs[0].IsAutoAddMargin) 44 | s.r().Equal(21, configs[0].Leverage) 45 | s.r().Equal("1000000", configs[0].MaxNotionalValue) 46 | } 47 | -------------------------------------------------------------------------------- /v2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/adshao/go-binance/v2 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/bitly/go-simplejson v0.5.0 7 | github.com/golang/mock v1.6.0 8 | github.com/google/uuid v1.6.0 9 | github.com/gorilla/websocket v1.5.3 10 | github.com/jpillora/backoff v1.0.0 11 | github.com/shopspring/decimal v1.4.0 12 | github.com/stretchr/testify v1.8.1 13 | ) 14 | 15 | require ( 16 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/kr/pretty v0.2.0 // indirect 19 | github.com/pmezard/go-difflib v1.0.0 // indirect 20 | github.com/stretchr/objx v0.5.0 // indirect 21 | gopkg.in/yaml.v3 v3.0.1 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /v2/margin_interest_rate_history_service_test.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/suite" 8 | ) 9 | 10 | type marginInterestRateHistoryServiceTestSuite struct { 11 | baseTestSuite 12 | } 13 | 14 | func TestMarginInterestRateHistoryService(t *testing.T) { 15 | suite.Run(t, new(marginInterestRateHistoryServiceTestSuite)) 16 | } 17 | 18 | func (s *marginInterestRateHistoryServiceTestSuite) TestMarginInterestRateHistory() { 19 | data := []byte(` 20 | [ 21 | { 22 | "asset": "BTC", 23 | "dailyInterestRate": "0.00025000", 24 | "timestamp": 1611544731000, 25 | "vipLevel": 1 26 | } 27 | ]`) 28 | s.mockDo(data, nil) 29 | defer s.assertDo() 30 | 31 | asset := "BTC" 32 | s.assertReq(func(r *request) { 33 | e := newSignedRequest().setParams(params{ 34 | "asset": asset, 35 | }) 36 | s.assertRequestEqual(e, r) 37 | }) 38 | 39 | history, err := s.client.NewMarginInterestRateHistoryService(). 40 | Asset(asset). 41 | Do(context.Background()) 42 | r := s.r() 43 | r.NoError(err) 44 | 45 | s.Len(*history, 1) 46 | 47 | item := &(*history)[0] 48 | s.Equal(int64(1611544731000), item.Timestamp) 49 | s.Equal(int64(1), item.VipLevel) 50 | s.Equal("0.00025000", item.DailyInterestRate) 51 | s.Equal("BTC", item.Asset) 52 | } 53 | -------------------------------------------------------------------------------- /v2/margin_next_hourly_interest_rate_service_test.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/suite" 8 | ) 9 | 10 | type marginNextHourlyInterestRateServiceTestSuite struct { 11 | baseTestSuite 12 | } 13 | 14 | func TestMarginNextHourlyInterestRateService(t *testing.T) { 15 | suite.Run(t, new(marginNextHourlyInterestRateServiceTestSuite)) 16 | } 17 | 18 | func (s *marginNextHourlyInterestRateServiceTestSuite) TestMarginNextHourlyInterestRate() { 19 | data := []byte(` 20 | [ 21 | { 22 | "asset": "BTC", 23 | "nextHourlyInterestRate": "0.00000571" 24 | } 25 | ]`) 26 | s.mockDo(data, nil) 27 | defer s.assertDo() 28 | 29 | assets := "BTC" 30 | s.assertReq(func(r *request) { 31 | e := newSignedRequest().setParams(params{ 32 | "assets": assets, 33 | }) 34 | s.assertRequestEqual(e, r) 35 | }) 36 | 37 | history, err := s.client.NewMarginNextHourlyInterestRateService(). 38 | Assets(assets). 39 | Do(context.Background()) 40 | r := s.r() 41 | r.NoError(err) 42 | 43 | s.Len(*history, 1) 44 | 45 | item := &(*history)[0] 46 | s.Equal("BTC", item.Asset) 47 | s.Equal("0.00000571", item.NextHourlyInterestRate) 48 | } 49 | -------------------------------------------------------------------------------- /v2/options/account_service.go: -------------------------------------------------------------------------------- 1 | package options 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // AccountService create order 10 | type AccountService struct { 11 | c *Client 12 | } 13 | 14 | // Do send request 15 | func (s *AccountService) Do(ctx context.Context, opts ...RequestOption) (res *Account, err error) { 16 | r := &request{ 17 | method: http.MethodGet, 18 | endpoint: "/eapi/v1/account", 19 | secType: secTypeSigned, 20 | } 21 | 22 | data, _, err := s.c.callAPI(ctx, r, opts...) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | res = new(Account) 28 | err = json.Unmarshal(data, res) 29 | 30 | if err != nil { 31 | return nil, err 32 | } 33 | return res, nil 34 | } 35 | 36 | type Asset struct { 37 | Asset string `json:"asset"` 38 | MarginBalance string `json:"marginBalance"` 39 | Equity string `json:"equity"` 40 | Available string `json:"available"` 41 | Locked string `json:"locked"` 42 | UnrealizedPNL string `json:"unrealizedPNL"` 43 | } 44 | 45 | type Greek struct { 46 | Underlying string `json:"underlying"` 47 | Delta string `json:"delta"` 48 | Gamma string `json:"gamma"` 49 | Theta string `json:"theta"` 50 | Vega string `json:"vega"` 51 | } 52 | 53 | // Account define create order response 54 | type Account struct { 55 | Asset []*Asset `json:"asset"` 56 | Greek []*Greek `json:"greek"` 57 | RiskLevel string `json:"riskLevel"` 58 | Time uint64 `json:"time"` 59 | } 60 | -------------------------------------------------------------------------------- /v2/options/exercise_history_service_test.go: -------------------------------------------------------------------------------- 1 | package options 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type ExerciseHistoryServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestExerciseHistoryService(t *testing.T) { 14 | suite.Run(t, new(ExerciseHistoryServiceTestSuite)) 15 | } 16 | 17 | func (s *ExerciseHistoryServiceTestSuite) TestExerciseHistory() { 18 | data := []byte(`[ 19 | { 20 | "symbol": "BTC-240529-67500-C", 21 | "strikePrice": "67500", 22 | "realStrikePrice": "68154.65503404", 23 | "expiryDate": 1716969600000, 24 | "strikeResult": "REALISTIC_VALUE_STRICKEN" 25 | }]`) 26 | s.mockDo(data, nil) 27 | defer s.assertDo() 28 | 29 | ETs, err := s.client.NewExerciseHistoryService().Do(newContext()) 30 | targetETs := []*ExerciseHistory{ 31 | { 32 | Symbol: "BTC-240529-67500-C", 33 | StrikePrice: "67500", 34 | RealStrikePrice: "68154.65503404", 35 | ExpiryDate: 1716969600000, 36 | StrikeResult: "REALISTIC_VALUE_STRICKEN", 37 | }, 38 | } 39 | 40 | s.r().Equal(err, nil, "err != nil") 41 | for i := range ETs { 42 | r := s.r() 43 | r.Equal(ETs[i].Symbol, targetETs[i].Symbol, "Symbol") 44 | r.Equal(ETs[i].StrikePrice, targetETs[i].StrikePrice, "StrikePrice") 45 | r.Equal(ETs[i].RealStrikePrice, targetETs[i].RealStrikePrice, "RealStrikePrice") 46 | r.Equal(ETs[i].ExpiryDate, targetETs[i].ExpiryDate, "ExpiryDate") 47 | r.Equal(ETs[i].StrikeResult, targetETs[i].StrikeResult, "StrikeResult") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /v2/options/index_service.go: -------------------------------------------------------------------------------- 1 | package options 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | type Index struct { 10 | Time uint64 `json:"time"` 11 | IndexPrice string `json:"indexPrice"` 12 | } 13 | 14 | // underlying: Spot trading pairs such as BTCUSDT 15 | type IndexService struct { 16 | c *Client 17 | underlying string 18 | } 19 | 20 | // Underlying set underlying 21 | func (s *IndexService) Underlying(underlying string) *IndexService { 22 | s.underlying = underlying 23 | return s 24 | } 25 | 26 | // Do send request 27 | func (s *IndexService) Do(ctx context.Context, opts ...RequestOption) (res *Index, err error) { 28 | r := &request{ 29 | method: http.MethodGet, 30 | endpoint: "/eapi/v1/index", 31 | } 32 | r.setParam("underlying", s.underlying) 33 | 34 | data, _, err := s.c.callAPI(ctx, r, opts...) 35 | if err != nil { 36 | return &Index{}, err 37 | } 38 | res = new(Index) 39 | err = json.Unmarshal(data, res) 40 | if err != nil { 41 | return &Index{}, err 42 | } 43 | return res, nil 44 | } 45 | -------------------------------------------------------------------------------- /v2/options/index_service_test.go: -------------------------------------------------------------------------------- 1 | package options 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type IndexServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestIndexService(t *testing.T) { 14 | suite.Run(t, new(IndexServiceTestSuite)) 15 | } 16 | 17 | func (s *IndexServiceTestSuite) TestIndex() { 18 | data := []byte(`{ 19 | "indexPrice": "68193.79851064", 20 | "time": 1717036339000 21 | }`) 22 | s.mockDo(data, nil) 23 | defer s.assertDo() 24 | 25 | index, err := s.client.NewIndexService().Do(newContext()) 26 | 27 | targetIndex := &Index{ 28 | IndexPrice: "68193.79851064", 29 | Time: 1717036339000, 30 | } 31 | 32 | s.r().Equal(err, nil, "err != nil") 33 | s.r().Equal(index.IndexPrice, targetIndex.IndexPrice, "IndexPrice") 34 | s.r().Equal(index.Time, targetIndex.Time, "Time") 35 | } 36 | -------------------------------------------------------------------------------- /v2/options/mark_service.go: -------------------------------------------------------------------------------- 1 | package options 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | type Mark struct { 10 | Symbol string `json:"symbol"` 11 | MarkPrice string `json:"markPrice"` 12 | BidIV string `json:"bidIV"` 13 | AskIV string `json:"askIV"` 14 | MarkIV string `json:"markIV"` 15 | Delta string `json:"delta"` 16 | Theta string `json:"theta"` 17 | Gamma string `json:"gamma"` 18 | Vega string `json:"vega"` 19 | HighPriceLimit string `json:"highPriceLimit"` 20 | LowPriceLimit string `json:"lowPriceLimit"` 21 | RiskFreeInterest string `json:"riskFreeInterest"` 22 | } 23 | 24 | // MarkService list recent trades in orderbook 25 | type MarkService struct { 26 | c *Client 27 | symbol *string 28 | } 29 | 30 | // Symbol set symbol 31 | func (s *MarkService) Symbol(symbol string) *MarkService { 32 | s.symbol = &symbol 33 | return s 34 | } 35 | 36 | // Do send request 37 | func (s *MarkService) Do(ctx context.Context, opts ...RequestOption) (res []*Mark, err error) { 38 | r := &request{ 39 | method: http.MethodGet, 40 | endpoint: "/eapi/v1/mark", 41 | } 42 | if s.symbol != nil { 43 | r.setParam("symbol", *s.symbol) 44 | } 45 | data, _, err := s.c.callAPI(ctx, r, opts...) 46 | if err != nil { 47 | return []*Mark{}, err 48 | } 49 | res = make([]*Mark, 0) 50 | err = json.Unmarshal(data, &res) 51 | if err != nil { 52 | return []*Mark{}, err 53 | } 54 | return res, nil 55 | } 56 | -------------------------------------------------------------------------------- /v2/options/open_interest_service.go: -------------------------------------------------------------------------------- 1 | package options 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | type OpenInterest struct { 10 | Symbol string `json:"symbol"` 11 | SumOpenInterest string `json:"sumOpenInterest"` 12 | SumOpenInterestUsd string `json:"sumOpenInterestUsd"` 13 | Timestamp string `json:"timestamp"` 14 | } 15 | 16 | // underlying: Spot trading pairs such as BTCUSDT 17 | type OpenInterestService struct { 18 | c *Client 19 | underlyingAsset string //Target assets, such as ETH or BTC 20 | expiration string //Maturity date, such as 221225 21 | } 22 | 23 | // Underlying set underlying 24 | func (s *OpenInterestService) UnderlyingAsset(underlyingAsset string) *OpenInterestService { 25 | s.underlyingAsset = underlyingAsset 26 | return s 27 | } 28 | 29 | func (s *OpenInterestService) Expiration(expiration string) *OpenInterestService { 30 | s.expiration = expiration 31 | return s 32 | } 33 | 34 | // Do send request 35 | func (s *OpenInterestService) Do(ctx context.Context, opts ...RequestOption) (res []*OpenInterest, err error) { 36 | r := &request{ 37 | method: http.MethodGet, 38 | endpoint: "/eapi/v1/openInterest", 39 | } 40 | r.setParam("underlyingAsset", s.underlyingAsset) 41 | r.setParam("expiration", s.expiration) 42 | 43 | data, _, err := s.c.callAPI(ctx, r, opts...) 44 | if err != nil { 45 | return []*OpenInterest{}, err 46 | } 47 | res = make([]*OpenInterest, 0) 48 | err = json.Unmarshal(data, &res) 49 | if err != nil { 50 | return []*OpenInterest{}, err 51 | } 52 | return res, nil 53 | } 54 | -------------------------------------------------------------------------------- /v2/options/server_service.go: -------------------------------------------------------------------------------- 1 | package options 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // PingService ping server 10 | type PingService struct { 11 | c *Client 12 | } 13 | 14 | // Do send request 15 | func (s *PingService) Do(ctx context.Context, opts ...RequestOption) (err error) { 16 | r := &request{ 17 | method: http.MethodGet, 18 | endpoint: "/eapi/v1/ping", 19 | } 20 | data, _, err := s.c.callAPI(ctx, r, opts...) 21 | if err != nil { 22 | return err 23 | } 24 | d := map[string]string{} 25 | err = json.Unmarshal(data, &d) 26 | return err 27 | } 28 | 29 | // ServerTimeService get server time 30 | type ServerTimeService struct { 31 | c *Client 32 | } 33 | 34 | // Do send request 35 | func (s *ServerTimeService) Do(ctx context.Context, opts ...RequestOption) (serverTime int64, err error) { 36 | r := &request{ 37 | method: http.MethodGet, 38 | endpoint: "/eapi/v1/time", 39 | } 40 | data, _, err := s.c.callAPI(ctx, r, opts...) 41 | if err != nil { 42 | return 0, err 43 | } 44 | j, err := newJSON(data) 45 | if err != nil { 46 | return 0, err 47 | } 48 | serverTime = j.Get("serverTime").MustInt64() 49 | return serverTime, nil 50 | } 51 | -------------------------------------------------------------------------------- /v2/options/server_service_test.go: -------------------------------------------------------------------------------- 1 | package options 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type ServerServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestPingService(t *testing.T) { 14 | suite.Run(t, new(ServerServiceTestSuite)) 15 | } 16 | 17 | func (s *ServerServiceTestSuite) TestPing() { 18 | 19 | data := []byte(`{}`) 20 | s.mockDo(data, nil) 21 | defer s.assertDo() 22 | 23 | err := s.client.NewPingService().Do(newContext()) 24 | s.r().Equal(err, nil, "err != nil") 25 | } 26 | 27 | func (s *ServerServiceTestSuite) TestServerTime() { 28 | 29 | data := []byte(`{ 30 | "serverTime": 1592387156596 31 | }`) 32 | s.mockDo(data, nil) 33 | defer s.assertDo() 34 | 35 | st, err := s.client.NewServerTimeService().Do(newContext()) 36 | var targetServerTime int64 = 1592387156596 37 | s.r().Equal(st, targetServerTime, "serverTime") 38 | s.r().Equal(err, nil, "err != nil") 39 | } 40 | -------------------------------------------------------------------------------- /v2/options/user_stream_service_test.go: -------------------------------------------------------------------------------- 1 | package options 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type UserStreamServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUserStreamService(t *testing.T) { 14 | suite.Run(t, new(UserStreamServiceTestSuite)) 15 | } 16 | 17 | func (s *UserStreamServiceTestSuite) TestStartUserStream() { 18 | data := []byte(`{"listenKey": "listenKeyxxzzyyaabbccdd"}`) 19 | s.mockDo(data, nil) 20 | defer s.assertDo() 21 | 22 | listenKey, err := s.client.NewStartUserStreamService().Do(newContext()) 23 | targetListenKey := "listenKeyxxzzyyaabbccdd" 24 | s.r().NoError(err) 25 | s.r().Equal(targetListenKey, listenKey) 26 | } 27 | -------------------------------------------------------------------------------- /v2/portfolio/account_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type accountServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestAccountServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &accountServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetAccount", func(t *testing.T) { 22 | service := &GetAccountService{c: suite.client} 23 | account, err := service.Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get account info: %v", err) 26 | } 27 | 28 | // Basic validation of returned data 29 | if account.AccountStatus == "" { 30 | t.Error("Expected non-empty account status") 31 | } 32 | 33 | if account.AccountEquity == "" { 34 | t.Error("Expected non-empty account equity") 35 | } 36 | 37 | if account.UniMMR == "" { 38 | t.Error("Expected non-empty uniMMR") 39 | } 40 | 41 | if account.UpdateTime == 0 { 42 | t.Error("Expected non-zero update time") 43 | } 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /v2/portfolio/auto_repay_futures_status_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // GetAutoRepayFuturesStatusService get auto-repay-futures status 10 | type GetAutoRepayFuturesStatusService struct { 11 | c *Client 12 | } 13 | 14 | // Do send request 15 | func (s *GetAutoRepayFuturesStatusService) Do(ctx context.Context, opts ...RequestOption) (*AutoRepayFuturesStatus, error) { 16 | r := &request{ 17 | method: http.MethodGet, 18 | endpoint: "/papi/v1/repay-futures-switch", 19 | secType: secTypeSigned, 20 | } 21 | 22 | data, _, err := s.c.callAPI(ctx, r, opts...) 23 | if err != nil { 24 | return nil, err 25 | } 26 | res := new(AutoRepayFuturesStatus) 27 | err = json.Unmarshal(data, res) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return res, nil 32 | } 33 | 34 | // AutoRepayFuturesStatus define auto repay futures status 35 | type AutoRepayFuturesStatus struct { 36 | AutoRepay bool `json:"autoRepay"` // true for turn on the auto-repay futures; false for turn off 37 | } 38 | -------------------------------------------------------------------------------- /v2/portfolio/auto_repay_futures_status_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type autoRepayFuturesStatusServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestAutoRepayFuturesStatusServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &autoRepayFuturesStatusServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetAutoRepayFuturesStatus", func(t *testing.T) { 22 | service := &GetAutoRepayFuturesStatusService{c: suite.client} 23 | status, err := service.Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get auto repay futures status: %v", err) 26 | } 27 | 28 | t.Logf("Auto repay futures is %v", status.AutoRepay) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /v2/portfolio/auto_repay_futures_status_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type autoRepayFuturesStatusServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestAutoRepayFuturesStatusService(t *testing.T) { 14 | suite.Run(t, new(autoRepayFuturesStatusServiceTestSuite)) 15 | } 16 | 17 | func (s *autoRepayFuturesStatusServiceTestSuite) TestGetAutoRepayFuturesStatus() { 18 | data := []byte(`{ 19 | "autoRepay": true 20 | }`) 21 | s.mockDo(data, nil) 22 | defer s.assertDo() 23 | 24 | s.assertReq(func(r *request) { 25 | e := newSignedRequest() 26 | s.assertRequestEqual(e, r) 27 | }) 28 | 29 | status, err := s.client.NewGetAutoRepayFuturesStatusService().Do(newContext()) 30 | s.r().NoError(err) 31 | s.r().True(status.AutoRepay) 32 | } 33 | -------------------------------------------------------------------------------- /v2/portfolio/auto_repay_futures_switch_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // ChangeAutoRepayFuturesStatusService change auto-repay-futures status 10 | type ChangeAutoRepayFuturesStatusService struct { 11 | c *Client 12 | autoRepay bool 13 | } 14 | 15 | // AutoRepay set auto repay status 16 | func (s *ChangeAutoRepayFuturesStatusService) AutoRepay(autoRepay bool) *ChangeAutoRepayFuturesStatusService { 17 | s.autoRepay = autoRepay 18 | return s 19 | } 20 | 21 | // Do send request 22 | func (s *ChangeAutoRepayFuturesStatusService) Do(ctx context.Context, opts ...RequestOption) (*SuccessResponse, error) { 23 | r := &request{ 24 | method: http.MethodPost, 25 | endpoint: "/papi/v1/repay-futures-switch", 26 | secType: secTypeSigned, 27 | } 28 | r.setParam("autoRepay", s.autoRepay) 29 | 30 | data, _, err := s.c.callAPI(ctx, r, opts...) 31 | if err != nil { 32 | return nil, err 33 | } 34 | res := new(SuccessResponse) 35 | err = json.Unmarshal(data, res) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return res, nil 40 | } 41 | 42 | // SuccessResponse define success response 43 | type SuccessResponse struct { 44 | Msg string `json:"msg"` 45 | } 46 | -------------------------------------------------------------------------------- /v2/portfolio/auto_repay_futures_switch_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type autoRepayFuturesSwitchServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestAutoRepayFuturesSwitchServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &autoRepayFuturesSwitchServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("ChangeAutoRepayFuturesStatus", func(t *testing.T) { 22 | service := &ChangeAutoRepayFuturesStatusService{c: suite.client} 23 | 24 | // Get current status 25 | currentStatus, err := suite.client.NewGetAutoRepayFuturesStatusService().Do(context.Background()) 26 | if err != nil { 27 | t.Fatalf("Failed to get current auto repay futures status: %v", err) 28 | } 29 | 30 | // Change status 31 | newStatus := !currentStatus.AutoRepay 32 | res, err := service.AutoRepay(newStatus).Do(context.Background()) 33 | if err != nil { 34 | t.Fatalf("Failed to change auto repay futures status: %v", err) 35 | } 36 | 37 | if res.Msg != "success" { 38 | t.Errorf("Expected success message, got %v", res.Msg) 39 | } 40 | 41 | // Revert to original status 42 | _, err = service.AutoRepay(currentStatus.AutoRepay).Do(context.Background()) 43 | if err != nil { 44 | t.Fatalf("Failed to revert auto repay futures status: %v", err) 45 | } 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /v2/portfolio/auto_repay_futures_switch_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type autoRepayFuturesSwitchServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestAutoRepayFuturesSwitchService(t *testing.T) { 14 | suite.Run(t, new(autoRepayFuturesSwitchServiceTestSuite)) 15 | } 16 | 17 | func (s *autoRepayFuturesSwitchServiceTestSuite) TestChangeAutoRepayFuturesStatus() { 18 | data := []byte(`{ 19 | "msg": "success" 20 | }`) 21 | s.mockDo(data, nil) 22 | defer s.assertDo() 23 | 24 | autoRepay := true 25 | s.assertReq(func(r *request) { 26 | e := newSignedRequest() 27 | e.setParam("autoRepay", autoRepay) 28 | s.assertRequestEqual(e, r) 29 | }) 30 | 31 | res, err := s.client.NewChangeAutoRepayFuturesStatusService().AutoRepay(autoRepay).Do(newContext()) 32 | s.r().NoError(err) 33 | s.r().Equal("success", res.Msg) 34 | } 35 | -------------------------------------------------------------------------------- /v2/portfolio/balance_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type balanceServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestBalanceServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &balanceServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetBalance", func(t *testing.T) { 22 | service := &GetBalanceService{c: suite.client} 23 | balances, err := service.Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get balance: %v", err) 26 | } 27 | 28 | if len(balances) == 0 { 29 | t.Error("Expected non-empty balances") 30 | } 31 | 32 | // You might want to add more specific assertions here 33 | for _, balance := range balances { 34 | if balance.Asset == "" { 35 | t.Error("Expected non-empty asset") 36 | } 37 | } 38 | }) 39 | t.Run("GetBalance of Asset", func(t *testing.T) { 40 | service := &GetBalanceService{c: suite.client} 41 | balances, err := service.Asset("USDT").Do(context.Background()) 42 | if err != nil { 43 | t.Fatalf("Failed to get balance: %v", err) 44 | } 45 | 46 | if len(balances) == 0 { 47 | t.Error("Expected non-empty balances") 48 | } 49 | 50 | for _, balance := range balances { 51 | if balance.Asset == "" { 52 | t.Error("Expected non-empty asset") 53 | } 54 | } 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /v2/portfolio/bnb_transfer_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // BNBTransferService transfer BNB in and out of UM 10 | type BNBTransferService struct { 11 | c *Client 12 | amount string 13 | transferSide string 14 | } 15 | 16 | // Amount set amount 17 | func (s *BNBTransferService) Amount(amount string) *BNBTransferService { 18 | s.amount = amount 19 | return s 20 | } 21 | 22 | // TransferSide set transfer side 23 | func (s *BNBTransferService) TransferSide(transferSide string) *BNBTransferService { 24 | s.transferSide = transferSide 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *BNBTransferService) Do(ctx context.Context, opts ...RequestOption) (*BNBTransferResponse, error) { 30 | r := &request{ 31 | method: http.MethodPost, 32 | endpoint: "/papi/v1/bnb-transfer", 33 | secType: secTypeSigned, 34 | } 35 | r.setParam("amount", s.amount) 36 | r.setParam("transferSide", s.transferSide) 37 | 38 | data, _, err := s.c.callAPI(ctx, r, opts...) 39 | if err != nil { 40 | return nil, err 41 | } 42 | res := new(BNBTransferResponse) 43 | err = json.Unmarshal(data, res) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return res, nil 48 | } 49 | 50 | // BNBTransferResponse define bnb transfer response 51 | type BNBTransferResponse struct { 52 | TranID int64 `json:"tranId"` // transaction id 53 | } 54 | 55 | // Constants for transfer side 56 | const ( 57 | TransferSideToUM = "TO_UM" 58 | TransferSideFromUM = "FROM_UM" 59 | ) 60 | -------------------------------------------------------------------------------- /v2/portfolio/bnb_transfer_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type bnbTransferServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestBNBTransferServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &bnbTransferServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("BNBTransfer", func(t *testing.T) { 22 | service := &BNBTransferService{c: suite.client} 23 | res, err := service. 24 | Amount("0.1"). 25 | TransferSide(TransferSideToUM). 26 | Do(context.Background()) 27 | 28 | if err != nil { 29 | t.Fatalf("Failed to execute BNB transfer: %v", err) 30 | } 31 | 32 | if res.TranID == 0 { 33 | t.Error("Expected non-zero transaction ID") 34 | } 35 | 36 | // Transfer back 37 | _, err = service. 38 | Amount("0.1"). 39 | TransferSide(TransferSideFromUM). 40 | Do(context.Background()) 41 | 42 | if err != nil { 43 | t.Fatalf("Failed to transfer BNB back: %v", err) 44 | } 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /v2/portfolio/bnb_transfer_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type bnbTransferServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestBNBTransferService(t *testing.T) { 14 | suite.Run(t, new(bnbTransferServiceTestSuite)) 15 | } 16 | 17 | func (s *bnbTransferServiceTestSuite) TestBNBTransfer() { 18 | data := []byte(`{ 19 | "tranId": 100000001 20 | }`) 21 | s.mockDo(data, nil) 22 | defer s.assertDo() 23 | 24 | amount := "1.0" 25 | transferSide := TransferSideToUM 26 | s.assertReq(func(r *request) { 27 | e := newSignedRequest() 28 | e.setParam("amount", amount) 29 | e.setParam("transferSide", transferSide) 30 | s.assertRequestEqual(e, r) 31 | }) 32 | 33 | res, err := s.client.NewBNBTransferService(). 34 | Amount(amount). 35 | TransferSide(transferSide). 36 | Do(newContext()) 37 | 38 | s.r().NoError(err) 39 | s.r().Equal(int64(100000001), res.TranID) 40 | } 41 | -------------------------------------------------------------------------------- /v2/portfolio/client_integration_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/suite" 8 | ) 9 | 10 | type baseIntegrationTestSuite struct { 11 | suite.Suite 12 | client *Client 13 | } 14 | 15 | func SetupTest(t *testing.T) *baseIntegrationTestSuite { 16 | apiKey := os.Getenv("BINANCE_API_KEY") 17 | secretKey := os.Getenv("BINANCE_SECRET_KEY") 18 | proxyURL := os.Getenv("BINANCE_PROXY_URL") 19 | 20 | if apiKey == "" || secretKey == "" { 21 | t.Skip("API key and secret are required for integration tests") 22 | } 23 | 24 | var client *Client 25 | if proxyURL != "" { 26 | client = NewProxiedClient(apiKey, secretKey, proxyURL) 27 | } else { 28 | client = NewClient(apiKey, secretKey) 29 | } 30 | 31 | client.Debug = true 32 | 33 | return &baseIntegrationTestSuite{ 34 | client: client, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /v2/portfolio/cm_account_detail_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type cmAccountDetailServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestCMAccountDetailServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &cmAccountDetailServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetCMAccountDetail", func(t *testing.T) { 22 | service := &GetCMAccountDetailService{c: suite.client} 23 | res, err := service.Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get CM account detail: %v", err) 26 | } 27 | 28 | if len(res.Assets) == 0 { 29 | t.Error("Expected non-empty assets") 30 | } 31 | for _, asset := range res.Assets { 32 | if asset.Asset == "" { 33 | t.Error("Expected non-empty asset name") 34 | } 35 | } 36 | 37 | if len(res.Positions) == 0 { 38 | t.Error("Expected non-empty positions") 39 | } 40 | for _, position := range res.Positions { 41 | if position.Symbol == "" { 42 | t.Error("Expected non-empty symbol") 43 | } 44 | } 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /v2/portfolio/cm_adl_quantile_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // CMADLQuantileService service to get CM position ADL quantile estimation 10 | type CMADLQuantileService struct { 11 | c *Client 12 | symbol *string 13 | recvWindow *int64 14 | } 15 | 16 | // Symbol set symbol 17 | func (s *CMADLQuantileService) Symbol(symbol string) *CMADLQuantileService { 18 | s.symbol = &symbol 19 | return s 20 | } 21 | 22 | // RecvWindow set recvWindow 23 | func (s *CMADLQuantileService) RecvWindow(recvWindow int64) *CMADLQuantileService { 24 | s.recvWindow = &recvWindow 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *CMADLQuantileService) Do(ctx context.Context) ([]*CMADLQuantileResponse, error) { 30 | r := &request{ 31 | method: http.MethodGet, 32 | endpoint: "/papi/v1/cm/adlQuantile", 33 | secType: secTypeSigned, 34 | } 35 | if s.symbol != nil { 36 | r.setParam("symbol", *s.symbol) 37 | } 38 | if s.recvWindow != nil { 39 | r.setParam("recvWindow", *s.recvWindow) 40 | } 41 | 42 | data, _, err := s.c.callAPI(ctx, r) 43 | if err != nil { 44 | return nil, err 45 | } 46 | var res []*CMADLQuantileResponse 47 | err = json.Unmarshal(data, &res) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return res, nil 52 | } 53 | 54 | // CMADLQuantileResponse define CM ADL quantile response 55 | type CMADLQuantileResponse struct { 56 | Symbol string `json:"symbol"` 57 | ADLQuantile ADLQuantile `json:"adlQuantile"` 58 | } 59 | -------------------------------------------------------------------------------- /v2/portfolio/cm_cancel_all_orders_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // CMCancelAllOrdersService service to cancel all open CM orders 10 | type CMCancelAllOrdersService struct { 11 | c *Client 12 | symbol string 13 | recvWindow *int64 14 | } 15 | 16 | // Symbol set symbol 17 | func (s *CMCancelAllOrdersService) Symbol(symbol string) *CMCancelAllOrdersService { 18 | s.symbol = symbol 19 | return s 20 | } 21 | 22 | // RecvWindow set recvWindow 23 | func (s *CMCancelAllOrdersService) RecvWindow(recvWindow int64) *CMCancelAllOrdersService { 24 | s.recvWindow = &recvWindow 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *CMCancelAllOrdersService) Do(ctx context.Context) (*CMCancelAllOrdersResponse, error) { 30 | r := &request{ 31 | method: http.MethodDelete, 32 | endpoint: "/papi/v1/cm/allOpenOrders", 33 | secType: secTypeSigned, 34 | } 35 | r.setParam("symbol", s.symbol) 36 | if s.recvWindow != nil { 37 | r.setParam("recvWindow", *s.recvWindow) 38 | } 39 | 40 | data, _, err := s.c.callAPI(ctx, r) 41 | if err != nil { 42 | return nil, err 43 | } 44 | res := new(CMCancelAllOrdersResponse) 45 | err = json.Unmarshal(data, res) 46 | if err != nil { 47 | return nil, err 48 | } 49 | return res, nil 50 | } 51 | 52 | // CMCancelAllOrdersResponse define cancel all orders response 53 | type CMCancelAllOrdersResponse struct { 54 | Code int `json:"code"` 55 | Msg string `json:"msg"` 56 | } 57 | -------------------------------------------------------------------------------- /v2/portfolio/cm_cancel_all_orders_service_integration_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | type cmCancelAllOrdersServiceIntegrationTestSuite struct { 9 | *baseIntegrationTestSuite 10 | } 11 | 12 | func TestCMCancelAllOrdersServiceIntegration(t *testing.T) { 13 | base := SetupTest(t) 14 | suite := &cmCancelAllOrdersServiceIntegrationTestSuite{ 15 | baseIntegrationTestSuite: base, 16 | } 17 | 18 | t.Run("CancelAllCMOrders", func(t *testing.T) { 19 | service := suite.client.NewCMCancelAllOrdersService() 20 | res, err := service.Symbol("BTCUSDC"). 21 | Do(context.Background()) 22 | if err != nil { 23 | t.Fatalf("Failed to cancel all CM orders: %v", err) 24 | } 25 | 26 | // Basic validation of returned data 27 | if res.Code != 200 { 28 | t.Errorf("Expected code 200, got %d", res.Code) 29 | } 30 | if res.Msg == "" { 31 | t.Error("Expected non-empty message") 32 | } 33 | }) 34 | 35 | t.Run("CancelAllCMOrders_Error_NoSymbol", func(t *testing.T) { 36 | service := suite.client.NewCMCancelAllOrdersService() 37 | _, err := service.Do(context.Background()) 38 | if err == nil { 39 | t.Fatal("Expected an error when symbol is not provided") 40 | } 41 | 42 | // Verify it's a Portfolio error 43 | portfolioErr, ok := err.(*Error) 44 | if !ok { 45 | t.Fatalf("Expected Error, got %T", err) 46 | } 47 | if portfolioErr.Code != ErrMandatoryParamEmptyOrMalformed { 48 | t.Errorf("Expected error code %d, got %d", ErrMandatoryParamEmptyOrMalformed, portfolioErr.Code) 49 | } 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /v2/portfolio/cm_commission_rate_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // GetCMCommissionRateService get user commission rate for CM 10 | type GetCMCommissionRateService struct { 11 | c *Client 12 | symbol string 13 | } 14 | 15 | // Symbol set symbol 16 | func (s *GetCMCommissionRateService) Symbol(symbol string) *GetCMCommissionRateService { 17 | s.symbol = symbol 18 | return s 19 | } 20 | 21 | // Do send request 22 | func (s *GetCMCommissionRateService) Do(ctx context.Context, opts ...RequestOption) (*CommissionRate, error) { 23 | r := &request{ 24 | method: http.MethodGet, 25 | endpoint: "/papi/v1/cm/commissionRate", 26 | secType: secTypeSigned, 27 | } 28 | r.setParam("symbol", s.symbol) 29 | 30 | data, _, err := s.c.callAPI(ctx, r, opts...) 31 | if err != nil { 32 | return nil, err 33 | } 34 | res := new(CommissionRate) 35 | err = json.Unmarshal(data, res) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return res, nil 40 | } 41 | -------------------------------------------------------------------------------- /v2/portfolio/cm_commission_rate_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type cmCommissionRateServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestCMCommissionRateServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &cmCommissionRateServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetCommissionRate", func(t *testing.T) { 22 | service := &GetCMCommissionRateService{c: suite.client} 23 | rates, err := service.Symbol("BTCUSD_PERP").Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get commission rates: %v", err) 26 | } 27 | 28 | if rates.Symbol != "BTCUSD_PERP" { 29 | t.Errorf("Expected symbol BTCUSD_PERP, got %v", rates.Symbol) 30 | } 31 | if rates.MakerCommissionRate == "" { 32 | t.Error("Expected non-empty maker commission rate") 33 | } 34 | if rates.TakerCommissionRate == "" { 35 | t.Error("Expected non-empty taker commission rate") 36 | } 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /v2/portfolio/cm_commission_rate_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type cmCommissionRateServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestCMCommissionRateService(t *testing.T) { 14 | suite.Run(t, new(cmCommissionRateServiceTestSuite)) 15 | } 16 | 17 | func (s *cmCommissionRateServiceTestSuite) TestGetCommissionRate() { 18 | data := []byte(`{ 19 | "symbol": "BTCUSD_PERP", 20 | "makerCommissionRate": "0.00015", 21 | "takerCommissionRate": "0.00040" 22 | }`) 23 | s.mockDo(data, nil) 24 | defer s.assertDo() 25 | 26 | symbol := "BTCUSD_PERP" 27 | s.assertReq(func(r *request) { 28 | e := newSignedRequest() 29 | e.setParam("symbol", symbol) 30 | s.assertRequestEqual(e, r) 31 | }) 32 | 33 | rates, err := s.client.NewGetCMCommissionRateService().Symbol(symbol).Do(newContext()) 34 | s.r().NoError(err) 35 | s.r().Equal("BTCUSD_PERP", rates.Symbol) 36 | s.r().Equal("0.00015", rates.MakerCommissionRate) 37 | s.r().Equal("0.00040", rates.TakerCommissionRate) 38 | } 39 | -------------------------------------------------------------------------------- /v2/portfolio/cm_conditional_order_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type cmConditionalOrderServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestCMConditionalOrderServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &cmConditionalOrderServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("PlaceTrailingStopOrder", func(t *testing.T) { 22 | service := &CMConditionalOrderService{c: suite.client} 23 | order, err := service.Symbol("BTCUSD_PERP"). 24 | Side(SideTypeBuy). 25 | StrategyType("TRAILING_STOP_MARKET"). 26 | CallbackRate("1"). 27 | Do(context.Background()) 28 | if err != nil { 29 | t.Fatalf("Failed to place conditional order: %v", err) 30 | } 31 | 32 | // Basic validation of returned data 33 | if order.Symbol != "BTCUSD_PERP" { 34 | t.Error("Expected symbol to be BTCUSD_PERP") 35 | } 36 | 37 | if order.Side != SideTypeBuy { 38 | t.Error("Expected side to be BUY") 39 | } 40 | 41 | if order.StrategyType != "TRAILING_STOP_MARKET" { 42 | t.Error("Expected strategy type to be TRAILING_STOP_MARKET") 43 | } 44 | 45 | if order.StrategyStatus == "" { 46 | t.Error("Expected non-empty strategy status") 47 | } 48 | 49 | if order.StrategyId == 0 { 50 | t.Error("Expected non-zero strategy ID") 51 | } 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /v2/portfolio/cm_income_history_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type cmIncomeHistoryServiceIntegrationTestSuite struct { 13 | *baseIntegrationTestSuite 14 | } 15 | 16 | func TestCMIncomeHistoryServiceIntegration(t *testing.T) { 17 | base := SetupTest(t) 18 | suite := &cmIncomeHistoryServiceIntegrationTestSuite{ 19 | baseIntegrationTestSuite: base, 20 | } 21 | 22 | t.Run("GetCMIncomeHistory", func(t *testing.T) { 23 | service := &GetCMIncomeHistoryService{c: suite.client} 24 | endTime := time.Now().UnixMilli() 25 | startTime := endTime - 7*24*60*60*1000 // 7 days ago 26 | 27 | incomes, err := service. 28 | StartTime(startTime). 29 | EndTime(endTime). 30 | Limit(100). 31 | Do(context.Background()) 32 | 33 | if err != nil { 34 | t.Fatalf("Failed to get CM income history: %v", err) 35 | } 36 | 37 | for _, income := range incomes { 38 | if income.Asset == "" { 39 | t.Error("Expected non-empty asset") 40 | } 41 | if income.IncomeType == "" { 42 | t.Error("Expected non-empty income type") 43 | } 44 | if income.TranID == 0 { 45 | t.Error("Expected non-zero transaction ID") 46 | } 47 | } 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /v2/portfolio/cm_leverage_bracket_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type cmLeverageBracketServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestCMLeverageBracketServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &cmLeverageBracketServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetLeverageBracket", func(t *testing.T) { 22 | service := &GetCMLeverageBracketService{c: suite.client} 23 | brackets, err := service.Symbol("BTCUSD_PERP").Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get leverage bracket: %v", err) 26 | } 27 | 28 | for _, bracket := range brackets { 29 | if bracket.Symbol == "" { 30 | t.Error("Expected non-empty symbol") 31 | } 32 | if len(bracket.Brackets) == 0 { 33 | t.Error("Expected non-empty brackets") 34 | } 35 | } 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /v2/portfolio/cm_leverage_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // ChangeCMInitialLeverageService change user's initial leverage of specific symbol in CM 10 | type ChangeCMInitialLeverageService struct { 11 | c *Client 12 | symbol string 13 | leverage int 14 | } 15 | 16 | // Symbol set symbol 17 | func (s *ChangeCMInitialLeverageService) Symbol(symbol string) *ChangeCMInitialLeverageService { 18 | s.symbol = symbol 19 | return s 20 | } 21 | 22 | // Leverage set leverage 23 | func (s *ChangeCMInitialLeverageService) Leverage(leverage int) *ChangeCMInitialLeverageService { 24 | s.leverage = leverage 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *ChangeCMInitialLeverageService) Do(ctx context.Context, opts ...RequestOption) (res *CMLeverage, err error) { 30 | r := &request{ 31 | method: http.MethodPost, 32 | endpoint: "/papi/v1/cm/leverage", 33 | secType: secTypeSigned, 34 | } 35 | r.setParam("symbol", s.symbol) 36 | r.setParam("leverage", s.leverage) 37 | 38 | data, _, err := s.c.callAPI(ctx, r, opts...) 39 | if err != nil { 40 | return nil, err 41 | } 42 | res = new(CMLeverage) 43 | err = json.Unmarshal(data, res) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return res, nil 48 | } 49 | 50 | // CMLeverage define leverage info 51 | type CMLeverage struct { 52 | Leverage int `json:"leverage"` 53 | MaxQty string `json:"maxQty"` 54 | Symbol string `json:"symbol"` 55 | } 56 | -------------------------------------------------------------------------------- /v2/portfolio/cm_leverage_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type cmLeverageServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestCMLeverageServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &cmLeverageServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("ChangeLeverage", func(t *testing.T) { 22 | service := &ChangeCMInitialLeverageService{c: suite.client} 23 | res, err := service. 24 | Symbol("BTCUSD_PERP"). 25 | Leverage(20). 26 | Do(context.Background()) 27 | if err != nil { 28 | t.Fatalf("Failed to change leverage: %v", err) 29 | } 30 | 31 | // Basic validation of returned data 32 | if res.Symbol != "BTCUSD_PERP" { 33 | t.Errorf("Expected symbol BTCUSD_PERP, got %v", res.Symbol) 34 | } 35 | 36 | if res.Leverage != 20 { 37 | t.Errorf("Expected leverage 20, got %v", res.Leverage) 38 | } 39 | 40 | if res.MaxQty == "" { 41 | t.Error("Expected non-empty max quantity") 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /v2/portfolio/cm_leverage_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type cmLeverageServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestCMLeverageService(t *testing.T) { 14 | suite.Run(t, new(cmLeverageServiceTestSuite)) 15 | } 16 | 17 | func (s *cmLeverageServiceTestSuite) TestChangeLeverage() { 18 | data := []byte(`{ 19 | "leverage": 21, 20 | "maxQty": "1000", 21 | "symbol": "BTCUSD_200925" 22 | }`) 23 | s.mockDo(data, nil) 24 | defer s.assertDo() 25 | 26 | symbol := "BTCUSD_200925" 27 | leverage := 21 28 | s.assertReq(func(r *request) { 29 | e := newSignedRequest() 30 | e.setParam("symbol", symbol) 31 | e.setParam("leverage", leverage) 32 | s.assertRequestEqual(e, r) 33 | }) 34 | 35 | res, err := s.client.NewChangeCMInitialLeverageService(). 36 | Symbol(symbol). 37 | Leverage(leverage). 38 | Do(newContext()) 39 | s.r().NoError(err) 40 | s.assertLeverageEqual(res, &CMLeverage{ 41 | Leverage: 21, 42 | MaxQty: "1000", 43 | Symbol: "BTCUSD_200925", 44 | }) 45 | } 46 | 47 | func (s *cmLeverageServiceTestSuite) assertLeverageEqual(a, e *CMLeverage) { 48 | r := s.r() 49 | r.Equal(e.Leverage, a.Leverage, "Leverage") 50 | r.Equal(e.MaxQty, a.MaxQty, "MaxQty") 51 | r.Equal(e.Symbol, a.Symbol, "Symbol") 52 | } 53 | -------------------------------------------------------------------------------- /v2/portfolio/cm_open_conditional_orders_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // CMOpenConditionalOrdersService service to get all current CM open conditional orders 10 | type CMOpenConditionalOrdersService struct { 11 | c *Client 12 | symbol *string 13 | recvWindow *int64 14 | } 15 | 16 | // Symbol set symbol 17 | func (s *CMOpenConditionalOrdersService) Symbol(symbol string) *CMOpenConditionalOrdersService { 18 | s.symbol = &symbol 19 | return s 20 | } 21 | 22 | // RecvWindow set recvWindow 23 | func (s *CMOpenConditionalOrdersService) RecvWindow(recvWindow int64) *CMOpenConditionalOrdersService { 24 | s.recvWindow = &recvWindow 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *CMOpenConditionalOrdersService) Do(ctx context.Context) ([]*CMOpenConditionalOrderResponse, error) { 30 | r := &request{ 31 | method: http.MethodGet, 32 | endpoint: "/papi/v1/cm/conditional/openOrders", 33 | secType: secTypeSigned, 34 | } 35 | if s.symbol != nil { 36 | r.setParam("symbol", *s.symbol) 37 | } 38 | if s.recvWindow != nil { 39 | r.setParam("recvWindow", *s.recvWindow) 40 | } 41 | 42 | data, _, err := s.c.callAPI(ctx, r) 43 | if err != nil { 44 | return nil, err 45 | } 46 | var res []*CMOpenConditionalOrderResponse 47 | err = json.Unmarshal(data, &res) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return res, nil 52 | } 53 | -------------------------------------------------------------------------------- /v2/portfolio/cm_order_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type cmOrderServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestCMOrderServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &cmOrderServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("PlaceOrder", func(t *testing.T) { 22 | service := &CMOrderService{c: suite.client} 23 | order, err := service.Symbol("BTCUSD_PERP"). 24 | Side(SideTypeBuy). 25 | Type(OrderTypeLimit). 26 | TimeInForce(TimeInForceTypeGTC). 27 | Quantity("1"). 28 | Price("20000"). 29 | Do(context.Background()) 30 | if err != nil { 31 | t.Fatalf("Failed to place order: %v", err) 32 | } 33 | 34 | // Basic validation of returned data 35 | if order.Symbol != "BTCUSD_PERP" { 36 | t.Error("Expected symbol to be BTCUSD_PERP") 37 | } 38 | 39 | if order.Side != SideTypeBuy { 40 | t.Error("Expected side to be BUY") 41 | } 42 | 43 | if order.Type != OrderTypeLimit { 44 | t.Error("Expected type to be LIMIT") 45 | } 46 | 47 | if order.TimeInForce != TimeInForceTypeGTC { 48 | t.Error("Expected timeInForce to be GTC") 49 | } 50 | 51 | if order.Status == "" { 52 | t.Error("Expected non-empty status") 53 | } 54 | 55 | if order.OrderID == 0 { 56 | t.Error("Expected non-zero order ID") 57 | } 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /v2/portfolio/cm_position_mode_get_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // GetCMPositionModeService get user's position mode on EVERY symbol in CM 10 | type GetCMPositionModeService struct { 11 | c *Client 12 | } 13 | 14 | // Do send request 15 | func (s *GetCMPositionModeService) Do(ctx context.Context, opts ...RequestOption) (res *PositionMode, err error) { 16 | r := &request{ 17 | method: http.MethodGet, 18 | endpoint: "/papi/v1/cm/positionSide/dual", 19 | secType: secTypeSigned, 20 | } 21 | 22 | data, _, err := s.c.callAPI(ctx, r, opts...) 23 | if err != nil { 24 | return nil, err 25 | } 26 | res = new(PositionMode) 27 | err = json.Unmarshal(data, res) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return res, nil 32 | } 33 | -------------------------------------------------------------------------------- /v2/portfolio/cm_position_mode_get_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type cmPositionModeGetServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestCMPositionModeGetServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &cmPositionModeGetServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetPositionMode", func(t *testing.T) { 22 | service := &GetCMPositionModeService{c: suite.client} 23 | res, err := service.Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get position mode: %v", err) 26 | } 27 | 28 | t.Logf("Current position mode: Hedge Mode: %v", res.DualSidePosition) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /v2/portfolio/cm_position_mode_get_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type cmPositionModeGetServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestCMPositionModeGetService(t *testing.T) { 14 | suite.Run(t, new(cmPositionModeGetServiceTestSuite)) 15 | } 16 | 17 | func (s *cmPositionModeGetServiceTestSuite) TestGetPositionMode() { 18 | data := []byte(`{ 19 | "dualSidePosition": true 20 | }`) 21 | s.mockDo(data, nil) 22 | defer s.assertDo() 23 | 24 | s.assertReq(func(r *request) { 25 | e := newSignedRequest() 26 | s.assertRequestEqual(e, r) 27 | }) 28 | 29 | res, err := s.client.NewGetCMPositionModeService().Do(newContext()) 30 | s.r().NoError(err) 31 | s.assertPositionModeEqual(res, &PositionMode{ 32 | DualSidePosition: true, 33 | }) 34 | } 35 | 36 | func (s *cmPositionModeGetServiceTestSuite) assertPositionModeEqual(a, e *PositionMode) { 37 | r := s.r() 38 | r.Equal(e.DualSidePosition, a.DualSidePosition, "DualSidePosition") 39 | } 40 | -------------------------------------------------------------------------------- /v2/portfolio/cm_position_mode_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // ChangeCMPositionModeService change user's position mode on EVERY symbol in CM 10 | type ChangeCMPositionModeService struct { 11 | c *Client 12 | dualSidePosition bool 13 | } 14 | 15 | // DualSidePosition set position mode 16 | func (s *ChangeCMPositionModeService) DualSidePosition(dualSidePosition bool) *ChangeCMPositionModeService { 17 | s.dualSidePosition = dualSidePosition 18 | return s 19 | } 20 | 21 | // Do send request 22 | func (s *ChangeCMPositionModeService) Do(ctx context.Context, opts ...RequestOption) (res *APIResponse, err error) { 23 | r := &request{ 24 | method: http.MethodPost, 25 | endpoint: "/papi/v1/cm/positionSide/dual", 26 | secType: secTypeSigned, 27 | } 28 | r.setParam("dualSidePosition", s.dualSidePosition) 29 | 30 | data, _, err := s.c.callAPI(ctx, r, opts...) 31 | if err != nil { 32 | return nil, err 33 | } 34 | res = new(APIResponse) 35 | err = json.Unmarshal(data, res) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return res, nil 40 | } 41 | -------------------------------------------------------------------------------- /v2/portfolio/cm_position_mode_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type cmPositionModeServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestCMPositionModeServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &cmPositionModeServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("ChangePositionMode", func(t *testing.T) { 22 | service := &ChangeCMPositionModeService{c: suite.client} 23 | res, err := service. 24 | DualSidePosition(true). // Enable Hedge Mode 25 | Do(context.Background()) 26 | if err != nil { 27 | t.Fatalf("Failed to change position mode: %v", err) 28 | } 29 | 30 | // Basic validation of returned data 31 | if res.Code != 200 { 32 | t.Errorf("Expected code 200, got %v", res.Code) 33 | } 34 | 35 | if res.Msg != "success" { 36 | t.Errorf("Expected msg 'success', got %v", res.Msg) 37 | } 38 | 39 | // Test changing back to One-way Mode 40 | res, err = service. 41 | DualSidePosition(false). 42 | Do(context.Background()) 43 | if err != nil { 44 | t.Fatalf("Failed to change position mode back: %v", err) 45 | } 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /v2/portfolio/cm_position_mode_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type cmPositionModeServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestCMPositionModeService(t *testing.T) { 14 | suite.Run(t, new(cmPositionModeServiceTestSuite)) 15 | } 16 | 17 | func (s *cmPositionModeServiceTestSuite) TestChangePositionMode() { 18 | data := []byte(`{ 19 | "code": 200, 20 | "msg": "success" 21 | }`) 22 | s.mockDo(data, nil) 23 | defer s.assertDo() 24 | 25 | dualSidePosition := true 26 | s.assertReq(func(r *request) { 27 | e := newSignedRequest() 28 | e.setParam("dualSidePosition", dualSidePosition) 29 | s.assertRequestEqual(e, r) 30 | }) 31 | 32 | res, err := s.client.NewChangeCMPositionModeService(). 33 | DualSidePosition(dualSidePosition). 34 | Do(newContext()) 35 | s.r().NoError(err) 36 | s.assertPositionModeResponseEqual(res, &APIResponse{ 37 | Code: 200, 38 | Msg: "success", 39 | }) 40 | } 41 | 42 | func (s *cmPositionModeServiceTestSuite) assertPositionModeResponseEqual(a, e *APIResponse) { 43 | r := s.r() 44 | r.Equal(e.Code, a.Code, "Code") 45 | r.Equal(e.Msg, a.Msg, "Msg") 46 | } 47 | -------------------------------------------------------------------------------- /v2/portfolio/cm_position_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // GetCMPositionRiskService get CM position risk information 10 | type GetCMPositionRiskService struct { 11 | c *Client 12 | marginAsset *string 13 | pair *string 14 | } 15 | 16 | // MarginAsset set margin asset 17 | func (s *GetCMPositionRiskService) MarginAsset(marginAsset string) *GetCMPositionRiskService { 18 | s.marginAsset = &marginAsset 19 | return s 20 | } 21 | 22 | // Pair set trading pair 23 | func (s *GetCMPositionRiskService) Pair(pair string) *GetCMPositionRiskService { 24 | s.pair = &pair 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *GetCMPositionRiskService) Do(ctx context.Context, opts ...RequestOption) (res []*CMPosition, err error) { 30 | r := &request{ 31 | method: http.MethodGet, 32 | endpoint: "/papi/v1/cm/positionRisk", 33 | secType: secTypeSigned, 34 | } 35 | if s.marginAsset != nil { 36 | r.setParam("marginAsset", *s.marginAsset) 37 | } 38 | if s.pair != nil { 39 | r.setParam("pair", *s.pair) 40 | } 41 | 42 | data, _, err := s.c.callAPI(ctx, r, opts...) 43 | if err != nil { 44 | return []*CMPosition{}, err 45 | } 46 | res = make([]*CMPosition, 0) 47 | err = json.Unmarshal(data, &res) 48 | if err != nil { 49 | return []*CMPosition{}, err 50 | } 51 | return res, nil 52 | } 53 | -------------------------------------------------------------------------------- /v2/portfolio/cm_position_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type cmPositionServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestCMPositionServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &cmPositionServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetPositionRisk", func(t *testing.T) { 22 | service := &GetCMPositionRiskService{c: suite.client} 23 | positions, err := service. 24 | MarginAsset("BTC"). 25 | Pair("BTCUSD"). 26 | Do(context.Background()) 27 | if err != nil { 28 | t.Fatalf("Failed to get position risk info: %v", err) 29 | } 30 | 31 | for _, position := range positions { 32 | if position.Symbol == "" { 33 | t.Error("Expected non-empty symbol") 34 | } 35 | 36 | if position.PositionSide == "" { 37 | t.Error("Expected non-empty position side") 38 | } 39 | 40 | if position.UpdateTime == 0 { 41 | t.Error("Expected non-zero update time") 42 | } 43 | } 44 | }) 45 | 46 | t.Run("GetAllPositionsRisk", func(t *testing.T) { 47 | service := &GetCMPositionRiskService{c: suite.client} 48 | positions, err := service.Do(context.Background()) 49 | if err != nil { 50 | t.Fatalf("Failed to get all positions risk info: %v", err) 51 | } 52 | 53 | for _, position := range positions { 54 | if position.Symbol == "" { 55 | t.Error("Expected non-empty symbol") 56 | } 57 | } 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /v2/portfolio/fund_auto_collection_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // FundAutoCollectionService fund auto-collection for Portfolio Margin 10 | type FundAutoCollectionService struct { 11 | c *Client 12 | } 13 | 14 | // Do send request 15 | func (s *FundAutoCollectionService) Do(ctx context.Context, opts ...RequestOption) (*SuccessResponse, error) { 16 | r := &request{ 17 | method: http.MethodPost, 18 | endpoint: "/papi/v1/auto-collection", 19 | secType: secTypeSigned, 20 | } 21 | 22 | data, _, err := s.c.callAPI(ctx, r, opts...) 23 | if err != nil { 24 | return nil, err 25 | } 26 | res := new(SuccessResponse) 27 | err = json.Unmarshal(data, res) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return res, nil 32 | } 33 | -------------------------------------------------------------------------------- /v2/portfolio/fund_auto_collection_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type fundAutoCollectionServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestFundAutoCollectionServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &fundAutoCollectionServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("FundAutoCollection", func(t *testing.T) { 22 | service := &FundAutoCollectionService{c: suite.client} 23 | res, err := service.Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to execute fund auto-collection: %v", err) 26 | } 27 | 28 | if res.Msg != "success" { 29 | t.Errorf("Expected success message, got %v", res.Msg) 30 | } 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /v2/portfolio/fund_auto_collection_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type fundAutoCollectionServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestFundAutoCollectionService(t *testing.T) { 14 | suite.Run(t, new(fundAutoCollectionServiceTestSuite)) 15 | } 16 | 17 | func (s *fundAutoCollectionServiceTestSuite) TestFundAutoCollection() { 18 | data := []byte(`{ 19 | "msg": "success" 20 | }`) 21 | s.mockDo(data, nil) 22 | defer s.assertDo() 23 | 24 | s.assertReq(func(r *request) { 25 | e := newSignedRequest() 26 | s.assertRequestEqual(e, r) 27 | }) 28 | 29 | res, err := s.client.NewFundAutoCollectionService().Do(newContext()) 30 | s.r().NoError(err) 31 | s.r().Equal("success", res.Msg) 32 | } 33 | -------------------------------------------------------------------------------- /v2/portfolio/fund_collection_by_asset_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // FundCollectionByAssetService transfers specific asset from Futures Account to Margin account 10 | type FundCollectionByAssetService struct { 11 | c *Client 12 | asset string 13 | } 14 | 15 | // Asset set asset 16 | func (s *FundCollectionByAssetService) Asset(asset string) *FundCollectionByAssetService { 17 | s.asset = asset 18 | return s 19 | } 20 | 21 | // Do send request 22 | func (s *FundCollectionByAssetService) Do(ctx context.Context, opts ...RequestOption) (*SuccessResponse, error) { 23 | r := &request{ 24 | method: http.MethodPost, 25 | endpoint: "/papi/v1/asset-collection", 26 | secType: secTypeSigned, 27 | } 28 | r.setParam("asset", s.asset) 29 | 30 | data, _, err := s.c.callAPI(ctx, r, opts...) 31 | if err != nil { 32 | return nil, err 33 | } 34 | res := new(SuccessResponse) 35 | err = json.Unmarshal(data, res) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return res, nil 40 | } 41 | -------------------------------------------------------------------------------- /v2/portfolio/fund_collection_by_asset_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type fundCollectionByAssetServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestFundCollectionByAssetServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &fundCollectionByAssetServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("FundCollectionByAsset", func(t *testing.T) { 22 | service := &FundCollectionByAssetService{c: suite.client} 23 | res, err := service.Asset("USDT").Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to execute fund collection by asset: %v", err) 26 | } 27 | 28 | if res.Msg != "success" { 29 | t.Errorf("Expected success message, got %v", res.Msg) 30 | } 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /v2/portfolio/fund_collection_by_asset_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type fundCollectionByAssetServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestFundCollectionByAssetService(t *testing.T) { 14 | suite.Run(t, new(fundCollectionByAssetServiceTestSuite)) 15 | } 16 | 17 | func (s *fundCollectionByAssetServiceTestSuite) TestFundCollectionByAsset() { 18 | data := []byte(`{ 19 | "msg": "success" 20 | }`) 21 | s.mockDo(data, nil) 22 | defer s.assertDo() 23 | 24 | asset := "USDT" 25 | s.assertReq(func(r *request) { 26 | e := newSignedRequest() 27 | e.setParam("asset", asset) 28 | s.assertRequestEqual(e, r) 29 | }) 30 | 31 | res, err := s.client.NewFundCollectionByAssetService().Asset(asset).Do(newContext()) 32 | s.r().NoError(err) 33 | s.r().Equal("success", res.Msg) 34 | } 35 | -------------------------------------------------------------------------------- /v2/portfolio/get_margin_loan_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type getMarginLoanServiceIntegrationTestSuite struct { 13 | *baseIntegrationTestSuite 14 | } 15 | 16 | func TestGetMarginLoanServiceIntegration(t *testing.T) { 17 | base := SetupTest(t) 18 | suite := &getMarginLoanServiceIntegrationTestSuite{ 19 | baseIntegrationTestSuite: base, 20 | } 21 | 22 | t.Run("GetMarginLoan", func(t *testing.T) { 23 | service := &GetMarginLoanService{c: suite.client} 24 | endTime := time.Now().UnixMilli() 25 | startTime := endTime - 7*24*60*60*1000 // 7 days ago 26 | 27 | loans, err := service. 28 | Asset("BNB"). 29 | StartTime(startTime). 30 | EndTime(endTime). 31 | Do(context.Background()) 32 | 33 | if err != nil { 34 | t.Fatalf("Failed to get margin loans: %v", err) 35 | } 36 | 37 | if loans.Total < 0 { 38 | t.Error("Expected non-negative total") 39 | } 40 | 41 | for _, loan := range loans.Rows { 42 | if loan.Asset == "" { 43 | t.Error("Expected non-empty asset") 44 | } 45 | if loan.Status == "" { 46 | t.Error("Expected non-empty status") 47 | } 48 | } 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /v2/portfolio/get_margin_loan_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type getMarginLoanServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestGetMarginLoanService(t *testing.T) { 14 | suite.Run(t, new(getMarginLoanServiceTestSuite)) 15 | } 16 | 17 | func (s *getMarginLoanServiceTestSuite) TestGetMarginLoan() { 18 | data := []byte(`{ 19 | "rows": [ 20 | { 21 | "txId": 12807067523, 22 | "asset": "BNB", 23 | "principal": "0.84624403", 24 | "timestamp": 1555056425000, 25 | "status": "CONFIRMED" 26 | } 27 | ], 28 | "total": 1 29 | }`) 30 | s.mockDo(data, nil) 31 | defer s.assertDo() 32 | 33 | asset := "BNB" 34 | txID := int64(12807067523) 35 | s.assertReq(func(r *request) { 36 | e := newSignedRequest() 37 | e.setParam("asset", asset) 38 | e.setParam("txId", txID) 39 | s.assertRequestEqual(e, r) 40 | }) 41 | 42 | loans, err := s.client.NewGetMarginLoanService().Asset(asset).TxID(txID).Do(newContext()) 43 | s.r().NoError(err) 44 | s.r().Equal(int64(1), loans.Total) 45 | s.r().Len(loans.Rows, 1) 46 | 47 | loan := loans.Rows[0] 48 | s.r().Equal(int64(12807067523), loan.TxID) 49 | s.r().Equal("BNB", loan.Asset) 50 | s.r().Equal("0.84624403", loan.Principal) 51 | s.r().Equal(int64(1555056425000), loan.Timestamp) 52 | s.r().Equal("CONFIRMED", loan.Status) 53 | } 54 | -------------------------------------------------------------------------------- /v2/portfolio/get_margin_open_orders_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // GetMarginOpenOrdersService service to get current margin open orders 10 | type GetMarginOpenOrdersService struct { 11 | c *Client 12 | symbol *string 13 | recvWindow *int64 14 | } 15 | 16 | // Symbol set symbol 17 | func (s *GetMarginOpenOrdersService) Symbol(symbol string) *GetMarginOpenOrdersService { 18 | s.symbol = &symbol 19 | return s 20 | } 21 | 22 | // RecvWindow set recvWindow 23 | func (s *GetMarginOpenOrdersService) RecvWindow(recvWindow int64) *GetMarginOpenOrdersService { 24 | s.recvWindow = &recvWindow 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *GetMarginOpenOrdersService) Do(ctx context.Context) ([]*MarginOrder, error) { 30 | r := &request{ 31 | method: http.MethodGet, 32 | endpoint: "/papi/v1/margin/openOrders", 33 | secType: secTypeSigned, 34 | } 35 | if s.symbol != nil { 36 | r.setParam("symbol", *s.symbol) 37 | } 38 | if s.recvWindow != nil { 39 | r.setParam("recvWindow", *s.recvWindow) 40 | } 41 | 42 | data, _, err := s.c.callAPI(ctx, r) 43 | if err != nil { 44 | return nil, err 45 | } 46 | var res []*MarginOrder 47 | err = json.Unmarshal(data, &res) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return res, nil 52 | } 53 | -------------------------------------------------------------------------------- /v2/portfolio/get_margin_repay_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type getMarginRepayServiceIntegrationTestSuite struct { 13 | *baseIntegrationTestSuite 14 | } 15 | 16 | func TestGetMarginRepayServiceIntegration(t *testing.T) { 17 | base := SetupTest(t) 18 | suite := &getMarginRepayServiceIntegrationTestSuite{ 19 | baseIntegrationTestSuite: base, 20 | } 21 | 22 | t.Run("GetMarginRepay", func(t *testing.T) { 23 | service := &GetMarginRepayService{c: suite.client} 24 | endTime := time.Now().UnixMilli() 25 | startTime := endTime - 7*24*60*60*1000 // 7 days ago 26 | 27 | repays, err := service. 28 | Asset("BNB"). 29 | StartTime(startTime). 30 | EndTime(endTime). 31 | Do(context.Background()) 32 | 33 | if err != nil { 34 | t.Fatalf("Failed to get margin repays: %v", err) 35 | } 36 | 37 | if repays.Total < 0 { 38 | t.Error("Expected non-negative total") 39 | } 40 | 41 | for _, repay := range repays.Rows { 42 | if repay.Asset == "" { 43 | t.Error("Expected non-empty asset") 44 | } 45 | if repay.Status == "" { 46 | t.Error("Expected non-empty status") 47 | } 48 | } 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /v2/portfolio/get_margin_repay_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type getMarginRepayServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestGetMarginRepayService(t *testing.T) { 14 | suite.Run(t, new(getMarginRepayServiceTestSuite)) 15 | } 16 | 17 | func (s *getMarginRepayServiceTestSuite) TestGetMarginRepay() { 18 | data := []byte(`{ 19 | "rows": [ 20 | { 21 | "amount": "14.00000000", 22 | "asset": "BNB", 23 | "interest": "0.01866667", 24 | "principal": "13.98133333", 25 | "status": "CONFIRMED", 26 | "timestamp": 1563438204000, 27 | "txId": 2970933056 28 | } 29 | ], 30 | "total": 1 31 | }`) 32 | s.mockDo(data, nil) 33 | defer s.assertDo() 34 | 35 | asset := "BNB" 36 | txID := int64(2970933056) 37 | s.assertReq(func(r *request) { 38 | e := newSignedRequest() 39 | e.setParam("asset", asset) 40 | e.setParam("txId", txID) 41 | s.assertRequestEqual(e, r) 42 | }) 43 | 44 | repays, err := s.client.NewGetMarginRepayService().Asset(asset).TxID(txID).Do(newContext()) 45 | s.r().NoError(err) 46 | s.r().Equal(int64(1), repays.Total) 47 | s.r().Len(repays.Rows, 1) 48 | 49 | repay := repays.Rows[0] 50 | s.r().Equal("14.00000000", repay.Amount) 51 | s.r().Equal("BNB", repay.Asset) 52 | s.r().Equal("0.01866667", repay.Interest) 53 | s.r().Equal("13.98133333", repay.Principal) 54 | s.r().Equal("CONFIRMED", repay.Status) 55 | s.r().Equal(int64(1563438204000), repay.Timestamp) 56 | s.r().Equal(int64(2970933056), repay.TxID) 57 | } 58 | -------------------------------------------------------------------------------- /v2/portfolio/margin_borrow_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // GetMarginMaxBorrowService get margin max borrowable amount 10 | type GetMarginMaxBorrowService struct { 11 | c *Client 12 | asset string 13 | } 14 | 15 | // Asset set asset 16 | func (s *GetMarginMaxBorrowService) Asset(asset string) *GetMarginMaxBorrowService { 17 | s.asset = asset 18 | return s 19 | } 20 | 21 | // Do send request 22 | func (s *GetMarginMaxBorrowService) Do(ctx context.Context, opts ...RequestOption) (res *MaxBorrow, err error) { 23 | r := &request{ 24 | method: http.MethodGet, 25 | endpoint: "/papi/v1/margin/maxBorrowable", 26 | secType: secTypeSigned, 27 | } 28 | r.setParam("asset", s.asset) 29 | 30 | data, _, err := s.c.callAPI(ctx, r, opts...) 31 | if err != nil { 32 | return nil, err 33 | } 34 | res = new(MaxBorrow) 35 | err = json.Unmarshal(data, res) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return res, nil 40 | } 41 | 42 | // MaxBorrow define margin max borrowable amount info 43 | type MaxBorrow struct { 44 | Amount string `json:"amount"` // account's currently max borrowable amount with sufficient system availability 45 | BorrowLimit string `json:"borrowLimit"` // max borrowable amount limited by the account level 46 | } 47 | -------------------------------------------------------------------------------- /v2/portfolio/margin_borrow_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type marginBorrowServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestMarginBorrowServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &marginBorrowServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetMaxBorrow", func(t *testing.T) { 22 | service := &GetMarginMaxBorrowService{c: suite.client} 23 | maxBorrow, err := service.Asset("USDT").Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get max borrow info: %v", err) 26 | } 27 | 28 | // Basic validation of returned data 29 | if maxBorrow.Amount == "" { 30 | t.Error("Expected non-empty amount") 31 | } 32 | 33 | if maxBorrow.BorrowLimit == "" { 34 | t.Error("Expected non-empty borrow limit") 35 | } 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /v2/portfolio/margin_borrow_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type marginBorrowServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestMarginBorrowService(t *testing.T) { 14 | suite.Run(t, new(marginBorrowServiceTestSuite)) 15 | } 16 | 17 | func (s *marginBorrowServiceTestSuite) TestGetMaxBorrow() { 18 | data := []byte(`{ 19 | "amount": "125", 20 | "borrowLimit": "60" 21 | }`) 22 | s.mockDo(data, nil) 23 | defer s.assertDo() 24 | 25 | asset := "USDT" 26 | s.assertReq(func(r *request) { 27 | e := newSignedRequest() 28 | e.setParam("asset", asset) 29 | s.assertRequestEqual(e, r) 30 | }) 31 | 32 | res, err := s.client.NewGetMarginMaxBorrowService().Asset(asset).Do(newContext()) 33 | s.r().NoError(err) 34 | s.assertMaxBorrowEqual(res, &MaxBorrow{ 35 | Amount: "125", 36 | BorrowLimit: "60", 37 | }) 38 | } 39 | 40 | func (s *marginBorrowServiceTestSuite) assertMaxBorrowEqual(a, e *MaxBorrow) { 41 | r := s.r() 42 | r.Equal(e.Amount, a.Amount, "Amount") 43 | r.Equal(e.BorrowLimit, a.BorrowLimit, "BorrowLimit") 44 | } 45 | -------------------------------------------------------------------------------- /v2/portfolio/margin_interest_history_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type marginInterestHistoryServiceIntegrationTestSuite struct { 13 | *baseIntegrationTestSuite 14 | } 15 | 16 | func TestMarginInterestHistoryServiceIntegration(t *testing.T) { 17 | base := SetupTest(t) 18 | suite := &marginInterestHistoryServiceIntegrationTestSuite{ 19 | baseIntegrationTestSuite: base, 20 | } 21 | 22 | t.Run("GetMarginInterestHistory", func(t *testing.T) { 23 | service := &GetMarginInterestHistoryService{c: suite.client} 24 | endTime := time.Now().UnixMilli() 25 | startTime := endTime - 7*24*60*60*1000 // 7 days ago 26 | 27 | history, err := service. 28 | StartTime(startTime). 29 | EndTime(endTime). 30 | Do(context.Background()) 31 | 32 | if err != nil { 33 | t.Fatalf("Failed to get margin interest history: %v", err) 34 | } 35 | 36 | if history.Total < 0 { 37 | t.Error("Expected non-negative total") 38 | } 39 | 40 | for _, interest := range history.Rows { 41 | if interest.Asset == "" { 42 | t.Error("Expected non-empty asset") 43 | } 44 | if interest.Type == "" { 45 | t.Error("Expected non-empty type") 46 | } 47 | } 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /v2/portfolio/margin_loan_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // MarginLoanService service to borrow margin loan 10 | type MarginLoanService struct { 11 | c *Client 12 | asset string 13 | amount string 14 | } 15 | 16 | // Asset set asset 17 | func (s *MarginLoanService) Asset(asset string) *MarginLoanService { 18 | s.asset = asset 19 | return s 20 | } 21 | 22 | // Amount set amount 23 | func (s *MarginLoanService) Amount(amount string) *MarginLoanService { 24 | s.amount = amount 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *MarginLoanService) Do(ctx context.Context, opts ...RequestOption) (res *MarginLoanResponse, err error) { 30 | r := &request{ 31 | method: http.MethodPost, 32 | endpoint: "/papi/v1/marginLoan", 33 | secType: secTypeSigned, 34 | } 35 | 36 | r.setParam("asset", s.asset) 37 | r.setParam("amount", s.amount) 38 | 39 | data, _, err := s.c.callAPI(ctx, r, opts...) 40 | if err != nil { 41 | return nil, err 42 | } 43 | res = new(MarginLoanResponse) 44 | err = json.Unmarshal(data, res) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return res, nil 49 | } 50 | 51 | // MarginLoanResponse define margin loan response 52 | type MarginLoanResponse struct { 53 | TranID int64 `json:"tranId"` 54 | } 55 | -------------------------------------------------------------------------------- /v2/portfolio/margin_loan_service_integration_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | type marginLoanServiceIntegrationTestSuite struct { 9 | *baseIntegrationTestSuite 10 | } 11 | 12 | func TestMarginLoanServiceIntegration(t *testing.T) { 13 | base := SetupTest(t) 14 | suite := &marginLoanServiceIntegrationTestSuite{ 15 | baseIntegrationTestSuite: base, 16 | } 17 | 18 | t.Run("MarginLoan", func(t *testing.T) { 19 | service := &MarginLoanService{c: suite.client} 20 | res, err := service.Asset("USDC"). 21 | Amount("10"). 22 | Do(context.Background()) 23 | if err != nil { 24 | t.Fatalf("Failed to borrow margin loan: %v", err) 25 | } 26 | 27 | // Basic validation of returned data 28 | if res.TranID == 0 { 29 | t.Error("Expected non-zero transaction ID") 30 | } 31 | }) 32 | 33 | t.Run("MarginLoan_USDT_Error", func(t *testing.T) { 34 | service := &MarginLoanService{c: suite.client} 35 | _, err := service.Asset("USDT"). 36 | Amount("10"). 37 | Do(context.Background()) 38 | if err == nil { 39 | t.Fatal("Expected an error for USDT margin loan") 40 | } 41 | 42 | // Verify it's a Portfolio error with the expected code 43 | portfolioErr, ok := err.(*Error) 44 | if !ok { 45 | t.Fatalf("Expected Error, got %T", err) 46 | } 47 | if portfolioErr.Code != 51138 { 48 | t.Errorf("Expected error code 51138, got %d", portfolioErr.Code) 49 | } 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /v2/portfolio/margin_loan_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type marginLoanServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestMarginLoanService(t *testing.T) { 14 | suite.Run(t, new(marginLoanServiceTestSuite)) 15 | } 16 | 17 | func (s *marginLoanServiceTestSuite) TestMarginLoan() { 18 | data := []byte(`{ 19 | "tranId": 100000001 20 | }`) 21 | s.mockDo(data, nil) 22 | defer s.assertDo() 23 | 24 | asset := "BTC" 25 | amount := "1.00000000" 26 | 27 | s.assertReq(func(r *request) { 28 | e := newSignedRequest() 29 | e.setParam("asset", asset) 30 | e.setParam("amount", amount) 31 | s.assertRequestEqual(e, r) 32 | }) 33 | 34 | res, err := s.client.NewMarginLoanService(). 35 | Asset(asset). 36 | Amount(amount). 37 | Do(newContext()) 38 | 39 | s.r().NoError(err) 40 | e := &MarginLoanResponse{ 41 | TranID: 100000001, 42 | } 43 | s.assertLoanResponseEqual(e, res) 44 | } 45 | 46 | func (s *marginLoanServiceTestSuite) assertLoanResponseEqual(e, a *MarginLoanResponse) { 47 | r := s.r() 48 | r.Equal(e.TranID, a.TranID, "TranID") 49 | } 50 | -------------------------------------------------------------------------------- /v2/portfolio/margin_oco_query_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // MarginOCOQueryService service to query margin OCO orders 10 | type MarginOCOQueryService struct { 11 | c *Client 12 | orderListID *int64 13 | origClientOrderID *string 14 | } 15 | 16 | // OrderListID set orderListID 17 | func (s *MarginOCOQueryService) OrderListID(orderListID int64) *MarginOCOQueryService { 18 | s.orderListID = &orderListID 19 | return s 20 | } 21 | 22 | // OrigClientOrderID set origClientOrderID 23 | func (s *MarginOCOQueryService) OrigClientOrderID(origClientOrderID string) *MarginOCOQueryService { 24 | s.origClientOrderID = &origClientOrderID 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *MarginOCOQueryService) Do(ctx context.Context, opts ...RequestOption) (res *MarginOCOResponse, err error) { 30 | r := &request{ 31 | method: http.MethodGet, 32 | endpoint: "/papi/v1/margin/orderList", 33 | secType: secTypeSigned, 34 | } 35 | 36 | if s.orderListID != nil { 37 | r.setParam("orderListId", *s.orderListID) 38 | } 39 | if s.origClientOrderID != nil { 40 | r.setParam("origClientOrderId", *s.origClientOrderID) 41 | } 42 | 43 | data, _, err := s.c.callAPI(ctx, r, opts...) 44 | if err != nil { 45 | return nil, err 46 | } 47 | res = new(MarginOCOResponse) 48 | err = json.Unmarshal(data, res) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return res, nil 53 | } 54 | -------------------------------------------------------------------------------- /v2/portfolio/margin_oco_query_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type marginOCOQueryServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestMarginOCOQueryServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &marginOCOQueryServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("QueryOCO", func(t *testing.T) { 22 | service := &MarginOCOQueryService{c: suite.client} 23 | res, err := service.OrderListID(27). 24 | Do(context.Background()) 25 | if err != nil { 26 | t.Fatalf("Failed to query OCO order: %v", err) 27 | } 28 | 29 | // Basic validation of returned data 30 | if res.OrderListID == 0 { 31 | t.Error("Expected non-zero order list ID") 32 | } 33 | 34 | if res.ContingencyType != "OCO" { 35 | t.Error("Expected contingency type to be OCO") 36 | } 37 | 38 | if len(res.Orders) == 0 { 39 | t.Error("Expected at least one order in the OCO") 40 | } 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /v2/portfolio/margin_open_oco_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // MarginOpenOCOService service to get margin account's open OCO orders 10 | type MarginOpenOCOService struct { 11 | c *Client 12 | recvWindow *int64 13 | } 14 | 15 | // RecvWindow set recvWindow 16 | func (s *MarginOpenOCOService) RecvWindow(recvWindow int64) *MarginOpenOCOService { 17 | s.recvWindow = &recvWindow 18 | return s 19 | } 20 | 21 | // Do send request 22 | func (s *MarginOpenOCOService) Do(ctx context.Context) ([]*MarginOCOResponse, error) { 23 | r := &request{ 24 | method: http.MethodGet, 25 | endpoint: "/papi/v1/margin/openOrderList", 26 | secType: secTypeSigned, 27 | } 28 | if s.recvWindow != nil { 29 | r.setParam("recvWindow", *s.recvWindow) 30 | } 31 | 32 | data, _, err := s.c.callAPI(ctx, r) 33 | if err != nil { 34 | return nil, err 35 | } 36 | var res []*MarginOCOResponse 37 | err = json.Unmarshal(data, &res) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return res, nil 42 | } 43 | 44 | // MarginOCOOrderDetail define margin OCO order detail 45 | type MarginOCOOrderDetail struct { 46 | Symbol string `json:"symbol"` 47 | OrderID int64 `json:"orderId"` 48 | ClientOrderID string `json:"clientOrderId"` 49 | } 50 | -------------------------------------------------------------------------------- /v2/portfolio/margin_order_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type marginOrderServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestMarginOrderServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &marginOrderServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("PlaceOrder", func(t *testing.T) { 22 | service := &MarginOrderService{c: suite.client} 23 | order, err := service.Symbol("BTCUSDT"). 24 | Side(SideTypeBuy). 25 | Type(OrderTypeLimit). 26 | TimeInForce(TimeInForceTypeGTC). 27 | Quantity("0.001"). 28 | Price("20000"). 29 | Do(context.Background()) 30 | if err != nil { 31 | t.Fatalf("Failed to place order: %v", err) 32 | } 33 | 34 | // Basic validation of returned data 35 | if order.Symbol != "BTCUSDT" { 36 | t.Error("Expected symbol to be BTCUSDT") 37 | } 38 | 39 | if order.Side != SideTypeBuy { 40 | t.Error("Expected side to be BUY") 41 | } 42 | 43 | if order.Type != OrderTypeLimit { 44 | t.Error("Expected type to be LIMIT") 45 | } 46 | 47 | if order.Status == "" { 48 | t.Error("Expected non-empty status") 49 | } 50 | 51 | if order.OrderID == 0 { 52 | t.Error("Expected non-zero order ID") 53 | } 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /v2/portfolio/margin_repay_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // MarginRepayService service to repay margin loans 10 | type MarginRepayService struct { 11 | c *Client 12 | asset string 13 | amount string 14 | recvWindow *int64 15 | } 16 | 17 | // Asset set asset 18 | func (s *MarginRepayService) Asset(asset string) *MarginRepayService { 19 | s.asset = asset 20 | return s 21 | } 22 | 23 | // Amount set amount 24 | func (s *MarginRepayService) Amount(amount string) *MarginRepayService { 25 | s.amount = amount 26 | return s 27 | } 28 | 29 | // RecvWindow set recvWindow 30 | func (s *MarginRepayService) RecvWindow(recvWindow int64) *MarginRepayService { 31 | s.recvWindow = &recvWindow 32 | return s 33 | } 34 | 35 | // Do send request 36 | func (s *MarginRepayService) Do(ctx context.Context, opts ...RequestOption) (res *MarginRepayResponse, err error) { 37 | r := &request{ 38 | method: http.MethodPost, 39 | endpoint: "/papi/v1/repayLoan", 40 | secType: secTypeSigned, 41 | } 42 | 43 | r.setParam("asset", s.asset) 44 | r.setParam("amount", s.amount) 45 | if s.recvWindow != nil { 46 | r.setParam("recvWindow", *s.recvWindow) 47 | } 48 | 49 | data, _, err := s.c.callAPI(ctx, r, opts...) 50 | if err != nil { 51 | return nil, err 52 | } 53 | res = new(MarginRepayResponse) 54 | err = json.Unmarshal(data, res) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return res, nil 59 | } 60 | 61 | // MarginRepayResponse define margin repay response 62 | type MarginRepayResponse struct { 63 | TranID int64 `json:"tranId"` 64 | } 65 | -------------------------------------------------------------------------------- /v2/portfolio/margin_repay_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type marginRepayServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestMarginRepayServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &marginRepayServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("RepayLoan", func(t *testing.T) { 22 | service := &MarginRepayService{c: suite.client} 23 | response, err := service. 24 | Asset("USDC"). 25 | Amount("10"). 26 | Do(context.Background()) 27 | if err != nil { 28 | t.Fatalf("Failed to repay loan: %v", err) 29 | } 30 | 31 | // Basic validation of returned data 32 | if response.TranID == 0 { 33 | t.Error("Expected non-zero transaction ID") 34 | } 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /v2/portfolio/margin_repay_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type marginRepayServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestMarginRepayService(t *testing.T) { 14 | suite.Run(t, new(marginRepayServiceTestSuite)) 15 | } 16 | 17 | func (s *marginRepayServiceTestSuite) TestMarginRepay() { 18 | data := []byte(`{ 19 | "tranId": 100000001 20 | }`) 21 | s.mockDo(data, nil) 22 | defer s.assertDo() 23 | 24 | asset := "BTC" 25 | amount := "1.0" 26 | recvWindow := int64(5000) 27 | 28 | s.assertReq(func(r *request) { 29 | e := newSignedRequest() 30 | e.setParam("asset", asset) 31 | e.setParam("amount", amount) 32 | e.setParam("recvWindow", recvWindow) 33 | s.assertRequestEqual(e, r) 34 | }) 35 | 36 | res, err := s.client.NewMarginRepayService(). 37 | Asset(asset). 38 | Amount(amount). 39 | RecvWindow(recvWindow). 40 | Do(newContext()) 41 | 42 | s.r().NoError(err) 43 | s.r().Equal(int64(100000001), res.TranID) 44 | } 45 | -------------------------------------------------------------------------------- /v2/portfolio/margin_withdraw_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // GetMarginMaxWithdrawService get margin max withdrawable amount 10 | type GetMarginMaxWithdrawService struct { 11 | c *Client 12 | asset string 13 | } 14 | 15 | // Asset set asset 16 | func (s *GetMarginMaxWithdrawService) Asset(asset string) *GetMarginMaxWithdrawService { 17 | s.asset = asset 18 | return s 19 | } 20 | 21 | // Do send request 22 | func (s *GetMarginMaxWithdrawService) Do(ctx context.Context, opts ...RequestOption) (res *MaxWithdraw, err error) { 23 | r := &request{ 24 | method: http.MethodGet, 25 | endpoint: "/papi/v1/margin/maxWithdraw", 26 | secType: secTypeSigned, 27 | } 28 | r.setParam("asset", s.asset) 29 | 30 | data, _, err := s.c.callAPI(ctx, r, opts...) 31 | if err != nil { 32 | return nil, err 33 | } 34 | res = new(MaxWithdraw) 35 | err = json.Unmarshal(data, res) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return res, nil 40 | } 41 | 42 | // MaxWithdraw define margin max withdrawable amount info 43 | type MaxWithdraw struct { 44 | Amount string `json:"amount"` // max withdrawable amount 45 | } 46 | -------------------------------------------------------------------------------- /v2/portfolio/margin_withdraw_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type marginWithdrawServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestMarginWithdrawServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &marginWithdrawServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetMaxWithdraw", func(t *testing.T) { 22 | service := &GetMarginMaxWithdrawService{c: suite.client} 23 | maxWithdraw, err := service.Asset("USDT").Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get max withdraw info: %v", err) 26 | } 27 | 28 | // Basic validation of returned data 29 | if maxWithdraw.Amount == "" { 30 | t.Error("Expected non-empty amount") 31 | } 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /v2/portfolio/margin_withdraw_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type marginWithdrawServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestMarginWithdrawService(t *testing.T) { 14 | suite.Run(t, new(marginWithdrawServiceTestSuite)) 15 | } 16 | 17 | func (s *marginWithdrawServiceTestSuite) TestGetMaxWithdraw() { 18 | data := []byte(`{ 19 | "amount": "60" 20 | }`) 21 | s.mockDo(data, nil) 22 | defer s.assertDo() 23 | 24 | asset := "USDT" 25 | s.assertReq(func(r *request) { 26 | e := newSignedRequest() 27 | e.setParam("asset", asset) 28 | s.assertRequestEqual(e, r) 29 | }) 30 | 31 | res, err := s.client.NewGetMarginMaxWithdrawService().Asset(asset).Do(newContext()) 32 | s.r().NoError(err) 33 | s.assertMaxWithdrawEqual(res, &MaxWithdraw{ 34 | Amount: "60", 35 | }) 36 | } 37 | 38 | func (s *marginWithdrawServiceTestSuite) assertMaxWithdrawEqual(a, e *MaxWithdraw) { 39 | r := s.r() 40 | r.Equal(e.Amount, a.Amount, "Amount") 41 | } 42 | -------------------------------------------------------------------------------- /v2/portfolio/negative_balance_interest_history_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type negativeBalanceInterestHistoryServiceIntegrationTestSuite struct { 13 | *baseIntegrationTestSuite 14 | } 15 | 16 | func TestNegativeBalanceInterestHistoryServiceIntegration(t *testing.T) { 17 | base := SetupTest(t) 18 | suite := &negativeBalanceInterestHistoryServiceIntegrationTestSuite{ 19 | baseIntegrationTestSuite: base, 20 | } 21 | 22 | t.Run("GetNegativeBalanceInterestHistory", func(t *testing.T) { 23 | service := &GetNegativeBalanceInterestHistoryService{c: suite.client} 24 | endTime := time.Now().UnixMilli() 25 | startTime := endTime - 7*24*60*60*1000 // 7 days ago 26 | 27 | interests, err := service. 28 | StartTime(startTime). 29 | EndTime(endTime). 30 | Do(context.Background()) 31 | 32 | if err != nil { 33 | t.Fatalf("Failed to get negative balance interest history: %v", err) 34 | } 35 | 36 | for _, interest := range interests { 37 | if interest.Asset == "" { 38 | t.Error("Expected non-empty asset") 39 | } 40 | if interest.Interest == "" { 41 | t.Error("Expected non-empty interest") 42 | } 43 | } 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /v2/portfolio/negative_balance_interest_history_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type negativeBalanceInterestHistoryServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestNegativeBalanceInterestHistoryService(t *testing.T) { 14 | suite.Run(t, new(negativeBalanceInterestHistoryServiceTestSuite)) 15 | } 16 | 17 | func (s *negativeBalanceInterestHistoryServiceTestSuite) TestGetNegativeBalanceInterestHistory() { 18 | data := []byte(`[ 19 | { 20 | "asset": "USDT", 21 | "interest": "24.4440", 22 | "interestAccuredTime": 1670227200000, 23 | "interestRate": "0.0001164", 24 | "principal": "210000" 25 | } 26 | ]`) 27 | s.mockDo(data, nil) 28 | defer s.assertDo() 29 | 30 | asset := "USDT" 31 | s.assertReq(func(r *request) { 32 | e := newSignedRequest() 33 | e.setParam("asset", asset) 34 | s.assertRequestEqual(e, r) 35 | }) 36 | 37 | interests, err := s.client.NewGetNegativeBalanceInterestHistoryService().Asset(asset).Do(newContext()) 38 | s.r().NoError(err) 39 | s.r().Len(interests, 1) 40 | 41 | interest := interests[0] 42 | s.r().Equal("USDT", interest.Asset) 43 | s.r().Equal("24.4440", interest.Interest) 44 | s.r().Equal(int64(1670227200000), interest.InterestAccuredTime) 45 | s.r().Equal("0.0001164", interest.InterestRate) 46 | s.r().Equal("210000", interest.Principal) 47 | } 48 | -------------------------------------------------------------------------------- /v2/portfolio/negative_balance_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type negativeBalanceServiceIntegrationTestSuite struct { 13 | *baseIntegrationTestSuite 14 | } 15 | 16 | func TestNegativeBalanceServiceIntegration(t *testing.T) { 17 | base := SetupTest(t) 18 | suite := &negativeBalanceServiceIntegrationTestSuite{ 19 | baseIntegrationTestSuite: base, 20 | } 21 | 22 | t.Run("GetNegativeBalanceExchangeRecord", func(t *testing.T) { 23 | endTime := time.Now().UnixMilli() 24 | startTime := endTime - 24*60*60*1000 // 24 hours ago 25 | 26 | service := suite.client.NewGetNegativeBalanceExchangeRecordService() 27 | res, err := service. 28 | StartTime(startTime). 29 | EndTime(endTime). 30 | Do(context.Background()) 31 | 32 | if err != nil { 33 | t.Fatalf("Failed to get negative balance exchange record: %v", err) 34 | } 35 | 36 | if res.Total < 0 { 37 | t.Error("Expected non-negative total") 38 | } 39 | 40 | for _, row := range res.Rows { 41 | if row.StartTime <= 0 { 42 | t.Error("Expected positive start time") 43 | } 44 | if row.EndTime <= 0 { 45 | t.Error("Expected positive end time") 46 | } 47 | if row.EndTime < row.StartTime { 48 | t.Error("Expected end time to be after start time") 49 | } 50 | 51 | for _, detail := range row.Details { 52 | if detail.Asset == "" { 53 | t.Error("Expected non-empty asset") 54 | } 55 | } 56 | } 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /v2/portfolio/rate_limit_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // GetRateLimitService get user rate limit 10 | type GetRateLimitService struct { 11 | c *Client 12 | recvWindow *int64 13 | } 14 | 15 | // RecvWindow set recvWindow 16 | func (s *GetRateLimitService) RecvWindow(recvWindow int64) *GetRateLimitService { 17 | s.recvWindow = &recvWindow 18 | return s 19 | } 20 | 21 | // Do send request 22 | func (s *GetRateLimitService) Do(ctx context.Context, opts ...RequestOption) ([]*RateLimit, error) { 23 | r := &request{ 24 | method: http.MethodGet, 25 | endpoint: "/papi/v1/rateLimit/order", 26 | secType: secTypeSigned, 27 | } 28 | if s.recvWindow != nil { 29 | r.setParam("recvWindow", *s.recvWindow) 30 | } 31 | data, _, err := s.c.callAPI(ctx, r, opts...) 32 | if err != nil { 33 | return nil, err 34 | } 35 | res := make([]*RateLimit, 0) 36 | err = json.Unmarshal(data, &res) 37 | if err != nil { 38 | return nil, err 39 | } 40 | return res, nil 41 | } 42 | 43 | // RateLimit define rate limit info 44 | type RateLimit struct { 45 | RateLimitType string `json:"rateLimitType"` 46 | Interval string `json:"interval"` 47 | IntervalNum int64 `json:"intervalNum"` 48 | Limit int64 `json:"limit"` 49 | } 50 | -------------------------------------------------------------------------------- /v2/portfolio/rate_limit_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type rateLimitServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestRateLimitServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &rateLimitServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetRateLimit", func(t *testing.T) { 22 | service := suite.client.NewGetRateLimitService() 23 | res, err := service.Do(context.Background()) 24 | 25 | if err != nil { 26 | t.Fatalf("Failed to get rate limit: %v", err) 27 | } 28 | 29 | if len(res) == 0 { 30 | t.Error("Expected at least one rate limit") 31 | } 32 | 33 | for _, limit := range res { 34 | if limit.RateLimitType == "" { 35 | t.Error("Expected non-empty rate limit type") 36 | } 37 | if limit.Interval == "" { 38 | t.Error("Expected non-empty interval") 39 | } 40 | if limit.IntervalNum <= 0 { 41 | t.Error("Expected positive interval number") 42 | } 43 | if limit.Limit <= 0 { 44 | t.Error("Expected positive limit") 45 | } 46 | } 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /v2/portfolio/rate_limit_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type rateLimitServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestRateLimitService(t *testing.T) { 14 | suite.Run(t, new(rateLimitServiceTestSuite)) 15 | } 16 | 17 | func (s *rateLimitServiceTestSuite) TestGetRateLimit() { 18 | data := []byte(`[ 19 | { 20 | "rateLimitType": "ORDERS", 21 | "interval": "MINUTE", 22 | "intervalNum": 1, 23 | "limit": 1200 24 | } 25 | ]`) 26 | s.mockDo(data, nil) 27 | defer s.assertDo() 28 | 29 | recvWindow := int64(5000) 30 | 31 | s.assertReq(func(r *request) { 32 | e := newSignedRequest() 33 | e.setParam("recvWindow", recvWindow) 34 | s.assertRequestEqual(e, r) 35 | }) 36 | 37 | res, err := s.client.NewGetRateLimitService(). 38 | RecvWindow(recvWindow). 39 | Do(newContext()) 40 | 41 | s.r().NoError(err) 42 | s.r().Len(res, 1) 43 | s.r().Equal("ORDERS", res[0].RateLimitType) 44 | s.r().Equal("MINUTE", res[0].Interval) 45 | s.r().Equal(int64(1), res[0].IntervalNum) 46 | s.r().Equal(int64(1200), res[0].Limit) 47 | } 48 | -------------------------------------------------------------------------------- /v2/portfolio/repay_futures_negative_balance_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // RepayFuturesNegativeBalanceService repay futures negative balance 10 | type RepayFuturesNegativeBalanceService struct { 11 | c *Client 12 | } 13 | 14 | // Do send request 15 | func (s *RepayFuturesNegativeBalanceService) Do(ctx context.Context, opts ...RequestOption) (*SuccessResponse, error) { 16 | r := &request{ 17 | method: http.MethodPost, 18 | endpoint: "/papi/v1/repay-futures-negative-balance", 19 | secType: secTypeSigned, 20 | } 21 | 22 | data, _, err := s.c.callAPI(ctx, r, opts...) 23 | if err != nil { 24 | return nil, err 25 | } 26 | res := new(SuccessResponse) 27 | err = json.Unmarshal(data, res) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return res, nil 32 | } 33 | -------------------------------------------------------------------------------- /v2/portfolio/repay_futures_negative_balance_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type repayFuturesNegativeBalanceServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestRepayFuturesNegativeBalanceServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &repayFuturesNegativeBalanceServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("RepayFuturesNegativeBalance", func(t *testing.T) { 22 | service := &RepayFuturesNegativeBalanceService{c: suite.client} 23 | res, err := service.Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to repay futures negative balance: %v", err) 26 | } 27 | 28 | if res.Msg != "success" { 29 | t.Errorf("Expected success message, got %v", res.Msg) 30 | } 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /v2/portfolio/repay_futures_negative_balance_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type repayFuturesNegativeBalanceServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestRepayFuturesNegativeBalanceService(t *testing.T) { 14 | suite.Run(t, new(repayFuturesNegativeBalanceServiceTestSuite)) 15 | } 16 | 17 | func (s *repayFuturesNegativeBalanceServiceTestSuite) TestRepayFuturesNegativeBalance() { 18 | data := []byte(`{ 19 | "msg": "success" 20 | }`) 21 | s.mockDo(data, nil) 22 | defer s.assertDo() 23 | 24 | s.assertReq(func(r *request) { 25 | e := newSignedRequest() 26 | s.assertRequestEqual(e, r) 27 | }) 28 | 29 | res, err := s.client.NewRepayFuturesNegativeBalanceService().Do(newContext()) 30 | s.r().NoError(err) 31 | s.r().Equal("success", res.Msg) 32 | } 33 | -------------------------------------------------------------------------------- /v2/portfolio/request_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestWithExtraForm(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | r *request 13 | m map[string]any 14 | wantRequest *request 15 | }{ 16 | { 17 | name: "place order use extra priceMatch and goodTillDate", 18 | r: &request{ 19 | form: map[string][]string{ 20 | "symbol": {"BTCUSDT"}, 21 | "orderId": {"1"}, 22 | }, 23 | }, 24 | m: map[string]any{ 25 | "priceMatch": "QUEUE", 26 | "goodTillDate": 1697796587000, 27 | }, 28 | wantRequest: &request{ 29 | form: map[string][]string{ 30 | "symbol": {"BTCUSDT"}, 31 | "orderId": {"1"}, 32 | "priceMatch": {"QUEUE"}, 33 | "goodTillDate": {"1697796587000"}, 34 | }, 35 | }, 36 | }, 37 | } 38 | for _, tt := range tests { 39 | t.Run(tt.name, func(t *testing.T) { 40 | 41 | opt := WithExtraForm(tt.m) 42 | opt(tt.r) 43 | 44 | assert.Equal(t, tt.wantRequest, tt.r) 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /v2/portfolio/server_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | // PingService ping server 9 | type PingService struct { 10 | c *Client 11 | } 12 | 13 | // Do send request 14 | func (s *PingService) Do(ctx context.Context, opts ...RequestOption) (err error) { 15 | r := &request{ 16 | method: http.MethodGet, 17 | endpoint: "/papi/v1/ping", 18 | } 19 | _, _, err = s.c.callAPI(ctx, r, opts...) 20 | return err 21 | } 22 | -------------------------------------------------------------------------------- /v2/portfolio/server_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type serverServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestServerServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &serverServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("Ping", func(t *testing.T) { 22 | service := &PingService{c: suite.client} 23 | err := service.Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to ping server: %v", err) 26 | } 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /v2/portfolio/server_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type serverServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestServerService(t *testing.T) { 14 | suite.Run(t, new(serverServiceTestSuite)) 15 | } 16 | 17 | func (s *serverServiceTestSuite) TestPing() { 18 | data := []byte(`{}`) 19 | s.mockDo(data, nil) 20 | defer s.assertDo() 21 | 22 | s.assertReq(func(r *request) { 23 | e := newRequest() 24 | s.assertRequestEqual(e, r) 25 | }) 26 | 27 | err := s.client.NewPingService().Do(newContext()) 28 | s.r().NoError(err) 29 | } 30 | -------------------------------------------------------------------------------- /v2/portfolio/um_account_config_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | ) 7 | 8 | // UMAccountConfigService get UM futures account configuration 9 | type UMAccountConfigService struct { 10 | c *Client 11 | } 12 | 13 | // UMAccountConfig define UM futures account configuration 14 | type UMAccountConfig struct { 15 | FeeTier int `json:"feeTier"` // Account commission tier 16 | CanTrade bool `json:"canTrade"` // If can trade 17 | CanDeposit bool `json:"canDeposit"` // If can transfer in asset 18 | CanWithdraw bool `json:"canWithdraw"` // If can transfer out asset 19 | DualSidePosition bool `json:"dualSidePosition"` // If dual side position is enabled 20 | UpdateTime int64 `json:"updateTime"` // Reserved property 21 | MultiAssetsMargin bool `json:"multiAssetsMargin"` 22 | TradeGroupId int `json:"tradeGroupId"` 23 | } 24 | 25 | // Do send request 26 | func (s *UMAccountConfigService) Do(ctx context.Context) (*UMAccountConfig, error) { 27 | r := &request{ 28 | method: "GET", 29 | endpoint: "/papi/v1/um/accountConfig", 30 | secType: secTypeSigned, 31 | } 32 | data, _, err := s.c.callAPI(ctx, r) 33 | if err != nil { 34 | return nil, err 35 | } 36 | res := new(UMAccountConfig) 37 | err = json.Unmarshal(data, res) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return res, nil 42 | } 43 | -------------------------------------------------------------------------------- /v2/portfolio/um_account_config_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umAccountConfigServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMAccountConfigServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umAccountConfigServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetUMAccountConfig", func(t *testing.T) { 22 | service := &UMAccountConfigService{c: suite.client} 23 | config, err := service.Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get UM account config: %v", err) 26 | } 27 | 28 | // Basic validation of returned data 29 | if config.UpdateTime == 0 { 30 | t.Error("Expected non-zero update time") 31 | } 32 | 33 | // Validate boolean fields are set 34 | if !config.CanTrade && !config.CanDeposit && !config.CanWithdraw { 35 | t.Error("Expected at least one permission to be true") 36 | } 37 | 38 | // Validate fee tier 39 | if config.FeeTier < 0 { 40 | t.Error("Expected non-negative fee tier") 41 | } 42 | 43 | // Validate trade group ID 44 | if config.TradeGroupId == 0 { 45 | t.Error("Expected non-zero trade group ID") 46 | } 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /v2/portfolio/um_account_config_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umAccountConfigServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMAccountConfigService(t *testing.T) { 14 | suite.Run(t, new(umAccountConfigServiceTestSuite)) 15 | } 16 | 17 | func (s *umAccountConfigServiceTestSuite) TestGetUMAccountConfig() { 18 | data := []byte(`{ 19 | "feeTier": 0, 20 | "canTrade": true, 21 | "canDeposit": true, 22 | "canWithdraw": true, 23 | "dualSidePosition": true, 24 | "updateTime": 1724416653850, 25 | "multiAssetsMargin": false, 26 | "tradeGroupId": -1 27 | }`) 28 | 29 | s.mockDo(data, nil) 30 | defer s.assertDo() 31 | 32 | expected := &UMAccountConfig{ 33 | FeeTier: 0, 34 | CanTrade: true, 35 | CanDeposit: true, 36 | CanWithdraw: true, 37 | DualSidePosition: true, 38 | UpdateTime: 1724416653850, 39 | MultiAssetsMargin: false, 40 | TradeGroupId: -1, 41 | } 42 | 43 | s.assertReq(func(r *request) { 44 | e := newSignedRequest() 45 | s.assertRequestEqual(e, r) 46 | }) 47 | 48 | config, err := s.client.NewGetUMAccountConfigService().Do(newContext()) 49 | s.r().NoError(err) 50 | s.r().Equal(expected, config) 51 | } 52 | -------------------------------------------------------------------------------- /v2/portfolio/um_account_detail_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umAccountDetailServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMAccountDetailServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umAccountDetailServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetUMAccountDetail", func(t *testing.T) { 22 | service := &GetUMAccountDetailService{c: suite.client} 23 | res, err := service.Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get UM account detail: %v", err) 26 | } 27 | 28 | if len(res.Assets) == 0 { 29 | t.Error("Expected non-empty assets") 30 | } 31 | for _, asset := range res.Assets { 32 | if asset.Asset == "" { 33 | t.Error("Expected non-empty asset name") 34 | } 35 | } 36 | 37 | if len(res.Positions) == 0 { 38 | t.Error("Expected non-empty positions") 39 | } 40 | for _, position := range res.Positions { 41 | if position.Symbol == "" { 42 | t.Error("Expected non-empty symbol") 43 | } 44 | } 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /v2/portfolio/um_account_detail_v2_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umAccountDetailV2ServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMAccountDetailV2ServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umAccountDetailV2ServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetUMAccountDetailV2", func(t *testing.T) { 22 | service := &UMAccountDetailV2Service{c: suite.client} 23 | res, err := service.Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get UM account detail v2: %v", err) 26 | } 27 | 28 | // Validate assets 29 | if len(res.Assets) == 0 { 30 | t.Error("Expected at least one asset") 31 | } 32 | 33 | for _, asset := range res.Assets { 34 | if asset.Asset == "" { 35 | t.Error("Expected non-empty asset name") 36 | } 37 | } 38 | 39 | // Validate positions 40 | for _, position := range res.Positions { 41 | if position.Symbol == "" { 42 | t.Error("Expected non-empty symbol") 43 | } 44 | if position.PositionSide == "" { 45 | t.Error("Expected non-empty position side") 46 | } 47 | // Position amount and notional can be "0" 48 | } 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /v2/portfolio/um_adl_quantile_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // UMADLQuantileService service to get UM position ADL quantile estimation 10 | type UMADLQuantileService struct { 11 | c *Client 12 | symbol *string 13 | recvWindow *int64 14 | } 15 | 16 | // Symbol set symbol 17 | func (s *UMADLQuantileService) Symbol(symbol string) *UMADLQuantileService { 18 | s.symbol = &symbol 19 | return s 20 | } 21 | 22 | // RecvWindow set recvWindow 23 | func (s *UMADLQuantileService) RecvWindow(recvWindow int64) *UMADLQuantileService { 24 | s.recvWindow = &recvWindow 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *UMADLQuantileService) Do(ctx context.Context) ([]*UMADLQuantileResponse, error) { 30 | r := &request{ 31 | method: http.MethodGet, 32 | endpoint: "/papi/v1/um/adlQuantile", 33 | secType: secTypeSigned, 34 | } 35 | if s.symbol != nil { 36 | r.setParam("symbol", *s.symbol) 37 | } 38 | if s.recvWindow != nil { 39 | r.setParam("recvWindow", *s.recvWindow) 40 | } 41 | 42 | data, _, err := s.c.callAPI(ctx, r) 43 | if err != nil { 44 | return nil, err 45 | } 46 | var res []*UMADLQuantileResponse 47 | err = json.Unmarshal(data, &res) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return res, nil 52 | } 53 | -------------------------------------------------------------------------------- /v2/portfolio/um_cancel_all_orders_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // UMCancelAllOrdersService service to cancel all active UM orders on a symbol 10 | type UMCancelAllOrdersService struct { 11 | c *Client 12 | symbol string 13 | recvWindow *int64 14 | } 15 | 16 | // Symbol set symbol 17 | func (s *UMCancelAllOrdersService) Symbol(symbol string) *UMCancelAllOrdersService { 18 | s.symbol = symbol 19 | return s 20 | } 21 | 22 | // RecvWindow set recvWindow 23 | func (s *UMCancelAllOrdersService) RecvWindow(recvWindow int64) *UMCancelAllOrdersService { 24 | s.recvWindow = &recvWindow 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *UMCancelAllOrdersService) Do(ctx context.Context, opts ...RequestOption) (res *UMCancelAllOrdersResponse, err error) { 30 | r := &request{ 31 | method: http.MethodDelete, 32 | endpoint: "/papi/v1/um/allOpenOrders", 33 | secType: secTypeSigned, 34 | } 35 | 36 | r.setParam("symbol", s.symbol) 37 | if s.recvWindow != nil { 38 | r.setParam("recvWindow", *s.recvWindow) 39 | } 40 | 41 | data, _, err := s.c.callAPI(ctx, r, opts...) 42 | if err != nil { 43 | return nil, err 44 | } 45 | res = new(UMCancelAllOrdersResponse) 46 | err = json.Unmarshal(data, res) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return res, nil 51 | } 52 | 53 | // UMCancelAllOrdersResponse defines cancel all orders response 54 | type UMCancelAllOrdersResponse struct { 55 | Code int `json:"code"` 56 | Msg string `json:"msg"` 57 | } 58 | -------------------------------------------------------------------------------- /v2/portfolio/um_cancel_all_orders_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umCancelAllOrdersServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMCancelAllOrdersService(t *testing.T) { 14 | suite.Run(t, new(umCancelAllOrdersServiceTestSuite)) 15 | } 16 | 17 | func (s *umCancelAllOrdersServiceTestSuite) TestCancelAllOrders() { 18 | data := []byte(`{ 19 | "code": 200, 20 | "msg": "The operation of cancel all open order is done." 21 | }`) 22 | s.mockDo(data, nil) 23 | defer s.assertDo() 24 | 25 | symbol := "BTCUSDT" 26 | recvWindow := int64(5000) 27 | 28 | s.assertReq(func(r *request) { 29 | e := newSignedRequest() 30 | e.setParam("symbol", symbol) 31 | e.setParam("recvWindow", recvWindow) 32 | s.assertRequestEqual(e, r) 33 | }) 34 | 35 | res, err := s.client.NewUMCancelAllOrdersService(). 36 | Symbol(symbol). 37 | RecvWindow(recvWindow). 38 | Do(newContext()) 39 | 40 | s.r().NoError(err) 41 | s.r().Equal(200, res.Code) 42 | s.r().Equal("The operation of cancel all open order is done.", res.Msg) 43 | } 44 | -------------------------------------------------------------------------------- /v2/portfolio/um_commission_rate_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // GetUMCommissionRateService get user commission rate for UM 10 | type GetUMCommissionRateService struct { 11 | c *Client 12 | symbol string 13 | } 14 | 15 | // Symbol set symbol 16 | func (s *GetUMCommissionRateService) Symbol(symbol string) *GetUMCommissionRateService { 17 | s.symbol = symbol 18 | return s 19 | } 20 | 21 | // Do send request 22 | func (s *GetUMCommissionRateService) Do(ctx context.Context, opts ...RequestOption) (*CommissionRate, error) { 23 | r := &request{ 24 | method: http.MethodGet, 25 | endpoint: "/papi/v1/um/commissionRate", 26 | secType: secTypeSigned, 27 | } 28 | r.setParam("symbol", s.symbol) 29 | 30 | data, _, err := s.c.callAPI(ctx, r, opts...) 31 | if err != nil { 32 | return nil, err 33 | } 34 | res := new(CommissionRate) 35 | err = json.Unmarshal(data, res) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return res, nil 40 | } 41 | 42 | // CommissionRate define commission rate info 43 | type CommissionRate struct { 44 | Symbol string `json:"symbol"` 45 | MakerCommissionRate string `json:"makerCommissionRate"` // 0.02% 46 | TakerCommissionRate string `json:"takerCommissionRate"` // 0.04% 47 | } 48 | -------------------------------------------------------------------------------- /v2/portfolio/um_commission_rate_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umCommissionRateServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMCommissionRateServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umCommissionRateServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetCommissionRate", func(t *testing.T) { 22 | service := &GetUMCommissionRateService{c: suite.client} 23 | rates, err := service.Symbol("BTCUSDT").Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get commission rates: %v", err) 26 | } 27 | 28 | if rates.Symbol != "BTCUSDT" { 29 | t.Errorf("Expected symbol BTCUSDT, got %v", rates.Symbol) 30 | } 31 | if rates.MakerCommissionRate == "" { 32 | t.Error("Expected non-empty maker commission rate") 33 | } 34 | if rates.TakerCommissionRate == "" { 35 | t.Error("Expected non-empty taker commission rate") 36 | } 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /v2/portfolio/um_commission_rate_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umCommissionRateServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMCommissionRateService(t *testing.T) { 14 | suite.Run(t, new(umCommissionRateServiceTestSuite)) 15 | } 16 | 17 | func (s *umCommissionRateServiceTestSuite) TestGetCommissionRate() { 18 | data := []byte(`{ 19 | "symbol": "BTCUSDT", 20 | "makerCommissionRate": "0.0002", 21 | "takerCommissionRate": "0.0004" 22 | }`) 23 | s.mockDo(data, nil) 24 | defer s.assertDo() 25 | 26 | symbol := "BTCUSDT" 27 | s.assertReq(func(r *request) { 28 | e := newSignedRequest() 29 | e.setParam("symbol", symbol) 30 | s.assertRequestEqual(e, r) 31 | }) 32 | 33 | rates, err := s.client.NewGetUMCommissionRateService().Symbol(symbol).Do(newContext()) 34 | s.r().NoError(err) 35 | s.r().Equal("BTCUSDT", rates.Symbol) 36 | s.r().Equal("0.0002", rates.MakerCommissionRate) 37 | s.r().Equal("0.0004", rates.TakerCommissionRate) 38 | } 39 | -------------------------------------------------------------------------------- /v2/portfolio/um_conditional_order_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umConditionalOrderServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMConditionalOrderServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umConditionalOrderServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("PlaceTrailingStopOrder", func(t *testing.T) { 22 | service := &UMConditionalOrderService{c: suite.client} 23 | order, err := service.Symbol("BTCUSDT"). 24 | Side(SideTypeBuy). 25 | StrategyType("TRAILING_STOP_MARKET"). 26 | CallbackRate("1"). 27 | Do(context.Background()) 28 | if err != nil { 29 | t.Fatalf("Failed to place conditional order: %v", err) 30 | } 31 | 32 | // Basic validation of returned data 33 | if order.Symbol != "BTCUSDT" { 34 | t.Error("Expected symbol to be BTCUSDT") 35 | } 36 | 37 | if order.Side != SideTypeBuy { 38 | t.Error("Expected side to be BUY") 39 | } 40 | 41 | if order.StrategyType != "TRAILING_STOP_MARKET" { 42 | t.Error("Expected strategy type to be TRAILING_STOP_MARKET") 43 | } 44 | 45 | if order.StrategyStatus == "" { 46 | t.Error("Expected non-empty strategy status") 47 | } 48 | 49 | if order.StrategyId == 0 { 50 | t.Error("Expected non-zero strategy ID") 51 | } 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /v2/portfolio/um_fee_burn_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // UMFeeBurnService service to toggle BNB burn on UM futures trade 10 | type UMFeeBurnService struct { 11 | c *Client 12 | feeBurn bool 13 | recvWindow *int64 14 | } 15 | 16 | // FeeBurn set feeBurn status 17 | func (s *UMFeeBurnService) FeeBurn(feeBurn bool) *UMFeeBurnService { 18 | s.feeBurn = feeBurn 19 | return s 20 | } 21 | 22 | // RecvWindow set recvWindow 23 | func (s *UMFeeBurnService) RecvWindow(recvWindow int64) *UMFeeBurnService { 24 | s.recvWindow = &recvWindow 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *UMFeeBurnService) Do(ctx context.Context) (*UMFeeBurnResponse, error) { 30 | r := &request{ 31 | method: http.MethodPost, 32 | endpoint: "/papi/v1/um/feeBurn", 33 | secType: secTypeSigned, 34 | } 35 | 36 | r.setParam("feeBurn", s.feeBurn) 37 | if s.recvWindow != nil { 38 | r.setParam("recvWindow", *s.recvWindow) 39 | } 40 | 41 | data, _, err := s.c.callAPI(ctx, r) 42 | if err != nil { 43 | return nil, err 44 | } 45 | res := new(UMFeeBurnResponse) 46 | err = json.Unmarshal(data, res) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return res, nil 51 | } 52 | 53 | // UMFeeBurnResponse define response for toggling BNB burn 54 | type UMFeeBurnResponse struct { 55 | Code int `json:"code"` 56 | Msg string `json:"msg"` 57 | } 58 | -------------------------------------------------------------------------------- /v2/portfolio/um_fee_burn_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umFeeBurnServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMFeeBurnService(t *testing.T) { 14 | suite.Run(t, new(umFeeBurnServiceTestSuite)) 15 | } 16 | 17 | func (s *umFeeBurnServiceTestSuite) TestToggleFeeBurn() { 18 | data := []byte(`{ 19 | "code": 200, 20 | "msg": "success" 21 | }`) 22 | s.mockDo(data, nil) 23 | defer s.assertDo() 24 | 25 | feeBurn := true 26 | s.assertReq(func(r *request) { 27 | e := newSignedRequest().setParams(params{ 28 | "feeBurn": feeBurn, 29 | }) 30 | s.assertRequestEqual(e, r) 31 | }) 32 | 33 | res, err := s.client.NewUMFeeBurnService(). 34 | FeeBurn(feeBurn). 35 | Do(newContext()) 36 | 37 | s.r().NoError(err) 38 | s.r().Equal(200, res.Code) 39 | s.r().Equal("success", res.Msg) 40 | } 41 | 42 | func (s *umFeeBurnServiceTestSuite) TestToggleFeeBurnWithRecvWindow() { 43 | data := []byte(`{ 44 | "code": 200, 45 | "msg": "success" 46 | }`) 47 | s.mockDo(data, nil) 48 | defer s.assertDo() 49 | 50 | feeBurn := false 51 | recvWindow := int64(1000) 52 | s.assertReq(func(r *request) { 53 | e := newSignedRequest().setParams(params{ 54 | "feeBurn": feeBurn, 55 | "recvWindow": recvWindow, 56 | }) 57 | s.assertRequestEqual(e, r) 58 | }) 59 | 60 | res, err := s.client.NewUMFeeBurnService(). 61 | FeeBurn(feeBurn). 62 | RecvWindow(recvWindow). 63 | Do(newContext()) 64 | 65 | s.r().NoError(err) 66 | s.r().Equal(200, res.Code) 67 | s.r().Equal("success", res.Msg) 68 | } 69 | -------------------------------------------------------------------------------- /v2/portfolio/um_fee_burn_status_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // UMFeeBurnStatusService service to get BNB burn status on UM futures trade 10 | type UMFeeBurnStatusService struct { 11 | c *Client 12 | recvWindow *int64 13 | } 14 | 15 | // RecvWindow set recvWindow 16 | func (s *UMFeeBurnStatusService) RecvWindow(recvWindow int64) *UMFeeBurnStatusService { 17 | s.recvWindow = &recvWindow 18 | return s 19 | } 20 | 21 | // Do send request 22 | func (s *UMFeeBurnStatusService) Do(ctx context.Context) (*UMFeeBurnStatusResponse, error) { 23 | r := &request{ 24 | method: http.MethodGet, 25 | endpoint: "/papi/v1/um/feeBurn", 26 | secType: secTypeSigned, 27 | } 28 | 29 | if s.recvWindow != nil { 30 | r.setParam("recvWindow", *s.recvWindow) 31 | } 32 | 33 | data, _, err := s.c.callAPI(ctx, r) 34 | if err != nil { 35 | return nil, err 36 | } 37 | res := new(UMFeeBurnStatusResponse) 38 | err = json.Unmarshal(data, res) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return res, nil 43 | } 44 | 45 | // UMFeeBurnStatusResponse define response for getting BNB burn status 46 | type UMFeeBurnStatusResponse struct { 47 | FeeBurn bool `json:"feeBurn"` 48 | } 49 | -------------------------------------------------------------------------------- /v2/portfolio/um_fee_burn_status_service_integration_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | type umFeeBurnStatusServiceIntegrationTestSuite struct { 9 | *baseIntegrationTestSuite 10 | } 11 | 12 | func TestUMFeeBurnStatusServiceIntegration(t *testing.T) { 13 | base := SetupTest(t) 14 | suite := &umFeeBurnStatusServiceIntegrationTestSuite{ 15 | baseIntegrationTestSuite: base, 16 | } 17 | 18 | t.Run("GetFeeBurnStatus", func(t *testing.T) { 19 | service := suite.client.NewUMFeeBurnStatusService() 20 | res, err := service.Do(context.Background()) 21 | if err != nil { 22 | t.Fatalf("Failed to get fee burn status: %v", err) 23 | } 24 | 25 | // Validate that we received a boolean response 26 | // The actual value could be either true or false depending on the account settings 27 | if res == nil { 28 | t.Fatal("Expected non-nil response") 29 | } 30 | }) 31 | 32 | t.Run("GetFeeBurnStatus_WithRecvWindow", func(t *testing.T) { 33 | service := suite.client.NewUMFeeBurnStatusService() 34 | res, err := service. 35 | RecvWindow(5000). 36 | Do(context.Background()) 37 | if err != nil { 38 | t.Fatalf("Failed to get fee burn status with recvWindow: %v", err) 39 | } 40 | 41 | // Validate that we received a boolean response 42 | if res == nil { 43 | t.Fatal("Expected non-nil response") 44 | } 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /v2/portfolio/um_fee_burn_status_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umFeeBurnStatusServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMFeeBurnStatusService(t *testing.T) { 14 | suite.Run(t, new(umFeeBurnStatusServiceTestSuite)) 15 | } 16 | 17 | func (s *umFeeBurnStatusServiceTestSuite) TestGetFeeBurnStatus() { 18 | data := []byte(`{ 19 | "feeBurn": true 20 | }`) 21 | s.mockDo(data, nil) 22 | defer s.assertDo() 23 | 24 | s.assertReq(func(r *request) { 25 | e := newSignedRequest() 26 | s.assertRequestEqual(e, r) 27 | }) 28 | 29 | res, err := s.client.NewUMFeeBurnStatusService(). 30 | Do(newContext()) 31 | 32 | s.r().NoError(err) 33 | s.r().True(res.FeeBurn) 34 | } 35 | 36 | func (s *umFeeBurnStatusServiceTestSuite) TestGetFeeBurnStatusWithRecvWindow() { 37 | data := []byte(`{ 38 | "feeBurn": false 39 | }`) 40 | s.mockDo(data, nil) 41 | defer s.assertDo() 42 | 43 | recvWindow := int64(1000) 44 | s.assertReq(func(r *request) { 45 | e := newSignedRequest().setParams(params{ 46 | "recvWindow": recvWindow, 47 | }) 48 | s.assertRequestEqual(e, r) 49 | }) 50 | 51 | res, err := s.client.NewUMFeeBurnStatusService(). 52 | RecvWindow(recvWindow). 53 | Do(newContext()) 54 | 55 | s.r().NoError(err) 56 | s.r().False(res.FeeBurn) 57 | } 58 | -------------------------------------------------------------------------------- /v2/portfolio/um_income_history_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type umIncomeHistoryServiceIntegrationTestSuite struct { 13 | *baseIntegrationTestSuite 14 | } 15 | 16 | func TestUMIncomeHistoryServiceIntegration(t *testing.T) { 17 | base := SetupTest(t) 18 | suite := &umIncomeHistoryServiceIntegrationTestSuite{ 19 | baseIntegrationTestSuite: base, 20 | } 21 | 22 | t.Run("GetUMIncomeHistory", func(t *testing.T) { 23 | service := &GetUMIncomeHistoryService{c: suite.client} 24 | endTime := time.Now().UnixMilli() 25 | startTime := endTime - 7*24*60*60*1000 // 7 days ago 26 | 27 | incomes, err := service. 28 | StartTime(startTime). 29 | EndTime(endTime). 30 | Limit(100). 31 | Do(context.Background()) 32 | 33 | if err != nil { 34 | t.Fatalf("Failed to get UM income history: %v", err) 35 | } 36 | 37 | for _, income := range incomes { 38 | if income.Asset == "" { 39 | t.Error("Expected non-empty asset") 40 | } 41 | if income.IncomeType == "" { 42 | t.Error("Expected non-empty income type") 43 | } 44 | if income.TranID == 0 { 45 | t.Error("Expected non-zero transaction ID") 46 | } 47 | } 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /v2/portfolio/um_leverage_bracket_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umLeverageBracketServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMLeverageBracketServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umLeverageBracketServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetLeverageBracket", func(t *testing.T) { 22 | service := &GetUMLeverageBracketService{c: suite.client} 23 | brackets, err := service.Symbol("BTCUSDT").Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get leverage bracket: %v", err) 26 | } 27 | 28 | for _, bracket := range brackets { 29 | if bracket.Symbol == "" { 30 | t.Error("Expected non-empty symbol") 31 | } 32 | if len(bracket.Brackets) == 0 { 33 | t.Error("Expected non-empty brackets") 34 | } 35 | } 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /v2/portfolio/um_leverage_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // ChangeUMInitialLeverageService change user's initial leverage of specific symbol in UM 10 | type ChangeUMInitialLeverageService struct { 11 | c *Client 12 | symbol string 13 | leverage int 14 | } 15 | 16 | // Symbol set symbol 17 | func (s *ChangeUMInitialLeverageService) Symbol(symbol string) *ChangeUMInitialLeverageService { 18 | s.symbol = symbol 19 | return s 20 | } 21 | 22 | // Leverage set leverage 23 | func (s *ChangeUMInitialLeverageService) Leverage(leverage int) *ChangeUMInitialLeverageService { 24 | s.leverage = leverage 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *ChangeUMInitialLeverageService) Do(ctx context.Context, opts ...RequestOption) (res *UMLeverage, err error) { 30 | r := &request{ 31 | method: http.MethodPost, 32 | endpoint: "/papi/v1/um/leverage", 33 | secType: secTypeSigned, 34 | } 35 | r.setParam("symbol", s.symbol) 36 | r.setParam("leverage", s.leverage) 37 | 38 | data, _, err := s.c.callAPI(ctx, r, opts...) 39 | if err != nil { 40 | return nil, err 41 | } 42 | res = new(UMLeverage) 43 | err = json.Unmarshal(data, res) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return res, nil 48 | } 49 | 50 | // UMLeverage define leverage info 51 | type UMLeverage struct { 52 | Leverage int `json:"leverage"` 53 | MaxNotionalValue string `json:"maxNotionalValue"` 54 | Symbol string `json:"symbol"` 55 | } 56 | -------------------------------------------------------------------------------- /v2/portfolio/um_leverage_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umLeverageServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMLeverageServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umLeverageServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("ChangeLeverage", func(t *testing.T) { 22 | service := &ChangeUMInitialLeverageService{c: suite.client} 23 | res, err := service. 24 | Symbol("BTCUSDT"). 25 | Leverage(20). 26 | Do(context.Background()) 27 | if err != nil { 28 | t.Fatalf("Failed to change leverage: %v", err) 29 | } 30 | 31 | // Basic validation of returned data 32 | if res.Symbol != "BTCUSDT" { 33 | t.Errorf("Expected symbol BTCUSDT, got %v", res.Symbol) 34 | } 35 | 36 | if res.Leverage != 20 { 37 | t.Errorf("Expected leverage 20, got %v", res.Leverage) 38 | } 39 | 40 | if res.MaxNotionalValue == "" { 41 | t.Error("Expected non-empty max notional value") 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /v2/portfolio/um_leverage_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umLeverageServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMLeverageService(t *testing.T) { 14 | suite.Run(t, new(umLeverageServiceTestSuite)) 15 | } 16 | 17 | func (s *umLeverageServiceTestSuite) TestChangeLeverage() { 18 | data := []byte(`{ 19 | "leverage": 21, 20 | "maxNotionalValue": "1000000", 21 | "symbol": "BTCUSDT" 22 | }`) 23 | s.mockDo(data, nil) 24 | defer s.assertDo() 25 | 26 | symbol := "BTCUSDT" 27 | leverage := 21 28 | s.assertReq(func(r *request) { 29 | e := newSignedRequest() 30 | e.setParam("symbol", symbol) 31 | e.setParam("leverage", leverage) 32 | s.assertRequestEqual(e, r) 33 | }) 34 | 35 | res, err := s.client.NewChangeUMInitialLeverageService(). 36 | Symbol(symbol). 37 | Leverage(leverage). 38 | Do(newContext()) 39 | s.r().NoError(err) 40 | s.assertLeverageEqual(res, &UMLeverage{ 41 | Leverage: 21, 42 | MaxNotionalValue: "1000000", 43 | Symbol: "BTCUSDT", 44 | }) 45 | } 46 | 47 | func (s *umLeverageServiceTestSuite) assertLeverageEqual(a, e *UMLeverage) { 48 | r := s.r() 49 | r.Equal(e.Leverage, a.Leverage, "Leverage") 50 | r.Equal(e.MaxNotionalValue, a.MaxNotionalValue, "MaxNotionalValue") 51 | r.Equal(e.Symbol, a.Symbol, "Symbol") 52 | } 53 | -------------------------------------------------------------------------------- /v2/portfolio/um_open_conditional_orders_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // UMOpenConditionalOrdersService service to get all current UM open conditional orders 10 | type UMOpenConditionalOrdersService struct { 11 | c *Client 12 | symbol *string 13 | recvWindow *int64 14 | } 15 | 16 | // Symbol set symbol 17 | func (s *UMOpenConditionalOrdersService) Symbol(symbol string) *UMOpenConditionalOrdersService { 18 | s.symbol = &symbol 19 | return s 20 | } 21 | 22 | // RecvWindow set recvWindow 23 | func (s *UMOpenConditionalOrdersService) RecvWindow(recvWindow int64) *UMOpenConditionalOrdersService { 24 | s.recvWindow = &recvWindow 25 | return s 26 | } 27 | 28 | // Do send request 29 | func (s *UMOpenConditionalOrdersService) Do(ctx context.Context) ([]*UMOpenConditionalOrderResponse, error) { 30 | r := &request{ 31 | method: http.MethodGet, 32 | endpoint: "/papi/v1/um/conditional/openOrders", 33 | secType: secTypeSigned, 34 | } 35 | if s.symbol != nil { 36 | r.setParam("symbol", *s.symbol) 37 | } 38 | if s.recvWindow != nil { 39 | r.setParam("recvWindow", *s.recvWindow) 40 | } 41 | 42 | data, _, err := s.c.callAPI(ctx, r) 43 | if err != nil { 44 | return nil, err 45 | } 46 | var res []*UMOpenConditionalOrderResponse 47 | err = json.Unmarshal(data, &res) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return res, nil 52 | } 53 | -------------------------------------------------------------------------------- /v2/portfolio/um_order_download_link_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umOrderDownloadLinkServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMOrderDownloadLinkServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umOrderDownloadLinkServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetUMOrderDownloadLink", func(t *testing.T) { 22 | downloadID := "953366156082814976" 23 | service := suite.client.NewGetUMOrderDownloadLinkService() 24 | res, err := service. 25 | DownloadID(downloadID). 26 | Do(context.Background()) 27 | 28 | if err != nil { 29 | t.Fatalf("Failed to get UM order download link: %v", err) 30 | } 31 | 32 | if res.DownloadID == "" { 33 | t.Error("Expected non-empty download ID") 34 | } 35 | 36 | if res.Status != "completed" && res.Status != "processing" { 37 | t.Error("Expected status to be either 'completed' or 'processing'") 38 | } 39 | 40 | if res.Status == "completed" && res.URL == "" { 41 | t.Error("Expected non-empty URL for completed status") 42 | } 43 | 44 | if res.ExpirationTimestamp <= 0 && res.Status == "completed" { 45 | t.Error("Expected positive expiration timestamp for completed status") 46 | } 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /v2/portfolio/um_order_download_link_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umOrderDownloadLinkServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMOrderDownloadLinkService(t *testing.T) { 14 | suite.Run(t, new(umOrderDownloadLinkServiceTestSuite)) 15 | } 16 | 17 | func (s *umOrderDownloadLinkServiceTestSuite) TestGetUMOrderDownloadLink() { 18 | data := []byte(`{ 19 | "downloadId": "545923594199212032", 20 | "status": "completed", 21 | "url": "www.binance.com", 22 | "s3Link": null, 23 | "notified": true, 24 | "expirationTimestamp": 1645009771000, 25 | "isExpired": null 26 | }`) 27 | s.mockDo(data, nil) 28 | defer s.assertDo() 29 | 30 | downloadID := "545923594199212032" 31 | recvWindow := int64(5000) 32 | 33 | s.assertReq(func(r *request) { 34 | e := newSignedRequest() 35 | e.setParam("downloadId", downloadID) 36 | e.setParam("recvWindow", recvWindow) 37 | s.assertRequestEqual(e, r) 38 | }) 39 | 40 | res, err := s.client.NewGetUMOrderDownloadLinkService(). 41 | DownloadID(downloadID). 42 | RecvWindow(recvWindow). 43 | Do(newContext()) 44 | 45 | s.r().NoError(err) 46 | s.r().Equal("545923594199212032", res.DownloadID) 47 | s.r().Equal("completed", res.Status) 48 | s.r().Equal("www.binance.com", res.URL) 49 | s.r().Equal(true, res.Notified) 50 | s.r().Equal(int64(1645009771000), res.ExpirationTimestamp) 51 | } 52 | -------------------------------------------------------------------------------- /v2/portfolio/um_order_history_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type umOrderHistoryServiceIntegrationTestSuite struct { 13 | *baseIntegrationTestSuite 14 | } 15 | 16 | func TestUMOrderHistoryServiceIntegration(t *testing.T) { 17 | base := SetupTest(t) 18 | suite := &umOrderHistoryServiceIntegrationTestSuite{ 19 | baseIntegrationTestSuite: base, 20 | } 21 | 22 | t.Run("GetUMOrderHistoryDownloadID", func(t *testing.T) { 23 | endTime := time.Now().UnixMilli() 24 | startTime := endTime - 24*60*60*1000 // 24 hours ago 25 | 26 | service := suite.client.NewGetUMOrderHistoryDownloadIDService() 27 | res, err := service. 28 | StartTime(startTime). 29 | EndTime(endTime). 30 | Do(context.Background()) 31 | 32 | if err != nil { 33 | t.Fatalf("Failed to get UM order history download ID: %v", err) 34 | } 35 | 36 | if res.DownloadID == "" { 37 | t.Error("Expected non-empty download ID") 38 | } 39 | 40 | if res.AvgCostTimestampOfLast30d <= 0 { 41 | t.Error("Expected positive average cost timestamp") 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /v2/portfolio/um_order_history_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umOrderHistoryServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMOrderHistoryService(t *testing.T) { 14 | suite.Run(t, new(umOrderHistoryServiceTestSuite)) 15 | } 16 | 17 | func (s *umOrderHistoryServiceTestSuite) TestGetUMOrderHistoryDownloadID() { 18 | data := []byte(`{ 19 | "avgCostTimestampOfLast30d": 7241837, 20 | "downloadId": "546975389218332672" 21 | }`) 22 | s.mockDo(data, nil) 23 | defer s.assertDo() 24 | 25 | startTime := int64(1622555222000) 26 | endTime := int64(1622555522000) 27 | recvWindow := int64(5000) 28 | 29 | s.assertReq(func(r *request) { 30 | e := newSignedRequest() 31 | e.setParam("startTime", startTime) 32 | e.setParam("endTime", endTime) 33 | e.setParam("recvWindow", recvWindow) 34 | s.assertRequestEqual(e, r) 35 | }) 36 | 37 | res, err := s.client.NewGetUMOrderHistoryDownloadIDService(). 38 | StartTime(startTime). 39 | EndTime(endTime). 40 | RecvWindow(recvWindow). 41 | Do(newContext()) 42 | 43 | s.r().NoError(err) 44 | s.r().Equal(int64(7241837), res.AvgCostTimestampOfLast30d) 45 | s.r().Equal("546975389218332672", res.DownloadID) 46 | } 47 | -------------------------------------------------------------------------------- /v2/portfolio/um_order_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umOrderServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMOrderServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umOrderServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("PlaceOrder", func(t *testing.T) { 22 | service := &UMOrderService{c: suite.client} 23 | order, err := service.Symbol("BTCUSDT"). 24 | Side(SideTypeBuy). 25 | Type(OrderTypeLimit). 26 | TimeInForce(TimeInForceTypeGTC). 27 | Quantity("0.001"). 28 | Price("20000"). 29 | Do(context.Background()) 30 | if err != nil { 31 | t.Fatalf("Failed to place order: %v", err) 32 | } 33 | 34 | // Basic validation of returned data 35 | if order.Symbol != "BTCUSDT" { 36 | t.Error("Expected symbol to be BTCUSDT") 37 | } 38 | 39 | if order.Side != SideTypeBuy { 40 | t.Error("Expected side to be BUY") 41 | } 42 | 43 | if order.Type != OrderTypeLimit { 44 | t.Error("Expected type to be LIMIT") 45 | } 46 | 47 | if order.TimeInForce != TimeInForceTypeGTC { 48 | t.Error("Expected timeInForce to be GTC") 49 | } 50 | 51 | if order.Status == "" { 52 | t.Error("Expected non-empty status") 53 | } 54 | 55 | if order.OrderID == 0 { 56 | t.Error("Expected non-zero order ID") 57 | } 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /v2/portfolio/um_position_mode_get_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // GetUMPositionModeService get user's position mode on EVERY symbol in UM 10 | type GetUMPositionModeService struct { 11 | c *Client 12 | } 13 | 14 | // Do send request 15 | func (s *GetUMPositionModeService) Do(ctx context.Context, opts ...RequestOption) (res *PositionMode, err error) { 16 | r := &request{ 17 | method: http.MethodGet, 18 | endpoint: "/papi/v1/um/positionSide/dual", 19 | secType: secTypeSigned, 20 | } 21 | 22 | data, _, err := s.c.callAPI(ctx, r, opts...) 23 | if err != nil { 24 | return nil, err 25 | } 26 | res = new(PositionMode) 27 | err = json.Unmarshal(data, res) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return res, nil 32 | } 33 | 34 | // PositionMode define position mode info 35 | type PositionMode struct { 36 | DualSidePosition bool `json:"dualSidePosition"` // true: Hedge Mode; false: One-way Mode 37 | } 38 | -------------------------------------------------------------------------------- /v2/portfolio/um_position_mode_get_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umPositionModeGetServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMPositionModeGetServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umPositionModeGetServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetPositionMode", func(t *testing.T) { 22 | service := &GetUMPositionModeService{c: suite.client} 23 | res, err := service.Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get position mode: %v", err) 26 | } 27 | 28 | // Basic validation that we got a response 29 | // The actual value could be either true or false depending on the account settings 30 | t.Logf("Current position mode: Hedge Mode: %v", res.DualSidePosition) 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /v2/portfolio/um_position_mode_get_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umPositionModeGetServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMPositionModeGetService(t *testing.T) { 14 | suite.Run(t, new(umPositionModeGetServiceTestSuite)) 15 | } 16 | 17 | func (s *umPositionModeGetServiceTestSuite) TestGetPositionMode() { 18 | data := []byte(`{ 19 | "dualSidePosition": true 20 | }`) 21 | s.mockDo(data, nil) 22 | defer s.assertDo() 23 | 24 | s.assertReq(func(r *request) { 25 | e := newSignedRequest() 26 | s.assertRequestEqual(e, r) 27 | }) 28 | 29 | res, err := s.client.NewGetUMPositionModeService().Do(newContext()) 30 | s.r().NoError(err) 31 | s.assertPositionModeEqual(res, &PositionMode{ 32 | DualSidePosition: true, 33 | }) 34 | } 35 | 36 | func (s *umPositionModeGetServiceTestSuite) assertPositionModeEqual(a, e *PositionMode) { 37 | r := s.r() 38 | r.Equal(e.DualSidePosition, a.DualSidePosition, "DualSidePosition") 39 | } 40 | -------------------------------------------------------------------------------- /v2/portfolio/um_position_mode_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // ChangeUMPositionModeService change user's position mode on EVERY symbol in UM 10 | type ChangeUMPositionModeService struct { 11 | c *Client 12 | dualSidePosition bool 13 | } 14 | 15 | // DualSidePosition set position mode 16 | func (s *ChangeUMPositionModeService) DualSidePosition(dualSidePosition bool) *ChangeUMPositionModeService { 17 | s.dualSidePosition = dualSidePosition 18 | return s 19 | } 20 | 21 | // Do send request 22 | func (s *ChangeUMPositionModeService) Do(ctx context.Context, opts ...RequestOption) (res *APIResponse, err error) { 23 | r := &request{ 24 | method: http.MethodPost, 25 | endpoint: "/papi/v1/um/positionSide/dual", 26 | secType: secTypeSigned, 27 | } 28 | r.setParam("dualSidePosition", s.dualSidePosition) 29 | 30 | data, _, err := s.c.callAPI(ctx, r, opts...) 31 | if err != nil { 32 | return nil, err 33 | } 34 | res = new(APIResponse) 35 | err = json.Unmarshal(data, res) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return res, nil 40 | } 41 | 42 | // APIResponse define API response 43 | type APIResponse struct { 44 | Code int `json:"code"` 45 | Msg string `json:"msg"` 46 | } 47 | -------------------------------------------------------------------------------- /v2/portfolio/um_position_mode_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umPositionModeServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMPositionModeServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umPositionModeServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("ChangePositionMode", func(t *testing.T) { 22 | service := &ChangeUMPositionModeService{c: suite.client} 23 | res, err := service. 24 | DualSidePosition(true). // Enable Hedge Mode 25 | Do(context.Background()) 26 | if err != nil { 27 | t.Fatalf("Failed to change position mode: %v", err) 28 | } 29 | 30 | // Basic validation of returned data 31 | if res.Code != 200 { 32 | t.Errorf("Expected code 200, got %v", res.Code) 33 | } 34 | 35 | if res.Msg != "success" { 36 | t.Errorf("Expected msg 'success', got %v", res.Msg) 37 | } 38 | 39 | // Test changing back to One-way Mode 40 | res, err = service. 41 | DualSidePosition(false). 42 | Do(context.Background()) 43 | if err != nil { 44 | t.Fatalf("Failed to change position mode back: %v", err) 45 | } 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /v2/portfolio/um_position_mode_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umPositionModeServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMPositionModeService(t *testing.T) { 14 | suite.Run(t, new(umPositionModeServiceTestSuite)) 15 | } 16 | 17 | func (s *umPositionModeServiceTestSuite) TestChangePositionMode() { 18 | data := []byte(`{ 19 | "code": 200, 20 | "msg": "success" 21 | }`) 22 | s.mockDo(data, nil) 23 | defer s.assertDo() 24 | 25 | dualSidePosition := true 26 | s.assertReq(func(r *request) { 27 | e := newSignedRequest() 28 | e.setParam("dualSidePosition", dualSidePosition) 29 | s.assertRequestEqual(e, r) 30 | }) 31 | 32 | res, err := s.client.NewChangeUMPositionModeService(). 33 | DualSidePosition(dualSidePosition). 34 | Do(newContext()) 35 | s.r().NoError(err) 36 | s.assertPositionModeResponseEqual(res, &APIResponse{ 37 | Code: 200, 38 | Msg: "success", 39 | }) 40 | } 41 | 42 | func (s *umPositionModeServiceTestSuite) assertPositionModeResponseEqual(a, e *APIResponse) { 43 | r := s.r() 44 | r.Equal(e.Code, a.Code, "Code") 45 | r.Equal(e.Msg, a.Msg, "Msg") 46 | } 47 | -------------------------------------------------------------------------------- /v2/portfolio/um_position_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // GetUMPositionRiskService get UM position risk information 10 | type GetUMPositionRiskService struct { 11 | c *Client 12 | symbol *string 13 | } 14 | 15 | // Symbol set symbol 16 | func (s *GetUMPositionRiskService) Symbol(symbol string) *GetUMPositionRiskService { 17 | s.symbol = &symbol 18 | return s 19 | } 20 | 21 | // Do send request 22 | func (s *GetUMPositionRiskService) Do(ctx context.Context, opts ...RequestOption) (res []*UMPosition, err error) { 23 | r := &request{ 24 | method: http.MethodGet, 25 | endpoint: "/papi/v1/um/positionRisk", 26 | secType: secTypeSigned, 27 | } 28 | if s.symbol != nil { 29 | r.setParam("symbol", *s.symbol) 30 | } 31 | 32 | data, _, err := s.c.callAPI(ctx, r, opts...) 33 | if err != nil { 34 | return []*UMPosition{}, err 35 | } 36 | res = make([]*UMPosition, 0) 37 | err = json.Unmarshal(data, &res) 38 | if err != nil { 39 | return []*UMPosition{}, err 40 | } 41 | return res, nil 42 | } 43 | -------------------------------------------------------------------------------- /v2/portfolio/um_position_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umPositionServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMPositionServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umPositionServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetPositionRisk", func(t *testing.T) { 22 | service := &GetUMPositionRiskService{c: suite.client} 23 | positions, err := service.Symbol("BTCUSDT").Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get position risk info: %v", err) 26 | } 27 | 28 | for _, position := range positions { 29 | if position.Symbol == "" { 30 | t.Error("Expected non-empty symbol") 31 | } 32 | 33 | if position.PositionSide == "" { 34 | t.Error("Expected non-empty position side") 35 | } 36 | 37 | if position.UpdateTime == 0 { 38 | t.Error("Expected non-zero update time") 39 | } 40 | } 41 | }) 42 | 43 | t.Run("GetAllPositionsRisk", func(t *testing.T) { 44 | service := &GetUMPositionRiskService{c: suite.client} 45 | positions, err := service.Do(context.Background()) 46 | if err != nil { 47 | t.Fatalf("Failed to get all positions risk info: %v", err) 48 | } 49 | 50 | for _, position := range positions { 51 | if position.Symbol == "" { 52 | t.Error("Expected non-empty symbol") 53 | } 54 | } 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /v2/portfolio/um_symbol_config_service.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | ) 7 | 8 | // UMSymbolConfigService get UM futures symbol configuration 9 | type UMSymbolConfigService struct { 10 | c *Client 11 | symbol *string 12 | } 13 | 14 | // UMSymbolConfig define UM futures symbol configuration 15 | type UMSymbolConfig struct { 16 | Symbol string `json:"symbol"` 17 | MarginType string `json:"marginType"` 18 | IsAutoAddMargin bool `json:"isAutoAddMargin"` 19 | Leverage int `json:"leverage"` 20 | MaxNotionalValue string `json:"maxNotionalValue"` 21 | } 22 | 23 | // Symbol set symbol 24 | func (s *UMSymbolConfigService) Symbol(symbol string) *UMSymbolConfigService { 25 | s.symbol = &symbol 26 | return s 27 | } 28 | 29 | // Do send request 30 | func (s *UMSymbolConfigService) Do(ctx context.Context) ([]*UMSymbolConfig, error) { 31 | r := &request{ 32 | method: "GET", 33 | endpoint: "/papi/v1/um/symbolConfig", 34 | secType: secTypeSigned, 35 | } 36 | if s.symbol != nil { 37 | r.setParam("symbol", *s.symbol) 38 | } 39 | data, _, err := s.c.callAPI(ctx, r) 40 | if err != nil { 41 | return nil, err 42 | } 43 | var res []*UMSymbolConfig 44 | err = json.Unmarshal(data, &res) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return res, nil 49 | } 50 | -------------------------------------------------------------------------------- /v2/portfolio/um_symbol_config_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umSymbolConfigServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMSymbolConfigServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umSymbolConfigServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetUMSymbolConfig", func(t *testing.T) { 22 | service := &UMSymbolConfigService{c: suite.client} 23 | 24 | // Test without symbol parameter first 25 | configs, err := service.Do(context.Background()) 26 | if err != nil { 27 | t.Fatalf("Failed to get UM symbol configs: %v", err) 28 | } 29 | 30 | if len(configs) == 0 { 31 | t.Error("Expected at least one symbol configuration") 32 | } 33 | 34 | // Test with specific symbol 35 | symbol := "BTCUSDT" 36 | configs, err = service.Symbol(symbol).Do(context.Background()) 37 | if err != nil { 38 | t.Fatalf("Failed to get UM symbol config for %s: %v", symbol, err) 39 | } 40 | 41 | // Validate returned data 42 | for _, config := range configs { 43 | if config.Symbol == "" { 44 | t.Error("Expected non-empty symbol") 45 | } 46 | if config.MarginType == "" { 47 | t.Error("Expected non-empty margin type") 48 | } 49 | if config.Leverage <= 0 { 50 | t.Error("Expected positive leverage") 51 | } 52 | if config.MaxNotionalValue == "" { 53 | t.Error("Expected non-empty maxNotionalValue") 54 | } 55 | } 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /v2/portfolio/um_symbol_config_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umSymbolConfigServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMSymbolConfigService(t *testing.T) { 14 | suite.Run(t, new(umSymbolConfigServiceTestSuite)) 15 | } 16 | 17 | func (s *umSymbolConfigServiceTestSuite) TestGetUMSymbolConfig() { 18 | data := []byte(`[ 19 | { 20 | "symbol": "BTCUSDT", 21 | "marginType": "CROSSED", 22 | "isAutoAddMargin": false, 23 | "leverage": 21, 24 | "maxNotionalValue": "1000000" 25 | } 26 | ]`) 27 | 28 | s.mockDo(data, nil) 29 | defer s.assertDo() 30 | 31 | symbol := "BTCUSDT" 32 | s.assertReq(func(r *request) { 33 | e := newSignedRequest() 34 | e.setParam("symbol", symbol) 35 | s.assertRequestEqual(e, r) 36 | }) 37 | 38 | configs, err := s.client.NewGetUMSymbolConfigService().Symbol(symbol).Do(newContext()) 39 | s.r().NoError(err) 40 | s.r().Len(configs, 1) 41 | s.r().Equal("BTCUSDT", configs[0].Symbol) 42 | s.r().Equal("CROSSED", configs[0].MarginType) 43 | s.r().Equal(false, configs[0].IsAutoAddMargin) 44 | s.r().Equal(21, configs[0].Leverage) 45 | s.r().Equal("1000000", configs[0].MaxNotionalValue) 46 | } 47 | -------------------------------------------------------------------------------- /v2/portfolio/um_trade_download_link_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umTradeDownloadLinkServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMTradeDownloadLinkServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umTradeDownloadLinkServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetUMTradeDownloadLink", func(t *testing.T) { 22 | downloadID := "953365044620652544" // This should be a valid download ID from a previous request 23 | service := suite.client.NewGetUMTradeDownloadLinkService() 24 | res, err := service. 25 | DownloadID(downloadID). 26 | Do(context.Background()) 27 | 28 | if err != nil { 29 | t.Fatalf("Failed to get UM trade download link: %v", err) 30 | } 31 | 32 | if res.DownloadID == "" { 33 | t.Error("Expected non-empty download ID") 34 | } 35 | 36 | if res.Status != "completed" && res.Status != "processing" { 37 | t.Error("Expected status to be either 'completed' or 'processing'") 38 | } 39 | 40 | if res.Status == "completed" && res.URL == "" { 41 | t.Error("Expected non-empty URL for completed status") 42 | } 43 | 44 | if res.ExpirationTimestamp <= 0 && res.Status == "completed" { 45 | t.Error("Expected positive expiration timestamp for completed status") 46 | } 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /v2/portfolio/um_trade_download_link_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umTradeDownloadLinkServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMTradeDownloadLinkService(t *testing.T) { 14 | suite.Run(t, new(umTradeDownloadLinkServiceTestSuite)) 15 | } 16 | 17 | func (s *umTradeDownloadLinkServiceTestSuite) TestGetUMTradeDownloadLink() { 18 | data := []byte(`{ 19 | "downloadId": "545923594199212032", 20 | "status": "completed", 21 | "url": "www.binance.com", 22 | "s3Link": null, 23 | "notified": true, 24 | "expirationTimestamp": 1645009771000, 25 | "isExpired": null 26 | }`) 27 | s.mockDo(data, nil) 28 | defer s.assertDo() 29 | 30 | downloadID := "545923594199212032" 31 | recvWindow := int64(5000) 32 | 33 | s.assertReq(func(r *request) { 34 | e := newSignedRequest() 35 | e.setParam("downloadId", downloadID) 36 | e.setParam("recvWindow", recvWindow) 37 | s.assertRequestEqual(e, r) 38 | }) 39 | 40 | res, err := s.client.NewGetUMTradeDownloadLinkService(). 41 | DownloadID(downloadID). 42 | RecvWindow(recvWindow). 43 | Do(newContext()) 44 | 45 | s.r().NoError(err) 46 | s.r().Equal("545923594199212032", res.DownloadID) 47 | s.r().Equal("completed", res.Status) 48 | s.r().Equal("www.binance.com", res.URL) 49 | s.r().Equal(true, res.Notified) 50 | s.r().Equal(int64(1645009771000), res.ExpirationTimestamp) 51 | } 52 | -------------------------------------------------------------------------------- /v2/portfolio/um_trade_history_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type umTradeHistoryServiceIntegrationTestSuite struct { 13 | *baseIntegrationTestSuite 14 | } 15 | 16 | func TestUMTradeHistoryServiceIntegration(t *testing.T) { 17 | base := SetupTest(t) 18 | suite := &umTradeHistoryServiceIntegrationTestSuite{ 19 | baseIntegrationTestSuite: base, 20 | } 21 | 22 | t.Run("GetUMTradeHistoryDownloadID", func(t *testing.T) { 23 | endTime := time.Now().UnixMilli() 24 | startTime := endTime - 24*60*60*1000 // 24 hours ago 25 | 26 | service := suite.client.NewGetUMTradeHistoryDownloadIDService() 27 | res, err := service. 28 | StartTime(startTime). 29 | EndTime(endTime). 30 | Do(context.Background()) 31 | 32 | if err != nil { 33 | t.Fatalf("Failed to get UM trade history download ID: %v", err) 34 | } 35 | 36 | if res.DownloadID == "" { 37 | t.Error("Expected non-empty download ID") 38 | } 39 | 40 | if res.AvgCostTimestampOfLast30d <= 0 { 41 | t.Error("Expected positive average cost timestamp") 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /v2/portfolio/um_trade_history_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umTradeHistoryServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMTradeHistoryService(t *testing.T) { 14 | suite.Run(t, new(umTradeHistoryServiceTestSuite)) 15 | } 16 | 17 | func (s *umTradeHistoryServiceTestSuite) TestGetUMTradeHistoryDownloadID() { 18 | data := []byte(`{ 19 | "avgCostTimestampOfLast30d": 7241837, 20 | "downloadId": "546975389218332672" 21 | }`) 22 | s.mockDo(data, nil) 23 | defer s.assertDo() 24 | 25 | startTime := int64(1622555222000) 26 | endTime := int64(1622555522000) 27 | recvWindow := int64(5000) 28 | 29 | s.assertReq(func(r *request) { 30 | e := newSignedRequest() 31 | e.setParam("startTime", startTime) 32 | e.setParam("endTime", endTime) 33 | e.setParam("recvWindow", recvWindow) 34 | s.assertRequestEqual(e, r) 35 | }) 36 | 37 | res, err := s.client.NewGetUMTradeHistoryDownloadIDService(). 38 | StartTime(startTime). 39 | EndTime(endTime). 40 | RecvWindow(recvWindow). 41 | Do(newContext()) 42 | 43 | s.r().NoError(err) 44 | s.r().Equal(int64(7241837), res.AvgCostTimestampOfLast30d) 45 | s.r().Equal("546975389218332672", res.DownloadID) 46 | } 47 | -------------------------------------------------------------------------------- /v2/portfolio/um_trading_status_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umTradingStatusServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMTradingStatusServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umTradingStatusServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetTradingStatus", func(t *testing.T) { 22 | service := &GetUMTradingStatusService{c: suite.client} 23 | status, err := service.Symbol("BTCUSDT").Do(context.Background()) 24 | if err != nil { 25 | t.Fatalf("Failed to get trading status: %v", err) 26 | } 27 | 28 | if status.UpdateTime == 0 { 29 | t.Error("Expected non-zero update time") 30 | } 31 | 32 | for symbol, indicators := range status.Indicators { 33 | if symbol == "" { 34 | t.Error("Expected non-empty symbol") 35 | } 36 | if len(indicators) == 0 { 37 | t.Error("Expected non-empty indicators") 38 | } 39 | } 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /v2/portfolio/um_transaction_download_link_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | type umTransactionDownloadLinkServiceIntegrationTestSuite struct { 12 | *baseIntegrationTestSuite 13 | } 14 | 15 | func TestUMTransactionDownloadLinkServiceIntegration(t *testing.T) { 16 | base := SetupTest(t) 17 | suite := &umTransactionDownloadLinkServiceIntegrationTestSuite{ 18 | baseIntegrationTestSuite: base, 19 | } 20 | 21 | t.Run("GetUMTransactionDownloadLink", func(t *testing.T) { 22 | downloadID := "953367130350170112" 23 | service := suite.client.NewGetUMTransactionDownloadLinkService() 24 | res, err := service. 25 | DownloadID(downloadID). 26 | Do(context.Background()) 27 | 28 | if err != nil { 29 | t.Fatalf("Failed to get UM transaction download link: %v", err) 30 | } 31 | 32 | if res.DownloadID == "" { 33 | t.Error("Expected non-empty download ID") 34 | } 35 | 36 | if res.Status != "completed" && res.Status != "processing" { 37 | t.Error("Expected status to be either 'completed' or 'processing'") 38 | } 39 | 40 | if res.Status == "completed" && res.URL == "" { 41 | t.Error("Expected non-empty URL for completed status") 42 | } 43 | 44 | if res.ExpirationTimestamp <= 0 && res.Status == "completed" { 45 | t.Error("Expected positive expiration timestamp for completed status") 46 | } 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /v2/portfolio/um_transaction_download_link_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umTransactionDownloadLinkServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMTransactionDownloadLinkService(t *testing.T) { 14 | suite.Run(t, new(umTransactionDownloadLinkServiceTestSuite)) 15 | } 16 | 17 | func (s *umTransactionDownloadLinkServiceTestSuite) TestGetUMTransactionDownloadLink() { 18 | data := []byte(`{ 19 | "downloadId": "545923594199212032", 20 | "status": "completed", 21 | "url": "www.binance.com", 22 | "s3Link": null, 23 | "notified": true, 24 | "expirationTimestamp": 1645009771000, 25 | "isExpired": null 26 | }`) 27 | s.mockDo(data, nil) 28 | defer s.assertDo() 29 | 30 | downloadID := "545923594199212032" 31 | recvWindow := int64(5000) 32 | 33 | s.assertReq(func(r *request) { 34 | e := newSignedRequest() 35 | e.setParam("downloadId", downloadID) 36 | e.setParam("recvWindow", recvWindow) 37 | s.assertRequestEqual(e, r) 38 | }) 39 | 40 | res, err := s.client.NewGetUMTransactionDownloadLinkService(). 41 | DownloadID(downloadID). 42 | RecvWindow(recvWindow). 43 | Do(newContext()) 44 | 45 | s.r().NoError(err) 46 | s.r().Equal("545923594199212032", res.DownloadID) 47 | s.r().Equal("completed", res.Status) 48 | s.r().Equal("www.binance.com", res.URL) 49 | s.r().Equal(true, res.Notified) 50 | s.r().Equal(int64(1645009771000), res.ExpirationTimestamp) 51 | } 52 | -------------------------------------------------------------------------------- /v2/portfolio/um_transaction_history_service_integration_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package portfolio 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type umTransactionHistoryServiceIntegrationTestSuite struct { 13 | *baseIntegrationTestSuite 14 | } 15 | 16 | func TestUMTransactionHistoryServiceIntegration(t *testing.T) { 17 | base := SetupTest(t) 18 | suite := &umTransactionHistoryServiceIntegrationTestSuite{ 19 | baseIntegrationTestSuite: base, 20 | } 21 | 22 | t.Run("GetUMTransactionHistoryDownloadID", func(t *testing.T) { 23 | endTime := time.Now().UnixMilli() 24 | startTime := endTime - 24*60*60*1000 // 24 hours ago 25 | 26 | service := suite.client.NewGetUMTransactionHistoryDownloadIDService() 27 | res, err := service. 28 | StartTime(startTime). 29 | EndTime(endTime). 30 | Do(context.Background()) 31 | 32 | if err != nil { 33 | t.Fatalf("Failed to get UM transaction history download ID: %v", err) 34 | } 35 | 36 | if res.DownloadID == "" { 37 | t.Error("Expected non-empty download ID") 38 | } 39 | 40 | if res.AvgCostTimestampOfLast30d <= 0 { 41 | t.Error("Expected positive average cost timestamp") 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /v2/portfolio/um_transaction_history_service_test.go: -------------------------------------------------------------------------------- 1 | package portfolio 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/suite" 7 | ) 8 | 9 | type umTransactionHistoryServiceTestSuite struct { 10 | baseTestSuite 11 | } 12 | 13 | func TestUMTransactionHistoryService(t *testing.T) { 14 | suite.Run(t, new(umTransactionHistoryServiceTestSuite)) 15 | } 16 | 17 | func (s *umTransactionHistoryServiceTestSuite) TestGetUMTransactionHistoryDownloadID() { 18 | data := []byte(`{ 19 | "avgCostTimestampOfLast30d": 7241837, 20 | "downloadId": "546975389218332672" 21 | }`) 22 | s.mockDo(data, nil) 23 | defer s.assertDo() 24 | 25 | startTime := int64(1622555222000) 26 | endTime := int64(1622555522000) 27 | recvWindow := int64(5000) 28 | 29 | s.assertReq(func(r *request) { 30 | e := newSignedRequest() 31 | e.setParam("startTime", startTime) 32 | e.setParam("endTime", endTime) 33 | e.setParam("recvWindow", recvWindow) 34 | s.assertRequestEqual(e, r) 35 | }) 36 | 37 | res, err := s.client.NewGetUMTransactionHistoryDownloadIDService(). 38 | StartTime(startTime). 39 | EndTime(endTime). 40 | RecvWindow(recvWindow). 41 | Do(newContext()) 42 | 43 | s.r().NoError(err) 44 | s.r().Equal(int64(7241837), res.AvgCostTimestampOfLast30d) 45 | s.r().Equal("546975389218332672", res.DownloadID) 46 | } 47 | -------------------------------------------------------------------------------- /v2/rate_limit_service.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // RateLimitService get rate limits 10 | type RateLimitService struct { 11 | c *Client 12 | } 13 | 14 | // Do send request 15 | func (s *RateLimitService) Do(ctx context.Context, opts ...RequestOption) (res []*RateLimitFull, err error) { 16 | res = make([]*RateLimitFull, 0) 17 | r := &request{ 18 | method: http.MethodGet, 19 | endpoint: "/api/v3/rateLimit/order", 20 | secType: secTypeSigned, 21 | } 22 | data, err := s.c.callAPI(ctx, r, opts...) 23 | if err != nil { 24 | return res, err 25 | } 26 | err = json.Unmarshal(data, &res) 27 | if err != nil { 28 | return res, err 29 | } 30 | return res, nil 31 | } 32 | 33 | type RateLimitFull struct { 34 | RateLimitType RateLimitType `json:"rateLimitType"` 35 | Interval RateLimitInterval `json:"interval"` 36 | IntervalNum int `json:"intervalNum"` 37 | Limit int `json:"limit"` 38 | Count int `json:"count"` 39 | } 40 | -------------------------------------------------------------------------------- /v2/rate_limit_service_test.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/suite" 8 | ) 9 | 10 | type assetRateLimitServiceSuite struct { 11 | baseTestSuite 12 | } 13 | 14 | func (a *assetRateLimitServiceSuite) assertRateLimitServiceEqual(expected, other *RateLimitFull) { 15 | r := a.r() 16 | 17 | r.EqualValues(expected, other) 18 | } 19 | 20 | func TestRateLimitService(t *testing.T) { 21 | suite.Run(t, new(assetRateLimitServiceSuite)) 22 | } 23 | 24 | func (s *assetRateLimitServiceSuite) TestListRateLimit() { 25 | data := []byte(` 26 | [ 27 | { 28 | "rateLimitType": "ORDERS", 29 | "interval": "SECOND", 30 | "intervalNum": 10, 31 | "limit": 10000, 32 | "count": 0 33 | }, 34 | { 35 | "rateLimitType": "RAW_REQUESTS", 36 | "interval": "MINUTE", 37 | "intervalNum": 5, 38 | "limit": 5000, 39 | "count": 100 40 | } 41 | ] 42 | `) 43 | 44 | s.mockDo(data, nil) 45 | defer s.assertDo() 46 | 47 | limits, err := s.client.NewRateLimitService().Do(context.Background()) 48 | s.r().NoError(err) 49 | rows := limits 50 | 51 | s.Len(rows, 2) 52 | s.assertRateLimitServiceEqual(&RateLimitFull{ 53 | RateLimitType: "ORDERS", 54 | Interval: "SECOND", 55 | IntervalNum: 10, 56 | Limit: 10000, 57 | Count: 0, 58 | }, 59 | rows[0]) 60 | s.assertRateLimitServiceEqual(&RateLimitFull{ 61 | RateLimitType: "RAW_REQUESTS", 62 | Interval: "MINUTE", 63 | IntervalNum: 5, 64 | Limit: 5000, 65 | Count: 100, 66 | }, 67 | rows[1]) 68 | } 69 | -------------------------------------------------------------------------------- /v2/server_service.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | // PingService ping server 9 | type PingService struct { 10 | c *Client 11 | } 12 | 13 | // Do send request 14 | func (s *PingService) Do(ctx context.Context, opts ...RequestOption) (err error) { 15 | r := &request{ 16 | method: http.MethodGet, 17 | endpoint: "/api/v3/ping", 18 | } 19 | _, err = s.c.callAPI(ctx, r, opts...) 20 | return err 21 | } 22 | 23 | // ServerTimeService get server time 24 | type ServerTimeService struct { 25 | c *Client 26 | } 27 | 28 | // Do send request 29 | func (s *ServerTimeService) Do(ctx context.Context, opts ...RequestOption) (serverTime int64, err error) { 30 | r := &request{ 31 | method: http.MethodGet, 32 | endpoint: "/api/v3/time", 33 | } 34 | data, err := s.c.callAPI(ctx, r, opts...) 35 | if err != nil { 36 | return 0, err 37 | } 38 | j, err := newJSON(data) 39 | if err != nil { 40 | return 0, err 41 | } 42 | serverTime = j.Get("serverTime").MustInt64() 43 | return serverTime, nil 44 | } 45 | 46 | // SetServerTimeService set server time 47 | type SetServerTimeService struct { 48 | c *Client 49 | } 50 | 51 | // Do send request 52 | func (s *SetServerTimeService) Do(ctx context.Context, opts ...RequestOption) (timeOffset int64, err error) { 53 | serverTime, err := s.c.NewServerTimeService().Do(ctx) 54 | if err != nil { 55 | return 0, err 56 | } 57 | timeOffset = currentTimestamp() - serverTime 58 | s.c.TimeOffset = timeOffset 59 | return timeOffset, nil 60 | } 61 | -------------------------------------------------------------------------------- /v2/trade_fee_service.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // TradeFeeService shows current trade fee for all symbols available 10 | type TradeFeeService struct { 11 | c *Client 12 | symbol *string 13 | } 14 | 15 | // Symbol set the symbol parameter for the request 16 | func (s *TradeFeeService) Symbol(symbol string) *TradeFeeService { 17 | s.symbol = &symbol 18 | return s 19 | } 20 | 21 | // Do send request 22 | func (s *TradeFeeService) Do(ctx context.Context) (res []*TradeFeeDetails, err error) { 23 | r := &request{ 24 | method: http.MethodGet, 25 | endpoint: "/sapi/v1/asset/tradeFee", 26 | secType: secTypeSigned, 27 | } 28 | 29 | if s.symbol != nil { 30 | r.setParam("symbol", *s.symbol) 31 | } 32 | 33 | data, err := s.c.callAPI(ctx, r) 34 | if err != nil { 35 | return res, err 36 | } 37 | res = make([]*TradeFeeDetails, 0) 38 | err = json.Unmarshal(data, &res) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return res, nil 43 | } 44 | 45 | // TradeFeeDetails represents details about fees 46 | type TradeFeeDetails struct { 47 | Symbol string `json:"symbol"` 48 | MakerCommission string `json:"makerCommission"` 49 | TakerCommission string `json:"takerCommission"` 50 | } 51 | -------------------------------------------------------------------------------- /v2/wallet_balance_service.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // WalletBalanceService fetches all user wallet balance. 10 | // 11 | // See https://developers.binance.com/docs/wallet/asset/query-user-wallet-balance 12 | type WalletBalanceService struct { 13 | c *Client 14 | quoteAsset *string 15 | } 16 | 17 | // QuoteAsset sets the quoteAsset parameter. 18 | func (s *WalletBalanceService) QuoteAsset(asset string) *WalletBalanceService { 19 | s.quoteAsset = &asset 20 | return s 21 | } 22 | 23 | // Do sends the request. 24 | func (s *WalletBalanceService) Do(ctx context.Context, opts ...RequestOption) (res []*WalletBalance, err error) { 25 | r := &request{ 26 | method: http.MethodGet, 27 | endpoint: "/sapi/v1/asset/wallet/balance", 28 | secType: secTypeSigned, 29 | } 30 | if s.quoteAsset != nil { 31 | r.setParam("quoteAsset", *s.quoteAsset) 32 | } 33 | 34 | data, err := s.c.callAPI(ctx, r, opts...) 35 | if err != nil { 36 | return nil, err 37 | } 38 | res = make([]*WalletBalance, 0) 39 | err = json.Unmarshal(data, &res) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return res, nil 44 | } 45 | 46 | // WalletBalanceResponse defines the response of WalletBalanceService 47 | type WalletBalance struct { 48 | Activate bool `json:"activate"` 49 | Balance string `json:"balance"` 50 | WalletName string `json:"walletName"` 51 | } 52 | --------------------------------------------------------------------------------