AugurProject/augur-ui

View on GitHub
src/modules/trades/helpers/generate-trade.js

Summary

Maintainability
D
1 day
Test Coverage
import { BigNumber, createBigNumber } from "utils/create-big-number";
import memoize from "memoizee";
import { formatPercent, formatShares, formatEther } from "utils/format-number";
import calcOrderProfitLossPercents from "modules/trades/helpers/calc-order-profit-loss-percents";
import { augur } from "services/augurjs";
import { calculateMaxPossibleShares } from "modules/markets/helpers/calculate-max-possible-shares";
import { BIDS, ASKS } from "modules/orders/constants/orders";
import { ZERO } from "modules/trades/constants/numbers";
import * as TRANSACTIONS_TYPES from "modules/transactions/constants/types";
import { updateTradesInProgress } from "modules/trades/actions/update-trades-in-progress";
import { selectAggregateOrderBook } from "modules/orders/helpers/select-order-book";
import store from "src/store";

/**
 * @param {Object} market
 * @param {Object} outcome
 * @param {Object} outcomeTradeInProgress
 * @param {Object} loginAccount
 * @param {Object} orderBooks Orders for market
 */
export const generateTrade = memoize(
  (market, outcome, outcomeTradeInProgress, orderBooks) => {
    const { loginAccount } = store.getState();
    const { settlementFee } = market;
    const side =
      (outcomeTradeInProgress && outcomeTradeInProgress.side) ||
      TRANSACTIONS_TYPES.BUY;
    const numShares =
      (outcomeTradeInProgress && outcomeTradeInProgress.numShares) || null;
    const sharesFilled =
      (outcomeTradeInProgress && outcomeTradeInProgress.sharesFilled) || null;
    const limitPrice =
      (outcomeTradeInProgress && outcomeTradeInProgress.limitPrice) || null;
    const totalFee = createBigNumber(
      (outcomeTradeInProgress && outcomeTradeInProgress.totalFee) || "0",
      10
    );
    const feePercent =
      (outcomeTradeInProgress && outcomeTradeInProgress.feePercent) || "0";
    const totalCost = createBigNumber(
      (outcomeTradeInProgress && outcomeTradeInProgress.totalCost) || "0",
      10
    );
    const shareCost = createBigNumber(
      (outcomeTradeInProgress && outcomeTradeInProgress.shareCost) || "0",
      10
    );
    const marketType = (market && market.marketType) || null;
    const minPrice =
      market &&
      (typeof market.minPrice === "number" ||
        BigNumber.isBigNumber(market.minPrice))
        ? market.minPrice
        : null;
    const maxPrice =
      market &&
      (typeof market.maxPrice === "number" ||
        BigNumber.isBigNumber(market.maxPrice))
        ? market.maxPrice
        : null;
    const adjustedTotalCost = totalCost.gt("0")
      ? totalCost
          .minus(totalFee)
          .abs()
          .toFixed()
      : null;
    const preOrderProfitLoss = calcOrderProfitLossPercents(
      numShares,
      limitPrice,
      side,
      minPrice,
      maxPrice,
      marketType,
      sharesFilled,
      adjustedTotalCost,
      settlementFee
    );

    let maxNumShares;
    if (limitPrice != null) {
      const orders = augur.trading.filterAndSortByPrice({
        singleOutcomeOrderBookSide: (orderBooks[outcome.id] || {})[
          side === TRANSACTIONS_TYPES.BUY
            ? TRANSACTIONS_TYPES.SELL
            : TRANSACTIONS_TYPES.BUY
        ],
        orderType: side,
        price: limitPrice,
        userAddress: loginAccount.address
      });
      maxNumShares = formatShares(
        calculateMaxPossibleShares(
          loginAccount,
          orders,
          market.makerFee,
          market.settlementFee,
          market.cumulativeScale,
          outcomeTradeInProgress,
          market.type === "scalar" ? market.minPrice : null
        )
      );
    } else {
      maxNumShares = formatShares(0);
    }

    return {
      side,
      numShares,
      limitPrice,
      maxNumShares,
      sharesFilled,

      potentialEthProfit: preOrderProfitLoss
        ? formatEther(preOrderProfitLoss.potentialEthProfit)
        : null,
      potentialEthLoss: preOrderProfitLoss
        ? formatEther(preOrderProfitLoss.potentialEthLoss)
        : null,
      potentialLossPercent: preOrderProfitLoss
        ? formatPercent(preOrderProfitLoss.potentialLossPercent)
        : null,
      potentialProfitPercent: preOrderProfitLoss
        ? formatPercent(preOrderProfitLoss.potentialProfitPercent)
        : null,

      tradingFees: preOrderProfitLoss
        ? formatEther(preOrderProfitLoss.tradingFees)
        : null,
      totalFee: formatEther(totalFee, { blankZero: true }),
      totalFeePercent: formatEther(feePercent, { blankZero: true }),
      totalCost: formatEther(totalCost.abs().toFixed(), { blankZero: false }),
      shareCost: formatEther(shareCost.abs().toFixed(), { blankZero: false }), // These are actually shares, but they can be formatted like ETH

      tradeTypeOptions: [
        { label: TRANSACTIONS_TYPES.BUY, value: TRANSACTIONS_TYPES.BUY },
        { label: TRANSACTIONS_TYPES.SELL, value: TRANSACTIONS_TYPES.SELL }
      ],

      tradeSummary: generateTradeSummary(
        generateTradeOrders(market, outcome, outcomeTradeInProgress)
      ),
      updateTradeOrder: (numShares, limitPrice, side, maxCost) =>
        store.dispatch(
          updateTradesInProgress({
            marketId: market.id,
            outcomeId: outcome.id,
            side,
            numShares,
            limitPrice,
            maxCost
          })
        ),
      totalSharesUpToOrder: (orderIndex, side) =>
        totalSharesUpToOrder(outcome.id, side, orderIndex, orderBooks)
    };
  },
  { max: 5 }
);

const totalSharesUpToOrder = memoize(
  (outcomeId, side, orderIndex, orderBooks) => {
    const { orderCancellation } = store.getState();

    const sideOrders = selectAggregateOrderBook(
      outcomeId,
      orderBooks,
      orderCancellation
    )[side === TRANSACTIONS_TYPES.BUY ? BIDS : ASKS];

    return sideOrders
      .filter((order, i) => i <= orderIndex)
      .reduce((p, order) => p + order.shares.value, 0);
  },
  { max: 5 }
);

export const generateTradeSummary = memoize(
  tradeOrders => {
    let tradeSummary = { totalGas: ZERO, tradeOrders: [] };

    if (tradeOrders && tradeOrders.length) {
      tradeSummary = tradeOrders.reduce((p, tradeOrder) => {
        // trade order
        p.tradeOrders.push(tradeOrder);

        return p;
      }, tradeSummary);
    }

    tradeSummary.totalGas = formatEther(tradeSummary.totalGas);

    return tradeSummary;
  },
  { max: 5 }
);

export const generateTradeOrders = memoize(
  (market, outcome, outcomeTradeInProgress) => {
    const tradeActions =
      outcomeTradeInProgress && outcomeTradeInProgress.tradeActions;
    if (
      !market ||
      !outcome ||
      !outcomeTradeInProgress ||
      !tradeActions ||
      !tradeActions.length
    ) {
      return [];
    }
    const { description, marketType, id: marketId } = market;
    const { id: outcomeId, name: outcomeName } = outcome;
    return tradeActions.map(tradeAction => {
      const numShares = createBigNumber(tradeAction.shares, 10);
      const costEth = createBigNumber(tradeAction.costEth, 10).abs();
      const avgPrice = createBigNumber(costEth, 10).dividedBy(
        createBigNumber(numShares, 10)
      );
      const noFeePrice =
        marketType === "scalar"
          ? outcomeTradeInProgress.limitPrice
          : tradeAction.noFeePrice;
      return {
        type: TRANSACTIONS_TYPES[tradeAction.action],
        data: {
          marketId,
          outcomeId,
          marketType,
          outcomeName
        },
        description,
        numShares: formatShares(tradeAction.shares),
        avgPrice: formatEther(avgPrice),
        noFeePrice: formatEther(noFeePrice),
        tradingFees: formatEther(tradeAction.feeEth),
        feePercent: formatPercent(tradeAction.feePercent)
      };
    });
  },
  { max: 5 }
);