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
Symbol table (DB)
Symbol.funding_rate_epoch_duration
Symbol.funding_rate_window_time
External exchange (Binance)
binance_client.futures_mark_price()
→lastFundingRate
,nextFundingTime
Hedger coefficients (from
settings.py
)FundingRateHedgerToUserCoefficient
FundingRateUserToHedgerCoefficient
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