Dwarves
Memo
Type ESC to close search bar

Implement Binance Futures PNL Analysis page by Phoenix LiveView

As Binance doesn’t allow Master Account see MSA account Future PNL Analysis, so we decide to clone Binance Future PNL Analysis page with Phoenix Live View to show all Account Future PNL

Why we use Phoenix Live View for Binance Future PNL Analysis page

Real-Time Data Handling

Server-Side State Management

Complex Calculations

Development Efficiency

How to optimize query with timescale

Data Source

Base on Binance Docs we have compound data from 2 timescale tables: ts_user_trades and ts_future_incomes

Timescale table

Use timescale style query to get summary data in date range

Ecto query to calculate PnL data from ts_user_trades and ts_future_incomes

from(t in TsUserTrades,
  where: t.account_id in ^account_ids,
  where: t.time >= ^start_time and t.time <= ^end_time,
  group_by: [
    t.account_id,
    fragment("time_bucket('1 day', ?)::date", t.time)
  ],
  select: %{
    account_id: t.account_id,
    date: fragment("time_bucket('1 day', ?)::date", t.time),
    commission: fragment("COALESCE(-1 * ABS(SUM(?)), 0)", t.commission),
    realized_pnl: coalesce(sum(t.realized_pnl), 0),
    trade_volume:
      fragment(
        "COALESCE(SUM(CASE WHEN ? IS NOT NULL THEN ? ELSE 0 END), 0)",
        t.quote_qty,
        t.quote_qty
      )
  }
)

from(i in TsFutureIncomes,
  where: i.account_id in ^account_ids,
  where: i.time >= ^start_time and i.time <= ^end_time,
  group_by: [
    i.account_id,
    fragment("time_bucket('1 day', ?)::date", i.time)
  ],
  select: %{
    account_id: i.account_id,
    date: fragment("time_bucket('1 day', ?)::date", i.time),
    net_inflow:
      sum(fragment("CASE WHEN ? = 'TRANSFER' THEN ? ELSE 0 END", i.income_type, i.income)),
    received_funding_fee:
      sum(
        fragment(
          "CASE WHEN ? = 'FUNDING_FEE' AND ? > 0 THEN ? ELSE 0 END",
          i.income_type,
          i.income,
          i.income
        )
      ),
    paid_funding_fee:
      sum(
        fragment(
          "CASE WHEN ? = 'FUNDING_FEE' AND ? < 0 THEN ? ELSE 0 END",
          i.income_type,
          i.income,
          i.income
        )
      ),
    insurance_clear:
      sum(
        fragment(
          "CASE WHEN ? = 'INSURANCE_CLEAR' THEN ? ELSE 0 END",
          i.income_type,
          i.income
        )
      )
  }
)

Because timescale table will be spitted into multiple chunks so if we use normal query it will have timeout issue if range too long. So we have to use time_bucket to let it join multiple chunks.

Create cronjob to fill ts_account_pnl_analysis

Screenshots

Figure 1: Future PNL Analysis Overview Tab

Figure 2: Future PNL Detail Tab

Figure 3: Future PNL Analysis Symbol Tab

Figure 4: Future PNL Funding and Transaction Tab