decred.org/dcrdex@v1.0.5/client/webserver/site/src/js/account.ts (about)

     1  import Doc from './doc'
     2  import {
     3    OrderTypeLimit,
     4    OrderTypeMarket,
     5    OrderTypeCancel,
     6    StatusEpoch,
     7    StatusBooked,
     8    RateEncodingFactor,
     9    MatchSideMaker,
    10    MakerRedeemed,
    11    TakerSwapCast,
    12    ImmediateTiF
    13  } from './orderutil'
    14  import {
    15    app,
    16    PageElement,
    17    ExchangeAuth,
    18    Order,
    19    Market
    20  } from './registry'
    21  
    22  export const bondReserveMultiplier = 2 // Reserves for next bond
    23  export const perTierBaseParcelLimit = 2
    24  export const parcelLimitScoreMultiplier = 3
    25  
    26  export class ReputationMeter {
    27    page: Record<string, PageElement>
    28    host: string
    29  
    30    constructor (div: PageElement) {
    31      this.page = Doc.parseTemplate(div)
    32      Doc.cleanTemplates(this.page.penaltyMarkerTmpl)
    33    }
    34  
    35    setHost (host: string) {
    36      this.host = host
    37    }
    38  
    39    update () {
    40      const { page, host } = this
    41      const { auth, maxScore, penaltyThreshold } = app().exchanges[host]
    42      const { rep: { score } } = auth
    43  
    44      const displayTier = strongTier(auth)
    45  
    46      const minScore = displayTier ? displayTier * penaltyThreshold * -1 : penaltyThreshold * -1 // Just for looks
    47      const warnPct = 25
    48      const scorePct = 100 - warnPct
    49      page.scoreWarn.style.width = `${warnPct}%`
    50      const pos = score >= 0 ? warnPct + (score / maxScore) * scorePct : warnPct - (Math.min(warnPct, score / minScore * warnPct))
    51  
    52      page.scorePointer.style.left = `${pos}%`
    53      page.scoreMin.textContent = String(minScore)
    54      page.scoreMax.textContent = String(maxScore)
    55      const bonus = limitBonus(score, maxScore)
    56      page.limitBonus.textContent = bonus.toFixed(1)
    57      for (const m of Doc.applySelector(page.scoreTray, '.penalty-marker')) m.remove()
    58      if (displayTier > 1) {
    59        const markerPct = warnPct / displayTier
    60        for (let i = 1; i < displayTier; i++) {
    61          const div = page.penaltyMarkerTmpl.cloneNode(true) as PageElement
    62          page.scoreTray.appendChild(div)
    63          div.style.left = `${markerPct * i}%`
    64        }
    65      }
    66      page.score.textContent = String(score)
    67      page.scoreData.classList.remove('negative', 'positive')
    68      if (score > 0) page.scoreData.classList.add('positive')
    69      else page.scoreData.classList.add('negative')
    70    }
    71  }
    72  
    73  /*
    74   * strongTier is the effective tier, with some respect for bond overlap, such
    75   * that we don't count weak bonds that have already had their replacements
    76   * confirmed.
    77   */
    78  export function strongTier (auth: ExchangeAuth): number {
    79    const { weakStrength, targetTier, effectiveTier } = auth
    80    if (effectiveTier > targetTier) {
    81      const diff = effectiveTier - targetTier
    82      if (weakStrength >= diff) return targetTier
    83      return targetTier + (diff - weakStrength)
    84    }
    85    return effectiveTier
    86  }
    87  
    88  export function likelyTaker (ord: Order, rate: number): boolean {
    89    if (ord.type === OrderTypeMarket || ord.tif === ImmediateTiF) return true
    90    // Must cross the spread to be a taker (not so conservative).
    91    if (rate === 0) return false
    92    if (ord.sell) return ord.rate < rate
    93    return ord.rate > rate
    94  }
    95  
    96  const preparcelQuantity = (ord: Order, mkt?: Market, midGap?: number) => {
    97    const qty = ord.qty - ord.filled
    98    if (ord.type === OrderTypeLimit) return qty
    99    if (ord.sell) return qty * ord.rate / RateEncodingFactor
   100    const rate = midGap || mkt?.spot?.rate || 0
   101    // Caller should not call this for market orders without a mkt arg.
   102    if (!mkt) return 0
   103    // This is tricky. The server will use the mid-gap rate to convert the
   104    // order qty. We don't have a mid-gap rate, only a spot rate.
   105    if (rate && (mkt?.spot?.bookVolume || 0) > 0) return qty * RateEncodingFactor / rate
   106    return mkt.lotsize // server uses same fallback if book is empty
   107  }
   108  
   109  export function epochWeight (ord: Order, mkt: Market, midGap?: number) {
   110    if (ord.status !== StatusEpoch) return 0
   111    const qty = preparcelQuantity(ord, mkt, midGap)
   112    const rate = midGap || mkt.spot?.rate || 0
   113    if (likelyTaker(ord, rate)) return qty * 2
   114    return qty
   115  }
   116  
   117  function bookWeight (ord: Order) {
   118    if (ord.status !== StatusBooked) return 0
   119    return preparcelQuantity(ord)
   120  }
   121  
   122  function settlingWeight (ord: Order) {
   123    let sum = 0
   124    for (const m of (ord.matches || [])) {
   125      if (m.side === MatchSideMaker) {
   126        if (m.status > MakerRedeemed) continue
   127      } else if (m.status > TakerSwapCast) continue
   128      sum += m.qty
   129    }
   130    return sum
   131  }
   132  
   133  function parcelWeight (ord: Order, mkt: Market, midGap?: number) {
   134    if (ord.type === OrderTypeCancel) return 0
   135    return epochWeight(ord, mkt, midGap) + bookWeight(ord) + settlingWeight(ord)
   136  }
   137  
   138  // function roundParcels (p: number): number {
   139  //   return Math.floor(Math.round((p * 1e8)) / 1e8)
   140  // }
   141  
   142  function limitBonus (score: number, maxScore: number): number {
   143    return score > 0 ? 1 + score / maxScore * (parcelLimitScoreMultiplier - 1) : 1
   144  }
   145  
   146  export function tradingLimits (host: string): [number, number] { // [usedParcels, parcelLimit]
   147    const { auth, maxScore, markets } = app().exchanges[host]
   148    const { rep: { score } } = auth
   149    const tier = strongTier(auth)
   150  
   151    let usedParcels = 0
   152    for (const mkt of Object.values(markets)) {
   153      let mktWeight = 0
   154      for (const ord of (mkt.inflight || [])) mktWeight += parcelWeight(ord, mkt)
   155      for (const ord of (mkt.orders || [])) mktWeight += parcelWeight(ord, mkt)
   156      usedParcels += (mktWeight / (mkt.parcelsize * mkt.lotsize))
   157    }
   158    const parcelLimit = perTierBaseParcelLimit * limitBonus(score, maxScore) * tier
   159    return [usedParcels, parcelLimit]
   160  }