Skip to content

Расчёт прибыли и ключевых показателей

Обзор процесса

Расчёт выполняется в TransactionAssets.getAssetData(). Транзакции сортируются по дате (от старых к новым), затем итеративно обрабатываются через TransactionAggregator.getTotalData(), который делегирует расчёт в класс конкретного типа.

mermaid
flowchart TD
    A["TransactionAssets.getAssetData()"] --> B["Сортировка транзакций по дате<br/>(от старых к новым)"]
    B --> C{"Итерация по<br/>каждой транзакции"}

    C --> D["TransactionAggregator.getTotalData()"]
    D --> E{"Определение типа"}

    E -->|"Buy (2)"| BUY["TransactionBuy.getTotalData()"]
    E -->|"Sell (1)"| SELL["TransactionSell.getTotalData()"]
    E -->|"Transfer In (3)"| TIN["TransactionTransferIn.getTotalData()"]
    E -->|"Transfer Out (4)"| TOUT["TransactionTransferOut.getTotalData()"]
    E -->|"Mining (5)"| MINE["TransactionMining.getTotalData()"]

    BUY --> C
    SELL --> C
    TIN --> C
    TOUT --> C
    MINE --> C

    C -->|"Все обработаны"| FINAL["Финальные расчёты"]
    FINAL --> DONE["Готовые данные ассета"]

Логика по типам транзакций

Buy (Покупка)

total.invested  += quantity × transactionPrice
total.buyCoins  += quantity
total.buyCash   += quantity × marketPriceByCurrency

avg = средневзвешенная:
  (prevAvg × prevAmount + transactionPrice × quantity) / (prevAmount + quantity)

holdings.buyOrSell += quantity
holdings.coins     += quantity

Sell (Продажа)

Если НЕ coin-to-coin:
  total.incomeCash += quantity × transactionPrice

  profit.realised += (transactionPrice − avg) × quantity

holdings.buyOrSell -= quantity
holdings.coins     -= quantity

Обмен монета↔монета НЕ считается реализованной прибылью и не добавляет в incomeCash.

Transfer In (Ввод средств)

holdings.transfer += quantity
holdings.coins    += quantity

Монеты считаются «бесплатными» — не влияют на avg и invested.

Transfer Out (Вывод средств)

Сложная логика с учётом баланса «бесплатных» монет:

transferDifference = holdings.transfer − quantity

Если transferDifference ≥ 0:
  // Хватает бесплатных монет
  holdings.transfer -= quantity
Иначе:
  // Бесплатных не хватает → забираем из buyOrSell
  profit.realised -= |transferDifference| × avg
  holdings.buyOrSell -= |transferDifference|
  holdings.transfer = 0

holdings.coins -= quantity

При выводе «платных» монет фиксируется убыток, т.к. монеты отдаются бесплатно.

Mining/Airdrop

Двойственное поведение зависит от поля invested:

Если invested > 0 (есть затраты):
  // Ведёт себя как Buy
  transactionPrice = invested / quantity
  total.invested += invested
  avg = пересчёт средневзвешенной
  holdings.buyOrSell += quantity

Если invested = 0 (бесплатно):
  // Ведёт себя как Transfer In
  holdings.transfer += quantity

holdings.coins += quantity

Финальные расчёты (после итерации)

Текущая стоимость холдингов

holdings.cash = marketPrice × (holdings.buyOrSell + holdings.transfer)

Общая прибыль

totalHoldings = total.incomeCash + holdings.cash
total.profit = totalHoldings − total.invested
total.profitPercent = (totalHoldings − total.invested) / total.invested

Особые случаи:

  • Если totalHoldings = 0profitPercent = 0
  • Если invested = 0profitPercent = 1 (100%)

Нереализованная прибыль

avgProfit = (marketPrice − avg) × holdings.buyOrSell
transferCoinsCash = holdings.transfer × marketPrice
profit.unrealised = avgProfit + transferCoinsCash
profit.current = avgProfit + transferCoinsCash

«Бесплатные» монеты учитываются отдельным слагаемым.

Реализованная прибыль

Накапливается при каждой продаже за фиат:

profit.realised += (sellPrice − avg) × sellQuantity

И при выводе платных монет:

profit.realised -= |transferDifference| × avg

Агрегация по всему портфолио

Геттер totalAssetsData в Vuex store суммирует данные всех ассетов:

mermaid
flowchart LR
    A1["Ассет BTC"] --> SUM["totalAssetsData"]
    A2["Ассет ETH"] --> SUM
    A3["Ассет N"] --> SUM

    SUM --> R1["cash = Σ holdings.cash"]
    SUM --> R2["invested = Σ total.invested"]
    SUM --> R3["realised = Σ profit.realised"]
    SUM --> R4["unrealised = Σ profit.unrealised"]
    SUM --> R5["incomeCash = Σ total.incomeCash"]
    SUM --> R6["profit.percent = (incomeCash + cash − invested) / invested"]
    SUM --> R7["profit.cash = incomeCash + cash − invested"]

Расчёт прибыли на отдельной транзакции

Формулы зависят от типа и пары:

Покупка за фиат

profitCash    = (marketPrice − transactionPrice) × quantity
profitPercent = (marketPrice / transactionPrice − 1) × 100

Продажа за фиат

profitCash    = (transactionPrice − marketPrice) × quantity
profitPercent = (1 − marketPrice / transactionPrice) × 100

Покупка/продажа coin vs coin

totalBuyValue     = buyQuantity × buyMarketPrice
potentialSellCoins = totalBuyValue / sellMarketPrice
profitInCoins      = potentialSellCoins − originalSellCoins
profitCash         = profitInCoins × sellMarketPrice
profitPercent      = profitInCoins / originalSellCoins × 100

Майнинг/аирдроп

Если invested > 0:
  profitCash    = marketPrice × quantity − invested
  profitPercent = (1 − invested / (marketPrice × quantity)) × 100

Если invested = 0:
  profitCash    = marketPrice × quantity
  profitPercent = 100%

Средняя цена актива

Формула из TransactionMath.getAvgValue():

avg = (prevAvg × prevAmount + newPrice × newAmount) / (prevAmount + newAmount)

Средняя цена пересчитывается при каждой транзакции типа Buy и Mining (с инвестициями). Transfer In и Transfer Out не влияют на avg.