[Python] python-binance get_universal_transfer_history returns empty for FUNDING_MAIN

Discussão em 'Python' iniciado por Stack, Maio 31, 2025.

  1. Stack

    Stack Membro Participativo

    I'm trying to fetch internal asset transfer history from Binance, specifically transfers between my Funding Wallet and Spot Wallet, using the python-binance library. I'm interested in FUNDING_MAIN (Funding to Spot) and MAIN_FUNDING (Spot to Funding) transfer types.

    I've confirmed with a direct curl command that FUNDING_MAIN transfers for USDT exist for my account and API key within a specific time range. Here's the successful curl command structure (API keys are illustrative):

    # Illustrative curl command (API keys/secret are examples)
    api_key="YOUR_API_KEY"
    api_secret="YOUR_API_SECRET"
    timestamp=$(date +%s%3N)
    # Example startTime/endTime (Unix ms)
    # startTime=1748239900000 (approx May 23, 2025 06:11:40 AM GMT)
    # endTime=1748253200000 (approx May 23, 2025 09:53:20 AM GMT)
    query="asset=USDT&type=FUNDING_MAIN&startTime=${startTime_ms}&endTime=${endTime_ms}&timestamp=$timestamp"
    signature=$(echo -n "$query" | openssl dgst -sha256 -hmac "$api_secret" | awk '{print $2}')
    curl -H "X-MBX-APIKEY: $api_key" -X GET "https://api.binance.com/sapi/v1/asset/transfer?$query&signature=$signature"



    This curl command successfully returns JSON data with a total count and rows of transfers, for example:

    {"total":3,"rows":[{"timestamp":1748253169000,"asset":"USDT","amount":"3.25206804","type":"FUNDING_MAIN","status":"CONFIRMED","tranId":264973612117}, /* ...more rows... */ ]}



    However, when I try to fetch this using python-binance's client.get_universal_transfer_history() method, I consistently get 0 results for these transfer types.

    Here's a simplified version of my Python function:

    from binance.client import Client
    from binance.exceptions import BinanceAPIException
    import datetime
    import time
    import pandas as pd
    import requests # for requests.exceptions
    import logging

    # Assume client is initialized: client = Client(api_key, api_secret)
    # Assume self.stablecoin_symbols and self.symbol_mappings are defined
    # Assume self._get_coingecko_historical_price is defined

    logger = logging.getLogger(__name__) # Basic logger

    class PortfolioTracker:
    def __init__(self, client, config): # Simplified
    self.binance_client = client
    self.config = config
    self.stablecoin_symbols = ["USDT", "USDC"] # Example
    self.symbol_mappings = {"USDT": "tether"} # Example for Coingecko ID
    self.norm_map = {} # For symbol normalization

    def _get_coingecko_historical_price(self, coin_id, date_str):
    # Dummy implementation for this example
    logger.debug(f"Fetching Coingecko price for {coin_id} on {date_str}")
    time.sleep(self.config.get("apis", {}).get("coingecko", {}).get("request_delay_ms_internal_transfer", 1000) / 1000.0)
    if coin_id == "tether": return 1.0
    return 0.05 # Dummy price

    def fetch_internal_transfers(self, days_back: int = 365) -> list:
    if not self.binance_client:
    logger.warning("Binance client not initialized.")
    return []

    all_processed_transfers = []
    transfer_configs = [
    {"api_type": "FUNDING_MAIN", "flow_type": "DEPOSIT", "log_label": "Funding to Spot"},
    {"api_type": "MAIN_FUNDING", "flow_type": "WITHDRAWAL", "log_label": "Spot to Funding"}
    ]

    batch_days = self.config.get("apis", {}).get("binance", {}).get("transfer_history_batch_days", 29)
    limit_per_batch = 100
    max_retries = self.config.get("apis", {}).get("binance", {}).get("max_retries_per_batch", 3)
    retry_delay_seconds = self.config.get("apis", {}).get("binance", {}).get("retry_delay_sec", 30)
    cg_delay_ms = self.config.get("apis", {}).get("coingecko", {}).get("request_delay_ms_internal_transfer", 10000)
    binance_api_delay_ms = self.config.get("apis", {}).get("binance", {}).get("request_delay_ms", 700)


    overall_end_time_dt = datetime.datetime.now(datetime.timezone.utc)
    overall_start_time_dt = overall_end_time_dt - datetime.timedelta(days=days_back)

    processed_tran_ids = set()

    for config_entry in transfer_configs:
    api_type = config_entry["api_type"]
    transaction_flow_type = config_entry["flow_type"]
    log_label = config_entry["log_label"]

    logger.info(f"Fetching internal transfers for type: {api_type} ({log_label}). Lookback: {days_back} days.")

    current_batch_end_time_dt = overall_end_time_dt
    while current_batch_end_time_dt > overall_start_time_dt:
    current_batch_start_time_dt = current_batch_end_time_dt - datetime.timedelta(days=batch_days)
    if current_batch_start_time_dt < overall_start_time_dt:
    current_batch_start_time_dt = overall_start_time_dt

    start_ms = int(current_batch_start_time_dt.timestamp() * 1000)
    end_ms = int(current_batch_end_time_dt.timestamp() * 1000)

    if start_ms >= end_ms: break

    logger.debug(f"Fetching {api_type} batch: {current_batch_start_time_dt.strftime('%Y-%m-%d %H:%M')} to {current_batch_end_time_dt.strftime('%Y-%m-%d %H:%M')}")

    retries_left = max_retries
    fetched_rows = None

    while retries_left > 0:
    try:
    # Attempting to fetch for ALL assets for this type and time window
    transfer_history = self.binance_client.get_universal_transfer_history(
    type=api_type,
    startTime=start_ms,
    endTime=end_ms,
    size=limit_per_batch
    )
    fetched_rows = transfer_history.get('rows', [])
    logger.debug(f"Successfully fetched {api_type} batch. Count: {len(fetched_rows) if fetched_rows else 0}")
    break
    except (requests.exceptions.ReadTimeout, requests.exceptions.ConnectionError) as e:
    retries_left -= 1
    logger.error(f"Network error fetching {api_type}: {e}. Retries left: {retries_left}.")
    if retries_left > 0: time.sleep(retry_delay_seconds)
    else: logger.error(f"Failed to fetch {api_type} batch after multiple retries.")
    except BinanceAPIException as e: # Make sure to import this
    logger.error(f"Binance API Error fetching {api_type}: {e}.")
    retries_left = 0
    except Exception as e:
    logger.error(f"Unexpected error fetching {api_type}: {e}", exc_info=True)
    retries_left = 0

    if fetched_rows:
    for item in fetched_rows:
    # ... (processing logic as provided in my script) ...
    pass # Simplified for brevity

    current_batch_end_time_dt = current_batch_start_time_dt - datetime.timedelta(milliseconds=1)
    if fetched_rows is not None:
    time.sleep(binance_api_delay_ms / 1000.0)

    logger.info(f"Fetched a total of {len(all_processed_transfers)} internal transactions.")
    return all_processed_transfers


    What I've tried/checked:

    • API keys have "Enable Reading" and "Enable Universal Transfer" permissions. The curl test confirms they work for this endpoint.
    • Tested with type="FUNDING_MAIN" and type="MAIN_FUNDING".
    • Ensured startTime and endTime are in milliseconds.
    • The python-binance library is up-to-date.
    • Other API calls (e.g., get_my_trades, get_deposit_history) work fine with the same client instance. *My curl test for FUNDING_MAIN with asset=USDT works, but the Python code iterating assets (including USDT) or trying to fetch all assets for FUNDING_MAIN returns 0.

    My Questions:

    • Is there a known reason why client.get_universal_transfer_history might return an empty list for type="FUNDING_MAIN" (or MAIN_FUNDING) when a direct API call with similar parameters successfully returns data?
    • Are there specific nuances or required parameters for get_universal_transfer_history with these funding-related types that I might be missing compared to a direct curl construction? (e.g., does asset behave differently if omitted for these types?)
    • Could there be an underlying issue in how python-binance constructs or signs the request for this specific endpoint/type combination?
    • What's the recommended way to reliably fetch all FUNDING_MAIN and MAIN_FUNDING transfers (across all relevant assets like USDT, BTC, ETH, etc.) using python-binance? Should I be iterating assets, or should querying without an asset parameter work for these types?

    Any insights or suggestions would be greatly appreciated!

    Continue reading...

Compartilhe esta Página