package net.quedex.marketmaker; import net.quedex.api.market.Instrument; import net.quedex.api.user.OrderSide; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collection; import java.util.List; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; public class UniformFuturesOrderPlacingStrategy implements OrderPlacingStrategy { private static final Logger LOGGER = LoggerFactory.getLogger(UniformFuturesOrderPlacingStrategy.class); private final FairPriceProvider fairPriceProvider; private final RiskManager riskManager; private final int levels; private final int qtyOnLevel; private final double deltaLimit; private final BigDecimal spreadFraction; public UniformFuturesOrderPlacingStrategy(final FairPriceProvider fairPriceProvider, final RiskManager riskManager, final int levels, final int qtyOnLevel, final double deltaLimit, final BigDecimal spreadFraction) { checkArgument(levels >= 0, "numLevels=%s < 0", levels); checkArgument(qtyOnLevel > 0, "qtyOnLevel=%s <= 0", qtyOnLevel); checkArgument(deltaLimit >= 0, "deltaLimit=%s < 0", deltaLimit); checkArgument(spreadFraction.compareTo(BigDecimal.ZERO) > 0, "spreadFraction=%s <= 0", spreadFraction); this.fairPriceProvider = checkNotNull(fairPriceProvider, "null fairPriceProvider"); this.riskManager = checkNotNull(riskManager, "null riskManager"); this.levels = levels; this.qtyOnLevel = qtyOnLevel; this.deltaLimit = deltaLimit; this.spreadFraction = spreadFraction; } @Override public Collection<GenericOrder> getOrders(final Instrument futures) { checkArgument(futures.isFutures(), "Expected futures"); final BigDecimal fairPrice = fairPriceProvider.getFairPrice(futures.getInstrumentId()); final BigDecimal spread = fairPrice.multiply(spreadFraction); final List<GenericOrder> orders = new ArrayList<>(levels * 2); final double totalDelta = riskManager.getTotalDelta(); BigDecimal bid = null; BigDecimal ask = null; if (totalDelta < deltaLimit) { final List<GenericOrder> buys = getOrders(futures, OrderSide.BUY, fairPrice, spread.negate()); bid = buys.get(0).getPrice(); orders.addAll(buys); } // otherwise above limit - don't want to increase delta if (totalDelta > -deltaLimit) { final List<GenericOrder> sells = getOrders(futures, OrderSide.SELL, fairPrice, spread); ask = sells.get(0).getPrice(); orders.addAll(sells); } // otherwise below limit - don't want to decrease delta LOGGER.info("Generated orders {}: Bid = {}, Ask = {}", futures.getSymbol(), bid, ask); return orders; } private List<GenericOrder> getOrders( final Instrument futures, final OrderSide side, final BigDecimal fairPrice, final BigDecimal spread) { final List<GenericOrder> orders = new ArrayList<>(levels); for (int i = 1; i <= levels; i++) { final BigDecimal priceRounded = roundPriceToTickSize( fairPrice.add(spread.multiply(BigDecimal.valueOf(i))), side == OrderSide.BUY ? RoundingMode.DOWN : RoundingMode.UP, futures.getTickSize() ); orders.add(new GenericOrder( futures.getInstrumentId(), side, priceRounded, qtyOnLevel )); } return orders; } /** * @param price price to be rounded, has to be positive * @param roundingMode rounding mode that should be used (only {@link RoundingMode#UP} and {@link RoundingMode#DOWN} * are supported) * @return price rounded to tick size * * @throws IllegalArgumentException if {@code roundingMode} is neither {@link RoundingMode#UP} nor * {@link RoundingMode#DOWN} or {@code price} is not positive */ static BigDecimal roundPriceToTickSize(final BigDecimal price, final RoundingMode roundingMode, final BigDecimal tickSize) { checkArgument( roundingMode == RoundingMode.UP || roundingMode == RoundingMode.DOWN, "Only rounding UP or DOWN supported" ); checkArgument(price.compareTo(BigDecimal.ZERO) >= 0, "price=%s < 0", price); final BigDecimal remainder = price.remainder(tickSize); if (remainder.compareTo(BigDecimal.ZERO) == 0) { return price; } final BigDecimal result = price.subtract(remainder); if (roundingMode == RoundingMode.UP) { return result.add(tickSize); } else { return result; } } }