All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.knowm.xchange.simulated.Account Maven / Gradle / Ivy

package org.knowm.xchange.simulated;

import static java.math.BigDecimal.ZERO;

import com.google.common.collect.Collections2;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import org.knowm.xchange.currency.Currency;
import org.knowm.xchange.dto.Order;
import org.knowm.xchange.dto.account.Balance;
import org.knowm.xchange.dto.trade.LimitOrder;
import org.knowm.xchange.dto.trade.UserTrade;
import org.knowm.xchange.exceptions.ExchangeException;
import org.knowm.xchange.exceptions.FundsExceededException;
import org.knowm.xchange.exceptions.NotAvailableFromExchangeException;

class Account {

  private final ConcurrentMap> balances =
      new ConcurrentHashMap<>();

  void initialize(Iterable currencies) {
    currencies.forEach(
        currency -> balances.put(currency, new AtomicReference<>(new Balance(currency, ZERO))));
  }

  public Collection balances() {
    return Collections2.transform(balances.values(), AtomicReference::get);
  }

  public void checkBalance(LimitOrder order) {
    checkBalance(order, order.getOriginalAmount().multiply(order.getLimitPrice()));
  }

  public void checkBalance(Order order, BigDecimal bidAmount) {
    switch (order.getType()) {
      case ASK:
        BigDecimal askAmount = order.getRemainingAmount();
        Balance askBalance =
            balances.computeIfAbsent(order.getCurrencyPair().base, this::defaultBalance).get();
        checkBalance(order, askAmount, askBalance);
        break;
      case BID:
        Balance bidBalance =
            balances.computeIfAbsent(order.getCurrencyPair().counter, this::defaultBalance).get();
        checkBalance(order, bidAmount, bidBalance);
        break;
      default:
        throw new NotAvailableFromExchangeException(
            "Order type " + order.getType() + " not supported");
    }
  }

  private void checkBalance(Order order, BigDecimal amount, Balance balance) {
    if (balance.getAvailable().compareTo(amount) < 0) {
      throw new FundsExceededException(
          "Insufficient balance: "
              + amount.toPlainString()
              + order.getCurrencyPair().base
              + " required but only "
              + balance.getAvailable()
              + " available");
    }
  }

  public void reserve(LimitOrder order) {
    reserve(order, false);
  }

  public void release(LimitOrder order) {
    reserve(order, true);
  }

  private AtomicReference defaultBalance(Currency currency) {
    return new AtomicReference<>(new Balance(currency, ZERO));
  }

  private void reserve(LimitOrder order, boolean negate) {
    switch (order.getType()) {
      case ASK:
        BigDecimal askAmount =
            negate ? order.getRemainingAmount().negate() : order.getRemainingAmount();
        balance(order.getCurrencyPair().base)
            .updateAndGet(
                b -> {
                  if (b.getAvailable().compareTo(askAmount) < 0) {
                    throw new ExchangeException(
                        "Insufficient balance: "
                            + askAmount.toPlainString()
                            + order.getCurrencyPair().base
                            + " required but only "
                            + b.getAvailable()
                            + " available");
                  }
                  return Balance.Builder.from(b)
                      .available(b.getAvailable().subtract(askAmount))
                      .frozen(b.getFrozen().add(askAmount))
                      .build();
                });
        break;
      case BID:
        BigDecimal bid = order.getRemainingAmount().multiply(order.getLimitPrice());
        BigDecimal bidAmount = negate ? bid.negate() : bid;
        balance(order.getCurrencyPair().counter)
            .updateAndGet(
                b -> {
                  if (b.getAvailable().compareTo(bidAmount) < 0) {
                    throw new ExchangeException(
                        "Insufficient balance: "
                            + bidAmount.toPlainString()
                            + order.getCurrencyPair().counter
                            + " required but only "
                            + b.getAvailable()
                            + " available");
                  }
                  return Balance.Builder.from(b)
                      .available(b.getAvailable().subtract(bidAmount))
                      .frozen(b.getFrozen().add(bidAmount))
                      .build();
                });
        break;
      default:
        throw new NotAvailableFromExchangeException(
            "Order type " + order.getType() + " not supported");
    }
  }

  public void fill(UserTrade userTrade, boolean reserved) {
    BigDecimal counterAmount = userTrade.getOriginalAmount().multiply(userTrade.getPrice());
    switch (userTrade.getType()) {
      case ASK:
        balance(userTrade.getCurrencyPair().base)
            .updateAndGet(
                b ->
                    Balance.Builder.from(b)
                        .available(
                            reserved
                                ? b.getAvailable()
                                : b.getAvailable().subtract(userTrade.getOriginalAmount()))
                        .frozen(
                            reserved
                                ? b.getFrozen().subtract(userTrade.getOriginalAmount())
                                : b.getFrozen())
                        .total(b.getTotal().subtract(userTrade.getOriginalAmount()))
                        .build());
        balance(userTrade.getCurrencyPair().counter)
            .updateAndGet(
                b ->
                    Balance.Builder.from(b)
                        .total(b.getTotal().add(counterAmount))
                        .available(b.getAvailable().add(counterAmount))
                        .build());
        break;
      case BID:
        balance(userTrade.getCurrencyPair().base)
            .updateAndGet(
                b ->
                    Balance.Builder.from(b)
                        .total(b.getTotal().add(userTrade.getOriginalAmount()))
                        .available(b.getAvailable().add(userTrade.getOriginalAmount()))
                        .build());
        balance(userTrade.getCurrencyPair().counter)
            .updateAndGet(
                b ->
                    Balance.Builder.from(b)
                        .available(
                            reserved ? b.getAvailable() : b.getAvailable().subtract(counterAmount))
                        .frozen(reserved ? b.getFrozen().subtract(counterAmount) : b.getFrozen())
                        .total(b.getTotal().subtract(counterAmount))
                        .build());
        break;
      default:
        throw new NotAvailableFromExchangeException(
            "Order type " + userTrade.getType() + " not supported");
    }
  }

  private AtomicReference balance(Currency currency) {
    return balances.computeIfAbsent(currency, this::defaultBalance);
  }

  public void deposit(Currency currency, BigDecimal amount) {
    balance(currency)
        .updateAndGet(
            b ->
                Balance.Builder.from(b)
                    .total(b.getTotal().add(amount))
                    .available(b.getAvailable().add(amount))
                    .build());
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy