package com.after_sunrise.cryptocurrency.cryptotrader.service.bitmex;

import com.after_sunrise.cryptocurrency.cryptotrader.framework.Context.Key;
import com.after_sunrise.cryptocurrency.cryptotrader.framework.Instruction.CancelInstruction;
import com.after_sunrise.cryptocurrency.cryptotrader.framework.Instruction.CreateInstruction;
import com.after_sunrise.cryptocurrency.cryptotrader.framework.Order;
import com.after_sunrise.cryptocurrency.cryptotrader.framework.Trade;
import com.after_sunrise.cryptocurrency.cryptotrader.service.bitmex.BitmexService.ProductType;
import com.after_sunrise.cryptocurrency.cryptotrader.service.template.TemplateContext.RequestType;
import com.google.common.collect.Sets;
import com.google.common.io.Resources;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.after_sunrise.cryptocurrency.cryptotrader.framework.Service.CurrencyType.*;
import static com.after_sunrise.cryptocurrency.cryptotrader.service.bitmex.BitmexService.FundingType.XBT;
import static com.after_sunrise.cryptocurrency.cryptotrader.service.template.TemplateContext.RequestType.*;
import static com.google.common.io.Resources.getResource;
import static java.math.BigDecimal.valueOf;
import static java.math.BigDecimal.*;
import static java.math.RoundingMode.DOWN;
import static java.math.RoundingMode.UP;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.*;
import static java.util.Optional.of;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
import static org.testng.Assert.*;

/**
 * @author takanori.takase
 * @version 0.0.1
 */
public class BitmexContextTest {

    private BitmexContext target;

    private Configuration configuration;

    @BeforeMethod
    public void setUp() throws Exception {

        configuration = new Configurations().properties(getResource("cryptotrader-test.properties"));

        target = spy(new BitmexContext());

        target.setConfiguration(configuration);

        doReturn(null).when(target).request(any(), any(), any(), any());

    }

    @AfterMethod
    public void tearDown() throws Exception {
        target.close();
    }

    @Test(enabled = false)
    public void test() throws Exception {

        Path path = Paths.get(System.getProperty("user.home"), ".cryptotrader");
        target.setConfiguration(new Configurations().properties(path.toAbsolutePath().toFile()));

        doCallRealMethod().when(target).request(any(), any(), any(), any());

        Key key = Key.builder().instrument("XBT_QT").timestamp(Instant.now()).build();

        // Tick
        System.out.println("Ask : " + target.getBestAskPrice(key));
        System.out.println("Bid : " + target.getBestBidPrice(key));
        System.out.println("Mid : " + target.getMidPrice(key));
        System.out.println("Ltp : " + target.getLastPrice(key));

        // Book
        System.out.println("ASZ : " + target.getBestAskSize(key));
        System.out.println("BSZ : " + target.getBestBidSize(key));

        // Trade
        System.out.println("TRD : " + target.listTrades(key, null));

        // Account
        System.out.println("IPS : " + target.getInstrumentPosition(key));
        System.out.println("FPS : " + target.getFundingPosition(key));

        // Reference
        System.out.println("COM : " + target.getCommissionRate(key));
        System.out.println("MGN : " + target.isMarginable(key));
        System.out.println("EXP : " + target.getExpiry(key));

        // Order Query
        System.out.println("ORD : " + target.findOrders(key));
        System.out.println("EXC : " + target.listExecutions(key));

    }

    @Test(enabled = false)
    public void test_Order() throws Exception {

        Path path = Paths.get(System.getProperty("user.home"), ".cryptotrader");
        target.setConfiguration(new Configurations().properties(path.toAbsolutePath().toFile()));

        doCallRealMethod().when(target).request(any(), any(), any(), any());

        Key key = Key.builder().instrument("XBJ_QT").timestamp(Instant.now()).build();

        Map<CreateInstruction, String> ids = target.createOrders(
                key, Sets.newHashSet(
                        CreateInstruction.builder()
                                .price(new BigDecimal("1000000"))
                                .size(new BigDecimal("10E-1")).build(),
                        CreateInstruction.builder()
                                .price(new BigDecimal("1000000"))
                                .size(new BigDecimal("0.2E1")).build()
                ));

        System.out.println("NEW : " + ids);

        Thread.sleep(TimeUnit.SECONDS.toMillis(5));

        System.out.println("CND : " + target.cancelOrders(key, ids.values().stream()
                .map(id -> CancelInstruction.builder().id(id).build()).collect(Collectors.toSet())
        ));

    }

    @Test
    public void testConvertAlias() throws Exception {

        doReturn(Resources.toString(getResource("json/bitmex_alias.json"), UTF_8)).when(target)
                .request(GET, "https://www.bitmex.com/api/v1/instrument/activeIntervals", null, null);

        for (ProductType product : ProductType.values()) {

            String expect = null;

            switch (product) {
                case BXBT:
                case BXBT30M:
                case BXBTJPY:
                case BXBTJPY30M:
                case ETHXBT:
                case ETHXBT30M:
                case ETCXBT:
                case ETCXBT30M:
                case BCHXBT:
                case BCHXBT30M:
                    expect = "." + product.name();
                    break;
                case XBT:
                    expect = "XBT";
                    break;
                case XBTUSD:
                case XBT_FR:
                    expect = "XBTUSD";
                    break;
                case XBT_QT:
                    expect = "XBTZ17";
                    break;
                case XBJ_QT:
                    expect = "XBJZ17";
                    break;
                case ETH_QT:
                    expect = "ETHZ17";
                    break;
                case ETC_WK:
                    expect = "ETC7D";
                    break;
                case BCH_MT:
                    expect = "BCHX17";
                    break;
            }

            assertEquals(target.convertAlias(Key.builder().instrument(product.name()).build()), expect);

        }

        configuration.setProperty(
                "com.after_sunrise.cryptocurrency.cryptotrader.service.bitmex.BitmexContext.alias.XBT_QT",
                "XBT???"
        );
        assertEquals(target.convertAlias(Key.builder().instrument("XBT_QT").build()), "XBT???");

        assertNull(target.convertAlias(Key.builder().instrument("foo").build()));
        assertNull(target.convertAlias(Key.builder().instrument(null).build()));
        assertNull(target.convertAlias(null));

    }

    @Test
    public void testQueryTick() throws Exception {

        doReturn(Resources.toString(getResource("json/bitmex_ticker.json"), UTF_8)).when(target)
                .request(GET, "https://www.bitmex.com/api/v1/instrument/activeAndIndices", null, null);

        Key key1 = Key.builder().instrument("XBTUSD").build();
        Key key2 = Key.builder().instrument("BXBT").build();
        Key key3 = Key.builder().instrument("XBT_QT").build();
        Key key4 = Key.builder().instrument("XBT_FR").build();
        Key key5 = Key.builder().instrument("XBT").timestamp(Instant.ofEpochMilli(123)).build();
        doReturn("XBTUSD").when(target).convertAlias(key1);
        doReturn(".BXBT").when(target).convertAlias(key2);
        doReturn("XBTZ17").when(target).convertAlias(key3);
        doReturn("XBTUSD").when(target).convertAlias(key4);

        Optional<BitmexTick> result = target.queryTick(key1);
        assertTrue(result.isPresent());
        assertEquals(result.get().getSymbol(), "XBTUSD");
        assertEquals(result.get().getSettleCurrency(), "XBt");
        assertEquals(result.get().getState(), "Open");
        assertEquals(result.get().getTimestamp(), Instant.parse("2017-11-01T22:13:32.101Z"));
        assertEquals(result.get().getLast(), new BigDecimal("6593.7"));
        assertEquals(result.get().getAsk(), new BigDecimal("6593.9"));
        assertEquals(result.get().getBid(), new BigDecimal("6593.8"));
        assertEquals(result.get().getMid(), new BigDecimal("6593.85"));
        assertEquals(result.get().getLotSize(), new BigDecimal("1"));
        assertEquals(result.get().getTickSize(), new BigDecimal("0.1"));
        assertEquals(result.get().getExpiry(), null);
        assertEquals(result.get().getReference(), ".BXBT");
        assertEquals(result.get().getMakerFee(), new BigDecimal("-0.00025"));
        assertEquals(result.get().getTakerFee(), new BigDecimal("0.00075"));
        assertEquals(result.get().getSettleFee(), new BigDecimal("0"));
        assertEquals(result.get().getFundingFee(), new BigDecimal("-0.001213"));

        result = target.queryTick(key2);
        assertTrue(result.isPresent());
        assertEquals(result.get().getSymbol(), ".BXBT");
        assertEquals(result.get().getSettleCurrency(), "");
        assertEquals(result.get().getState(), "Unlisted");
        assertEquals(result.get().getTimestamp(), Instant.parse("2017-11-01T22:13:20.000Z"));
        assertEquals(result.get().getLast(), new BigDecimal("6601.72"));
        assertEquals(result.get().getAsk(), null);
        assertEquals(result.get().getBid(), null);
        assertEquals(result.get().getMid(), null);
        assertEquals(result.get().getLotSize(), null);
        assertEquals(result.get().getTickSize(), new BigDecimal("0.01"));
        assertEquals(result.get().getExpiry(), null);
        assertEquals(result.get().getReference(), ".BXBT");
        assertEquals(result.get().getMakerFee(), null);
        assertEquals(result.get().getTakerFee(), null);
        assertEquals(result.get().getSettleFee(), null);
        assertEquals(result.get().getFundingFee(), null);

        result = target.queryTick(key3);
        assertTrue(result.isPresent());
        assertEquals(result.get().getSymbol(), "XBTZ17");
        assertEquals(result.get().getSettleCurrency(), "XBt");
        assertEquals(result.get().getState(), "Open");
        assertEquals(result.get().getTimestamp(), Instant.parse("2017-11-03T07:28:24.468Z"));
        assertEquals(result.get().getLast(), new BigDecimal("6712.3"));
        assertEquals(result.get().getAsk(), new BigDecimal("6712.5"));
        assertEquals(result.get().getBid(), new BigDecimal("6712.4"));
        assertEquals(result.get().getMid(), new BigDecimal("6712.45"));
        assertEquals(result.get().getLotSize(), new BigDecimal("1"));
        assertEquals(result.get().getTickSize(), new BigDecimal("0.1"));
        assertEquals(result.get().getExpiry(), Instant.parse("2017-12-29T12:00:00.000Z"));
        assertEquals(result.get().getReference(), ".BXBT30M");
        assertEquals(result.get().getMakerFee(), new BigDecimal("-0.00025"));
        assertEquals(result.get().getTakerFee(), new BigDecimal("0.00075"));
        assertEquals(result.get().getSettleFee(), new BigDecimal("0.0005"));
        assertEquals(result.get().getFundingFee(), null);

        result = target.queryTick(key4);
        assertTrue(result.isPresent());
        assertEquals(result.get().getSymbol(), "XBT_FR");
        assertEquals(result.get().getSettleCurrency(), null);
        assertEquals(result.get().getState(), "Unlisted");
        assertEquals(result.get().getTimestamp(), Instant.parse("2017-11-01T22:13:32.101Z"));
        assertEquals(result.get().getLast(), new BigDecimal("0.998787"));
        assertEquals(result.get().getAsk(), null);
        assertEquals(result.get().getBid(), null);
        assertEquals(result.get().getMid(), null);
        assertEquals(result.get().getLotSize(), null);
        assertEquals(result.get().getTickSize(), null);
        assertEquals(result.get().getExpiry(), null);
        assertEquals(result.get().getReference(), null);
        assertEquals(result.get().getMakerFee(), null);
        assertEquals(result.get().getTakerFee(), null);
        assertEquals(result.get().getSettleFee(), null);
        assertEquals(result.get().getFundingFee(), new BigDecimal("-0.001213"));

        // Empty
        target.clear();
        doReturn(null).when(target).request(any(), any(), any(), any());
        assertFalse(target.queryTick(key1).isPresent());

        // Exception
        target.clear();
        doThrow(new IOException("test")).when(target).request(any(), any(), any(), any());
        assertFalse(target.queryTick(key1).isPresent());

        // XBT
        target.clear();
        result = target.queryTick(key5);
        assertTrue(result.isPresent());
        assertEquals(result.get().getSymbol(), "XBT");
        assertEquals(result.get().getSettleCurrency(), "XBt");
        assertEquals(result.get().getState(), "Unlisted");
        assertEquals(result.get().getTimestamp(), Instant.ofEpochMilli(123));
        assertEquals(result.get().getLast(), ONE);
        assertEquals(result.get().getAsk(), null);
        assertEquals(result.get().getBid(), null);
        assertEquals(result.get().getMid(), null);
        assertEquals(result.get().getLotSize(), null);
        assertEquals(result.get().getTickSize(), null);
        assertEquals(result.get().getExpiry(), null);
        assertEquals(result.get().getReference(), null);
        assertEquals(result.get().getMakerFee(), null);
        assertEquals(result.get().getTakerFee(), null);
        assertEquals(result.get().getSettleFee(), null);
        assertEquals(result.get().getFundingFee(), null);

    }

    @Test
    public void testQueryBooks() throws Exception {

        doReturn(Resources.toString(getResource("json/bitmex_book.json"), UTF_8)).when(target)
                .request(GET, "https://www.bitmex.com/api/v1/orderBook/L2?symbol=XBTUSD&depth=10", null, null);

        Key key = Key.builder().instrument("XBT???").build();
        doReturn("XBTUSD").when(target).convertAlias(key);

        List<BitmexBook> result = target.queryBooks(key);
        assertEquals(result.size(), 4);
        assertEquals(result.get(0).getSide(), "Sell");
        assertEquals(result.get(0).getPrice(), new BigDecimal("6573.5"));
        assertEquals(result.get(0).getSize(), new BigDecimal("137"));
        assertEquals(result.get(1).getSide(), "Sell");
        assertEquals(result.get(1).getPrice(), new BigDecimal("6573"));
        assertEquals(result.get(1).getSize(), new BigDecimal("65348"));
        assertEquals(result.get(2).getSide(), "Buy");
        assertEquals(result.get(2).getPrice(), new BigDecimal("6572.5"));
        assertEquals(result.get(2).getSize(), new BigDecimal("22050"));
        assertEquals(result.get(3).getSide(), "Buy");
        assertEquals(result.get(3).getPrice(), new BigDecimal("6571.5"));
        assertEquals(result.get(3).getSize(), new BigDecimal("797"));

        // Empty
        target.clear();
        doReturn(null).when(target).request(any(), any(), any(), any());
        assertEquals(target.queryBooks(key).size(), 0);

        // Exception
        target.clear();
        doThrow(new IOException("test")).when(target).request(any(), any(), any(), any());
        assertEquals(target.queryBooks(key).size(), 0);

    }

    @Test
    public void testGetBestAskPrice() throws Exception {

        Key key = Key.builder().build();

        // Listed
        doReturn(of(BitmexTick.builder().ask(TEN).last(ONE).build())).when(target).queryTick(key);
        assertEquals(target.getBestAskPrice(key), TEN);

        // Unlisted
        doReturn(of(BitmexTick.builder().ask(TEN).last(ONE).state("Unlisted").build())).when(target).queryTick(key);
        assertEquals(target.getBestAskPrice(key), ONE);

        doReturn(Optional.empty()).when(target).queryTick(key);
        assertEquals(target.getBestAskPrice(key), null);

    }

    @Test
    public void testGetBestBidPrice() throws Exception {

        Key key = Key.builder().build();

        // Listed
        doReturn(of(BitmexTick.builder().bid(TEN).last(ONE).build())).when(target).queryTick(key);
        assertEquals(target.getBestBidPrice(key), TEN);

        // Unlisted
        doReturn(of(BitmexTick.builder().bid(TEN).last(ONE).state("Unlisted").build())).when(target).queryTick(key);
        assertEquals(target.getBestBidPrice(key), ONE);

        doReturn(Optional.empty()).when(target).queryTick(key);
        assertEquals(target.getBestBidPrice(key), null);

    }

    @Test
    public void testGetBestBboSize() throws Exception {

        Key key = Key.builder().build();

        List<BitmexBook> books = new ArrayList<>();
        books.add(BitmexBook.builder().side("Sell").price(valueOf(9)).size(valueOf(19)).build());
        books.add(BitmexBook.builder().side("Sell").price(null).size(valueOf(18)).build());
        books.add(BitmexBook.builder().side("Sell").price(valueOf(7)).size(valueOf(17)).build());
        books.add(BitmexBook.builder().side("Sell").price(valueOf(6)).size(null).build());
        books.add(BitmexBook.builder().side(null).price(valueOf(5)).size(valueOf(15)).build());
        books.add(BitmexBook.builder().side("Buy").price(null).size(valueOf(26)).build());
        books.add(BitmexBook.builder().side("Buy").price(valueOf(3)).size(valueOf(27)).build());
        books.add(BitmexBook.builder().side("Buy").price(valueOf(2)).size(null).build());
        books.add(BitmexBook.builder().side("Buy").price(valueOf(1)).size(valueOf(29)).build());
        books.add(null);
        doReturn(books).when(target).queryBooks(key);

        // Plain
        doReturn(Optional.empty()).when(target).queryTick(key);
        assertEquals(target.getBestAskSize(key), valueOf(17));
        assertEquals(target.getBestBidSize(key), valueOf(27));

        // Unlisted
        BitmexTick tick = mock(BitmexTick.class);
        when(tick.getState()).thenReturn("Unlisted");
        doReturn(Optional.of(tick)).when(target).queryTick(key);
        assertEquals(target.getBestAskSize(key), ZERO);
        assertEquals(target.getBestBidSize(key), ZERO);

        // No data
        doReturn(Optional.empty()).when(target).queryTick(key);
        doReturn(Collections.emptyList()).when(target).queryBooks(key);
        assertEquals(target.getBestAskSize(key), null);
        assertEquals(target.getBestBidSize(key), null);

    }

    @Test
    public void testGetMidPrice() throws Exception {

        Key key = Key.builder().build();

        // Listed
        doReturn(of(BitmexTick.builder().mid(TEN).last(ONE).build())).when(target).queryTick(key);
        assertEquals(target.getMidPrice(key), TEN);

        // Unlisted
        doReturn(of(BitmexTick.builder().mid(TEN).last(ONE).state("Unlisted").build())).when(target).queryTick(key);
        assertEquals(target.getMidPrice(key), ONE);

        doReturn(Optional.empty()).when(target).queryTick(key);
        assertEquals(target.getMidPrice(key), null);

    }

    @Test
    public void testGetLastPrice() throws Exception {

        Key key = Key.builder().build();

        doReturn(of(BitmexTick.builder().last(TEN).build())).when(target).queryTick(key);
        assertEquals(target.getLastPrice(key), TEN);

        doReturn(Optional.empty()).when(target).queryTick(key);
        assertEquals(target.getLastPrice(key), null);

    }

    @Test
    public void testListTrades() throws Exception {

        doReturn(Resources.toString(getResource("json/bitmex_trade.json"), UTF_8))
                .when(target).request(GET,
                "https://www.bitmex.com/api/v1/trade?count=500&reverse=true&symbol=XBTZ17",
                null, null);
        doReturn(Resources.toString(getResource("json/bitmex_bucket.json"), UTF_8))
                .when(target).request(GET,
                "https://www.bitmex.com/api/v1/trade/bucketed?binSize=1m&partial=true&count=500&reverse=true&symbol=XBJZ17",
                null, null);

        Key key = Key.builder().instrument("XBT_QT").timestamp(Instant.parse("2017-11-01T23:15:48.000Z")).build();
        BitmexTick tick = spy(BitmexTick.builder().timestamp(Instant.now()).last(TEN).build());
        doReturn(Optional.of(tick)).when(target).queryTick(key);
        doReturn("XBTZ17").when(target).convertAlias(key);

        // Unlisted (Index)
        when(tick.getState()).thenReturn("Unlisted");
        List<Trade> trades = target.listTrades(key, null);
        assertEquals(trades.size(), 1);
        assertEquals(trades.get(0).getPrice(), tick.getLast());
        assertEquals(trades.get(0).getSize(), ZERO);
        assertEquals(trades.get(0).getTimestamp(), tick.getTimestamp());

        // Listed
        when(tick.getState()).thenReturn(null);
        trades = target.listTrades(key, null);
        assertEquals(trades.size(), 2);
        assertEquals(trades.get(0).getPrice(), new BigDecimal("6601.7"));
        assertEquals(trades.get(0).getSize(), new BigDecimal("686"));
        assertEquals(trades.get(0).getTimestamp(), Instant.parse("2017-11-01T22:15:47.303Z"));
        assertEquals(trades.get(1).getPrice(), new BigDecimal("6601.6"));
        assertEquals(trades.get(1).getSize(), new BigDecimal("5"));
        assertEquals(trades.get(1).getTimestamp(), Instant.parse("2017-11-01T22:15:47.000Z"));

        // Filtered in (all)
        trades = target.listTrades(key, Instant.parse("2017-11-01T22:15:47.000Z"));
        assertEquals(trades.size(), 2);

        // Filtered in
        trades = target.listTrades(key, Instant.parse("2017-11-01T22:15:47.303Z"));
        assertEquals(trades.size(), 1);
        assertEquals(trades.get(0).getTimestamp(), Instant.parse("2017-11-01T22:15:47.303Z"));

        // Filtered out
        trades = target.listTrades(key, Instant.parse("2017-11-01T22:15:47.304Z"));
        assertEquals(trades.size(), 0);

        // Bucketed
        key = Key.builder().instrument("XBJ_QT").timestamp(key.getTimestamp()).build();
        doReturn("XBJZ17").when(target).convertAlias(key);
        trades = target.listTrades(key, null);
        assertEquals(trades.size(), 2);
        assertEquals(trades.get(0).getPrice(), new BigDecimal("793399"));
        assertEquals(trades.get(0).getSize(), new BigDecimal("15000"));
        assertEquals(trades.get(0).getTimestamp(), Instant.parse("2017-11-05T06:50:00.000Z"));
        assertEquals(trades.get(1).getPrice(), new BigDecimal("793399"));
        assertEquals(trades.get(1).getSize(), new BigDecimal("5000"));
        assertEquals(trades.get(1).getTimestamp(), Instant.parse("2017-11-05T06:48:00.000Z"));

        // Filtered in (all)
        trades = target.listTrades(key, Instant.parse("2017-11-05T06:48:00.000Z"));
        assertEquals(trades.size(), 2);

        // Filtered in
        trades = target.listTrades(key, Instant.parse("2017-11-05T06:50:00.000Z"));
        assertEquals(trades.size(), 1);
        assertEquals(trades.get(0).getTimestamp(), Instant.parse("2017-11-05T06:50:00.000Z"));

        // Filtered out
        trades = target.listTrades(key, Instant.parse("2017-11-05T06:50:00.001Z"));
        assertEquals(trades.size(), 0);

        // No data
        key = Key.builder().instrument("ETH_QT").timestamp(key.getTimestamp()).build();
        trades = target.listTrades(key, null);
        assertEquals(trades.size(), 0);

        // Null key
        trades = target.listTrades(null, null);
        assertEquals(trades.size(), 0);

    }

    @Test
    public void testGetInstrumentCurrency() {

        Key key = Key.builder().build();
        assertEquals(target.getInstrumentCurrency(key), null);
        assertEquals(target.getInstrumentCurrency(null), null);

        key = Key.builder().instrument("XBTUSD").build();
        assertEquals(target.getInstrumentCurrency(key), BTC);

        key = Key.builder().instrument("XBJ_QT").build();
        assertEquals(target.getInstrumentCurrency(key), BTC);

        key = Key.builder().instrument("ETH_QT").build();
        assertEquals(target.getInstrumentCurrency(key), ETH);

    }

    @Test
    public void testGetFundingCurrency() {

        Key key = Key.builder().build();
        assertEquals(target.getFundingCurrency(key), null);
        assertEquals(target.getFundingCurrency(null), null);

        key = Key.builder().instrument("XBTUSD").build();
        assertEquals(target.getFundingCurrency(key), USD);

        key = Key.builder().instrument("XBJ_QT").build();
        assertEquals(target.getFundingCurrency(key), JPY);

        key = Key.builder().instrument("ETH_QT").build();
        assertEquals(target.getFundingCurrency(key), BTC);

    }

    @Test
    public void testFindProduct() {
        assertEquals(target.findProduct(null, BTC, USD), "BXBT");
        assertEquals(target.findProduct(null, BTC, JPY), "BXBTJPY");
        assertEquals(target.findProduct(null, BCH, BTC), "BCHXBT");
        assertEquals(target.findProduct(null, ETC, BTC), "ETCXBT");
        assertEquals(target.findProduct(null, ETH, BTC), "ETHXBT");
        assertEquals(target.findProduct(null, JPY, BTC), null);
    }

    @Test
    public void testGetConversionPrice() {

        Key key1 = Key.builder().instrument("XBTUSD").build();
        Key key2 = Key.builder().instrument("XBJ_QT").build();
        Key key3 = Key.builder().instrument("ETH_QT").build();
        Key key4 = Key.builder().instrument("FOOBAR").build();

        doReturn(null).when(target).getMidPrice(any());
        doReturn(new BigDecimal("6543")).when(target).getMidPrice(key1);
        doReturn(new BigDecimal("765432")).when(target).getMidPrice(key2);
        doReturn(new BigDecimal("0.0432")).when(target).getMidPrice(key3);

        // Structure
        assertEquals(target.getConversionPrice(key1, BTC), new BigDecimal("6543.0000000000"));
        assertEquals(target.getConversionPrice(key2, BTC), new BigDecimal("7654.3200000000"));
        assertEquals(target.getConversionPrice(key3, BTC), new BigDecimal("0023.1481481481"));
        assertEquals(target.getConversionPrice(key4, BTC), null);

        // Funding
        assertEquals(target.getConversionPrice(key1, USD), new BigDecimal("0.0001528351"));
        assertEquals(target.getConversionPrice(key2, JPY), new BigDecimal("0.0000000131"));
        assertEquals(target.getConversionPrice(key3, ETH), new BigDecimal("0.0432000000"));

        // Incompatible
        assertEquals(target.getConversionPrice(key1, ETH), null);
        assertEquals(target.getConversionPrice(key2, USD), null);
        assertEquals(target.getConversionPrice(key3, JPY), null);

        // Invalid Price
        doReturn(null).when(target).getMidPrice(key1);
        doReturn(ZERO).when(target).getMidPrice(key2);
        doReturn(ZERO).when(target).getMidPrice(key3);
        assertEquals(target.getConversionPrice(key1, BTC), null);
        assertEquals(target.getConversionPrice(key2, BTC), null);
        assertEquals(target.getConversionPrice(key3, BTC), null);

    }

    @Test
    public void testComputeHash() throws Exception {

        // Samples from : https://www.bitmex.com/app/apiKeysUsage
        String secret = "chNOOS4KvNXR_Xq4k4c9qsfoKWvnDecLATCRlcBwyKDYnWgO";

        // GET
        String path = "/api/v1/instrument?filter=%7B%22symbol%22%3A+%22XBTM15%22%7D";
        String verb = "GET";
        String nonce = "1429631577690";
        String data = null;
        assertEquals(target.computeHash(secret, verb, path, nonce, data),
                "9f1753e2db64711e39d111bc2ecace3dc9e7f026e6f65b65c4f53d3d14a60e5f");

        // POST
        path = "/api/v1/order";
        verb = "POST";
        nonce = "1429631577995";
        data = "{'symbol':'XBTM15','price':219.0,'clOrdID':'mm_bitmex_1a/oemUeQ4CAJZgP3fjHsA','orderQty':98}"
                .replaceAll("'", "\"");
        assertEquals(target.computeHash(secret, verb, path, nonce, data),
                "93912e048daa5387759505a76c28d6e92c6a0d782504fc9980f4fb8adfc13e25");

    }

    @Test
    public void testExecutePrivate() throws Exception {

        String path = "/hoge";
        String data = "test data";
        String body = "test body";

        Map<String, String> parameters = new LinkedHashMap<>();
        parameters.put("k?", "v&");
        parameters.put(null, "v ");
        parameters.put("k/", null);
        parameters.put("k:", "v;");

        configuration.setProperty(
                "com.after_sunrise.cryptocurrency.cryptotrader.service.bitmex.BitmexContext.api.id",
                "my_id"
        );
        configuration.setProperty(
                "com.after_sunrise.cryptocurrency.cryptotrader.service.bitmex.BitmexContext.api.secret",
                "my_secret"
        );

        doReturn(Instant.ofEpochMilli(12345)).when(target).getNow();

        doAnswer(i -> {

            assertEquals(i.getArgumentAt(0, RequestType.class), PUT);
            assertEquals(i.getArgumentAt(1, String.class), "https://www.bitmex.com/hoge?k%3F=v%26&k%3A=v%3B");
            assertEquals(i.getArgumentAt(3, String.class), "test data");

            Map<?, ?> headers = i.getArgumentAt(2, Map.class);
            assertEquals(headers.remove("Content-Type"), "application/json");
            assertEquals(headers.remove("api-nonce"), "12345");
            assertEquals(headers.remove("api-key"), "my_id");
            assertEquals(headers.remove("api-signature"),
                    "6ef0c2129a11f4dcd8d92ddfa35481a923767e98bbcdd075718dde54e600017a");
            assertEquals(headers.size(), 0, headers.toString());

            return body;

        }).when(target).request(any(), any(), any(), any());

        // Proper request
        assertEquals(target.executePrivate(PUT, path, parameters, data), body);

        // Tokens not configured
        configuration.clear();
        assertNull(target.executePrivate(PUT, path, parameters, data));

    }

    @Test
    public void testGetInstrumentPosition() throws Exception {

        doReturn(Resources.toString(getResource("json/bitmex_position.json"), UTF_8))
                .when(target).executePrivate(GET, "/api/v1/position", null, null);

        Key key1 = Key.builder().instrument("XBT_QT").build();
        Key key2 = Key.builder().instrument("XBJ_QT").build();
        doReturn("XBTZ17").when(target).convertAlias(key1);
        doReturn("XBJZ17").when(target).convertAlias(key2);

        // Long
        assertEquals(target.getInstrumentPosition(key1), new BigDecimal("100"));
        verify(target, times(1)).executePrivate(any(), any(), any(), any());

        // Short
        assertEquals(target.getInstrumentPosition(key2), new BigDecimal("-100"));
        verify(target, times(1)).executePrivate(any(), any(), any(), any());

        // No match
        assertEquals(target.getInstrumentPosition(Key.builder().build()), ZERO);
        verify(target, times(1)).executePrivate(any(), any(), any(), any());

        // No data
        doReturn(null).when(target).executePrivate(any(), any(), any(), any());
        target.clear();
        assertEquals(target.getInstrumentPosition(key2), null);
        verify(target, times(2)).executePrivate(any(), any(), any(), any());

        // Error
        doThrow(new IOException("test")).when(target).executePrivate(any(), any(), any(), any());
        target.clear();
        assertEquals(target.getInstrumentPosition(key2), null);
        verify(target, times(2 + 3)).executePrivate(any(), any(), any(), any());

    }

    @Test
    public void testGetFundingPosition() throws Exception {

        doReturn(Resources.toString(getResource("json/bitmex_margin.json"), UTF_8))
                .when(target).executePrivate(GET, "/api/v1/user/margin", singletonMap("currency", "all"), null);

        Key key1 = Key.builder().instrument("XBT_QT").build();
        Key key2 = Key.builder().instrument("XBJ_QT").build();

        doReturn(Optional.empty()).when(target).queryTick(any());
        doReturn(of(BitmexTick.builder().settleCurrency("XBt").build())).when(target).queryTick(key1);
        doReturn(of(BitmexTick.builder().settleCurrency("XBt").build())).when(target).queryTick(key2);

        doReturn(null).when(target).getFundingConversionRate(any(), any());
        doReturn(new BigDecimal("6500.12")).when(target).getFundingConversionRate(key1, XBT);
        doReturn(new BigDecimal("750000")).when(target).getFundingConversionRate(key2, XBT);

        // Found (margin = 49990819 SATOSHI)
        assertEquals(target.getFundingPosition(key1), new BigDecimal("3246.7830945044"));
        assertEquals(target.getFundingPosition(key2), new BigDecimal("374621.90250000"));
        verify(target, times(1)).executePrivate(any(), any(), any(), any());

        // No data
        doReturn(null).when(target).executePrivate(any(), any(), any(), any());
        target.clear();
        assertEquals(target.getFundingPosition(key1), null);
        assertEquals(target.getFundingPosition(key2), null);
        verify(target, times(1 + 1)).executePrivate(any(), any(), any(), any());

        // Error
        doThrow(new IOException("test")).when(target).executePrivate(any(), any(), any(), any());
        target.clear();
        assertEquals(target.getFundingPosition(key1), null);
        assertEquals(target.getFundingPosition(key2), null);
        verify(target, times(1 + 1 + 3)).executePrivate(any(), any(), any(), any());

        // No rate
        doReturn(null).when(target).getFundingConversionRate(any(), any());
        target.clear();
        assertEquals(target.getFundingPosition(key1), null);
        assertEquals(target.getFundingPosition(key2), null);
        verify(target, times(1 + 1 + 3)).executePrivate(any(), any(), any(), any());

    }

    @Test
    public void testGetFundingConversionRate() {

        Key key = Key.builder().instrument(ProductType.XBJ_QT.name()).build();

        // Null Price
        doReturn(null).when(target).getMidPrice(key);
        assertNull(target.getFundingConversionRate(key, XBT));

        // Zero Price
        doReturn(new BigDecimal("0.0")).when(target).getMidPrice(key);
        assertNull(target.getFundingConversionRate(key, XBT));

        // Valid
        doReturn(new BigDecimal("795000")).when(target).getMidPrice(key);
        assertEquals(target.getFundingConversionRate(key, XBT), new BigDecimal("6320250000.0000000000"));

        // Null Arguments
        assertNull(target.getFundingConversionRate(key, null));
        assertNull(target.getFundingConversionRate(null, XBT));

        // Unknown Product
        key = Key.builder().instrument("foo").build();
        assertNull(target.getFundingConversionRate(key, XBT));

        // Not traded
        key = Key.builder().instrument(ProductType.BXBTJPY.name()).build();
        assertNull(target.getFundingConversionRate(key, XBT));

        // Funding Instrument
        key = Key.builder().instrument(ProductType.ETH_QT.name()).build();
        assertEquals(target.getFundingConversionRate(key, XBT), new BigDecimal("1.0000000000"));

    }

    @Test
    public void testRoundLotSize() throws Exception {

        Key key = Key.builder().build();

        // OK
        doReturn(of(BitmexTick.builder().lotSize(TEN).build())).when(target).queryTick(key);
        assertEquals(target.roundLotSize(key, valueOf(5), UP), TEN);
        assertEquals(target.roundLotSize(key, valueOf(5), DOWN), ZERO);

        // Null input
        assertEquals(target.roundLotSize(key, null, UP), null);
        assertEquals(target.roundLotSize(key, valueOf(5), null), null);

        // Zero lot
        doReturn(of(BitmexTick.builder().lotSize(ZERO).build())).when(target).queryTick(key);
        assertEquals(target.roundLotSize(key, valueOf(5), UP), null);
        assertEquals(target.roundLotSize(key, valueOf(5), DOWN), null);

        // Null lot
        doReturn(of(BitmexTick.builder().build())).when(target).queryTick(key);
        assertEquals(target.roundLotSize(key, valueOf(5), UP), null);
        assertEquals(target.roundLotSize(key, valueOf(5), DOWN), null);

        // Null tick
        doReturn(Optional.empty()).when(target).queryTick(key);
        assertEquals(target.roundLotSize(key, valueOf(5), UP), null);
        assertEquals(target.roundLotSize(key, valueOf(5), DOWN), null);

    }

    @Test
    public void testRoundTickSize() throws Exception {

        Key key = Key.builder().build();

        // OK
        doReturn(of(BitmexTick.builder().tickSize(TEN).build())).when(target).queryTick(key);
        assertEquals(target.roundTickSize(key, valueOf(15), UP), TEN.add(TEN));
        assertEquals(target.roundTickSize(key, valueOf(15), DOWN), TEN);

        // Rounded to zero
        assertEquals(target.roundTickSize(key, valueOf(+9), DOWN), null);
        assertEquals(target.roundTickSize(key, valueOf(-9), DOWN), null);

        // Null input
        assertEquals(target.roundTickSize(key, null, UP), null);
        assertEquals(target.roundTickSize(key, valueOf(15), null), null);

        // Zero tick
        doReturn(of(BitmexTick.builder().tickSize(ZERO).build())).when(target).queryTick(key);
        assertEquals(target.roundTickSize(key, valueOf(15), UP), null);
        assertEquals(target.roundTickSize(key, valueOf(15), DOWN), null);

        // Null tick
        doReturn(of(BitmexTick.builder().build())).when(target).queryTick(key);
        assertEquals(target.roundTickSize(key, valueOf(15), UP), null);
        assertEquals(target.roundTickSize(key, valueOf(15), DOWN), null);

        // Null tick
        doReturn(Optional.empty()).when(target).queryTick(key);
        assertEquals(target.roundTickSize(key, valueOf(15), UP), null);
        assertEquals(target.roundTickSize(key, valueOf(15), DOWN), null);

    }

    @Test
    public void testGetCommissionRate() throws Exception {

        Key key = Key.builder().build();

        // With commission
        doReturn(of(BitmexTick.builder().makerFee(ONE).settleFee(TEN).build())).when(target).queryTick(key);
        assertEquals(target.getCommissionRate(key), ONE.add(TEN));

        // With negative commission
        doReturn(of(BitmexTick.builder().makerFee(ONE.negate()).settleFee(TEN).build())).when(target).queryTick(key);
        assertEquals(target.getCommissionRate(key), TEN);

        // Without settle
        doReturn(of(BitmexTick.builder().makerFee(ONE).build())).when(target).queryTick(key);
        assertEquals(target.getCommissionRate(key), ONE);

        // Without maker
        doReturn(of(BitmexTick.builder().settleFee(TEN).build())).when(target).queryTick(key);
        assertEquals(target.getCommissionRate(key), TEN);

        // Null commission
        doReturn(of(BitmexTick.builder().build())).when(target).queryTick(key);
        assertEquals(target.getCommissionRate(key), ZERO);

        // Null tick
        doReturn(Optional.empty()).when(target).queryTick(key);
        assertEquals(target.getCommissionRate(key), null);

    }

    @Test
    public void testIsMarginable() throws Exception {
        assertTrue(target.isMarginable(null));
    }

    @Test
    public void testGetExpiry() throws Exception {

        Key key = Key.builder().build();

        // With expiry
        Instant expiry = Instant.now();
        doReturn(of(BitmexTick.builder().expiry(expiry).build())).when(target).queryTick(key);
        assertEquals(target.getExpiry(key).toInstant(), expiry);

        // No expiry
        doReturn(of(BitmexTick.builder().build())).when(target).queryTick(key);
        assertEquals(target.getExpiry(key), null);

        // Null tick
        doReturn(Optional.empty()).when(target).queryTick(key);
        assertEquals(target.getExpiry(key), null);

    }

    @Test
    public void testFindOrders() throws Exception {

        Map<String, String> parameters = new HashMap<>();
        parameters.put("count", "500");
        parameters.put("reverse", "true");
        parameters.put("symbol", "XBTZ17");
        doReturn(Resources.toString(getResource("json/bitmex_order.json"), UTF_8))
                .when(target).executePrivate(GET, "/api/v1/order", parameters, null);

        Key key = Key.builder().instrument("XBT_QT").build();
        doReturn("XBTZ17").when(target).convertAlias(key);

        List<BitmexOrder> orders = target.findOrders(key);
        assertEquals(orders.size(), 2);

        assertEquals(orders.get(0).getOrderId(), "94ed7a64-58de-172e-c0cc-1d1011b9e505");
        assertEquals(orders.get(0).getClientId(), "");
        assertEquals(orders.get(0).getActive(), Boolean.FALSE);
        assertEquals(orders.get(0).getProduct(), "XBTZ17");
        assertEquals(orders.get(0).getSide(), "Buy");
        assertEquals(orders.get(0).getOrderPrice(), new BigDecimal("6150"));
        assertEquals(orders.get(0).getQuantity(), new BigDecimal("9"));
        assertEquals(orders.get(0).getFilled(), new BigDecimal("9"));
        assertEquals(orders.get(0).getRemaining(), new BigDecimal("0"));
        assertEquals(orders.get(0).getId(), "94ed7a64-58de-172e-c0cc-1d1011b9e505");
        assertEquals(orders.get(0).getOrderQuantity(), new BigDecimal("9"));
        assertEquals(orders.get(0).getFilledQuantity(), new BigDecimal("9"));
        assertEquals(orders.get(0).getRemainingQuantity(), new BigDecimal("0"));

        assertEquals(orders.get(1).getOrderId(), "3f776031-32b2-2546-607c-07b1f14e70b3");
        assertEquals(orders.get(1).getClientId(), "my_order_id");
        assertEquals(orders.get(1).getActive(), Boolean.TRUE);
        assertEquals(orders.get(1).getProduct(), "XBTZ17");
        assertEquals(orders.get(1).getSide(), "Sell");
        assertEquals(orders.get(1).getOrderPrice(), new BigDecimal("6150"));
        assertEquals(orders.get(1).getQuantity(), new BigDecimal("9"));
        assertEquals(orders.get(1).getFilled(), new BigDecimal("8"));
        assertEquals(orders.get(1).getRemaining(), new BigDecimal("1"));
        assertEquals(orders.get(1).getId(), "my_order_id");
        assertEquals(orders.get(1).getOrderQuantity(), new BigDecimal("-9"));
        assertEquals(orders.get(1).getFilledQuantity(), new BigDecimal("-8"));
        assertEquals(orders.get(1).getRemainingQuantity(), new BigDecimal("-1"));

        doReturn(null).when(target).executePrivate(any(), any(), any(), any());
        target.clear();
        assertEquals(target.findOrders(Key.builder().instrument("XBTZ17").build()).size(), 0);

    }

    @Test
    public void testFindOrder() throws Exception {

        Key key = Key.builder().build();

        List<BitmexOrder> orders = new ArrayList<>();
        orders.add(BitmexOrder.builder().build());
        orders.add(BitmexOrder.builder().orderId("oid").build());
        orders.add(BitmexOrder.builder().clientId("cid").build());
        doReturn(orders).when(target).findOrders(key);

        assertSame(target.findOrder(key, "oid"), orders.get(1));
        assertSame(target.findOrder(key, "cid"), orders.get(2));
        assertNull(target.findOrder(key, "foo"));
        assertNull(target.findOrder(key, null));

    }

    @Test
    public void testListActiveOrders() throws Exception {

        Key key = Key.builder().build();

        List<BitmexOrder> orders = new ArrayList<>();
        orders.add(BitmexOrder.builder().active(null).build());
        orders.add(BitmexOrder.builder().active(true).build());
        orders.add(BitmexOrder.builder().active(false).build());
        orders.add(BitmexOrder.builder().active(true).build());
        doReturn(orders).when(target).findOrders(key);

        List<Order> result = target.listActiveOrders(key);
        assertEquals(result.size(), 2);
        assertSame(result.get(0), orders.get(1));
        assertSame(result.get(1), orders.get(3));

    }

    @Test
    public void testListExecutions() throws Exception {

        Map<String, String> parameters = new HashMap<>();
        parameters.put("count", "500");
        parameters.put("reverse", "true");
        parameters.put("symbol", "XBTZ17");
        doReturn(Resources.toString(getResource("json/bitmex_execution.json"), UTF_8))
                .when(target).executePrivate(GET, "/api/v1/execution/tradeHistory", parameters, null);

        Key key = Key.builder().instrument("XBT_QT").build();
        doReturn("XBTZ17").when(target).convertAlias(key);

        List<Order.Execution> executions = target.listExecutions(key);
        assertEquals(executions.size(), 2);

        assertEquals(executions.get(0).getId(), "ca5a97aa-a1bc-0629-3f11-b1937fd1bd3a");
        assertEquals(executions.get(0).getOrderId(), "3f776031-32b2-2546-607c-07b1f14e70b3");
        assertEquals(executions.get(0).getTime(), Instant.parse("2017-11-01T12:42:39.566Z"));
        assertEquals(executions.get(0).getPrice(), new BigDecimal("6150"));
        assertEquals(executions.get(0).getSize(), new BigDecimal("1"));

        assertEquals(executions.get(1).getId(), "ca5a97aa-a1bc-0629-3f11-b1937fd1bd3Z");
        assertEquals(executions.get(1).getOrderId(), "my_order_id");
        assertEquals(executions.get(1).getTime(), Instant.parse("2017-11-01T12:42:39.566Z"));
        assertEquals(executions.get(1).getPrice(), new BigDecimal("6150"));
        assertEquals(executions.get(1).getSize(), new BigDecimal("-1"));

        doReturn(null).when(target).executePrivate(any(), any(), any(), any());
        target.clear();
        assertEquals(target.listExecutions(Key.builder().instrument("XBTZ17").build()).size(), 0);

    }

    @Test
    public void testCreateOrders_Buy() throws Exception {

        Key key = Key.builder().instrument("XBT_QT").build();
        doReturn("XBTZ17").when(target).convertAlias(key);
        doReturn("uid1").when(target).getUniqueId();

        doAnswer(i -> {

            assertEquals(i.getArgumentAt(0, RequestType.class), POST);
            assertEquals(i.getArgumentAt(1, String.class), "/api/v1/order/bulk");
            assertEquals(i.getArgumentAt(2, Map.class), emptyMap());
            String data = i.getArgumentAt(3, String.class);

            Map<String, List<Map<String, String>>> map = new Gson().fromJson(data, new TypeToken<Map<String, List<Map<String, String>>>>() {
            }.getType());

            List<Map<String, String>> orders = map.get("orders");
            assertEquals(orders.size(), 1);

            Map<String, String> m = orders.get(0);
            assertEquals(m.remove("clOrdID"), "uid1");
            assertEquals(m.remove("execInst"), "LastPrice");
            assertEquals(m.remove("ordType"), "Limit");
            assertEquals(m.remove("orderQty"), "10");
            assertEquals(m.remove("price"), "1");
            assertEquals(m.remove("side"), "Buy");
            assertEquals(m.remove("symbol"), "XBTZ17");
            assertEquals(m.size(), 0, map.toString());

            return new Gson().toJson(singleton(singletonMap("clOrdID", "uid1")));

        }).when(target).executePrivate(any(), any(), any(), any());

        CreateInstruction i1 = CreateInstruction.builder().price(ZERO).size(TEN).strategy("LastPrice").build();
        CreateInstruction i2 = CreateInstruction.builder().price(null).size(TEN).strategy("LastPrice").build();
        CreateInstruction i3 = CreateInstruction.builder().price(ONE).size(TEN).strategy("LastPrice").build(); // Valid
        CreateInstruction i4 = CreateInstruction.builder().price(ONE).size(ZERO).strategy("LastPrice").build();
        CreateInstruction i5 = CreateInstruction.builder().price(ONE).size(null).strategy("LastPrice").build();
        Set<CreateInstruction> instructions = Sets.newHashSet(i1, i2, null, i3, i4, i5);

        Map<CreateInstruction, String> result = target.createOrders(key, instructions);
        assertEquals(result.size(), 5);
        assertEquals(result.get(i1), null);
        assertEquals(result.get(i2), null);
        assertEquals(result.get(i3), "uid1");
        assertEquals(result.get(i4), null);
        assertEquals(result.get(i5), null);

    }

    @Test
    public void testCreateOrders_Sell() throws Exception {

        Key key = Key.builder().instrument("XBT_QT").build();
        doReturn("XBTZ17").when(target).convertAlias(key);
        doReturn("uid1").when(target).getUniqueId();

        doAnswer(i -> {

            assertEquals(i.getArgumentAt(0, RequestType.class), POST);
            assertEquals(i.getArgumentAt(1, String.class), "/api/v1/order/bulk");
            assertEquals(i.getArgumentAt(2, Map.class), emptyMap());
            String data = i.getArgumentAt(3, String.class);

            Map<String, List<Map<String, String>>> map = new Gson().fromJson(data, new TypeToken<Map<String, List<Map<String, String>>>>() {
            }.getType());

            List<Map<String, String>> orders = map.get("orders");
            assertEquals(orders.size(), 1);

            Map<String, String> m = orders.get(0);
            assertEquals(m.remove("clOrdID"), "uid1");
            assertEquals(m.remove("execInst"), "Fixed");
            assertEquals(m.remove("ordType"), "Limit");
            assertEquals(m.remove("orderQty"), "10");
            assertEquals(m.remove("price"), "1");
            assertEquals(m.remove("side"), "Sell");
            assertEquals(m.remove("symbol"), "XBTZ17");
            assertEquals(m.size(), 0, map.toString());

            return new Gson().toJson(singleton(singletonMap("clOrdID", "uid1")));

        }).when(target).executePrivate(any(), any(), any(), any());

        CreateInstruction i1 = CreateInstruction.builder().price(ZERO).size(TEN).strategy("Fixed").build();
        CreateInstruction i2 = CreateInstruction.builder().price(null).size(TEN).strategy("Fixed").build();
        CreateInstruction i3 = CreateInstruction.builder().price(ONE).size(TEN.negate()).strategy("Fixed").build(); // Valid
        CreateInstruction i4 = CreateInstruction.builder().price(ONE).size(ZERO).strategy("Fixed").build();
        CreateInstruction i5 = CreateInstruction.builder().price(ONE).size(null).strategy("Fixed").build();
        Set<CreateInstruction> instructions = Sets.newHashSet(i1, i2, null, i3, i4, i5);

        Map<CreateInstruction, String> result = target.createOrders(key, instructions);
        assertEquals(result.size(), 5);
        assertEquals(result.get(i1), null);
        assertEquals(result.get(i2), null);
        assertEquals(result.get(i3), "uid1");
        assertEquals(result.get(i4), null);
        assertEquals(result.get(i5), null);

    }

    @Test
    public void testCancelOrders() throws Exception {

        doAnswer(i -> {

            assertEquals(i.getArgumentAt(0, RequestType.class), DELETE);
            assertEquals(i.getArgumentAt(1, String.class), "/api/v1/order");
            assertEquals(i.getArgumentAt(2, Map.class), emptyMap());
            String data = i.getArgumentAt(3, String.class);

            Map<String, Set<String>> map = new Gson().fromJson(data, new TypeToken<Map<String, Set<String>>>() {
            }.getType());
            assertEquals(map.remove("clOrdID"), Sets.newHashSet(null, "uid1"));

            return new Gson().toJson(singleton(singletonMap("clOrdID", "uid1")));

        }).when(target).executePrivate(any(), any(), any(), any());

        CancelInstruction i1 = CancelInstruction.builder().id(null).build();
        CancelInstruction i2 = CancelInstruction.builder().id("uid1").build();

        Key key = Key.builder().instrument("XBTZ17").build();
        Map<CancelInstruction, String> result = target.cancelOrders(key, Sets.newHashSet(i1, null, i2));
        assertEquals(result.size(), 2);
        assertEquals(result.get(i1), null);
        assertEquals(result.get(i2), "uid1");

    }

}