GET Get Funding Info

/get_funding_info

/get_funding_info

Example Query:

https://base-hedger82.rasa.capital/get_funding_info

Example Response:

  "BTCUSDT": {
    "next_funding_time": 1744070400000,
    "next_funding_rate_short": "0.000040455",
    "next_funding_rate_long": "-0.000053940",
    "funding_rate_epoch_duration": 14400
  },
  "FILUSDT": {
    "next_funding_time": 1744070400000,
    "next_funding_rate_short": "0.000045081",
    "next_funding_rate_long": "-0.000060108",
    "funding_rate_epoch_duration": 14400
  },

Get Funding Info Rasa Implementation

Router

@common_router.get(
    '/get_funding_info',
    response_model=Dict[str, FundingInfoResponseSchema],
    dependencies=[
        Depends(CustomRateLimiter(times=1, seconds=1)),
        Depends(CustomRateLimiter(times=40, minutes=1)),
        Depends(CustomRateLimiter(times=1500, hours=1)),
    ]
)
async def get_funding_info(symbols: List[symbol_enum] = Query(None)):
    # symbols: optional list of tickers; defaults to BaseSymbols if None
    return get_funding_rates(symbols or BaseSymbols)

Output:

  • Dict mapping ticker → FundingInfoResponseSchema.

class FundingInfoResponseSchema(BaseModel):
    next_funding_time: int
    next_funding_rate_short: Decimal
    next_funding_rate_long: Decimal

Data Sources

  1. Symbol table (DB)

    • Symbol.funding_rate_epoch_duration

    • Symbol.funding_rate_window_time

  2. External exchange (Binance)

    • binance_client.futures_mark_price()lastFundingRate, nextFundingTime

  3. Hedger coefficients (from settings.py)

    • FundingRateHedgerToUserCoefficient

    • FundingRateUserToHedgerCoefficient

  4. Positions table (DB)

    • Used for filtering and in expressions (Positions.position_type, Positions.opened_price, etc.)

Logic

In short, the logic can be summarized like this:

GET /get_funding_info

get_funding_info(symbols) #Entry point 

get_funding_rates(symbols)    
    ├─→ FundingRate.get_symbol_funding_details()
    ├    # read Symbol.is_valid from DB
    ├    # call binance_client.futures_mark_price()
    ├    # return List[SymbolFundingDetail]
    └─→ FundingRate.get_funding_details(..., to_apply=False)
    ├    # 1) build SQL CTE from symbol_funding_details
    ├    # 2) apply hedger’s long/short coefficients
    ├    # 3) compute contract_rate expression
    ├    # 4) group by Positions.party_a_address
    ├    # 5) optional $-amount filtering if to_apply=True
    ├    # 6) session.all_(stmt) → [FundingDetail(...)]

map into Dict[str, FundingInfoResponseSchema]

return JSON

Fetch Symbol & Market Details

@staticmethod
def get_symbol_funding_details() -> List[SymbolFundingDetail]:
    # 1) Fetch mark‐price & funding‐time info for all symbols from Binance
    binance_markets: Dict[str, BinanceMarketDetail] = binance_client.futures_mark_price()

    # 2) Record current time (in seconds) to compare against each symbol’s funding window
    start_time = get_now_epoch()

    result: List[SymbolFundingDetail] = []
    # 3) Load every valid symbol row from your local Symbol table
    for symbol in session.scalars_all(
        Symbol.select().where(Symbol.is_valid.is_(true()))
    ):  

        # 4) Read the next funding timestamp (ms→s) from Binance’s data
        binance_next_funding_time = binance_markets[symbol.title].nextFundingTime // 1000
        
        # 5) Only keep symbols whose next funding falls within the on-chain funding window
        if binance_next_funding_time < start_time + symbol.funding_rate_window_time:
            symbol_funding_detail = SymbolFundingDetail(
                symbol_id                = symbol.symbol_id,
                binance_mark_price       = binance_markets[symbol.title].markPrice,
                binance_last_funding_rate= binance_markets[symbol.title].lastFundingRate,
                binance_next_funding_time= binance_next_funding_time,
                funding_rate_window_time = symbol.funding_rate_window_time,
            )

            # 7) Sanity check: confirm on-chain epoch aligns with Binance’s tick
            #    If it doesn’t, emit an alert and skip this symbol
            if binance_next_funding_time % symbol.funding_rate_epoch_duration != 0:
                send_instant_error_message(
                    title='Funding time of contract not compatible with Binance.',
                    amend=asdict(symbol_funding_detail)
                )
                continue

            # 8) Add to the list of symbols we’ll compute funding for
            result.append(symbol_funding_detail)

    # 9) Return all valid SymbolFundingDetail entries
    return result

Coefficient Application Logic

@staticmethod
def get_funding_details(
    symbol_funding_details: List[SymbolFundingDetail],
    to_apply: bool = True
) -> Optional[List[FundingDetail]]:
    # 1) Build an in-SQL CTE of symbol timing & rates
    symbol_details_expr = values(
        column('symbol_id', Integer),
        column('price',     Numeric(**numeric_args)),
        column('funding_rate', Numeric(**numeric_args)),
        column('funding_window_open_time', BigInteger),
        column('binance_next_funding_time', BigInteger),
        name='funding_details'
    ).data([
        detail.get_data_for_cte()
        for detail in symbol_funding_details
    ])

    # 2) Apply hedger coefficients to raw funding_rate
    funding_rate_expr = symbol_details_expr.c.funding_rate * case(
        # SHORT & positive → hedger pays user (negate HedgerToUserCoef)
        (and_(Positions.position_type == PositionType.SHORT,
              symbol_details_expr.c.funding_rate >= 0),
         -FundingRateHedgerToUserCoefficient),

        # SHORT & negative → user pays hedger (negate UserToHedgerCoef)
        (and_(Positions.position_type == PositionType.SHORT,
              symbol_details_expr.c.funding_rate < 0),
         -FundingRateUserToHedgerCoefficient),

        # LONG & positive → user pays hedger
        (and_(Positions.position_type == PositionType.LONG,
              symbol_details_expr.c.funding_rate >= 0),
         FundingRateUserToHedgerCoefficient),

        # LONG & negative → hedger pays user
        (and_(Positions.position_type == PositionType.LONG,
              symbol_details_expr.c.funding_rate < 0),
         FundingRateHedgerToUserCoefficient)
    )

    # 3) Embed into full “contract rate” formula
    sign = case((Positions.position_type == PositionType.SHORT, -1), else_=1)
    contract_rate = func.round(
        (
            (
                (funding_rate_expr * symbol_details_expr.c.price)
                / (Positions.funding_rate_diff * Positions.opened_price)
                * sign + 1
            ) * Positions.funding_rate_diff - 1
        ) * sign,
        NumberFloatingPoint
    )

    # 4) Build query: group by counterparty (party A)
    stmt = Positions.select(
        Positions.party_a_address,
        func.json_agg(Positions.quote_id),
        func.json_agg(contract_rate)
    ).outerjoin(
        AppliedFundingRate, Positions.quote_id == AppliedFundingRate.quote_id
    ).join(Symbol).select_from(symbol_details_expr).join(
        symbol_details_expr,
        Symbol.symbol_id == symbol_details_expr.c.symbol_id
    ).where(
        AppliedFundingRate.id.is_(None),
        Positions.state.in_([PositionState.OPEN, PositionState.CLOSE_PENDING]),
        Positions.open_time <= symbol_details_expr.c.binance_next_funding_time,
        or_(
            Positions.last_funding_time < symbol_details_expr.c.funding_window_open_time,
            Positions.last_funding_time.is_(None)
        )
    ).group_by(Positions.party_a_address)

    # 5) If “to_apply”, also calculate $-amount and filter tiny ones
    if to_apply:
        funding_amount = (
            (Positions.quantity - Positions.filled_closed_amount)
            * Positions.opened_price * contract_rate
        )
        stmt = stmt.add_columns(
            func.json_agg(
                case(
                    (contract_rate > Positions.max_funding_rate,
                     Positions.max_funding_rate),
                    else_=contract_rate
                )
            ),
            func.sum(funding_amount).label('funding_amount')
        ).where(
            func.abs(funding_amount) > GasWorthyFundingThresholdPerPosition
        ).order_by(desc('funding_amount'))

    # 6) Execute and map to dataclass
    rows = session.all_(stmt)
    return [FundingDetail(*row) for row in rows]

Last updated