
info.bitrich.xchangestream.lgo.LgoStreamingTradeService Maven / Gradle / Ivy
The newest version!
package info.bitrich.xchangestream.lgo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import info.bitrich.xchangestream.core.StreamingTradeService;
import info.bitrich.xchangestream.lgo.domain.LgoGroupedUserUpdate;
import info.bitrich.xchangestream.lgo.domain.LgoMatchOrderEvent;
import info.bitrich.xchangestream.lgo.domain.LgoOrderEvent;
import info.bitrich.xchangestream.lgo.dto.LgoAckUpdate;
import info.bitrich.xchangestream.lgo.dto.LgoSocketPlaceOrder;
import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper;
import io.reactivex.rxjava3.core.Observable;
import java.io.IOException;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.knowm.xchange.currency.CurrencyPair;
import org.knowm.xchange.dto.Order;
import org.knowm.xchange.dto.trade.LimitOrder;
import org.knowm.xchange.dto.trade.MarketOrder;
import org.knowm.xchange.dto.trade.OpenOrders;
import org.knowm.xchange.dto.trade.UserTrade;
import org.knowm.xchange.lgo.LgoAdapters;
import org.knowm.xchange.lgo.dto.key.LgoKey;
import org.knowm.xchange.lgo.dto.order.LgoEncryptedOrder;
import org.knowm.xchange.lgo.dto.order.LgoOrderSignature;
import org.knowm.xchange.lgo.dto.order.LgoPlaceCancelOrder;
import org.knowm.xchange.lgo.dto.order.LgoPlaceOrder;
import org.knowm.xchange.lgo.service.CryptoUtils;
import org.knowm.xchange.lgo.service.LgoKeyService;
import org.knowm.xchange.lgo.service.LgoSignatureService;
import si.mazi.rescu.SynchronizedValueFactory;
public class LgoStreamingTradeService implements StreamingTradeService {
private final LgoStreamingService streamingService;
private final LgoKeyService keyService;
private final LgoSignatureService signatureService;
private final SynchronizedValueFactory nonceFactory;
private final Map batchSubscriptions =
new ConcurrentHashMap<>();
private Observable afrSubscription;
LgoStreamingTradeService(
LgoStreamingService streamingService,
LgoKeyService keyService,
LgoSignatureService signatureService,
SynchronizedValueFactory nonceFactory) {
this.streamingService = streamingService;
this.keyService = keyService;
this.signatureService = signatureService;
this.nonceFactory = nonceFactory;
}
/** {@inheritDoc} First sent orders will be current open orders. */
@Override
public Observable getOrderChanges(CurrencyPair currencyPair, Object... args) {
return getOrderBatchChanges(currencyPair).flatMap(Observable::fromIterable);
}
/**
* Get an up-to-date view of all your open orders after each LGO batch execution. First sending
* will be the actual open orders list.
*/
public Observable getOpenOrders(CurrencyPair currencyPair) {
return getOrderUpdates(currencyPair)
.map(
u ->
u.getAllOpenOrders().values().stream()
.filter(order -> order instanceof LimitOrder)
.map(order -> (LimitOrder) order)
.collect(Collectors.toList()))
.map(OpenOrders::new);
}
/**
* Receive all updated orders, for each LGO batches. First sending will be the actual open orders
* list.
*/
public Observable> getOrderBatchChanges(CurrencyPair currencyPair) {
return getOrderUpdates(currencyPair).map(LgoGroupedUserUpdate::getUpdatedOrders);
}
private Observable getOrderUpdates(CurrencyPair currencyPair) {
return batchSubscriptions
.computeIfAbsent(currencyPair, this::createBatchSubscription)
.getPublisher();
}
private LgoUserBatchSubscription createBatchSubscription(CurrencyPair currencyPair) {
return LgoUserBatchSubscription.create(streamingService, currencyPair);
}
@Override
public Observable getUserTrades(CurrencyPair currencyPair, Object... args) {
return getRawBatchOrderEvents(currencyPair)
.filter(lgoOrderEvent -> "match".equals(lgoOrderEvent.getType()))
.map(
matchEvent -> LgoAdapter.adaptUserTrade(currencyPair, (LgoMatchOrderEvent) matchEvent));
}
/**
* Receive all events for the selected currency pairs. Merges batch order events and ack (AFR)
* events.
*/
public Observable getRawAllOrderEvents(Collection currencyPairs) {
Observable ackObservable = getRawReceivedOrderEvents();
return currencyPairs.stream()
.map(this::getRawBatchOrderEvents)
.reduce(Observable::mergeWith)
.map(ackObservable::mergeWith)
.orElse(ackObservable);
}
/**
* Get ack for your placed orders. "received" events indicate the orderId associated to your
* order, if you set a reference on order placement you will have it in the event. "failed" events
* indicate that the order could not be read or was invalid and not added to a batch.
*/
public Observable getRawReceivedOrderEvents() {
if (afrSubscription == null) {
createAfrSubscription();
}
return afrSubscription;
}
private void createAfrSubscription() {
final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper();
afrSubscription =
streamingService
.subscribeChannel("afr")
.map(s -> mapper.treeToValue(s, LgoAckUpdate.class))
.map(LgoAckUpdate::getData)
.flatMap(Observable::fromIterable)
.share();
}
/**
* Get all events of your orders happened during batch execution, for a currency pair. "pending"
* events indicate that the order was added to a batch and received by the execution engine.
* "invalid" events indicate that the order was not suitable for execution. "match" events
* indicate that the order did match against another order. "open" events indicate that the order
* entered the order book. "done" events indicate that the order was filled, canceled or rejected.
*/
public Observable getRawBatchOrderEvents(CurrencyPair currencyPair) {
return getOrderUpdates(currencyPair)
.map(LgoGroupedUserUpdate::getEvents)
.flatMap(Observable::fromIterable);
}
/**
* Place a market order
*
* @return the order reference
*/
public String placeMarketOrder(MarketOrder marketOrder) throws IOException {
Long ref = nonceFactory.createValue();
LgoPlaceOrder lgoOrder = LgoAdapters.adaptEncryptedMarketOrder(marketOrder);
return placeOrder(ref, lgoOrder);
}
/**
* Place a limit order
*
* @return the order reference
*/
public String placeLimitOrder(LimitOrder limitOrder) throws IOException {
Long ref = nonceFactory.createValue();
LgoPlaceOrder lgoOrder = LgoAdapters.adaptLimitOrder(limitOrder);
return placeOrder(ref, lgoOrder);
}
/**
* Place a cancel order
*
* @return true
*/
public boolean cancelOrder(String orderId) throws IOException {
Long ref = nonceFactory.createValue();
LgoPlaceCancelOrder lgoOrder = new LgoPlaceCancelOrder(ref, orderId, new Date().toInstant());
placeOrder(ref, lgoOrder);
return true;
}
private String placeOrder(Long ref, LgoPlaceOrder lgoOrder) throws JsonProcessingException {
LgoKey lgoKey = keyService.selectKey();
String encryptedOrder = CryptoUtils.encryptOrder(lgoKey, lgoOrder);
LgoOrderSignature signature = signatureService.signOrder(encryptedOrder);
LgoSocketPlaceOrder placeOrder =
new LgoSocketPlaceOrder(
new LgoEncryptedOrder(lgoKey.getId(), encryptedOrder, signature, ref));
String payload = StreamingObjectMapperHelper.getObjectMapper().writeValueAsString(placeOrder);
streamingService.sendMessage(payload);
return ref.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy