Skip to content
markpaper

src/assets/leverage.ts

v0.3.0 · 1.7 KB

Download file
import type { AssetInfo } from './types.js';

const LEVERAGE_EPSILON = 1e-9;

export interface LeverageParams {
  /** Integer leverage to send in `updateLeverage`: `min(max(1, floor(wanted + 1e-9)), floor(maxLeverage))`. */
  readonly leverage: number;
  /** `!onlyIsolated`. Sending `isCross: true` for an isolated-only market is rejected. */
  readonly isCross: boolean;
  /** True when `leverage` differs from `wanted` by more than float noise (floored or capped). Scale the notional down accordingly. */
  readonly clamped: boolean;
}

/**
 * Leverage parameters for `updateLeverage` derived from asset meta.
 *
 * Why: leverage above the pair's `maxLeverage` (xyz ranges from 10x to 50x) makes `updateLeverage` fail, and
 * `isCross: true` on an isolated-only market (most xyz pairs) is rejected too. Leverage on HL is an integer ≥ 1.
 *
 * @throws RangeError for spot assets or a non-positive / non-finite `wanted`.
 */
export function leverageParams(
  asset: Pick<AssetInfo, 'coin' | 'market' | 'maxLeverage' | 'onlyIsolated'>,
  wanted: number,
): LeverageParams {
  if (asset.market !== 'perp') throw new RangeError(`${asset.coin}: leverage does not apply to spot`);
  if (!Number.isFinite(wanted) || wanted <= 0) throw new RangeError(`${asset.coin}: wanted leverage must be > 0, got ${wanted}`);
  const cap = Math.max(1, Math.floor(asset.maxLeverage + LEVERAGE_EPSILON));
  // Epsilon before floor, same approach as size flooring: a computed 0.3 / 0.1 = 2.9999999999999996 must give 3, not 2.
  const leverage = Math.min(Math.max(1, Math.floor(wanted + LEVERAGE_EPSILON)), cap);
  return { leverage, isCross: !asset.onlyIsolated, clamped: Math.abs(leverage - wanted) > LEVERAGE_EPSILON };
}
All files