From ae7106986d94e2a52eb7ced1825c2981ea802ea2 Mon Sep 17 00:00:00 2001 From: Colin DuPlantis Date: Tue, 11 Apr 2023 15:34:25 -0700 Subject: [PATCH 01/10] MATP-1157 Implement Trade Suggestions --- .../trade/SuggestionListener.java | 20 +++ .../trade/client/TradeClient.java | 13 ++ .../trade/service/TradeService.java | 28 ++++- .../trading/rpc/TradeRpcClient.java | 114 ++++++++++++++++++ .../trading/rpc/TradeRpcUtil.java | 22 ++++ .../src/main/proto/rpc_trade.proto | 19 +++ .../src/main/proto/rpc_trade_types.proto | 6 + .../client/rpc/server/TradeRpcService.java | 105 ++++++++++++++++ .../trade/client/DirectTradeClient.java | 17 +++ .../trade/service/impl/TradeServiceImpl.java | 87 ++++++++++--- 10 files changed, 408 insertions(+), 23 deletions(-) create mode 100644 trade/trade-api/src/main/java/org/marketcetera/trade/SuggestionListener.java diff --git a/trade/trade-api/src/main/java/org/marketcetera/trade/SuggestionListener.java b/trade/trade-api/src/main/java/org/marketcetera/trade/SuggestionListener.java new file mode 100644 index 0000000000..a0fbf0bb22 --- /dev/null +++ b/trade/trade-api/src/main/java/org/marketcetera/trade/SuggestionListener.java @@ -0,0 +1,20 @@ +package org.marketcetera.trade; + +/* $License$ */ + +/** + * Listens for trade suggestions. + * + * @author Colin DuPlantis + * @version $Id$ + * @since $Release$ + */ +public interface SuggestionListener +{ + /** + * Receive a trade suggestion. + * + * @param inSuggestion a Suggestion value + */ + void receiveSuggestion(Suggestion inSuggestion); +} diff --git a/trade/trade-api/src/main/java/org/marketcetera/trade/client/TradeClient.java b/trade/trade-api/src/main/java/org/marketcetera/trade/client/TradeClient.java index fc38aa6b6f..39e973d16d 100644 --- a/trade/trade-api/src/main/java/org/marketcetera/trade/client/TradeClient.java +++ b/trade/trade-api/src/main/java/org/marketcetera/trade/client/TradeClient.java @@ -23,6 +23,7 @@ import org.marketcetera.trade.OrderSummary; import org.marketcetera.trade.Report; import org.marketcetera.trade.ReportID; +import org.marketcetera.trade.SuggestionListener; import org.marketcetera.trade.TradeMessagePublisher; /* $License$ */ @@ -181,4 +182,16 @@ void addReport(HasFIXMessage inReport, * @return a CollectionPageResponse<AveragePriceFill> value */ CollectionPageResponse getAveragePriceFills(PageRequest inPageRequest); + /** + * Add the given suggestion listener. + * + * @param inSuggestionListener a SuggestionListener value + */ + void addSuggestionListener(SuggestionListener inSuggestionListener); + /** + * Remove the given suggestion listener. + * + * @param inSuggestionListener a SuggestionListener value + */ + void removeSuggestionListener(SuggestionListener inSuggestionListener); } diff --git a/trade/trade-api/src/main/java/org/marketcetera/trade/service/TradeService.java b/trade/trade-api/src/main/java/org/marketcetera/trade/service/TradeService.java index 431f2679fc..b543682d53 100644 --- a/trade/trade-api/src/main/java/org/marketcetera/trade/service/TradeService.java +++ b/trade/trade-api/src/main/java/org/marketcetera/trade/service/TradeService.java @@ -6,11 +6,11 @@ import org.marketcetera.fix.ServerFixSession; import org.marketcetera.trade.MessageCreationException; import org.marketcetera.trade.Order; +import org.marketcetera.trade.Suggestion; +import org.marketcetera.trade.SuggestionListener; import org.marketcetera.trade.TradeMessage; import org.marketcetera.trade.TradeMessagePublisher; -import quickfix.Message; - /* $License$ */ /** @@ -36,12 +36,12 @@ public interface TradeService * * @param inOrder an Order value * @param inServerFixSession a ServerFixSession value - * @return a Message value + * @return a quickfix.Message value * @throws BrokerUnavailable if the broker is unavailable or unknown * @throws MessageIntercepted if the order should not be sent on in the data flow */ - Message convertOrder(Order inOrder, - ServerFixSession inServerFixSession); + quickfix.Message convertOrder(Order inOrder, + ServerFixSession inServerFixSession); /** * Convert the given message from the given broker to a TradeMessage. * @@ -61,4 +61,22 @@ TradeMessage convertResponse(HasFIXMessage inMessage, */ void sendOrder(User inUser, Order inOrder); + /** + * Add the given trade message listener. + * + * @param inSuggestionListener a SuggestionListener value + */ + void addSuggestionListener(SuggestionListener inSuggestionListener); + /** + * Remove the given trade message listener. + * + * @param inSuggestionListener a SuggestionListener value + */ + void removeSuggestionListener(SuggestionListener inSuggestionListener); + /** + * Report a suggestion to be broadcast. + * + * @param inSuggestion a Suggestion value + */ + void reportSuggestion(Suggestion inSuggestion); } diff --git a/trade/trade-rpc-client/src/main/java/org/marketcetera/trading/rpc/TradeRpcClient.java b/trade/trade-rpc-client/src/main/java/org/marketcetera/trading/rpc/TradeRpcClient.java index 23eba06203..e1d13787c4 100644 --- a/trade/trade-rpc-client/src/main/java/org/marketcetera/trading/rpc/TradeRpcClient.java +++ b/trade/trade-rpc-client/src/main/java/org/marketcetera/trading/rpc/TradeRpcClient.java @@ -62,11 +62,14 @@ import org.marketcetera.trade.RelatedOrder; import org.marketcetera.trade.Report; import org.marketcetera.trade.ReportID; +import org.marketcetera.trade.Suggestion; +import org.marketcetera.trade.SuggestionListener; import org.marketcetera.trade.TradeMessage; import org.marketcetera.trade.TradeMessageListener; import org.marketcetera.trade.client.SendOrderResponse; import org.marketcetera.trade.client.TradeClient; import org.marketcetera.trade.rpc.TradeRpc; +import org.marketcetera.trade.rpc.TradeRpc.SuggestionListenerResponse; import org.marketcetera.trade.rpc.TradeRpc.TradeMessageListenerResponse; import org.marketcetera.trade.rpc.TradeRpcServiceGrpc; import org.marketcetera.trade.rpc.TradeRpcServiceGrpc.TradeRpcServiceBlockingStub; @@ -172,6 +175,80 @@ public Void call() } }); } + /* (non-Javadoc) + * @see org.marketcetera.trade.client.TradeClient#addSuggestionListener(org.marketcetera.trade.SuggestionListener) + */ + @Override + public void addSuggestionListener(SuggestionListener inSuggestionListener) + { + // check to see if this listener is already registered + if(listenerProxies.asMap().containsKey(inSuggestionListener)) { + return; + } + // make sure that this listener wasn't just whisked out from under us + final AbstractClientListenerProxy listener = listenerProxies.getUnchecked(inSuggestionListener); + if(listener == null) { + return; + } + executeCall(new Callable() { + @Override + public Void call() + throws Exception + { + SLF4JLoggerProxy.trace(TradeRpcClient.this, + "{} adding suggestion listener", + getSessionId()); + TradeRpc.AddSuggestionListenerRequest.Builder requestBuilder = TradeRpc.AddSuggestionListenerRequest.newBuilder(); + requestBuilder.setSessionId(getSessionId().getValue()); + requestBuilder.setListenerId(listener.getId()); + TradeRpc.AddSuggestionListenerRequest addSuggestionListenerRequest = requestBuilder.build(); + SLF4JLoggerProxy.trace(TradeRpcClient.this, + "{} sending {}", + getSessionId(), + addSuggestionListenerRequest); + getAsyncStub().addSuggestionListener(addSuggestionListenerRequest, + (SuggestionListenerProxy)listener); + return null; + } + }); + } + /* (non-Javadoc) + * @see org.marketcetera.trade.client.TradeClient#removeSuggestionListener(org.marketcetera.trade.SuggestionListener) + */ + @Override + public void removeSuggestionListener(SuggestionListener inSuggestionListener) + { + final AbstractClientListenerProxy proxy = listenerProxies.getIfPresent(inSuggestionListener); + listenerProxies.invalidate(inSuggestionListener); + if(proxy == null) { + return; + } + listenerProxiesById.invalidate(proxy.getId()); + executeCall(new Callable() { + @Override + public Void call() + throws Exception + { + SLF4JLoggerProxy.trace(TradeRpcClient.this, + "{} removing report listener", + getSessionId()); + TradeRpc.RemoveSuggestionListenerRequest.Builder requestBuilder = TradeRpc.RemoveSuggestionListenerRequest.newBuilder(); + requestBuilder.setSessionId(getSessionId().getValue()); + requestBuilder.setListenerId(proxy.getId()); + TradeRpc.RemoveSuggestionListenerRequest removeSuggestionListenerRequest = requestBuilder.build(); + SLF4JLoggerProxy.trace(TradeRpcClient.this, + "{} sending {}", + getSessionId(), + removeSuggestionListenerRequest); + TradeRpc.RemoveSuggestionListenerResponse response = getBlockingStub().removeSuggestionListener(removeSuggestionListenerRequest); + SLF4JLoggerProxy.trace(TradeRpcClient.this, + "{} received {}", + getSessionId(), + response); + return null; + } + }); + } /* (non-Javadoc) * @see org.marketcetera.trade.client.TradingClient#findRootOrderIdFor(org.marketcetera.trade.OrderID) */ @@ -1094,6 +1171,43 @@ protected TradeMessageListenerProxy(TradeMessageListener inTradeMessageListener) super(inTradeMessageListener); } } + /** + * Provides an interface between suggestion stream listeners and their handlers. + * + * @author Colin DuPlantis + * @version $Id$ + * @since $Release$ + */ + private static class SuggestionListenerProxy + extends BaseRpcUtil.AbstractClientListenerProxy + { + /* (non-Javadoc) + * @see org.marketcetera.trade.rpc.TradeRpcClient.AbstractListenerProxy#translateMessage(java.lang.Object) + */ + @Override + protected Suggestion translateMessage(SuggestionListenerResponse inResponse) + { + return TradeRpcUtil.getSuggestion(inResponse); + } + /* (non-Javadoc) + * @see org.marketcetera.trade.rpc.TradeRpcClient.AbstractListenerProxy#sendMessage(java.lang.Object, java.lang.Object) + */ + @Override + protected void sendMessage(SuggestionListener inMessageListener, + Suggestion inMessage) + { + inMessageListener.receiveSuggestion(inMessage); + } + /** + * Create a new SuggestionListenerProxy instance. + * + * @param inSuggestionListener a SuggestionListener value + */ + protected SuggestionListenerProxy(SuggestionListener inSuggestionListener) + { + super(inSuggestionListener); + } + } /** * creates {@link AverageFillPrice} objects */ diff --git a/trade/trade-rpc-core/src/main/java/org/marketcetera/trading/rpc/TradeRpcUtil.java b/trade/trade-rpc-core/src/main/java/org/marketcetera/trading/rpc/TradeRpcUtil.java index 73d6fcd89e..8ff359bab8 100644 --- a/trade/trade-rpc-core/src/main/java/org/marketcetera/trading/rpc/TradeRpcUtil.java +++ b/trade/trade-rpc-core/src/main/java/org/marketcetera/trading/rpc/TradeRpcUtil.java @@ -71,6 +71,7 @@ import org.marketcetera.trade.ReportType; import org.marketcetera.trade.SecurityType; import org.marketcetera.trade.Side; +import org.marketcetera.trade.Suggestion; import org.marketcetera.trade.TimeInForce; import org.marketcetera.trade.TradeMessage; import org.marketcetera.trade.UserID; @@ -1578,6 +1579,27 @@ public static Order getOrder(TradeTypesRpc.Order inRpcOrder) } return orderBase; } + /** + * Get the suggestion value from the given RPC message. + * + * @param inResponse a TradeRpc.SuggestionListenerResponse value + * @return a Suggestion value + */ + public static Suggestion getSuggestion(TradeRpc.SuggestionListenerResponse inResponse) + { + throw new UnsupportedOperationException(); + } + /** + * Set the suggestion value into the given RPC builder, if possible. + * + * @param inSuggestion a Suggestion value + * @param inResponseBuilder a TradeRpc.SuggestionListenerResponse.Builder value + */ + public static void setSuggestion(Suggestion inSuggestion, + TradeRpc.SuggestionListenerResponse.Builder inResponseBuilder) + { + throw new UnsupportedOperationException(); // TODO + } /** * Get the trade message value from the given RPC message. * diff --git a/trade/trade-rpc-core/src/main/proto/rpc_trade.proto b/trade/trade-rpc-core/src/main/proto/rpc_trade.proto index 83c21e34d1..433e3717d9 100644 --- a/trade/trade-rpc-core/src/main/proto/rpc_trade.proto +++ b/trade/trade-rpc-core/src/main/proto/rpc_trade.proto @@ -87,6 +87,23 @@ message RemoveTradeMessageListenerRequest { message RemoveTradeMessageListenerResponse { } +message AddSuggestionListenerRequest { + string sessionId = 1; + string listenerId = 2; +} + +message SuggestionListenerResponse { + Suggestion suggestion = 1; +} + +message RemoveSuggestionListenerRequest { + string sessionId = 1; + string listenerId = 2; +} + +message RemoveSuggestionListenerResponse { +} + message ResolveSymbolRequest { string sessionId = 1; string symbol = 2; @@ -180,4 +197,6 @@ service TradeRpcService { rpc getReports(GetReportsRequest) returns (GetReportsResponse); rpc getFills(GetFillsRequest) returns (GetFillsResponse); rpc getAverageFillPrices(GetAverageFillPricesRequest) returns (GetAverageFillPricesResponse); + rpc addSuggestionListener(AddSuggestionListenerRequest) returns (stream SuggestionListenerResponse); + rpc removeSuggestionListener(RemoveSuggestionListenerRequest) returns (RemoveSuggestionListenerResponse); } diff --git a/trade/trade-rpc-core/src/main/proto/rpc_trade_types.proto b/trade/trade-rpc-core/src/main/proto/rpc_trade_types.proto index 179ef9c286..89b4cfc03a 100644 --- a/trade/trade-rpc-core/src/main/proto/rpc_trade_types.proto +++ b/trade/trade-rpc-core/src/main/proto/rpc_trade_types.proto @@ -313,3 +313,9 @@ message Position { PositionKey positionKey = 1; Qty position = 2; } + +message Suggestion { + string identifier = 1; + Qty score = 2; + Order order = 3; +} diff --git a/trade/trade-rpc-server/src/main/java/org/marketcetera/client/rpc/server/TradeRpcService.java b/trade/trade-rpc-server/src/main/java/org/marketcetera/client/rpc/server/TradeRpcService.java index 05f5c6beee..57fa95c0eb 100644 --- a/trade/trade-rpc-server/src/main/java/org/marketcetera/client/rpc/server/TradeRpcService.java +++ b/trade/trade-rpc-server/src/main/java/org/marketcetera/client/rpc/server/TradeRpcService.java @@ -44,6 +44,8 @@ import org.marketcetera.trade.OrderSummary; import org.marketcetera.trade.Report; import org.marketcetera.trade.ReportID; +import org.marketcetera.trade.Suggestion; +import org.marketcetera.trade.SuggestionListener; import org.marketcetera.trade.TradeMessage; import org.marketcetera.trade.TradeMessageListener; import org.marketcetera.trade.TradePermissions; @@ -405,6 +407,63 @@ public void resolveSymbol(ResolveSymbolRequest inRequest, inResponseObserver); } } + /* (non-Javadoc) + * @see org.marketcetera.trade.rpc.TradeRpcServiceGrpc.TradeRpcServiceImplBase#addSuggestionListener(org.marketcetera.trade.rpc.TradeRpc.AddSuggestionListenerRequest, io.grpc.stub.StreamObserver) + */ + @Override + public void addSuggestionListener(TradeRpc.AddSuggestionListenerRequest inRequest, + StreamObserver inResponseObserver) + { + try { + validateAndReturnSession(inRequest.getSessionId()); + SLF4JLoggerProxy.trace(TradeRpcService.this, + "Received add suggestion listener request {}", + inRequest); + String listenerId = inRequest.getListenerId(); + BaseRpcUtil.AbstractServerListenerProxy suggestionListenerProxy = listenerProxiesById.getIfPresent(listenerId); + if(suggestionListenerProxy == null) { + suggestionListenerProxy = new SuggestionListenerProxy(listenerId, + inResponseObserver); + listenerProxiesById.put(suggestionListenerProxy.getId(), + suggestionListenerProxy); + tradeService.addSuggestionListener((SuggestionListener)suggestionListenerProxy); + } + } catch (Exception e) { + handleError(e, + inResponseObserver); + } + } + /* (non-Javadoc) + * @see org.marketcetera.trade.rpc.TradeRpcServiceGrpc.TradeRpcServiceImplBase#removeSuggestionListener(org.marketcetera.trade.rpc.TradeRpc.RemoveSuggestionListenerRequest, io.grpc.stub.StreamObserver) + */ + @Override + public void removeSuggestionListener(TradeRpc.RemoveSuggestionListenerRequest inRequest, + StreamObserver inResponseObserver) + { + try { + validateAndReturnSession(inRequest.getSessionId()); + SLF4JLoggerProxy.trace(TradeRpcService.this, + "Received remove suggestion listener request {}", + inRequest); + String listenerId = inRequest.getListenerId(); + BaseRpcUtil.AbstractServerListenerProxy suggestionListenerProxy = listenerProxiesById.getIfPresent(listenerId); + listenerProxiesById.invalidate(listenerId); + if(suggestionListenerProxy != null) { + tradeService.removeSuggestionListener((SuggestionListener)suggestionListenerProxy); + suggestionListenerProxy.close(); + } + TradeRpc.RemoveSuggestionListenerResponse.Builder responseBuilder = TradeRpc.RemoveSuggestionListenerResponse.newBuilder(); + TradeRpc.RemoveSuggestionListenerResponse response = responseBuilder.build(); + SLF4JLoggerProxy.trace(TradeRpcService.this, + "Returning {}", + response); + inResponseObserver.onNext(response); + inResponseObserver.onCompleted(); + } catch (Exception e) { + handleError(e, + inResponseObserver); + } + } /* (non-Javadoc) * @see org.marketcetera.trade.rpc.TradeRpcServiceGrpc.TradeRpcServiceImplBase#addTradeMessageListener(org.marketcetera.trade.rpc.TradeRpc.AddTradeMessageListenerRequest, io.grpc.stub.StreamObserver) */ @@ -840,6 +899,52 @@ private TradeMessageListenerProxy(String inId, */ private final TradeRpc.TradeMessageListenerResponse.Builder responseBuilder = TradeRpc.TradeMessageListenerResponse.newBuilder(); } + /** + * Wraps a {@link SuggestionListener} with the RPC call from the client. + * + * @author Colin DuPlantis + * @version $Id$ + * @since $Release$ + */ + private static class SuggestionListenerProxy + extends BaseRpcUtil.AbstractServerListenerProxy + implements SuggestionListener + { + /* (non-Javadoc) + * @see org.marketcetera.trade.TradeMessageListener#receiveTradeMessage(org.marketcetera.trade.TradeMessage) + */ + @Override + public void receiveSuggestion(Suggestion inSuggestion) + { + TradeRpcUtil.setSuggestion(inSuggestion, + responseBuilder); + TradeRpc.SuggestionListenerResponse response = responseBuilder.build(); + SLF4JLoggerProxy.trace(TradeRpcService.class, + "{} received suggestion {}, sending {}", + getId(), + inSuggestion, + response); + // TODO does the user have permissions (including supervisor) to view this report? + getObserver().onNext(response); + responseBuilder.clear(); + } + /** + * Create a new SuggestionListenerProxy instance. + * + * @param inId a String value + * @param inObserver a StreamObserver<TradeRpc.SuggestionListenerResponse> value + */ + private SuggestionListenerProxy(String inId, + StreamObserver inObserver) + { + super(inId, + inObserver); + } + /** + * builder used to construct messages + */ + private final TradeRpc.SuggestionListenerResponse.Builder responseBuilder = TradeRpc.SuggestionListenerResponse.newBuilder(); + } /** * provides authorization services */ diff --git a/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClient.java b/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClient.java index 7376732ef2..f40515a1ba 100644 --- a/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClient.java +++ b/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClient.java @@ -33,6 +33,7 @@ import org.marketcetera.trade.Report; import org.marketcetera.trade.ReportID; import org.marketcetera.trade.SendOrderFailed; +import org.marketcetera.trade.SuggestionListener; import org.marketcetera.trade.TradeMessageListener; import org.marketcetera.trade.TradeMessagePublisher; import org.marketcetera.trade.service.OrderSummaryService; @@ -118,6 +119,22 @@ public void removeTradeMessageListener(TradeMessageListener inTradeMessageListen { tradeMessagePublisher.removeTradeMessageListener(inTradeMessageListener); } + /* (non-Javadoc) + * @see org.marketcetera.trade.client.TradeClient#addSuggestionListener(org.marketcetera.trade.SuggestionListener) + */ + @Override + public void addSuggestionListener(SuggestionListener inSuggestionListener) + { + tradeService.addSuggestionListener(inSuggestionListener); + } + /* (non-Javadoc) + * @see org.marketcetera.trade.client.TradeClient#removeSuggestionListener(org.marketcetera.trade.SuggestionListener) + */ + @Override + public void removeSuggestionListener(SuggestionListener inSuggestionListener) + { + tradeService.removeSuggestionListener(inSuggestionListener); + } /* (non-Javadoc) * @see org.marketcetera.trade.client.TradeClient#getOpenOrders() */ diff --git a/trade/trade-server/src/main/java/org/marketcetera/trade/service/impl/TradeServiceImpl.java b/trade/trade-server/src/main/java/org/marketcetera/trade/service/impl/TradeServiceImpl.java index 2fca1b9cfc..4c6deaf97a 100644 --- a/trade/trade-server/src/main/java/org/marketcetera/trade/service/impl/TradeServiceImpl.java +++ b/trade/trade-server/src/main/java/org/marketcetera/trade/service/impl/TradeServiceImpl.java @@ -25,6 +25,8 @@ import org.marketcetera.trade.OrderID; import org.marketcetera.trade.Originator; import org.marketcetera.trade.SendOrderFailed; +import org.marketcetera.trade.Suggestion; +import org.marketcetera.trade.SuggestionListener; import org.marketcetera.trade.TradeMessage; import org.marketcetera.trade.TradeMessageBroadcaster; import org.marketcetera.trade.TradeMessageListener; @@ -46,9 +48,6 @@ import com.google.common.collect.Sets; import com.google.common.eventbus.Subscribe; -import quickfix.FieldNotFound; -import quickfix.Message; - /* $License$ */ /** @@ -105,8 +104,8 @@ public ServerFixSession selectServerFixSession(Order inOrder) * @see org.marketcetera.trade.service.TradeService#convertOrder(org.marketcetera.trade.Order, org.marketcetera.brokers.Broker) */ @Override - public Message convertOrder(Order inOrder, - ServerFixSession inServerFixSession) + public quickfix.Message convertOrder(Order inOrder, + ServerFixSession inServerFixSession) { boolean failed = false; String message = null; @@ -143,7 +142,7 @@ public Message convertOrder(Order inOrder, } else if(fixMessage != null && fixMessage.isSetField(quickfix.field.OrderID.FIELD)) { try { orderId = new OrderID(fixMessage.getString(quickfix.field.OrderID.FIELD)); - } catch (FieldNotFound ignored) {} // this exception cannot occur because we explicitly check for the existance of the field above + } catch (quickfix.FieldNotFound ignored) {} // this exception cannot occur because we explicitly check for the existence of the field above } if(orderId == null) { SLF4JLoggerProxy.warn(this, @@ -168,14 +167,14 @@ public Message convertOrder(Order inOrder, public TradeMessage convertResponse(HasFIXMessage inMessage, ServerFixSession inServerFixSession) { - Message fixMessage = inMessage.getMessage(); + quickfix.Message fixMessage = inMessage.getMessage(); try { if(FIXMessageUtil.isTradingSessionStatus(fixMessage)) { Messages.TRADE_SESSION_STATUS.info(this, inServerFixSession.getFIXDataDictionary().getHumanFieldValue(quickfix.field.TradSesStatus.FIELD, fixMessage.getString(quickfix.field.TradSesStatus.FIELD))); } - } catch (FieldNotFound e) { + } catch (quickfix.FieldNotFound e) { PlatformServices.handleException(this, "Unable to process trading session status message", e); @@ -208,7 +207,9 @@ public TradeMessage convertResponse(HasFIXMessage inMessage, @Override public void addTradeMessageListener(TradeMessageListener inTradeMessageListener) { - tradeMessageListeners.add(inTradeMessageListener); + synchronized(tradeMessageListeners) { + tradeMessageListeners.add(inTradeMessageListener); + } } /* (non-Javadoc) * @see org.marketcetera.trade.TradeMessagePublisher#removeTradeMessageListener(org.marketcetera.trade.TradeMessageListener) @@ -216,7 +217,51 @@ public void addTradeMessageListener(TradeMessageListener inTradeMessageListener) @Override public void removeTradeMessageListener(TradeMessageListener inTradeMessageListener) { - tradeMessageListeners.remove(inTradeMessageListener); + synchronized(tradeMessageListeners) { + tradeMessageListeners.remove(inTradeMessageListener); + } + } + /* (non-Javadoc) + * @see org.marketcetera.trade.service.TradeService#addSuggestionListener(org.marketcetera.trade.SuggestionListener) + */ + @Override + public void addSuggestionListener(SuggestionListener inSuggestionListener) + { + synchronized(suggestionListeners) { + suggestionListeners.add(inSuggestionListener); + } + } + /* (non-Javadoc) + * @see org.marketcetera.trade.service.TradeService#removeSuggestionListener(org.marketcetera.trade.SuggestionListener) + */ + @Override + public void removeSuggestionListener(SuggestionListener inSuggestionListener) + { + synchronized(suggestionListeners) { + suggestionListeners.remove(inSuggestionListener); + } + } + /* (non-Javadoc) + * @see org.marketcetera.trade.service.TradeService#reportSuggestion(org.marketcetera.trade.Suggestion) + */ + @Override + public void reportSuggestion(Suggestion inSuggestion) + { + SLF4JLoggerProxy.debug(this, + "Reporting {}", + inSuggestion); + synchronized(suggestionListeners) { + for(SuggestionListener suggestionListener : suggestionListeners) { + try { + suggestionListener.receiveSuggestion(inSuggestion); + } catch (Exception e) { + SLF4JLoggerProxy.warn(this, + e, + "Error broadcasting suggestion, offending listener removed"); + removeSuggestionListener(suggestionListener); + } + } + } } /* (non-Javadoc) * @see org.marketcetera.trade.TradeMessageBroadcaster#reportTradeMessage(org.marketcetera.trade.TradeMessage) @@ -224,14 +269,16 @@ public void removeTradeMessageListener(TradeMessageListener inTradeMessageListen @Override public void reportTradeMessage(TradeMessage inTradeMessage) { - for(TradeMessageListener tradeMessageListener : tradeMessageListeners) { - try { - tradeMessageListener.receiveTradeMessage(inTradeMessage); - } catch (Exception e) { - PlatformServices.handleException(this, - "Error broadcasting trade message", - e); - removeTradeMessageListener(tradeMessageListener); + synchronized(tradeMessageListeners) { + for(TradeMessageListener tradeMessageListener : tradeMessageListeners) { + try { + tradeMessageListener.receiveTradeMessage(inTradeMessage); + } catch (Exception e) { + PlatformServices.handleException(this, + "Error broadcasting trade message, offending listener removed", + e); + removeTradeMessageListener(tradeMessageListener); + } } } } @@ -350,4 +397,8 @@ private ServerFixSession resolveVirtualServerFixSession(ServerFixSession inServe * holds trade message listener subscribers */ private final Set tradeMessageListeners = Sets.newConcurrentHashSet(); + /** + * holds suggestion listener subscribers + */ + private final Set suggestionListeners = Sets.newConcurrentHashSet(); } From 10cfbf0008b6ebed291ba9769ecd6e951e8ff26f Mon Sep 17 00:00:00 2001 From: Colin DuPlantis Date: Wed, 12 Apr 2023 10:59:33 -0700 Subject: [PATCH 02/10] MATP-1157 Implement Trade Suggestions --- .../trade/AbstractSuggestion.java | 74 +++++++++ .../java/org/marketcetera/trade/Factory.java | 14 ++ .../org/marketcetera/trade/FactoryImpl.java | 17 +- .../trade/OrderCancelSuggestion.java | 23 +++ .../trade/OrderCancelSuggestionImpl.java | 78 +++++++++ .../trade/OrderReplaceSuggestionImpl.java | 78 +++++++++ .../marketdata}/MarketDataCacheElement.java | 7 +- .../cache/MarketDataCacheModule.java | 2 +- .../core/manager/MarketDataManagerModule.java | 2 +- .../provider/MarketdataCacheElementTest.java | 1 + .../test/cmd_exec/conf/application.properties | 1 + strategy/strategy-sample/pom.xml | 8 + .../strategy/sample/TestStrategy.java | 86 ++++++++++ strategy/strategy-server/pom.xml | 4 + .../strategy/StrategyServiceImpl.java | 13 +- .../marketcetera/trade/TradePermissions.java | 2 + .../trade/client/TradeClient.java | 7 + .../trading/rpc/TradeRpcClient.java | 140 ++++------------ .../trading/rpc/TradeRpcUtil.java | 152 +++++++++++++++++- .../src/main/proto/rpc_trade.proto | 9 ++ .../client/rpc/server/TradeRpcService.java | 45 +++++- .../trade/client/DirectTradeClient.java | 13 +- 22 files changed, 650 insertions(+), 126 deletions(-) create mode 100644 core/src/main/java/org/marketcetera/trade/AbstractSuggestion.java create mode 100644 core/src/main/java/org/marketcetera/trade/OrderCancelSuggestion.java create mode 100644 core/src/main/java/org/marketcetera/trade/OrderCancelSuggestionImpl.java create mode 100644 core/src/main/java/org/marketcetera/trade/OrderReplaceSuggestionImpl.java rename marketdata/{marketdata-server/src/main/java/org/marketcetera/marketdata/core/provider => marketdata-core/src/main/java/org/marketcetera/marketdata}/MarketDataCacheElement.java (96%) diff --git a/core/src/main/java/org/marketcetera/trade/AbstractSuggestion.java b/core/src/main/java/org/marketcetera/trade/AbstractSuggestion.java new file mode 100644 index 0000000000..7984c13260 --- /dev/null +++ b/core/src/main/java/org/marketcetera/trade/AbstractSuggestion.java @@ -0,0 +1,74 @@ +package org.marketcetera.trade; + +import java.math.BigDecimal; + +/* $License$ */ + +/** + * + * + * @author Colin DuPlantis + * @version $Id$ + * @since $Release$ + */ +public abstract class AbstractSuggestion + implements Suggestion +{ + /* (non-Javadoc) + * @see org.marketcetera.trade.Suggestion#getIdentifier() + */ + @Override + public String getIdentifier() + { + return identifier; + } + /* (non-Javadoc) + * @see org.marketcetera.trade.Suggestion#setIdentifier(java.lang.String) + */ + @Override + public void setIdentifier(String inIdentifier) + { + identifier = inIdentifier; + } + /* (non-Javadoc) + * @see org.marketcetera.trade.Suggestion#getScore() + */ + @Override + public BigDecimal getScore() + { + return score; + } + /* (non-Javadoc) + * @see org.marketcetera.trade.Suggestion#setScore(java.math.BigDecimal) + */ + @Override + public void setScore(BigDecimal inScore) + { + score = inScore; + } + /** + * Create a new AbstractSuggestion instance. + */ + protected AbstractSuggestion() {} + /** + * Create a new AbstractSuggestion instance. + * + * @param inIdentifier a String value + * @param inScore a BigDecimal value + */ + protected AbstractSuggestion(String inIdentifier, + BigDecimal inScore) + { + setIdentifier(inIdentifier); + setScore(inScore); + } + /** + * identifier value + */ + private String identifier; + /** + * score value + */ + private BigDecimal score; + private static final long serialVersionUID = 7713915144622410613L; +} diff --git a/core/src/main/java/org/marketcetera/trade/Factory.java b/core/src/main/java/org/marketcetera/trade/Factory.java index 7912e65781..cec44fb464 100644 --- a/core/src/main/java/org/marketcetera/trade/Factory.java +++ b/core/src/main/java/org/marketcetera/trade/Factory.java @@ -55,6 +55,20 @@ public abstract OrderSingle createOrderSingle( */ public abstract OrderSingleSuggestion createOrderSingleSuggestion(); + /** + * Creates a suggestion for a cancel order. + * + * @return an OrderCancelSuggestion value + */ + public abstract OrderCancelSuggestion createOrderCancelSuggestion(); + + /** + * Creates a suggestion for a replace order. + * + * @return an OrderReplaceSuggestion value + */ + public abstract OrderReplaceSuggestion createOrderReplaceSuggestion(); + /** * Creates an order to cancel a previously placed order as * identified by the supplied execution report. The execution diff --git a/core/src/main/java/org/marketcetera/trade/FactoryImpl.java b/core/src/main/java/org/marketcetera/trade/FactoryImpl.java index ef023618a3..feded57fe7 100644 --- a/core/src/main/java/org/marketcetera/trade/FactoryImpl.java +++ b/core/src/main/java/org/marketcetera/trade/FactoryImpl.java @@ -45,7 +45,22 @@ public OrderSingle createOrderSingle() { public OrderSingleSuggestion createOrderSingleSuggestion() { return new OrderSingleSuggestionImpl(); } - + /* (non-Javadoc) + * @see org.marketcetera.trade.Factory#createOrderCancelSuggestion() + */ + @Override + public OrderCancelSuggestion createOrderCancelSuggestion() + { + return new OrderCancelSuggestionImpl(); + } + /* (non-Javadoc) + * @see org.marketcetera.trade.Factory#createOrderReplaceSuggestion() + */ + @Override + public OrderReplaceSuggestion createOrderReplaceSuggestion() + { + return new OrderReplaceSuggestionImpl(); + } @Override public OrderCancel createOrderCancel(ExecutionReport inLatestReport) { OrderCancelImpl order = new OrderCancelImpl(); diff --git a/core/src/main/java/org/marketcetera/trade/OrderCancelSuggestion.java b/core/src/main/java/org/marketcetera/trade/OrderCancelSuggestion.java new file mode 100644 index 0000000000..5c4dc9ec21 --- /dev/null +++ b/core/src/main/java/org/marketcetera/trade/OrderCancelSuggestion.java @@ -0,0 +1,23 @@ +package org.marketcetera.trade; + +/* $License$ */ + +/** + * Contains a suggestion for a cancel order. + * + *

Instances of this type can be created via {@link Factory#createOrderCancelSuggestion()} + * + * @author Colin DuPlantis + * @version $Id$ + * @since $Release$ + */ +public interface OrderCancelSuggestion + extends Suggestion,HasOrderCancel +{ + /** + * Set the cancel order value. + * + * @param inOrderCancel the suggested order. + */ + void setOrderCancel(OrderCancel inOrderCancel); +} diff --git a/core/src/main/java/org/marketcetera/trade/OrderCancelSuggestionImpl.java b/core/src/main/java/org/marketcetera/trade/OrderCancelSuggestionImpl.java new file mode 100644 index 0000000000..9fd3a1b8fd --- /dev/null +++ b/core/src/main/java/org/marketcetera/trade/OrderCancelSuggestionImpl.java @@ -0,0 +1,78 @@ +package org.marketcetera.trade; + +import java.math.BigDecimal; + +/* $License$ */ + +/** + * Provides an {@link OrderCancelSuggestion} implementation. + * + * @author Colin DuPlantis + * @version $Id$ + * @since $Release$ + */ +public class OrderCancelSuggestionImpl + extends AbstractSuggestion + implements OrderCancelSuggestion +{ + /** + * Create a new OrderCancelSuggestionImpl instance. + */ + public OrderCancelSuggestionImpl() {} + /** + * Create a new OrderCancelSuggestionImpl instance. + * + * @param inOrderCancel an OrderCancel value + */ + public OrderCancelSuggestionImpl(OrderCancel inOrderCancel) + { + setOrderCancel(inOrderCancel); + } + /** + * Create a new OrderCancelSuggestionImpl instance. + * + * @param inIdentifier a String value + * @param inScore a BigDecimal value + * @param inOrderCancel an OrderCancel value + */ + public OrderCancelSuggestionImpl(String inIdentifier, + BigDecimal inScore, + OrderCancel inOrderCancel) + { + super(inIdentifier, + inScore); + setOrderCancel(inOrderCancel); + } + /* (non-Javadoc) + * @see org.marketcetera.trade.HasOrderCancel#getOrderCancel() + */ + @Override + public OrderCancel getOrderCancel() + { + return orderCancel; + } + /* (non-Javadoc) + * @see org.marketcetera.trade.OrderCancelSuggestion#setOrderCancel(org.marketcetera.trade.OrderCancel) + */ + @Override + public void setOrderCancel(OrderCancel inOrderCancel) + { + orderCancel = inOrderCancel; + } + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("OrderCancelSuggestionImpl [identifier=").append(getIdentifier()).append(", score=") + .append(getScore()).append(", orderCancel=").append(orderCancel).append("]"); + return builder.toString(); + } + /** + * order Cancel value + */ + private OrderCancel orderCancel; + private static final long serialVersionUID = 2069691082438028206L; +} diff --git a/core/src/main/java/org/marketcetera/trade/OrderReplaceSuggestionImpl.java b/core/src/main/java/org/marketcetera/trade/OrderReplaceSuggestionImpl.java new file mode 100644 index 0000000000..a04c56a27a --- /dev/null +++ b/core/src/main/java/org/marketcetera/trade/OrderReplaceSuggestionImpl.java @@ -0,0 +1,78 @@ +package org.marketcetera.trade; + +import java.math.BigDecimal; + +/* $License$ */ + +/** + * Provides an {@link OrderReplaceSuggestion} implementation. + * + * @author Colin DuPlantis + * @version $Id$ + * @since $Release$ + */ +public class OrderReplaceSuggestionImpl + extends AbstractSuggestion + implements OrderReplaceSuggestion +{ + /** + * Create a new OrderReplaceSuggestionImpl instance. + */ + public OrderReplaceSuggestionImpl() {} + /** + * Create a new OrderReplaceSuggestionImpl instance. + * + * @param inOrderReplace an OrderReplace value + */ + public OrderReplaceSuggestionImpl(OrderReplace inOrderReplace) + { + setOrderReplace(inOrderReplace); + } + /** + * Create a new OrderReplaceSuggestionImpl instance. + * + * @param inIdentifier a String value + * @param inScore a BigDecimal value + * @param inOrderReplace an OrderReplace value + */ + public OrderReplaceSuggestionImpl(String inIdentifier, + BigDecimal inScore, + OrderReplace inOrderReplace) + { + super(inIdentifier, + inScore); + setOrderReplace(inOrderReplace); + } + /* (non-Javadoc) + * @see org.marketcetera.trade.HasOrderReplace#getOrderReplace() + */ + @Override + public OrderReplace getOrderReplace() + { + return orderReplace; + } + /* (non-Javadoc) + * @see org.marketcetera.trade.OrderReplaceSuggestion#setOrderReplace(org.marketcetera.trade.OrderReplace) + */ + @Override + public void setOrderReplace(OrderReplace inOrderReplace) + { + orderReplace = inOrderReplace; + } + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("OrderReplaceSuggestionImpl [identifier=").append(getIdentifier()).append(", score=") + .append(getScore()).append(", orderReplace=").append(orderReplace).append("]"); + return builder.toString(); + } + /** + * order replace value + */ + private OrderReplace orderReplace; + private static final long serialVersionUID = 2069691082438028206L; +} diff --git a/marketdata/marketdata-server/src/main/java/org/marketcetera/marketdata/core/provider/MarketDataCacheElement.java b/marketdata/marketdata-core/src/main/java/org/marketcetera/marketdata/MarketDataCacheElement.java similarity index 96% rename from marketdata/marketdata-server/src/main/java/org/marketcetera/marketdata/core/provider/MarketDataCacheElement.java rename to marketdata/marketdata-core/src/main/java/org/marketcetera/marketdata/MarketDataCacheElement.java index 0abc15948a..2f1b33877e 100644 --- a/marketdata/marketdata-server/src/main/java/org/marketcetera/marketdata/core/provider/MarketDataCacheElement.java +++ b/marketdata/marketdata-core/src/main/java/org/marketcetera/marketdata/MarketDataCacheElement.java @@ -1,4 +1,4 @@ -package org.marketcetera.marketdata.core.provider; +package org.marketcetera.marketdata; import java.util.ArrayList; import java.util.Collection; @@ -18,9 +18,6 @@ import org.marketcetera.event.TradeEvent; import org.marketcetera.event.impl.QuoteEventBuilder; import org.marketcetera.event.util.MarketstatEventCache; -import org.marketcetera.marketdata.Content; -import org.marketcetera.marketdata.OrderBook; -import org.marketcetera.marketdata.core.Messages; import org.marketcetera.trade.Instrument; import org.marketcetera.util.misc.ClassVersion; @@ -203,8 +200,6 @@ private void doBookUpdate(Content inContent, orderbook.process(quoteEvent); latestTop = orderbook.getTopOfBook(); inoutResults.add(quoteEvent); - } else { - throw new IllegalArgumentException(Messages.CONTENT_REQUIRES_QUOTE_EVENTS.getText(inContent,event.getClass().getName())); } } } diff --git a/marketdata/marketdata-server/src/main/java/org/marketcetera/marketdata/cache/MarketDataCacheModule.java b/marketdata/marketdata-server/src/main/java/org/marketcetera/marketdata/cache/MarketDataCacheModule.java index a6a1022074..f97f9d985a 100644 --- a/marketdata/marketdata-server/src/main/java/org/marketcetera/marketdata/cache/MarketDataCacheModule.java +++ b/marketdata/marketdata-server/src/main/java/org/marketcetera/marketdata/cache/MarketDataCacheModule.java @@ -4,7 +4,7 @@ import org.marketcetera.event.Event; import org.marketcetera.event.HasInstrument; import org.marketcetera.marketdata.Content; -import org.marketcetera.marketdata.core.provider.MarketDataCacheElement; +import org.marketcetera.marketdata.MarketDataCacheElement; import org.marketcetera.marketdata.service.MarketDataCacheManager; import org.marketcetera.marketdata.service.MarketDataCacheProvider; import org.marketcetera.module.AbstractDataReemitterModule; diff --git a/marketdata/marketdata-server/src/main/java/org/marketcetera/marketdata/core/manager/MarketDataManagerModule.java b/marketdata/marketdata-server/src/main/java/org/marketcetera/marketdata/core/manager/MarketDataManagerModule.java index 615cfcd021..a01daf31e0 100644 --- a/marketdata/marketdata-server/src/main/java/org/marketcetera/marketdata/core/manager/MarketDataManagerModule.java +++ b/marketdata/marketdata-server/src/main/java/org/marketcetera/marketdata/core/manager/MarketDataManagerModule.java @@ -10,9 +10,9 @@ import org.marketcetera.event.Event; import org.marketcetera.event.HasInstrument; import org.marketcetera.marketdata.Content; +import org.marketcetera.marketdata.MarketDataCacheElement; import org.marketcetera.marketdata.MarketDataRequest; import org.marketcetera.marketdata.core.Messages; -import org.marketcetera.marketdata.core.provider.MarketDataCacheElement; import org.marketcetera.module.AbstractDataReemitterModule; import org.marketcetera.module.DataFlowID; import org.marketcetera.module.DataFlowRequester; diff --git a/marketdata/marketdata-server/src/test/java/org/marketcetera/marketdata/core/provider/MarketdataCacheElementTest.java b/marketdata/marketdata-server/src/test/java/org/marketcetera/marketdata/core/provider/MarketdataCacheElementTest.java index 2d9e081480..d6aadf3be9 100644 --- a/marketdata/marketdata-server/src/test/java/org/marketcetera/marketdata/core/provider/MarketdataCacheElementTest.java +++ b/marketdata/marketdata-server/src/test/java/org/marketcetera/marketdata/core/provider/MarketdataCacheElementTest.java @@ -18,6 +18,7 @@ import org.marketcetera.event.impl.QuoteEventBuilder; import org.marketcetera.event.impl.TopOfBookEventBuilder; import org.marketcetera.marketdata.Content; +import org.marketcetera.marketdata.MarketDataCacheElement; import org.marketcetera.marketdata.OrderBook; import org.marketcetera.trade.Equity; import org.marketcetera.trade.Instrument; diff --git a/packages/dare-package/src/test/cmd_exec/conf/application.properties b/packages/dare-package/src/test/cmd_exec/conf/application.properties index 513dcedaed..05ab13935d 100644 --- a/packages/dare-package/src/test/cmd_exec/conf/application.properties +++ b/packages/dare-package/src/test/cmd_exec/conf/application.properties @@ -14,6 +14,7 @@ spring.flyway.enabled=true metc.ws.hostname=0.0.0.0 # interval in seconds at which to record metrics metc.metric.service.log.reporter.interval=10 +# list of classes that can be marshaled to and from XML metc.xml.context.path.classes=org.marketcetera.trade.Equity,org.marketcetera.trade.Future # determines how long to delay evaluation of work units metc.cluster.work.unit.evaluation.delay=1000 diff --git a/strategy/strategy-sample/pom.xml b/strategy/strategy-sample/pom.xml index 242641b0bf..4dd5b82464 100644 --- a/strategy/strategy-sample/pom.xml +++ b/strategy/strategy-sample/pom.xml @@ -29,6 +29,14 @@ ${project.groupId} marketdata-api + + ${project.groupId} + trade-api + + + ${project.groupId} + marketdata-core + diff --git a/strategy/strategy-sample/src/main/java/org/marketcetera/strategy/sample/TestStrategy.java b/strategy/strategy-sample/src/main/java/org/marketcetera/strategy/sample/TestStrategy.java index 879491d966..a9a580f201 100644 --- a/strategy/strategy-sample/src/main/java/org/marketcetera/strategy/sample/TestStrategy.java +++ b/strategy/strategy-sample/src/main/java/org/marketcetera/strategy/sample/TestStrategy.java @@ -1,16 +1,32 @@ package org.marketcetera.strategy.sample; +import java.math.BigDecimal; +import java.security.SecureRandom; +import java.util.Random; +import java.util.UUID; + import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.marketcetera.core.notifications.INotification.Severity; import org.marketcetera.event.Event; +import org.marketcetera.event.QuoteEvent; +import org.marketcetera.event.TopOfBookEvent; +import org.marketcetera.event.TradeEvent; import org.marketcetera.marketdata.Content; +import org.marketcetera.marketdata.MarketDataCacheElement; import org.marketcetera.marketdata.MarketDataClient; import org.marketcetera.marketdata.MarketDataListener; import org.marketcetera.marketdata.MarketDataRequest; import org.marketcetera.marketdata.MarketDataRequestBuilder; import org.marketcetera.strategy.StrategyClient; +import org.marketcetera.trade.Factory; +import org.marketcetera.trade.Instrument; +import org.marketcetera.trade.OrderSingle; +import org.marketcetera.trade.OrderSingleSuggestion; +import org.marketcetera.trade.OrderType; +import org.marketcetera.trade.Side; +import org.marketcetera.trade.client.TradeClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -18,6 +34,10 @@ import org.springframework.context.annotation.PropertySources; import org.springframework.stereotype.Component; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + /* $License$ */ /** @@ -55,6 +75,21 @@ public void receiveMarketData(Event inEvent) { strategyClient.emitMessage(Severity.INFO, String.valueOf(inEvent)); + if(inEvent instanceof QuoteEvent) { + QuoteEvent quoteEvent = (QuoteEvent)inEvent; + MarketDataCacheElement topOfBookCache = marketDataCache.getUnchecked(quoteEvent.getInstrument()); + topOfBookCache.update(Content.TOP_OF_BOOK, + inEvent); + } else if(inEvent instanceof TradeEvent) { + TradeEvent tradeEvent = (TradeEvent)inEvent; + MarketDataCacheElement topOfBookCache = marketDataCache.getUnchecked(tradeEvent.getInstrument()); + topOfBookCache.update(Content.LATEST_TICK, + inEvent); + issueSuggestion(topOfBookCache); + } else { + strategyClient.emitMessage(Severity.WARN, + "Ignored unexpected event: " + inEvent); + } } }); } @@ -73,6 +108,52 @@ public void stop() } catch (Exception ignored) {} } } + private void issueSuggestion(MarketDataCacheElement inCacheElement) + { + TopOfBookEvent topOfBook = (TopOfBookEvent)inCacheElement.getSnapshot(Content.TOP_OF_BOOK); + if(topOfBook == null) { + return; + } + QuoteEvent quote; + Side side; + if(random.nextBoolean()) { + // trade on the bid + quote = topOfBook.getBid(); + side = Side.Sell; + } else { + // trade on the ask + quote = topOfBook.getAsk(); + side = Side.Buy; + } + if(quote == null) { + return; + } + OrderSingleSuggestion orderSingleSuggestion = Factory.getInstance().createOrderSingleSuggestion(); + orderSingleSuggestion.setIdentifier("Suggestion created by test strategy: " + UUID.randomUUID().toString()); + orderSingleSuggestion.setScore(new BigDecimal(random.nextDouble())); + OrderSingle orderSingle = Factory.getInstance().createOrderSingle(); + orderSingle.setInstrument(quote.getInstrument()); + orderSingle.setOrderType(OrderType.Limit); + orderSingle.setPegToMidpoint(true); + orderSingle.setQuantity(new BigDecimal(10*(random.nextInt(10)+1))); + orderSingle.setSide(side); +// tradeClient.sendSuggestion(orderSingleSuggestion); + } + /** + * caches market data + */ + private final LoadingCache marketDataCache = CacheBuilder.newBuilder().build(new CacheLoader() { + @Override + public MarketDataCacheElement load(Instrument inKey) + throws Exception + { + return new MarketDataCacheElement(inKey); + }} + ); + /** + * generates random numbers + */ + private Random random = new SecureRandom(); /** * holds the market data request id */ @@ -87,4 +168,9 @@ public void stop() */ @Autowired private MarketDataClient marketDataClient; + /** + * provides access to trade services + */ + @Autowired + private TradeClient tradeClient; } diff --git a/strategy/strategy-server/pom.xml b/strategy/strategy-server/pom.xml index 01308f2b80..91aa0a24e1 100644 --- a/strategy/strategy-server/pom.xml +++ b/strategy/strategy-server/pom.xml @@ -38,6 +38,10 @@ ${project.groupId} eventbus-api + + ${project.groupId} + trade-server + com.querydsl diff --git a/strategy/strategy-server/src/main/java/org/marketcetera/strategy/StrategyServiceImpl.java b/strategy/strategy-server/src/main/java/org/marketcetera/strategy/StrategyServiceImpl.java index b9f18973a1..53b44cb80f 100644 --- a/strategy/strategy-server/src/main/java/org/marketcetera/strategy/StrategyServiceImpl.java +++ b/strategy/strategy-server/src/main/java/org/marketcetera/strategy/StrategyServiceImpl.java @@ -56,6 +56,8 @@ import org.marketcetera.strategy.events.SimpleStrategyUploadFailedEvent; import org.marketcetera.strategy.events.SimpleStrategyUploadSucceededEvent; import org.marketcetera.strategy.events.StrategyEvent; +import org.marketcetera.trade.client.DirectTradeClient; +import org.marketcetera.trade.client.TradeClient; import org.marketcetera.util.log.SLF4JLoggerProxy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -675,11 +677,17 @@ public StrategyInstance getStrategyInstance() return strategyInstance; }} ); + String strategyUsername = strategyInstance.getUser().getName(); // TODO this needs to be configurable - this could be a direct client for DARE or an RPC client for an SE DirectStrategyClient strategyClient = new DirectStrategyClient(newContext, - "trader"); + strategyUsername); beanFactory.registerSingleton(StrategyClient.class.getCanonicalName(), strategyClient); + // create a special trade client just for this strategy + DirectTradeClient tradeClient = new DirectTradeClient(newContext, + strategyUsername); + beanFactory.registerSingleton(TradeClient.class.getCanonicalName(), + tradeClient); // refresh the context, which allows it to prepare to use the strategy JAR newContext.refresh(); // start the context @@ -687,6 +695,9 @@ public StrategyInstance getStrategyInstance() // start the embedded strategy client strategyClient.start(); } + /** + * Stop the running strategy. + */ private void stop() { try { diff --git a/trade/trade-api/src/main/java/org/marketcetera/trade/TradePermissions.java b/trade/trade-api/src/main/java/org/marketcetera/trade/TradePermissions.java index 13269041df..326fdbb3d7 100644 --- a/trade/trade-api/src/main/java/org/marketcetera/trade/TradePermissions.java +++ b/trade/trade-api/src/main/java/org/marketcetera/trade/TradePermissions.java @@ -15,6 +15,8 @@ public enum TradePermissions implements GrantedAuthority { SendOrderAction, + SendSuggestionAction, + ViewSuggestionsAction, ViewOpenOrdersAction, ViewReportAction, ViewPositionAction, diff --git a/trade/trade-api/src/main/java/org/marketcetera/trade/client/TradeClient.java b/trade/trade-api/src/main/java/org/marketcetera/trade/client/TradeClient.java index 39e973d16d..f8c654c84d 100644 --- a/trade/trade-api/src/main/java/org/marketcetera/trade/client/TradeClient.java +++ b/trade/trade-api/src/main/java/org/marketcetera/trade/client/TradeClient.java @@ -23,6 +23,7 @@ import org.marketcetera.trade.OrderSummary; import org.marketcetera.trade.Report; import org.marketcetera.trade.ReportID; +import org.marketcetera.trade.Suggestion; import org.marketcetera.trade.SuggestionListener; import org.marketcetera.trade.TradeMessagePublisher; @@ -64,6 +65,12 @@ public interface TradeClient * @return a List<SendOrderResponse> value */ List sendOrders(List inOrders); + /** + * Submit a trade suggestion. + * + * @param inSuggestion a Suggestion value + */ + void sendOrderSuggestion(Suggestion inSuggestion); /** * Submit the given order. * diff --git a/trade/trade-rpc-client/src/main/java/org/marketcetera/trading/rpc/TradeRpcClient.java b/trade/trade-rpc-client/src/main/java/org/marketcetera/trading/rpc/TradeRpcClient.java index e1d13787c4..af7f1208eb 100644 --- a/trade/trade-rpc-client/src/main/java/org/marketcetera/trading/rpc/TradeRpcClient.java +++ b/trade/trade-rpc-client/src/main/java/org/marketcetera/trading/rpc/TradeRpcClient.java @@ -42,7 +42,6 @@ import org.marketcetera.trade.BrokerID; import org.marketcetera.trade.ExecutionReport; import org.marketcetera.trade.ExecutionReportSummary; -import org.marketcetera.trade.FIXOrder; import org.marketcetera.trade.Instrument; import org.marketcetera.trade.MutableExecutionReportSummary; import org.marketcetera.trade.MutableExecutionReportSummaryFactory; @@ -50,16 +49,10 @@ import org.marketcetera.trade.MutableOrderSummaryFactory; import org.marketcetera.trade.MutableReport; import org.marketcetera.trade.MutableReportFactory; -import org.marketcetera.trade.NewOrReplaceOrder; import org.marketcetera.trade.Option; import org.marketcetera.trade.Order; -import org.marketcetera.trade.OrderBase; -import org.marketcetera.trade.OrderCancel; import org.marketcetera.trade.OrderID; -import org.marketcetera.trade.OrderReplace; -import org.marketcetera.trade.OrderSingle; import org.marketcetera.trade.OrderSummary; -import org.marketcetera.trade.RelatedOrder; import org.marketcetera.trade.Report; import org.marketcetera.trade.ReportID; import org.marketcetera.trade.Suggestion; @@ -651,6 +644,38 @@ public ExecutionReport call() } }); } + /* (non-Javadoc) + * @see org.marketcetera.trade.client.TradeClient#sendOrderSuggestion(org.marketcetera.trade.Suggestion) + */ + @Override + public void sendOrderSuggestion(Suggestion inSuggestion) + { + executeCall(new Callable(){ + @Override + public Void call() + throws Exception + { + SLF4JLoggerProxy.trace(TradeRpcClient.this, + "{} sending {}", + getSessionId(), + inSuggestion); + TradeRpc.SendSuggestionRequest.Builder requestBuilder = TradeRpc.SendSuggestionRequest.newBuilder(); + requestBuilder.setSessionId(getSessionId().getValue()); + TradeRpcUtil.getSuggestion(inSuggestion).ifPresent(rpcSuggestion -> requestBuilder.addSuggestion(rpcSuggestion)); + TradeRpc.SendSuggestionRequest sendSuggestionRequest = requestBuilder.build(); + SLF4JLoggerProxy.trace(TradeRpcClient.this, + "{} sending {}", + getSessionId(), + sendSuggestionRequest); + TradeRpc.SendSuggestionResponse response = getBlockingStub().sendSuggestion(sendSuggestionRequest); + SLF4JLoggerProxy.trace(TradeRpcClient.this, + "{} returning {}", + getSessionId(), + response); + return null; + } + }); + } /* (non-Javadoc) * @see org.marketcetera.trade.client.TradingClient#sendOrders(java.util.List) */ @@ -667,109 +692,10 @@ public List call() getSessionId(), inOrders.size()); TradeRpc.SendOrderRequest.Builder requestBuilder = TradeRpc.SendOrderRequest.newBuilder(); - TradeTypesRpc.Order.Builder orderBuilder = TradeTypesRpc.Order.newBuilder(); - TradeTypesRpc.OrderBase.Builder orderBaseBuilder = null; - TradeTypesRpc.FIXOrder.Builder fixOrderBuilder = null; - BaseRpc.Map.Builder mapBuilder = null; - BaseRpc.KeyValuePair.Builder keyValuePairBuilder = null; requestBuilder.setSessionId(getSessionId().getValue()); for(Order order : inOrders) { try { - if(order instanceof FIXOrder) { - FIXOrder fixOrder = (FIXOrder)order; - if(fixOrderBuilder == null) { - fixOrderBuilder = TradeTypesRpc.FIXOrder.newBuilder(); - } else { - fixOrderBuilder.clear(); - } - if(mapBuilder == null) { - mapBuilder = BaseRpc.Map.newBuilder(); - } else { - mapBuilder.clear(); - } - for(Map.Entry entry : fixOrder.getFields().entrySet()) { - if(keyValuePairBuilder == null) { - keyValuePairBuilder = BaseRpc.KeyValuePair.newBuilder(); - } else { - keyValuePairBuilder.clear(); - } - keyValuePairBuilder.setKey(String.valueOf(entry.getKey())); - keyValuePairBuilder.setValue(entry.getValue()); - mapBuilder.addKeyValuePairs(keyValuePairBuilder.build()); - } - orderBuilder.setMatpOrderType(TradeTypesRpc.MatpOrderType.FIXOrderType); - TradeRpcUtil.setBrokerId(fixOrder, - fixOrderBuilder); - // TODO -// fixOrderBuilder.setMessage(mapBuilder.build()); - orderBuilder.setFixOrder(fixOrderBuilder.build()); - } else if(order instanceof OrderBase) { - if(orderBaseBuilder == null) { - orderBaseBuilder = TradeTypesRpc.OrderBase.newBuilder(); - } else { - orderBaseBuilder.clear(); - } - // either an OrderSingle, OrderReplace, or OrderCancel - // the types overlap some, first, set all the common fields on OrderBase - OrderBase orderBase = (OrderBase)order; - TradeRpcUtil.setAccount(orderBase, - orderBaseBuilder); - TradeRpcUtil.setBrokerId(orderBase, - orderBaseBuilder); - TradeRpcUtil.setRpcCustomFields(orderBase, - orderBaseBuilder); - TradeRpcUtil.setInstrument(orderBase, - orderBaseBuilder); - TradeRpcUtil.setOrderId(orderBase, - orderBaseBuilder); - TradeRpcUtil.setQuantity(orderBase, - orderBaseBuilder); - TradeRpcUtil.setSide(orderBase, - orderBaseBuilder); - TradeRpcUtil.setText(orderBase, - orderBaseBuilder); - // now, check for various special order types - if(orderBase instanceof NewOrReplaceOrder) { - NewOrReplaceOrder newOrReplaceOrder = (NewOrReplaceOrder)orderBase; - TradeRpcUtil.setDisplayQuantity(newOrReplaceOrder, - orderBaseBuilder); - TradeRpcUtil.setExecutionDestination(newOrReplaceOrder, - orderBaseBuilder); - TradeRpcUtil.setOrderCapacity(newOrReplaceOrder, - orderBaseBuilder); - TradeRpcUtil.setOrderType(newOrReplaceOrder, - orderBaseBuilder); - TradeRpcUtil.setPositionEffect(newOrReplaceOrder, - orderBaseBuilder); - TradeRpcUtil.setPrice(newOrReplaceOrder, - orderBaseBuilder); - TradeRpcUtil.setTimeInForce(newOrReplaceOrder, - orderBaseBuilder); - } - if(order instanceof RelatedOrder) { - RelatedOrder relatedOrder = (RelatedOrder)order; - TradeRpcUtil.setOriginalOrderId(relatedOrder, - orderBaseBuilder); - } - TradeTypesRpc.OrderBase rpcOrderBase = orderBaseBuilder.build(); - orderBuilder.setOrderBase(rpcOrderBase); - if(order instanceof OrderCancel) { - orderBuilder.setMatpOrderType(TradeTypesRpc.MatpOrderType.OrderCancelType); - } else if(order instanceof OrderSingle) { - orderBuilder.setMatpOrderType(TradeTypesRpc.MatpOrderType.OrderSingleType); - } else if(order instanceof OrderReplace) { - orderBuilder.setMatpOrderType(TradeTypesRpc.MatpOrderType.OrderReplaceType); - } else { - throw new UnsupportedOperationException("Unsupported order type: " + order.getClass().getSimpleName()); - } - } else { - throw new UnsupportedOperationException("Unsupported order type: " + order.getClass().getSimpleName()); - } - requestBuilder.addOrder(orderBuilder.build()); - orderBuilder.clear(); - if(orderBaseBuilder != null) { - orderBaseBuilder.clear(); - } + TradeRpcUtil.getOrder(order).ifPresent(rpcOrder -> requestBuilder.addOrder(rpcOrder)); } catch (Exception e) { PlatformServices.handleException(TradeRpcClient.this, "Unable to send " + order, diff --git a/trade/trade-rpc-core/src/main/java/org/marketcetera/trading/rpc/TradeRpcUtil.java b/trade/trade-rpc-core/src/main/java/org/marketcetera/trading/rpc/TradeRpcUtil.java index 8ff359bab8..a1a9ea8ab1 100644 --- a/trade/trade-rpc-core/src/main/java/org/marketcetera/trading/rpc/TradeRpcUtil.java +++ b/trade/trade-rpc-core/src/main/java/org/marketcetera/trading/rpc/TradeRpcUtil.java @@ -55,10 +55,16 @@ import org.marketcetera.trade.OrderBase; import org.marketcetera.trade.OrderCancel; import org.marketcetera.trade.OrderCancelReject; +import org.marketcetera.trade.OrderCancelSuggestion; +import org.marketcetera.trade.OrderCancelSuggestionImpl; import org.marketcetera.trade.OrderCapacity; import org.marketcetera.trade.OrderID; import org.marketcetera.trade.OrderReplace; +import org.marketcetera.trade.OrderReplaceSuggestion; +import org.marketcetera.trade.OrderReplaceSuggestionImpl; import org.marketcetera.trade.OrderSingle; +import org.marketcetera.trade.OrderSingleSuggestion; +import org.marketcetera.trade.OrderSingleSuggestionImpl; import org.marketcetera.trade.OrderStatus; import org.marketcetera.trade.OrderSummary; import org.marketcetera.trade.OrderType; @@ -1587,7 +1593,34 @@ public static Order getOrder(TradeTypesRpc.Order inRpcOrder) */ public static Suggestion getSuggestion(TradeRpc.SuggestionListenerResponse inResponse) { - throw new UnsupportedOperationException(); + if(!inResponse.hasSuggestion()) { + throw new UnsupportedOperationException(); + } + return getSuggestion(inResponse.getSuggestion()); + } + /** + * Get the suggestion value from the given RPC value. + * + * @param inRpcSuggestion a TradeTypesRpc.Suggestion value + * @return a Suggestion value + */ + public static Suggestion getSuggestion(TradeTypesRpc.Suggestion inRpcSuggestion) + { + Order order = getOrder(inRpcSuggestion.getOrder()); + Suggestion suggestion; + if(order instanceof OrderSingle) { + suggestion = new OrderSingleSuggestionImpl(); + ((OrderSingleSuggestionImpl)suggestion).setOrder((OrderSingle)order); + } else if(order instanceof OrderReplace) { + suggestion = new OrderReplaceSuggestionImpl((OrderReplace)order); + } else if(order instanceof OrderCancel) { + suggestion = new OrderCancelSuggestionImpl((OrderCancel)order); + } else { + throw new UnsupportedOperationException("Unexpected RPC order type: " + inRpcSuggestion.getOrder().getMatpOrderTypeValue()); + } + suggestion.setIdentifier(inRpcSuggestion.getIdentifier()); + BaseRpcUtil.getScaledQuantity(inRpcSuggestion.getScore()).ifPresent(qty -> suggestion.setScore(qty)); + return suggestion; } /** * Set the suggestion value into the given RPC builder, if possible. @@ -1598,7 +1631,122 @@ public static Suggestion getSuggestion(TradeRpc.SuggestionListenerResponse inRes public static void setSuggestion(Suggestion inSuggestion, TradeRpc.SuggestionListenerResponse.Builder inResponseBuilder) { - throw new UnsupportedOperationException(); // TODO + getSuggestion(inSuggestion).ifPresent(rpcSuggestion -> inResponseBuilder.setSuggestion(rpcSuggestion)); + } + /** + * Get the RPC suggestion value from the given value. + * + * @param inSuggestion a Suggestion value + * @return an Optional<TradeTypesRpc.Suggestion> value + */ + public static Optional getSuggestion(Suggestion inSuggestion) + { + if(inSuggestion == null) { + return Optional.empty(); + } + TradeTypesRpc.Suggestion.Builder builder = TradeTypesRpc.Suggestion.newBuilder(); + builder.setIdentifier(inSuggestion.getIdentifier()); + BaseRpcUtil.getRpcQty(inSuggestion.getScore()).ifPresent(rpcQty -> builder.setScore(rpcQty)); + if(inSuggestion instanceof OrderSingleSuggestion) { + OrderSingleSuggestion suggestion = (OrderSingleSuggestion)inSuggestion; + getOrder(suggestion.getOrder()).ifPresent(rpcOrder -> builder.setOrder(rpcOrder)); + } else if(inSuggestion instanceof OrderReplaceSuggestion) { + OrderReplaceSuggestion suggestion = (OrderReplaceSuggestion)inSuggestion; + getOrder(suggestion.getOrderReplace()).ifPresent(rpcOrder -> builder.setOrder(rpcOrder)); + } else if(inSuggestion instanceof OrderCancelSuggestion) { + OrderCancelSuggestion suggestion = (OrderCancelSuggestion)inSuggestion; + getOrder(suggestion.getOrderCancel()).ifPresent(rpcOrder -> builder.setOrder(rpcOrder)); + } else { + throw new UnsupportedOperationException("Unexpected suggestion type: " + inSuggestion.getClass().getSimpleName()); + } + return Optional.of(builder.build()); + } + /** + * Get the RPC order value from the given value. + * + * @param inOrder an Order value + */ + public static Optional getOrder(Order inOrder) + { + if(inOrder == null) { + return Optional.empty(); + } + TradeTypesRpc.Order.Builder orderBuilder = TradeTypesRpc.Order.newBuilder(); + TradeTypesRpc.OrderBase.Builder orderBaseBuilder = TradeTypesRpc.OrderBase.newBuilder(); + if(inOrder instanceof FIXOrder) { + FIXOrder fixOrder = (FIXOrder)inOrder; + TradeTypesRpc.FIXOrder.Builder fixOrderBuilder = TradeTypesRpc.FIXOrder.newBuilder(); + BaseRpc.Map.Builder mapBuilder = BaseRpc.Map.newBuilder(); + BaseRpc.KeyValuePair.Builder keyValuePairBuilder = BaseRpc.KeyValuePair.newBuilder(); + for(Map.Entry entry : fixOrder.getFields().entrySet()) { + keyValuePairBuilder.setKey(String.valueOf(entry.getKey())); + keyValuePairBuilder.setValue(entry.getValue()); + mapBuilder.addKeyValuePairs(keyValuePairBuilder.build()); + } + orderBuilder.setMatpOrderType(TradeTypesRpc.MatpOrderType.FIXOrderType); + TradeRpcUtil.setBrokerId(fixOrder, + fixOrderBuilder); + // TODO +// fixOrderBuilder.setMessage(mapBuilder.build()); + orderBuilder.setFixOrder(fixOrderBuilder.build()); + } else if(inOrder instanceof OrderBase) { + // either an OrderSingle, OrderReplace, or OrderCancel + // the types overlap some, first, set all the common fields on OrderBase + OrderBase orderBase = (OrderBase)inOrder; + TradeRpcUtil.setAccount(orderBase, + orderBaseBuilder); + TradeRpcUtil.setBrokerId(orderBase, + orderBaseBuilder); + TradeRpcUtil.setRpcCustomFields(orderBase, + orderBaseBuilder); + TradeRpcUtil.setInstrument(orderBase, + orderBaseBuilder); + TradeRpcUtil.setOrderId(orderBase, + orderBaseBuilder); + TradeRpcUtil.setQuantity(orderBase, + orderBaseBuilder); + TradeRpcUtil.setSide(orderBase, + orderBaseBuilder); + TradeRpcUtil.setText(orderBase, + orderBaseBuilder); + // now, check for various special order types + if(orderBase instanceof NewOrReplaceOrder) { + NewOrReplaceOrder newOrReplaceOrder = (NewOrReplaceOrder)orderBase; + TradeRpcUtil.setDisplayQuantity(newOrReplaceOrder, + orderBaseBuilder); + TradeRpcUtil.setExecutionDestination(newOrReplaceOrder, + orderBaseBuilder); + TradeRpcUtil.setOrderCapacity(newOrReplaceOrder, + orderBaseBuilder); + TradeRpcUtil.setOrderType(newOrReplaceOrder, + orderBaseBuilder); + TradeRpcUtil.setPositionEffect(newOrReplaceOrder, + orderBaseBuilder); + TradeRpcUtil.setPrice(newOrReplaceOrder, + orderBaseBuilder); + TradeRpcUtil.setTimeInForce(newOrReplaceOrder, + orderBaseBuilder); + } + if(inOrder instanceof RelatedOrder) { + RelatedOrder relatedOrder = (RelatedOrder)inOrder; + TradeRpcUtil.setOriginalOrderId(relatedOrder, + orderBaseBuilder); + } + TradeTypesRpc.OrderBase rpcOrderBase = orderBaseBuilder.build(); + orderBuilder.setOrderBase(rpcOrderBase); + if(inOrder instanceof OrderCancel) { + orderBuilder.setMatpOrderType(TradeTypesRpc.MatpOrderType.OrderCancelType); + } else if(inOrder instanceof OrderSingle) { + orderBuilder.setMatpOrderType(TradeTypesRpc.MatpOrderType.OrderSingleType); + } else if(inOrder instanceof OrderReplace) { + orderBuilder.setMatpOrderType(TradeTypesRpc.MatpOrderType.OrderReplaceType); + } else { + throw new UnsupportedOperationException("Unsupported order type: " + inOrder.getClass().getSimpleName()); + } + } else { + throw new UnsupportedOperationException("Unsupported order type: " + inOrder.getClass().getSimpleName()); + } + return Optional.of(orderBuilder.build()); } /** * Get the trade message value from the given RPC message. diff --git a/trade/trade-rpc-core/src/main/proto/rpc_trade.proto b/trade/trade-rpc-core/src/main/proto/rpc_trade.proto index 433e3717d9..58dffb2539 100644 --- a/trade/trade-rpc-core/src/main/proto/rpc_trade.proto +++ b/trade/trade-rpc-core/src/main/proto/rpc_trade.proto @@ -177,6 +177,14 @@ message ReadAvailableFixInitiatorSessionsResponse { repeated ActiveFixSession fixSession = 1; } +message SendSuggestionRequest { + string sessionId = 1; + repeated Suggestion suggestion = 2; +} + +message SendSuggestionResponse { +} + service TradeRpcService { rpc login(LoginRequest) returns (LoginResponse); rpc logout(LogoutRequest) returns (LogoutResponse); @@ -199,4 +207,5 @@ service TradeRpcService { rpc getAverageFillPrices(GetAverageFillPricesRequest) returns (GetAverageFillPricesResponse); rpc addSuggestionListener(AddSuggestionListenerRequest) returns (stream SuggestionListenerResponse); rpc removeSuggestionListener(RemoveSuggestionListenerRequest) returns (RemoveSuggestionListenerResponse); + rpc sendSuggestion(SendSuggestionRequest) returns (SendSuggestionResponse); } diff --git a/trade/trade-rpc-server/src/main/java/org/marketcetera/client/rpc/server/TradeRpcService.java b/trade/trade-rpc-server/src/main/java/org/marketcetera/client/rpc/server/TradeRpcService.java index 57fa95c0eb..f3e488c3d1 100644 --- a/trade/trade-rpc-server/src/main/java/org/marketcetera/client/rpc/server/TradeRpcService.java +++ b/trade/trade-rpc-server/src/main/java/org/marketcetera/client/rpc/server/TradeRpcService.java @@ -80,8 +80,6 @@ import org.marketcetera.trade.rpc.TradeRpc.RemoveTradeMessageListenerResponse; import org.marketcetera.trade.rpc.TradeRpc.ResolveSymbolRequest; import org.marketcetera.trade.rpc.TradeRpc.ResolveSymbolResponse; -import org.marketcetera.trade.rpc.TradeRpc.SendOrderRequest; -import org.marketcetera.trade.rpc.TradeRpc.SendOrderResponse; import org.marketcetera.trade.rpc.TradeRpc.TradeMessageListenerResponse; import org.marketcetera.trade.rpc.TradeRpcServiceGrpc; import org.marketcetera.trade.rpc.TradeRpcServiceGrpc.TradeRpcServiceImplBase; @@ -332,11 +330,48 @@ public void getLatestExecutionReportForOrderChain(GetLatestExecutionReportForOrd } } /* (non-Javadoc) - * @see org.marketcetera.trade.rpc.TradeRpcServiceGrpc.TradeRpcServiceImplBase#sendOrders(org.marketcetera.trade.rpc.TradeRpc.SendOrderRequest, io.grpc.stub.StreamObserver) + * @see org.marketcetera.trade.rpc.TradeRpcServiceGrpc.TradeRpcServiceImplBase#sendSuggestion(org.marketcetera.trade.rpc.TradeRpc.SendSuggestionRequest, io.grpc.stub.StreamObserver) */ @Override - public void sendOrders(SendOrderRequest inRequest, - StreamObserver inResponseObserver) + public void sendSuggestion(TradeRpc.SendSuggestionRequest inRequest, + StreamObserver inResponseObserver) + { + try { + SessionHolder sessionHolder = validateAndReturnSession(inRequest.getSessionId()); + SLF4JLoggerProxy.trace(TradeRpcService.this, + "Received send suggestion request {} from {}", + inRequest, + sessionHolder); + authzService.authorize(sessionHolder.getUser(), + TradePermissions.SendSuggestionAction.name()); + TradeRpc.SendSuggestionResponse.Builder responseBuilder = TradeRpc.SendSuggestionResponse.newBuilder(); + for(TradeTypesRpc.Suggestion rpcSuggestion : inRequest.getSuggestionList()) { + try { + Suggestion matpSuggestion = TradeRpcUtil.getSuggestion(rpcSuggestion); +// User user = userService.findByName(sessionHolder.getUser()); + // TODO need to attach a user to a suggestion + tradeService.reportSuggestion(matpSuggestion); + } catch (Exception e) { + SLF4JLoggerProxy.warn(TradeRpcService.this, + e, + "Unable to submit order {}", + rpcSuggestion); + } + } + TradeRpc.SendSuggestionResponse response = responseBuilder.build(); + inResponseObserver.onNext(response); + inResponseObserver.onCompleted(); + } catch (Exception e) { + handleError(e, + inResponseObserver); + } + } + /* (non-Javadoc) + * @see org.marketcetera.trade.rpc.TradeRpcServiceGrpc.TradeRpcServiceImplBase#sendSuggestions(org.marketcetera.trade.rpc.TradeRpc.SendOrderRequest, io.grpc.stub.StreamObserver) + */ + @Override + public void sendOrders(TradeRpc.SendOrderRequest inRequest, + StreamObserver inResponseObserver) { try { SessionHolder sessionHolder = validateAndReturnSession(inRequest.getSessionId()); diff --git a/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClient.java b/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClient.java index f40515a1ba..d49597e491 100644 --- a/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClient.java +++ b/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClient.java @@ -33,6 +33,7 @@ import org.marketcetera.trade.Report; import org.marketcetera.trade.ReportID; import org.marketcetera.trade.SendOrderFailed; +import org.marketcetera.trade.Suggestion; import org.marketcetera.trade.SuggestionListener; import org.marketcetera.trade.TradeMessageListener; import org.marketcetera.trade.TradeMessagePublisher; @@ -210,6 +211,14 @@ public SendOrderResponse sendOrder(Order inOrder) response.setOrderId(orderId); return response; } + /* (non-Javadoc) + * @see org.marketcetera.trade.client.TradeClient#sendOrderSuggestion(org.marketcetera.trade.Suggestion) + */ + @Override + public void sendOrderSuggestion(Suggestion inSuggestion) + { + tradeService.reportSuggestion(inSuggestion); + } /* (non-Javadoc) * @see org.marketcetera.trade.client.TradeClient#getPositionAsOf(java.util.Date, org.marketcetera.trade.Instrument) */ @@ -356,8 +365,8 @@ public void setApplicationContext(ApplicationContext inApplicationContext) * @param inApplicationContext an ApplicationContext value * @param inUsername a String value */ - protected DirectTradeClient(ApplicationContext inApplicationContext, - String inUsername) + public DirectTradeClient(ApplicationContext inApplicationContext, + String inUsername) { applicationContext = inApplicationContext; username = StringUtils.trimToNull(inUsername); From ebcdc31634c6128a7d1d2a77f4d599143a7ecb25 Mon Sep 17 00:00:00 2001 From: Colin DuPlantis Date: Wed, 12 Apr 2023 12:15:46 -0700 Subject: [PATCH 03/10] MATP-1157 Implement Trade Suggestions --- .../org/marketcetera/ui/PhotonServices.java | 26 ++ .../ui/service/trade/TradeClientService.java | 29 ++ .../ui/strategy/view/StrategyView.java | 1 - .../ui/strategy/view/StrategyViewFactory.java | 2 +- .../trade/view/AbstractTradeViewFactory.java | 4 + .../averageprice/AveragePriceViewFactory.java | 9 - .../view/suggestions/DisplaySuggestion.java | 173 +++++++++ .../view/suggestions/SuggestionsView.java | 335 ++++++++++++++++++ .../suggestions/SuggestionsViewFactory.java | 90 +++++ .../src/main/resources/images/light-bulb.svg | 5 + 10 files changed, 663 insertions(+), 11 deletions(-) create mode 100644 photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/DisplaySuggestion.java create mode 100644 photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java create mode 100644 photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsViewFactory.java create mode 100644 photon/src/main/resources/images/light-bulb.svg diff --git a/photon/src/main/java/org/marketcetera/ui/PhotonServices.java b/photon/src/main/java/org/marketcetera/ui/PhotonServices.java index a5d224feab..d42d0fb48f 100644 --- a/photon/src/main/java/org/marketcetera/ui/PhotonServices.java +++ b/photon/src/main/java/org/marketcetera/ui/PhotonServices.java @@ -21,6 +21,8 @@ import org.marketcetera.core.BigDecimalUtil; import org.marketcetera.core.time.TimeFactoryImpl; import org.marketcetera.persist.SummaryNDEntityBase; +import org.marketcetera.trade.HasInstrument; +import org.marketcetera.trade.Instrument; import org.marketcetera.ui.service.ServiceManager; import org.marketcetera.ui.service.SessionUser; import org.marketcetera.ui.service.admin.AdminClientService; @@ -183,6 +185,30 @@ public void updateItem(Clazz inData, }; } } + /** + * Render the given column as an Instrument cell. + * + * @param inTableColumn a TableColumn<? extends HasInstrument,Instrument> value + * @return a TableCell<? extends HasInstrument,Instrument> value + */ + public static TableCell renderInstrumentCell(TableColumn inTableColumn) + { + TableCell tableCell = new TableCell<>() { + @Override + protected void updateItem(Instrument inItem, + boolean isEmpty) + { + super.updateItem(inItem, + isEmpty); + this.setText(null); + this.setGraphic(null); + if(!isEmpty && inItem != null){ + this.setText(inItem.getFullSymbol()); + } + } + }; + return tableCell; + } public static TableCell renderNumberCell(TableColumn inTableColumn) { TableCell tableCell = new TableCell<>() { diff --git a/photon/src/main/java/org/marketcetera/ui/service/trade/TradeClientService.java b/photon/src/main/java/org/marketcetera/ui/service/trade/TradeClientService.java index 50b9d4df3c..aa0d3d7335 100644 --- a/photon/src/main/java/org/marketcetera/ui/service/trade/TradeClientService.java +++ b/photon/src/main/java/org/marketcetera/ui/service/trade/TradeClientService.java @@ -18,6 +18,8 @@ import org.marketcetera.trade.OrderSummary; import org.marketcetera.trade.Report; import org.marketcetera.trade.ReportID; +import org.marketcetera.trade.Suggestion; +import org.marketcetera.trade.SuggestionListener; import org.marketcetera.trade.TradeMessageListener; import org.marketcetera.trade.client.SendOrderResponse; import org.marketcetera.trade.client.TradeClient; @@ -192,6 +194,33 @@ public SendOrderResponse send(Order inOrder) { return tradeClient.sendOrder(inOrder); } + /** + * Submit a trade suggestion. + * + * @param inSuggestion a Suggestion value + */ + public void sendOrderSuggestion(Suggestion inSuggestion) + { + tradeClient.sendOrderSuggestion(inSuggestion); + } + /** + * Add the given suggestion listener. + * + * @param inSuggestionListener a SuggestionListener value + */ + public void addSuggestionListener(SuggestionListener inSuggestionListener) + { + tradeClient.addSuggestionListener(inSuggestionListener); + } + /** + * Remove the given suggestion listener. + * + * @param inSuggestionListener a SuggestionListener value + */ + public void removeSuggestionListener(SuggestionListener inSuggestionListener) + { + tradeClient.removeSuggestionListener(inSuggestionListener); + } /* (non-Javadoc) * @see org.marketcetera.web.service.ConnectableService#isRunning() */ diff --git a/photon/src/main/java/org/marketcetera/ui/strategy/view/StrategyView.java b/photon/src/main/java/org/marketcetera/ui/strategy/view/StrategyView.java index c98a4a66fb..0680c9593d 100644 --- a/photon/src/main/java/org/marketcetera/ui/strategy/view/StrategyView.java +++ b/photon/src/main/java/org/marketcetera/ui/strategy/view/StrategyView.java @@ -158,7 +158,6 @@ public void changed(ObservableValue inObservable, eventTable.prefWidthProperty().bind(mainLayout.widthProperty()); strategyTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); eventTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); -// mainLayout.prefHeightProperty().bind(getParentWindow().heightProperty()); mainLayout.getChildren().addAll(strategyTable, new Separator(Orientation.HORIZONTAL), buttonLayout, diff --git a/photon/src/main/java/org/marketcetera/ui/strategy/view/StrategyViewFactory.java b/photon/src/main/java/org/marketcetera/ui/strategy/view/StrategyViewFactory.java index deebaf44b6..0be6e53c4a 100644 --- a/photon/src/main/java/org/marketcetera/ui/strategy/view/StrategyViewFactory.java +++ b/photon/src/main/java/org/marketcetera/ui/strategy/view/StrategyViewFactory.java @@ -19,7 +19,7 @@ /* $License$ */ /** - * + * Creates {@link StrategyView} objects. * * @author Colin DuPlantis * @version $Id$ diff --git a/photon/src/main/java/org/marketcetera/ui/trade/view/AbstractTradeViewFactory.java b/photon/src/main/java/org/marketcetera/ui/trade/view/AbstractTradeViewFactory.java index 7fcbc9587f..97f0a80bf0 100644 --- a/photon/src/main/java/org/marketcetera/ui/trade/view/AbstractTradeViewFactory.java +++ b/photon/src/main/java/org/marketcetera/ui/trade/view/AbstractTradeViewFactory.java @@ -139,6 +139,10 @@ protected Image getIcon(String inName) * weight of order ticket menu item */ protected static final int orderTicketWeight = 500; + /** + * weight of trade suggestions menu item + */ + protected static final int tradeSuggestionsWeight = 600; /** * provides access to web message services */ diff --git a/photon/src/main/java/org/marketcetera/ui/trade/view/averageprice/AveragePriceViewFactory.java b/photon/src/main/java/org/marketcetera/ui/trade/view/averageprice/AveragePriceViewFactory.java index 382a618519..2f80588935 100644 --- a/photon/src/main/java/org/marketcetera/ui/trade/view/averageprice/AveragePriceViewFactory.java +++ b/photon/src/main/java/org/marketcetera/ui/trade/view/averageprice/AveragePriceViewFactory.java @@ -81,15 +81,6 @@ protected Class getViewFactoryType() { return AveragePriceViewFactory.class; } -// /* (non-Javadoc) -// * @see org.marketcetera.web.trade.openorders.view.AbstractTradeViewFactory#getWindowSize() -// */ -// @Override -// protected Pair getWindowSize() -// { -// return Pair.create(800.0, -// 200.0); -// } /** * permission(s) required to execute average price view */ diff --git a/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/DisplaySuggestion.java b/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/DisplaySuggestion.java new file mode 100644 index 0000000000..e7483b50c9 --- /dev/null +++ b/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/DisplaySuggestion.java @@ -0,0 +1,173 @@ +package org.marketcetera.ui.trade.view.suggestions; + +import java.math.BigDecimal; +import java.util.Date; + +import org.marketcetera.trade.HasInstrument; +import org.marketcetera.trade.Instrument; +import org.marketcetera.trade.OrderSingleSuggestion; +import org.marketcetera.trade.OrderType; +import org.marketcetera.trade.Side; +import org.marketcetera.trade.Suggestion; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +/* $License$ */ + +/** + * Provides a display-oriented {@link Suggestion} implementation. + * + * @author Colin DuPlantis + * @version $Id$ + * @since $Release$ + */ +public class DisplaySuggestion + implements HasInstrument +{ + /** + * Create a new DisplaySuggestion instance. + * + * @param inSuggestion a Suggestion value + */ + public DisplaySuggestion(Suggestion inSuggestion) + { + identifierProperty.set(inSuggestion.getIdentifier()); + scoreProperty.set(inSuggestion.getScore()); + if(inSuggestion instanceof OrderSingleSuggestion) { + OrderSingleSuggestion orderSingleSuggestion = (OrderSingleSuggestion)inSuggestion; + instrumentProperty.set(orderSingleSuggestion.getOrder().getInstrument()); + orderTypeProperty.set(orderSingleSuggestion.getOrder().getOrderType()); + priceProperty.set(orderSingleSuggestion.getOrder().getPrice()); + quantityProperty.set(orderSingleSuggestion.getOrder().getQuantity()); + sideProperty.set(orderSingleSuggestion.getOrder().getSide()); + timestampProperty.set(new Date()); // TODO should be the timestamp from the suggestion, probably + } else { + throw new UnsupportedOperationException("Unsupported suggestion type: " + inSuggestion.getClass().getSimpleName()); + } + } + /* (non-Javadoc) + * @see org.marketcetera.trade.HasInstrument#getInstrument() + */ + @Override + public Instrument getInstrument() + { + return instrumentProperty.get(); + } + /* (non-Javadoc) + * @see org.marketcetera.trade.HasInstrument#setInstrument(org.marketcetera.trade.Instrument) + */ + @Override + public void setInstrument(Instrument inInstrument) + { + instrumentProperty.set(inInstrument); + } + /** + * Get the identifierProperty value. + * + * @return a ReadOnlyStringProperty value + */ + public ReadOnlyStringProperty identifierProperty() + { + return identifierProperty; + } + /** + * Get the scoreProperty value. + * + * @return a ReadOnlyObjectProperty<BigDecimal> value + */ + public ReadOnlyObjectProperty scoreProperty() + { + return scoreProperty; + } + /** + * Get the sideProperty value. + * + * @return a ReadOnlyObjectProperty<Side> value + */ + public ReadOnlyObjectProperty sideProperty() + { + return sideProperty; + } + /** + * Get the quantityProperty value. + * + * @return a ReadOnlyObjectProperty<BigDecimal> value + */ + public ReadOnlyObjectProperty quantityProperty() + { + return quantityProperty; + } + /** + * Get the priceProperty value. + * + * @return a ReadOnlyObjectProperty<BigDecimal> value + */ + public ReadOnlyObjectProperty priceProperty() + { + return priceProperty; + } + /** + * Get the instrumentProperty value. + * + * @return a ReadOnlyObjectProperty<Instrument> value + */ + public ReadOnlyObjectProperty instrumentProperty() + { + return instrumentProperty; + } + /** + * Get the orderTypeProperty value. + * + * @return a ReadOnlyObjectProperty<OrderType> value + */ + public ReadOnlyObjectProperty orderTypeProperty() + { + return orderTypeProperty; + } + /** + * Get the timestampProperty value. + * + * @return a ReadOnlyObjectProperty<Date> value + */ + public ReadOnlyObjectProperty timestampProperty() + { + return timestampProperty; + } + /** + * identifier property value + */ + private final StringProperty identifierProperty = new SimpleStringProperty(); + /** + * score property value + */ + private final ObjectProperty scoreProperty = new SimpleObjectProperty<>(); + /** + * side property value + */ + private final ObjectProperty sideProperty = new SimpleObjectProperty<>(); + /** + * quantity property value + */ + private final ObjectProperty quantityProperty = new SimpleObjectProperty<>(); + /** + * price property value + */ + private final ObjectProperty priceProperty = new SimpleObjectProperty<>(); + /** + * instrument property value + */ + private final ObjectProperty instrumentProperty = new SimpleObjectProperty<>(); + /** + * order type property value + */ + private final ObjectProperty orderTypeProperty = new SimpleObjectProperty<>(); + /** + * timestamp property value + */ + private final ObjectProperty timestampProperty = new SimpleObjectProperty<>(); +} diff --git a/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java b/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java new file mode 100644 index 0000000000..3da29b111e --- /dev/null +++ b/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java @@ -0,0 +1,335 @@ +package org.marketcetera.ui.trade.view.suggestions; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Date; +import java.util.Properties; + +import org.marketcetera.core.PlatformServices; +import org.marketcetera.trade.Instrument; +import org.marketcetera.trade.OrderType; +import org.marketcetera.trade.Side; +import org.marketcetera.trade.Suggestion; +import org.marketcetera.trade.SuggestionListener; +import org.marketcetera.ui.PhotonServices; +import org.marketcetera.ui.events.NewWindowEvent; +import org.marketcetera.ui.service.trade.TradeClientService; +import org.marketcetera.ui.strategy.view.DisplayStrategyMessage; +import org.marketcetera.ui.view.AbstractContentView; +import org.nocrala.tools.texttablefmt.BorderStyle; +import org.nocrala.tools.texttablefmt.ShownBorders; +import org.nocrala.tools.texttablefmt.Table; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javafx.application.Platform; +import javafx.scene.control.Label; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; + +/* $License$ */ + +/** + * Displays trade suggestions. + * + * @author Colin DuPlantis + * @version $Id$ + * @since $Release$ + */ +@Component +@AutoConfiguration +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class SuggestionsView + extends AbstractContentView +{ + /* (non-Javadoc) + * @see org.marketcetera.ui.view.AbstractContentView#onStart() + */ + @Override + protected void onStart() + { + tradeClient = serviceManager.getService(TradeClientService.class); + mainLayout = new VBox(10); + mainLayout.getChildren().add(suggestionTable); + initializeSuggestionTable(); + suggestionTable.prefWidthProperty().bind(mainLayout.widthProperty()); + suggestionTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + } + /* (non-Javadoc) + * @see org.marketcetera.ui.view.ContentView#onClose() + */ + @Override + public void onClose() + { + if(suggestionListener != null) { + try { + tradeClient.removeSuggestionListener(suggestionListener); + suggestionListener = null; + } catch (Exception ignored) {} + } + super.onClose(); + } + /* (non-Javadoc) + * @see org.marketcetera.ui.view.ContentView#getMainLayout() + */ + @Override + public Region getMainLayout() + { + return mainLayout; + } + /* (non-Javadoc) + * @see org.marketcetera.ui.view.ContentView#getViewName() + */ + @Override + public String getViewName() + { + return NAME; + } + /* (non-Javadoc) + * @see org.marketcetera.ui.view.AbstractContentView#onClientConnect() + */ + @Override + protected void onClientConnect() + { + updateSuggestions(); + initializeSuggestionListener(); + } + /* (non-Javadoc) + * @see org.marketcetera.ui.view.AbstractContentView#onClientDisconnect() + */ + @Override + protected void onClientDisconnect() + { + Platform.runLater(() -> { + suggestionTable.getItems().clear(); + }); + } + /** + * Create a new SuggestionsView instance. + * + * @param inParent a Region value + * @param inNewWindowEvent a NewWindowEvent value + * @param inProperties a Properties value + */ + public SuggestionsView(Region inParent, + NewWindowEvent inEvent, + Properties inProperties) + { + super(inParent, + inEvent, + inProperties); + } + /** + * Set up the strategy event listener. + */ + private void initializeSuggestionListener() + { + if(suggestionListener != null) { + try { + tradeClient.removeSuggestionListener(suggestionListener); + suggestionListener = null; + } catch (Exception ignored) {} + } + suggestionListener = new SuggestionListener() { + @Override + public void receiveSuggestion(Suggestion inSuggestion) + { + suggestionTable.getItems().add(new DisplaySuggestion(inSuggestion)); + } + }; + tradeClient.addSuggestionListener(suggestionListener); + } + /** + * Update the strategies table. + */ + private void updateSuggestions() + { + // TODO nothing really to do right now because the suggestions are not persisted - see MATP-1159 +// Platform.runLater(() -> { +// }); + } + /** + * Initialize the strategy table. + */ + private void initializeSuggestionTable() + { + suggestionTable = new TableView<>(); + suggestionTable.setPlaceholder(new Label("no suggestions")); + initializeSuggestionTableColumns(); + initializeSuggestionContextMenu(); + suggestionTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + } + /** + * Render the given column as an Instrument cell. + * + * @param inTableColumn a TableColumn<DisplaySuggestion,Instrument> value + * @return a TableCell<DisplaySuggestion,Instrument> value + */ + public static TableCell renderInstrumentCell(TableColumn inTableColumn) + { + TableCell tableCell = new TableCell<>() { + @Override + protected void updateItem(Instrument inItem, + boolean isEmpty) + { + super.updateItem(inItem, + isEmpty); + this.setText(null); + this.setGraphic(null); + if(!isEmpty && inItem != null){ + this.setText(inItem.getFullSymbol()); + } + } + }; + return tableCell; + } + /** + * Initialize the strategy table columns. + */ + private void initializeSuggestionTableColumns() + { +// suggestionTable.getSelectionModel().selectedItemProperty().addListener((ChangeListener) (inObservable,inOldValue,inNewValue) -> { +// enableSuggestionContextMenuItems(inNewValue); +// }); + identifierColumn = new TableColumn<>("Identifier"); + identifierColumn.setCellValueFactory(new PropertyValueFactory<>("identifier")); + scoreColumn = new TableColumn<>("Score"); + scoreColumn.setCellValueFactory(new PropertyValueFactory<>("score")); + scoreColumn.setCellFactory(tableColumn -> PhotonServices.renderNumberCell(tableColumn)); + quantityColumn = new TableColumn<>("Quantity"); + quantityColumn.setCellValueFactory(new PropertyValueFactory<>("quantity")); + quantityColumn.setCellFactory(tableColumn -> PhotonServices.renderNumberCell(tableColumn)); + sideColumn = new TableColumn<>("Side"); + sideColumn.setCellValueFactory(new PropertyValueFactory<>("side")); + quantityColumn = new TableColumn<>("Quantity"); + quantityColumn.setCellValueFactory(new PropertyValueFactory<>("quantity")); + quantityColumn.setCellFactory(tableColumn -> PhotonServices.renderNumberCell(tableColumn)); + instrumentColumn = new TableColumn<>("Instrument"); + instrumentColumn.setCellValueFactory(new PropertyValueFactory<>("instrument")); + instrumentColumn.setCellFactory(tableColumn -> renderInstrumentCell(tableColumn)); + priceColumn = new TableColumn<>("Price"); + priceColumn.setCellValueFactory(new PropertyValueFactory<>("price")); + priceColumn.setCellFactory(tableColumn -> PhotonServices.renderCurrencyCell(tableColumn)); + orderTypeColumn = new TableColumn<>("Order Type"); + orderTypeColumn.setCellValueFactory(new PropertyValueFactory<>("orderType")); + timestampColumn = new TableColumn<>("Timestamp"); + timestampColumn.setCellValueFactory(new PropertyValueFactory<>("timestamp")); + timestampColumn.setCellFactory(tableColumn -> PhotonServices.renderDateCell(tableColumn)); + suggestionTable.getColumns().add(identifierColumn); + suggestionTable.getColumns().add(scoreColumn); + suggestionTable.getColumns().add(sideColumn); + suggestionTable.getColumns().add(instrumentColumn); + suggestionTable.getColumns().add(priceColumn); + suggestionTable.getColumns().add(orderTypeColumn); + suggestionTable.getColumns().add(timestampColumn); + } + /** + * Get the selected suggestions. + * + * @return a Collection<DisplaySuggestion> value + */ + private Collection getSelectedSuggestions() + { + return suggestionTable.getSelectionModel().getSelectedItems(); + } + /** + * Create a human-readable representation of the given strategy messages. + * + * @param inStrategyMessages a Collection<DisplayStrategymessage> value + * @return a String value + */ + private String renderStrategyMessages(Collection inStrategyMessages) + { + Table table = new Table(4, + BorderStyle.CLASSIC_COMPATIBLE_WIDE, + ShownBorders.ALL, + false); + table.addCell("Strategy Messages", + PlatformServices.cellStyle, + 4); + table.addCell("Timestamp", + PlatformServices.cellStyle); + table.addCell("Strategy", + PlatformServices.cellStyle); + table.addCell("Severity", + PlatformServices.cellStyle); + table.addCell("Message", + PlatformServices.cellStyle); + for(DisplayStrategyMessage message : inStrategyMessages) { + table.addCell(String.valueOf(message.timestampProperty().get())); + table.addCell(message.strategyNameProperty().get()); + table.addCell(message.severityProperty().get().name()); + table.addCell(message.messageProperty().get()); + } + return table.render(); + } + /** + * Initialize the strategy context menu. + */ + private void initializeSuggestionContextMenu() + { + // execute + // delete + } + /** + * identifier column + */ + private TableColumn identifierColumn; + /** + * side column + */ + private TableColumn sideColumn; + /** + * quantity column + */ + private TableColumn quantityColumn; + /** + * price column + */ + private TableColumn priceColumn; + /** + * score column + */ + private TableColumn scoreColumn; + /** + * instrument column + */ + private TableColumn instrumentColumn; + /** + * order type column + */ + private TableColumn orderTypeColumn; + /** + * timestamp column + */ + private TableColumn timestampColumn; + // TODO suggestion type column? + // TODO suggestion owner column? + /** + * listens for suggestions + */ + private SuggestionListener suggestionListener; + /** + * main view layout + */ + private VBox mainLayout; + /** + * strategy table + */ + private TableView suggestionTable; + /** + * provides access to trade services + */ + private TradeClientService tradeClient; + /** + * global name of the view + */ + private static final String NAME = "Suggestions View"; +} diff --git a/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsViewFactory.java b/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsViewFactory.java new file mode 100644 index 0000000000..d1bbb43026 --- /dev/null +++ b/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsViewFactory.java @@ -0,0 +1,90 @@ +package org.marketcetera.ui.trade.view.suggestions; + +import java.net.URL; +import java.util.Collections; +import java.util.Set; + +import org.marketcetera.trade.TradePermissions; +import org.marketcetera.ui.trade.view.AbstractTradeViewFactory; +import org.marketcetera.ui.view.ContentView; +import org.marketcetera.ui.view.MenuContent; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Component; + +import com.google.common.collect.Sets; + +/* $License$ */ + +/** + * Creates new {@link SuggestionsView} objects. + * + * @author Colin DuPlantis + * @version $Id$ + * @since $Release$ + */ +@Component +public class SuggestionsViewFactory + extends AbstractTradeViewFactory + implements MenuContent +{ + /* (non-Javadoc) + * @see org.marketcetera.ui.trade.view.AbstractTradeViewFactory#getViewFactoryType() + */ + @Override + protected Class getViewFactoryType() + { + return SuggestionsViewFactory.class; + } + /* (non-Javadoc) + * @see org.marketcetera.ui.trade.view.AbstractTradeViewFactory#getViewName() + */ + @Override + protected String getViewName() + { + return getMenuCaption(); + } + /* (non-Javadoc) + * @see org.marketcetera.ui.view.MenuContent#getMenuCaption() + */ + @Override + public String getMenuCaption() + { + return "Trade Suggestions"; + } + /* (non-Javadoc) + * @see org.marketcetera.ui.view.MenuContent#getWeight() + */ + @Override + public int getWeight() + { + return tradeSuggestionsWeight; + } + /* (non-Javadoc) + * @see org.marketcetera.ui.view.MenuContent#getMenuIcon() + */ + @Override + public URL getMenuIcon() + { + return getClass().getClassLoader().getResource("images/light-bulb.svg"); + } + /* (non-Javadoc) + * @see org.marketcetera.ui.view.AbstractContentViewFactory#getViewType() + */ + @Override + protected Class getViewType() + { + return SuggestionsView.class; + } + /* (non-Javadoc) + * @see org.marketcetera.web.view.MenuContent#getAllPermissions() + */ + @Override + public Set getAllPermissions() + { + return requiredPermissions; + } + /** + * permission(s) required to execute strategy session view + */ + private static final Set requiredPermissions = Collections.unmodifiableSet(Sets.newHashSet(TradePermissions.ViewSuggestionsAction)); +} diff --git a/photon/src/main/resources/images/light-bulb.svg b/photon/src/main/resources/images/light-bulb.svg new file mode 100644 index 0000000000..0e1fccec7f --- /dev/null +++ b/photon/src/main/resources/images/light-bulb.svg @@ -0,0 +1,5 @@ + + +light-bulb + + From 7643b0703bbc132156615a105ec91f636e63656a Mon Sep 17 00:00:00 2001 From: Colin DuPlantis Date: Thu, 13 Apr 2023 09:11:03 -0700 Subject: [PATCH 04/10] MATP-1157 Implement Trade Suggestions --- .../marketcetera/admin/HasCurrentUser.java | 15 ++++ .../marketcetera/server/DareApplication.java | 11 +++ .../psql/V115__suggestion_permissions.sql | 3 + .../src/test/cmd_exec/conf/log4j2.xml | 1 + .../view/suggestions/SuggestionsView.java | 6 +- photon/src/main/resources/log4j2.xml | 1 + pom.xml | 16 ++-- .../strategy/StrategyInstance.java | 24 ++++-- .../strategy/StrategyInstanceHolder.java | 7 +- .../strategy/sample/TestStrategy.java | 28 ++++++- .../strategy/StrategyServiceImpl.java | 10 +-- .../rpc/server/TradeRpcService.java | 2 +- .../trade/client/DirectTradeClient.java | 82 ++++++++----------- .../client/DirectTradeClientFactory.java | 12 +-- 14 files changed, 132 insertions(+), 86 deletions(-) create mode 100644 admin/admin-api/src/main/java/org/marketcetera/admin/HasCurrentUser.java create mode 100644 packages/dare-package/src/main/resources/db/migration/psql/V115__suggestion_permissions.sql rename trade/trade-rpc-server/src/main/java/org/marketcetera/{client => trade}/rpc/server/TradeRpcService.java (99%) diff --git a/admin/admin-api/src/main/java/org/marketcetera/admin/HasCurrentUser.java b/admin/admin-api/src/main/java/org/marketcetera/admin/HasCurrentUser.java new file mode 100644 index 0000000000..36a5ae3e6a --- /dev/null +++ b/admin/admin-api/src/main/java/org/marketcetera/admin/HasCurrentUser.java @@ -0,0 +1,15 @@ +package org.marketcetera.admin; + +/* $License$ */ + +/** + * Tagging interface that provides the current user, whatever that means in a given context. + * + * @author Colin DuPlantis + * @version $Id$ + * @since $Release$ + */ +public interface HasCurrentUser + extends HasUser +{ +} diff --git a/packages/dare-package/src/main/java/org/marketcetera/server/DareApplication.java b/packages/dare-package/src/main/java/org/marketcetera/server/DareApplication.java index cfcdc341f6..5102ca6e94 100644 --- a/packages/dare-package/src/main/java/org/marketcetera/server/DareApplication.java +++ b/packages/dare-package/src/main/java/org/marketcetera/server/DareApplication.java @@ -59,6 +59,7 @@ import org.marketcetera.trade.AverageFillPriceFactory; import org.marketcetera.trade.BasicSelector; import org.marketcetera.trade.SimpleAverageFillPriceFactory; +import org.marketcetera.trade.client.DirectTradeClient; import org.marketcetera.trade.event.connector.IncomingTradeMessageBroadcastConnector; import org.marketcetera.trade.event.connector.IncomingTradeMessageConverterConnector; import org.marketcetera.trade.event.connector.IncomingTradeMessagePersistenceConnector; @@ -197,6 +198,16 @@ public DirectMarketDataClient getMarketDataClient() { return new DirectMarketDataClient(); } + /** + * Get the trade client value. + * + * @return a DirectTradeClient value + */ + @Bean + public DirectTradeClient getTradeClient() + { + return new DirectTradeClient(); + } /** * Get the strategy message factory value. * diff --git a/packages/dare-package/src/main/resources/db/migration/psql/V115__suggestion_permissions.sql b/packages/dare-package/src/main/resources/db/migration/psql/V115__suggestion_permissions.sql new file mode 100644 index 0000000000..f03691cd96 --- /dev/null +++ b/packages/dare-package/src/main/resources/db/migration/psql/V115__suggestion_permissions.sql @@ -0,0 +1,3 @@ +insert into metc_permissions values(((select max(id) from metc_permissions)+1),now(),0,'Access to read ltrade suggestions','ViewSuggestionsAction'); +insert into metc_permissions values(((select max(id) from metc_permissions)+1),now(),0,'Access to create ltrade suggestions','SendSuggestionAction'); +insert into metc_roles_permissions select a.id as roles_id,b.id as permissions_id from metc_roles a,metc_permissions b where b.name like '%Suggestion%Action' and (a.name='Trader' or a.name='TraderAdmin'); diff --git a/packages/dare-package/src/test/cmd_exec/conf/log4j2.xml b/packages/dare-package/src/test/cmd_exec/conf/log4j2.xml index bad4a77513..ccaecea738 100644 --- a/packages/dare-package/src/test/cmd_exec/conf/log4j2.xml +++ b/packages/dare-package/src/test/cmd_exec/conf/log4j2.xml @@ -59,6 +59,7 @@ + diff --git a/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java b/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java index 3da29b111e..f415624242 100644 --- a/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java +++ b/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java @@ -16,6 +16,7 @@ import org.marketcetera.ui.service.trade.TradeClientService; import org.marketcetera.ui.strategy.view.DisplayStrategyMessage; import org.marketcetera.ui.view.AbstractContentView; +import org.marketcetera.util.log.SLF4JLoggerProxy; import org.nocrala.tools.texttablefmt.BorderStyle; import org.nocrala.tools.texttablefmt.ShownBorders; import org.nocrala.tools.texttablefmt.Table; @@ -57,8 +58,8 @@ protected void onStart() { tradeClient = serviceManager.getService(TradeClientService.class); mainLayout = new VBox(10); - mainLayout.getChildren().add(suggestionTable); initializeSuggestionTable(); + mainLayout.getChildren().add(suggestionTable); suggestionTable.prefWidthProperty().bind(mainLayout.widthProperty()); suggestionTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); } @@ -141,6 +142,9 @@ private void initializeSuggestionListener() @Override public void receiveSuggestion(Suggestion inSuggestion) { + SLF4JLoggerProxy.trace(SuggestionsView.this, + "Received {}", + inSuggestion); suggestionTable.getItems().add(new DisplaySuggestion(inSuggestion)); } }; diff --git a/photon/src/main/resources/log4j2.xml b/photon/src/main/resources/log4j2.xml index 40eaaafb99..713da27729 100644 --- a/photon/src/main/resources/log4j2.xml +++ b/photon/src/main/resources/log4j2.xml @@ -11,5 +11,6 @@ + diff --git a/pom.xml b/pom.xml index 347fa0321b..88593fec60 100644 --- a/pom.xml +++ b/pom.xml @@ -429,9 +429,9 @@ - info.schnatterer.moby-names-generator - moby-names-generator - 20.10.0-r0 + info.schnatterer.moby-names-generator + moby-names-generator + 20.10.0-r0 org.flywaydb @@ -1129,8 +1129,8 @@ kr.motd.maven os-maven-plugin 1.7.0 - - + + @@ -1151,6 +1151,12 @@ + + ide-build + + ${project.basedir}/target-ide + + release-sign-artifacts diff --git a/strategy/strategy-api/src/main/java/org/marketcetera/strategy/StrategyInstance.java b/strategy/strategy-api/src/main/java/org/marketcetera/strategy/StrategyInstance.java index 559af3046a..0c11fa3bb1 100644 --- a/strategy/strategy-api/src/main/java/org/marketcetera/strategy/StrategyInstance.java +++ b/strategy/strategy-api/src/main/java/org/marketcetera/strategy/StrategyInstance.java @@ -3,6 +3,11 @@ // package org.marketcetera.strategy; +import java.util.Date; + +import org.marketcetera.admin.HasCurrentUser; +import org.marketcetera.core.Preserve; + /* $License$ */ /** @@ -12,8 +17,9 @@ * @version $Id$ * @since $Release$ */ +@Preserve public interface StrategyInstance - extends org.marketcetera.admin.HasUser + extends HasCurrentUser { /** * Get the name value. @@ -66,25 +72,25 @@ public interface StrategyInstance /** * Get the started value. * - * @return a java.util.Date value + * @return a Date value */ - java.util.Date getStarted(); + Date getStarted(); /** * Set the started value. * - * @param inStarted a java.util.Date value + * @param inStarted a Date value */ - void setStarted(java.util.Date inStarted); + void setStarted(Date inStarted); /** * Get the status value. * - * @return an org.marketcetera.strategy.StrategyStatus value + * @return an StrategyStatus value */ - org.marketcetera.strategy.StrategyStatus getStatus(); + StrategyStatus getStatus(); /** * Set the status value. * - * @param inStatus an org.marketcetera.strategy.StrategyStatus value + * @param inStatus an StrategyStatus value */ - void setStatus(org.marketcetera.strategy.StrategyStatus inStatus); + void setStatus(StrategyStatus inStatus); } diff --git a/strategy/strategy-api/src/main/java/org/marketcetera/strategy/StrategyInstanceHolder.java b/strategy/strategy-api/src/main/java/org/marketcetera/strategy/StrategyInstanceHolder.java index 45162dce47..168534daca 100644 --- a/strategy/strategy-api/src/main/java/org/marketcetera/strategy/StrategyInstanceHolder.java +++ b/strategy/strategy-api/src/main/java/org/marketcetera/strategy/StrategyInstanceHolder.java @@ -3,7 +3,7 @@ /* $License$ */ /** - * + * Provides a strategy instance value. * * @author Colin DuPlantis * @version $Id$ @@ -11,5 +11,10 @@ */ public interface StrategyInstanceHolder { + /** + * Get the strategy instance value. + * + * @return a StrategyInstance value + */ StrategyInstance getStrategyInstance(); } diff --git a/strategy/strategy-sample/src/main/java/org/marketcetera/strategy/sample/TestStrategy.java b/strategy/strategy-sample/src/main/java/org/marketcetera/strategy/sample/TestStrategy.java index a9a580f201..a022be9411 100644 --- a/strategy/strategy-sample/src/main/java/org/marketcetera/strategy/sample/TestStrategy.java +++ b/strategy/strategy-sample/src/main/java/org/marketcetera/strategy/sample/TestStrategy.java @@ -3,7 +3,6 @@ import java.math.BigDecimal; import java.security.SecureRandom; import java.util.Random; -import java.util.UUID; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -20,6 +19,7 @@ import org.marketcetera.marketdata.MarketDataRequest; import org.marketcetera.marketdata.MarketDataRequestBuilder; import org.marketcetera.strategy.StrategyClient; +import org.marketcetera.trade.Equity; import org.marketcetera.trade.Factory; import org.marketcetera.trade.Instrument; import org.marketcetera.trade.OrderSingle; @@ -92,6 +92,7 @@ public void receiveMarketData(Event inEvent) } } }); + sendSuggestion(); } /** * Stop the object. @@ -108,6 +109,11 @@ public void stop() } catch (Exception ignored) {} } } + /** + * Create an order suggestion using the cached market data. + * + * @param inCacheElement a MarketDataCacheElement value + */ private void issueSuggestion(MarketDataCacheElement inCacheElement) { TopOfBookEvent topOfBook = (TopOfBookEvent)inCacheElement.getSnapshot(Content.TOP_OF_BOOK); @@ -129,7 +135,7 @@ private void issueSuggestion(MarketDataCacheElement inCacheElement) return; } OrderSingleSuggestion orderSingleSuggestion = Factory.getInstance().createOrderSingleSuggestion(); - orderSingleSuggestion.setIdentifier("Suggestion created by test strategy: " + UUID.randomUUID().toString()); + orderSingleSuggestion.setIdentifier("Test Strategy"); orderSingleSuggestion.setScore(new BigDecimal(random.nextDouble())); OrderSingle orderSingle = Factory.getInstance().createOrderSingle(); orderSingle.setInstrument(quote.getInstrument()); @@ -137,7 +143,23 @@ private void issueSuggestion(MarketDataCacheElement inCacheElement) orderSingle.setPegToMidpoint(true); orderSingle.setQuantity(new BigDecimal(10*(random.nextInt(10)+1))); orderSingle.setSide(side); -// tradeClient.sendSuggestion(orderSingleSuggestion); + orderSingleSuggestion.setOrder(orderSingle); + tradeClient.sendOrderSuggestion(orderSingleSuggestion); + } + private void sendSuggestion() + { + OrderSingleSuggestion orderSingleSuggestion = Factory.getInstance().createOrderSingleSuggestion(); + orderSingleSuggestion.setIdentifier("Test Strategy"); + orderSingleSuggestion.setScore(new BigDecimal(random.nextDouble())); + OrderSingle orderSingle = Factory.getInstance().createOrderSingle(); + orderSingle.setInstrument(new Equity("AAPL")); + orderSingle.setOrderType(OrderType.Limit); + orderSingle.setPegToMidpoint(true); + orderSingle.setQuantity(new BigDecimal(10*(random.nextInt(10)+1))); + orderSingle.setSide(Side.Buy); + orderSingle.setPrice(new BigDecimal(50.00)); + orderSingleSuggestion.setOrder(orderSingle); + tradeClient.sendOrderSuggestion(orderSingleSuggestion); } /** * caches market data diff --git a/strategy/strategy-server/src/main/java/org/marketcetera/strategy/StrategyServiceImpl.java b/strategy/strategy-server/src/main/java/org/marketcetera/strategy/StrategyServiceImpl.java index 53b44cb80f..6fd84dea0c 100644 --- a/strategy/strategy-server/src/main/java/org/marketcetera/strategy/StrategyServiceImpl.java +++ b/strategy/strategy-server/src/main/java/org/marketcetera/strategy/StrategyServiceImpl.java @@ -57,7 +57,6 @@ import org.marketcetera.strategy.events.SimpleStrategyUploadSucceededEvent; import org.marketcetera.strategy.events.StrategyEvent; import org.marketcetera.trade.client.DirectTradeClient; -import org.marketcetera.trade.client.TradeClient; import org.marketcetera.util.log.SLF4JLoggerProxy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -683,11 +682,10 @@ public StrategyInstance getStrategyInstance() strategyUsername); beanFactory.registerSingleton(StrategyClient.class.getCanonicalName(), strategyClient); - // create a special trade client just for this strategy - DirectTradeClient tradeClient = new DirectTradeClient(newContext, - strategyUsername); - beanFactory.registerSingleton(TradeClient.class.getCanonicalName(), - tradeClient); + // get the trade client that we expect to be defined in the parent context + DirectTradeClient tradeClient = applicationContext.getBean(DirectTradeClient.class); + // define the current user, which is the user that owns the strategy + tradeClient.setCurrentUser(strategyInstance); // refresh the context, which allows it to prepare to use the strategy JAR newContext.refresh(); // start the context diff --git a/trade/trade-rpc-server/src/main/java/org/marketcetera/client/rpc/server/TradeRpcService.java b/trade/trade-rpc-server/src/main/java/org/marketcetera/trade/rpc/server/TradeRpcService.java similarity index 99% rename from trade/trade-rpc-server/src/main/java/org/marketcetera/client/rpc/server/TradeRpcService.java rename to trade/trade-rpc-server/src/main/java/org/marketcetera/trade/rpc/server/TradeRpcService.java index f3e488c3d1..a5701c0628 100644 --- a/trade/trade-rpc-server/src/main/java/org/marketcetera/client/rpc/server/TradeRpcService.java +++ b/trade/trade-rpc-server/src/main/java/org/marketcetera/trade/rpc/server/TradeRpcService.java @@ -1,4 +1,4 @@ -package org.marketcetera.client.rpc.server; +package org.marketcetera.trade.rpc.server; import java.math.BigDecimal; import java.time.Instant; diff --git a/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClient.java b/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClient.java index d49597e491..2f0b88d093 100644 --- a/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClient.java +++ b/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClient.java @@ -6,14 +6,14 @@ import java.util.List; import java.util.Map; +import javax.annotation.PostConstruct; + import org.apache.commons.lang.Validate; import org.apache.commons.lang.exception.ExceptionUtils; -import org.apache.commons.lang3.StringUtils; +import org.marketcetera.admin.HasCurrentUser; import org.marketcetera.admin.User; -import org.marketcetera.admin.service.UserService; import org.marketcetera.brokers.service.BrokerService; import org.marketcetera.core.ClientStatusListener; -import org.marketcetera.core.PlatformServices; import org.marketcetera.core.position.PositionKey; import org.marketcetera.event.HasFIXMessage; import org.marketcetera.fix.ActiveFixSession; @@ -41,7 +41,7 @@ import org.marketcetera.trade.service.ReportService; import org.marketcetera.trade.service.TradeService; import org.marketcetera.util.log.SLF4JLoggerProxy; -import org.springframework.context.ApplicationContext; +import org.springframework.beans.factory.annotation.Autowired; import com.google.common.collect.Lists; @@ -61,25 +61,12 @@ public class DirectTradeClient * @see org.marketcetera.core.BaseClient#start() */ @Override + @PostConstruct public void start() throws Exception { SLF4JLoggerProxy.info(this, "Starting direct trade client"); - Validate.notNull(applicationContext); - userService = applicationContext.getBean(UserService.class); - orderSummaryService = applicationContext.getBean(OrderSummaryService.class); - tradeMessagePublisher = applicationContext.getBean(TradeMessagePublisher.class); - reportService = applicationContext.getBean(ReportService.class); - tradeService = applicationContext.getBean(TradeService.class); - brokerService = applicationContext.getBean(BrokerService.class); - symbolResolverService = applicationContext.getBean(SymbolResolverService.class); - SLF4JLoggerProxy.debug(this, - "Direct client {} owned by user {}", - clientId, - username); - user = userService.findByName(username); - Validate.notNull(user); running = true; } /* (non-Javadoc) @@ -191,6 +178,7 @@ public List sendOrders(List inOrders) @Override public SendOrderResponse sendOrder(Order inOrder) { + validateUser(); SLF4JLoggerProxy.info(this, "{} submitting outgoing {}", user.getName(), @@ -236,6 +224,7 @@ public BigDecimal getPositionAsOf(Date inDate, @Override public Map,BigDecimal> getAllPositionsAsOf(Date inDate) { + validateUser(); return reportService.getAllPositionsAsOf(user, inDate); } @@ -246,6 +235,7 @@ public Map,BigDecimal> getAllPositionsAsOf(Dat public Map,BigDecimal> getOptionPositionsAsOf(Date inDate, String... inRootSymbols) { + validateUser(); return reportService.getOptionPositionsAsOf(user, inDate, inRootSymbols); @@ -257,6 +247,7 @@ public Map,BigDecimal> getOptionPositionsAsOf(Date inDate, public void addReport(HasFIXMessage inReport, BrokerID inBrokerID) { + validateUser(); reportService.addReport(inReport, inBrokerID, user.getUserID()); @@ -342,44 +333,34 @@ public void removeClientStatusListener(ClientStatusListener inListener) // no-op } /** - * Get the applicationContext value. + * Get the currentUser value. * - * @return an ApplicationContext value + * @return a HasCurrentUser value */ - public ApplicationContext getApplicationContext() + public HasCurrentUser getCurrentUser() { - return applicationContext; + return currentUser; } /** - * Sets the applicationContext value. + * Sets the currentUser value. * - * @param inApplicationContext an ApplicationContext value + * @param inCurrentUser a HasCurrentUser value */ - public void setApplicationContext(ApplicationContext inApplicationContext) + public void setCurrentUser(HasCurrentUser inCurrentUser) { - applicationContext = inApplicationContext; + currentUser = inCurrentUser; } /** - * Create a new DirectTradeClient instance. - * - * @param inApplicationContext an ApplicationContext value - * @param inUsername a String value + * Validate that the user is set. */ - public DirectTradeClient(ApplicationContext inApplicationContext, - String inUsername) + private void validateUser() { - applicationContext = inApplicationContext; - username = StringUtils.trimToNull(inUsername); - Validate.notNull(username); + if(user == null) { + Validate.notNull(currentUser, + "Must provide a HasCurrentUser to " + getClass().getSimpleName()); + user = currentUser.getUser(); + } } - /** - * provides access to the application context - */ - private ApplicationContext applicationContext; - /** - * name of user - */ - private final String username; /** * user which owns the activity of this client */ @@ -389,37 +370,40 @@ public DirectTradeClient(ApplicationContext inApplicationContext, */ private boolean running = false; /** - * provides access to user services + * provides access to the current user */ - private UserService userService; + @Autowired(required=false) + private HasCurrentUser currentUser; /** * provides access to broker services */ + @Autowired private BrokerService brokerService; /** * provides access to trade messages */ + @Autowired private TradeMessagePublisher tradeMessagePublisher; /** * provides access to report services */ + @Autowired private ReportService reportService; /** * provides access to trade services */ + @Autowired private TradeService tradeService; /** * provides access to order summary services */ + @Autowired private OrderSummaryService orderSummaryService; /** * resolves symbols */ + @Autowired private SymbolResolverService symbolResolverService; - /** - * uniquely identifies this client - */ - private final String clientId = PlatformServices.generateId(); /** * order id for unknown orders */ diff --git a/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClientFactory.java b/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClientFactory.java index 0263fde722..0feb1d32fd 100644 --- a/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClientFactory.java +++ b/trade/trade-server/src/main/java/org/marketcetera/trade/client/DirectTradeClientFactory.java @@ -1,8 +1,5 @@ package org.marketcetera.trade.client; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; - /* $License$ */ /** @@ -21,13 +18,6 @@ public class DirectTradeClientFactory @Override public TradeClient create(DirectTradeClientParameters inParameterClazz) { - DirectTradeClient tradeClient = new DirectTradeClient(applicationContext, - inParameterClazz.getUsername()); - return tradeClient; + return new DirectTradeClient(); } - /** - * provides access to the application context - */ - @Autowired - private ApplicationContext applicationContext; } From 55d5dfaea014b02153aae93dca6e5a73c721ffcc Mon Sep 17 00:00:00 2001 From: Colin DuPlantis Date: Thu, 13 Apr 2023 09:59:38 -0700 Subject: [PATCH 05/10] MATP-1160 Create Maven profile for IDE builds --- .gitignore | 1 + admin/admin-api/.gitignore | 1 + admin/admin-core/.gitignore | 1 + admin/admin-rest-server/.gitignore | 1 + admin/admin-rpc-client/.gitignore | 1 + admin/admin-rpc-core/.gitignore | 1 + admin/admin-rpc-server/.gitignore | 1 + admin/admin-server/.gitignore | 1 + cluster/cluster-api/.gitignore | 1 + cluster/cluster-core/.gitignore | 1 + cluster/cluster-rpc-client/.gitignore | 1 + cluster/cluster-rpc-core/.gitignore | 1 + cluster/cluster-rpc-server/.gitignore | 1 + cluster/cluster-simple/.gitignore | 1 + core/.gitignore | 1 + dare/.gitignore | 1 + dataflow/dataflow-api/.gitignore | 1 + dataflow/dataflow-core/.gitignore | 1 + dataflow/dataflow-rpc-client/.gitignore | 1 + dataflow/dataflow-rpc-core/.gitignore | 1 + dataflow/dataflow-rpc-server/.gitignore | 1 + dataflow/dataflow-server/.gitignore | 1 + eventbus/eventbus-api/.gitignore | 1 + eventbus/eventbus-core/.gitignore | 1 + eventbus/eventbus-guava/.gitignore | 1 + eventbus/eventbus-server/.gitignore | 1 + fix/fix-acceptor/.gitignore | 1 + fix/fix-api/.gitignore | 1 + fix/fix-core/.gitignore | 1 + fix/fix-rpc-client/.gitignore | 1 + fix/fix-rpc-core/.gitignore | 1 + fix/fix-rpc-server/.gitignore | 1 + fix/fix-server/.gitignore | 1 + fork/commons-csv/.gitignore | 1 + fork/commons-i18n/.gitignore | 1 + marketdata/marketdata-api/.gitignore | 1 + marketdata/marketdata-core/.gitignore | 1 + marketdata/marketdata-rpc-client/.gitignore | 1 + marketdata/marketdata-rpc-core/.gitignore | 1 + marketdata/marketdata-rpc-server/.gitignore | 1 + marketdata/marketdata-server/.gitignore | 1 + metrics/metrics-db/.gitignore | 1 + metrics/metrics-log/.gitignore | 1 + modules/cep/esper/.gitignore | 1 + modules/cep/system/.gitignore | 1 + modules/machine-learning/tensorflow/.gitignore | 1 + modules/marketdata/bogus/.gitignore | 1 + modules/marketdata/csv/.gitignore | 1 + modules/marketdata/marketdata-exsim/.gitignore | 1 + modules/marketdata/marketdata-manual/.gitignore | 1 + modules/marketdata/marketdata-recorder/.gitignore | 1 + modules/marketdata/yahoo/.gitignore | 1 + modules/misc/.gitignore | 1 + packages/dare-package/.gitignore | 1 + .../src/main/java/org/marketcetera/server/DareApplication.java | 2 +- packages/docker-package/.gitignore | 1 + photon/.gitignore | 1 + rpc-core/.gitignore | 1 + strategy/strategy-api/.gitignore | 1 + strategy/strategy-core/.gitignore | 1 + strategy/strategy-rpc-client/.gitignore | 1 + strategy/strategy-rpc-core/.gitignore | 1 + strategy/strategy-rpc-server/.gitignore | 1 + strategy/strategy-sample/.gitignore | 1 + strategy/strategy-server/.gitignore | 1 + trade/trade-api/.gitignore | 1 + trade/trade-core/.gitignore | 1 + trade/trade-rpc-client/.gitignore | 1 + trade/trade-rpc-core/.gitignore | 1 + trade/trade-rpc-server/.gitignore | 1 + trade/trade-server/.gitignore | 1 + util-test/.gitignore | 1 + util/.gitignore | 1 + 73 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 admin/admin-api/.gitignore create mode 100644 admin/admin-core/.gitignore create mode 100644 admin/admin-rest-server/.gitignore create mode 100644 admin/admin-rpc-client/.gitignore create mode 100644 admin/admin-rpc-core/.gitignore create mode 100644 admin/admin-rpc-server/.gitignore create mode 100644 admin/admin-server/.gitignore create mode 100644 cluster/cluster-api/.gitignore create mode 100644 cluster/cluster-core/.gitignore create mode 100644 cluster/cluster-rpc-client/.gitignore create mode 100644 cluster/cluster-rpc-core/.gitignore create mode 100644 cluster/cluster-rpc-server/.gitignore create mode 100644 cluster/cluster-simple/.gitignore create mode 100644 core/.gitignore create mode 100644 dare/.gitignore create mode 100644 dataflow/dataflow-api/.gitignore create mode 100644 dataflow/dataflow-core/.gitignore create mode 100644 dataflow/dataflow-rpc-client/.gitignore create mode 100644 dataflow/dataflow-rpc-core/.gitignore create mode 100644 dataflow/dataflow-rpc-server/.gitignore create mode 100644 dataflow/dataflow-server/.gitignore create mode 100644 eventbus/eventbus-api/.gitignore create mode 100644 eventbus/eventbus-core/.gitignore create mode 100644 eventbus/eventbus-guava/.gitignore create mode 100644 eventbus/eventbus-server/.gitignore create mode 100644 fix/fix-acceptor/.gitignore create mode 100644 fix/fix-api/.gitignore create mode 100644 fix/fix-core/.gitignore create mode 100644 fix/fix-rpc-client/.gitignore create mode 100644 fix/fix-rpc-core/.gitignore create mode 100644 fix/fix-rpc-server/.gitignore create mode 100644 fix/fix-server/.gitignore create mode 100644 fork/commons-csv/.gitignore create mode 100644 fork/commons-i18n/.gitignore create mode 100644 marketdata/marketdata-api/.gitignore create mode 100644 marketdata/marketdata-core/.gitignore create mode 100644 marketdata/marketdata-rpc-client/.gitignore create mode 100644 marketdata/marketdata-rpc-core/.gitignore create mode 100644 marketdata/marketdata-rpc-server/.gitignore create mode 100644 marketdata/marketdata-server/.gitignore create mode 100644 metrics/metrics-db/.gitignore create mode 100644 metrics/metrics-log/.gitignore create mode 100644 modules/cep/esper/.gitignore create mode 100644 modules/cep/system/.gitignore create mode 100644 modules/machine-learning/tensorflow/.gitignore create mode 100644 modules/marketdata/bogus/.gitignore create mode 100644 modules/marketdata/csv/.gitignore create mode 100644 modules/marketdata/marketdata-exsim/.gitignore create mode 100644 modules/marketdata/marketdata-manual/.gitignore create mode 100644 modules/marketdata/marketdata-recorder/.gitignore create mode 100644 modules/marketdata/yahoo/.gitignore create mode 100644 modules/misc/.gitignore create mode 100644 packages/dare-package/.gitignore create mode 100644 packages/docker-package/.gitignore create mode 100644 photon/.gitignore create mode 100644 rpc-core/.gitignore create mode 100644 strategy/strategy-api/.gitignore create mode 100644 strategy/strategy-core/.gitignore create mode 100644 strategy/strategy-rpc-client/.gitignore create mode 100644 strategy/strategy-rpc-core/.gitignore create mode 100644 strategy/strategy-rpc-server/.gitignore create mode 100644 strategy/strategy-sample/.gitignore create mode 100644 strategy/strategy-server/.gitignore create mode 100644 trade/trade-api/.gitignore create mode 100644 trade/trade-core/.gitignore create mode 100644 trade/trade-rpc-client/.gitignore create mode 100644 trade/trade-rpc-core/.gitignore create mode 100644 trade/trade-rpc-server/.gitignore create mode 100644 trade/trade-server/.gitignore create mode 100644 util-test/.gitignore create mode 100644 util/.gitignore diff --git a/.gitignore b/.gitignore index 5e4f0b1275..5ab7a2d9fa 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ hs_err_pid* # Maven files target +target-ide # Eclipse files .classpath diff --git a/admin/admin-api/.gitignore b/admin/admin-api/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/admin/admin-api/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/admin/admin-core/.gitignore b/admin/admin-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/admin/admin-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/admin/admin-rest-server/.gitignore b/admin/admin-rest-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/admin/admin-rest-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/admin/admin-rpc-client/.gitignore b/admin/admin-rpc-client/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/admin/admin-rpc-client/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/admin/admin-rpc-core/.gitignore b/admin/admin-rpc-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/admin/admin-rpc-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/admin/admin-rpc-server/.gitignore b/admin/admin-rpc-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/admin/admin-rpc-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/admin/admin-server/.gitignore b/admin/admin-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/admin/admin-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/cluster/cluster-api/.gitignore b/cluster/cluster-api/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/cluster/cluster-api/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/cluster/cluster-core/.gitignore b/cluster/cluster-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/cluster/cluster-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/cluster/cluster-rpc-client/.gitignore b/cluster/cluster-rpc-client/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/cluster/cluster-rpc-client/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/cluster/cluster-rpc-core/.gitignore b/cluster/cluster-rpc-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/cluster/cluster-rpc-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/cluster/cluster-rpc-server/.gitignore b/cluster/cluster-rpc-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/cluster/cluster-rpc-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/cluster/cluster-simple/.gitignore b/cluster/cluster-simple/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/cluster/cluster-simple/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/dare/.gitignore b/dare/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/dare/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/dataflow/dataflow-api/.gitignore b/dataflow/dataflow-api/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/dataflow/dataflow-api/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/dataflow/dataflow-core/.gitignore b/dataflow/dataflow-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/dataflow/dataflow-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/dataflow/dataflow-rpc-client/.gitignore b/dataflow/dataflow-rpc-client/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/dataflow/dataflow-rpc-client/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/dataflow/dataflow-rpc-core/.gitignore b/dataflow/dataflow-rpc-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/dataflow/dataflow-rpc-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/dataflow/dataflow-rpc-server/.gitignore b/dataflow/dataflow-rpc-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/dataflow/dataflow-rpc-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/dataflow/dataflow-server/.gitignore b/dataflow/dataflow-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/dataflow/dataflow-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/eventbus/eventbus-api/.gitignore b/eventbus/eventbus-api/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/eventbus/eventbus-api/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/eventbus/eventbus-core/.gitignore b/eventbus/eventbus-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/eventbus/eventbus-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/eventbus/eventbus-guava/.gitignore b/eventbus/eventbus-guava/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/eventbus/eventbus-guava/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/eventbus/eventbus-server/.gitignore b/eventbus/eventbus-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/eventbus/eventbus-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/fix/fix-acceptor/.gitignore b/fix/fix-acceptor/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/fix/fix-acceptor/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/fix/fix-api/.gitignore b/fix/fix-api/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/fix/fix-api/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/fix/fix-core/.gitignore b/fix/fix-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/fix/fix-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/fix/fix-rpc-client/.gitignore b/fix/fix-rpc-client/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/fix/fix-rpc-client/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/fix/fix-rpc-core/.gitignore b/fix/fix-rpc-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/fix/fix-rpc-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/fix/fix-rpc-server/.gitignore b/fix/fix-rpc-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/fix/fix-rpc-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/fix/fix-server/.gitignore b/fix/fix-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/fix/fix-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/fork/commons-csv/.gitignore b/fork/commons-csv/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/fork/commons-csv/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/fork/commons-i18n/.gitignore b/fork/commons-i18n/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/fork/commons-i18n/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/marketdata/marketdata-api/.gitignore b/marketdata/marketdata-api/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/marketdata/marketdata-api/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/marketdata/marketdata-core/.gitignore b/marketdata/marketdata-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/marketdata/marketdata-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/marketdata/marketdata-rpc-client/.gitignore b/marketdata/marketdata-rpc-client/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/marketdata/marketdata-rpc-client/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/marketdata/marketdata-rpc-core/.gitignore b/marketdata/marketdata-rpc-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/marketdata/marketdata-rpc-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/marketdata/marketdata-rpc-server/.gitignore b/marketdata/marketdata-rpc-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/marketdata/marketdata-rpc-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/marketdata/marketdata-server/.gitignore b/marketdata/marketdata-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/marketdata/marketdata-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/metrics/metrics-db/.gitignore b/metrics/metrics-db/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/metrics/metrics-db/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/metrics/metrics-log/.gitignore b/metrics/metrics-log/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/metrics/metrics-log/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/modules/cep/esper/.gitignore b/modules/cep/esper/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/modules/cep/esper/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/modules/cep/system/.gitignore b/modules/cep/system/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/modules/cep/system/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/modules/machine-learning/tensorflow/.gitignore b/modules/machine-learning/tensorflow/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/modules/machine-learning/tensorflow/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/modules/marketdata/bogus/.gitignore b/modules/marketdata/bogus/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/modules/marketdata/bogus/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/modules/marketdata/csv/.gitignore b/modules/marketdata/csv/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/modules/marketdata/csv/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/modules/marketdata/marketdata-exsim/.gitignore b/modules/marketdata/marketdata-exsim/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/modules/marketdata/marketdata-exsim/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/modules/marketdata/marketdata-manual/.gitignore b/modules/marketdata/marketdata-manual/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/modules/marketdata/marketdata-manual/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/modules/marketdata/marketdata-recorder/.gitignore b/modules/marketdata/marketdata-recorder/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/modules/marketdata/marketdata-recorder/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/modules/marketdata/yahoo/.gitignore b/modules/marketdata/yahoo/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/modules/marketdata/yahoo/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/modules/misc/.gitignore b/modules/misc/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/modules/misc/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/packages/dare-package/.gitignore b/packages/dare-package/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/packages/dare-package/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/packages/dare-package/src/main/java/org/marketcetera/server/DareApplication.java b/packages/dare-package/src/main/java/org/marketcetera/server/DareApplication.java index 5102ca6e94..387f7a0b96 100644 --- a/packages/dare-package/src/main/java/org/marketcetera/server/DareApplication.java +++ b/packages/dare-package/src/main/java/org/marketcetera/server/DareApplication.java @@ -23,7 +23,6 @@ import org.marketcetera.admin.user.PersistentUserFactory; import org.marketcetera.brokers.BrokerSelector; import org.marketcetera.brokers.service.FixSessionProvider; -import org.marketcetera.client.rpc.server.TradeRpcService; import org.marketcetera.cluster.ClusterDataFactory; import org.marketcetera.cluster.SimpleClusterDataFactory; import org.marketcetera.cluster.rpc.ClusterRpcService; @@ -66,6 +65,7 @@ import org.marketcetera.trade.event.connector.OrderConverterConnector; import org.marketcetera.trade.event.connector.OutgoingMessageCachingConnector; import org.marketcetera.trade.event.connector.OutgoingMessagePersistenceConnector; +import org.marketcetera.trade.rpc.server.TradeRpcService; import org.marketcetera.trade.service.MessageOwnerService; import org.marketcetera.trade.service.impl.MessageOwnerServiceImpl; import org.marketcetera.util.log.SLF4JLoggerProxy; diff --git a/packages/docker-package/.gitignore b/packages/docker-package/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/packages/docker-package/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/photon/.gitignore b/photon/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/photon/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/rpc-core/.gitignore b/rpc-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/rpc-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/strategy/strategy-api/.gitignore b/strategy/strategy-api/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/strategy/strategy-api/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/strategy/strategy-core/.gitignore b/strategy/strategy-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/strategy/strategy-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/strategy/strategy-rpc-client/.gitignore b/strategy/strategy-rpc-client/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/strategy/strategy-rpc-client/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/strategy/strategy-rpc-core/.gitignore b/strategy/strategy-rpc-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/strategy/strategy-rpc-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/strategy/strategy-rpc-server/.gitignore b/strategy/strategy-rpc-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/strategy/strategy-rpc-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/strategy/strategy-sample/.gitignore b/strategy/strategy-sample/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/strategy/strategy-sample/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/strategy/strategy-server/.gitignore b/strategy/strategy-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/strategy/strategy-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/trade/trade-api/.gitignore b/trade/trade-api/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/trade/trade-api/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/trade/trade-core/.gitignore b/trade/trade-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/trade/trade-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/trade/trade-rpc-client/.gitignore b/trade/trade-rpc-client/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/trade/trade-rpc-client/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/trade/trade-rpc-core/.gitignore b/trade/trade-rpc-core/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/trade/trade-rpc-core/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/trade/trade-rpc-server/.gitignore b/trade/trade-rpc-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/trade/trade-rpc-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/trade/trade-server/.gitignore b/trade/trade-server/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/trade/trade-server/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/util-test/.gitignore b/util-test/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/util-test/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/util/.gitignore b/util/.gitignore new file mode 100644 index 0000000000..eacf31a678 --- /dev/null +++ b/util/.gitignore @@ -0,0 +1 @@ +/target-ide/ From 5e312bf1f4779c0e559d7b4cfdbf89cc771bbbaa Mon Sep 17 00:00:00 2001 From: Colin DuPlantis Date: Thu, 13 Apr 2023 11:04:38 -0700 Subject: [PATCH 06/10] MATP-1160 Create Maven profile for IDE builds --- .../src/test/cmd_exec/conf/log4j2.xml | 1 - .../org/marketcetera/ui/PhotonServices.java | 31 ++++ .../marketdata/view/MarketDataDetailView.java | 4 +- .../marketdata/view/MarketDataListView.java | 6 +- .../event/SuggestionEvent.java} | 12 +- .../view/suggestions/DisplaySuggestion.java | 22 +++ .../view/suggestions/SuggestionsView.java | 170 +++++++++++++++--- photon/src/main/resources/log4j2.xml | 1 - .../trading/rpc/TradeRpcClient.java | 2 + 9 files changed, 211 insertions(+), 38 deletions(-) rename photon/src/main/java/org/marketcetera/ui/{marketdata/event/MarketDataSuggestionEvent.java => trade/event/SuggestionEvent.java} (81%) diff --git a/packages/dare-package/src/test/cmd_exec/conf/log4j2.xml b/packages/dare-package/src/test/cmd_exec/conf/log4j2.xml index ccaecea738..bad4a77513 100644 --- a/packages/dare-package/src/test/cmd_exec/conf/log4j2.xml +++ b/packages/dare-package/src/test/cmd_exec/conf/log4j2.xml @@ -59,7 +59,6 @@ - diff --git a/photon/src/main/java/org/marketcetera/ui/PhotonServices.java b/photon/src/main/java/org/marketcetera/ui/PhotonServices.java index d42d0fb48f..6b3212b558 100644 --- a/photon/src/main/java/org/marketcetera/ui/PhotonServices.java +++ b/photon/src/main/java/org/marketcetera/ui/PhotonServices.java @@ -227,6 +227,37 @@ protected void updateItem(BigDecimal inItem, }; return tableCell; } + /** + * Create a TableCell BigDecimal implementation that is rendered with the given scale. + * + * @param the type of the row item in the table + * @param inTableColumn a TableColumn<T,BigDecimal value + * @param inPreferredScale an int value + * @param inMaxScale an int value + * @return a TableCell<T,BigDecimal> value + */ + public static TableCell renderNumberCell(TableColumn inTableColumn, + int inPreferredScale, + int inMaxScale) + { + TableCell tableCell = new TableCell<>() { + @Override + protected void updateItem(BigDecimal inItem, + boolean isEmpty) + { + super.updateItem(inItem, + isEmpty); + this.setText(null); + this.setGraphic(null); + if(!isEmpty && inItem != null){ + this.setText(BigDecimalUtil.renderDecimal(inItem, + inPreferredScale, + inMaxScale)); + } + } + }; + return tableCell; + } public static TableCell renderCurrencyCell(TableColumn inTableColumn) { TableCell tableCell = new TableCell<>() { diff --git a/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataDetailView.java b/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataDetailView.java index a656b9f08a..871a0044d8 100644 --- a/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataDetailView.java +++ b/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataDetailView.java @@ -24,9 +24,9 @@ import org.marketcetera.ui.PhotonServices; import org.marketcetera.ui.events.NewWindowEvent; import org.marketcetera.ui.marketdata.event.MarketDataDetailEvent; -import org.marketcetera.ui.marketdata.event.MarketDataSuggestionEvent; import org.marketcetera.ui.marketdata.service.MarketDataClientService; import org.marketcetera.ui.service.trade.TradeClientService; +import org.marketcetera.ui.trade.event.SuggestionEvent; import org.marketcetera.ui.view.AbstractContentView; import org.marketcetera.ui.view.ContentView; import org.marketcetera.util.log.SLF4JLoggerProxy; @@ -313,7 +313,7 @@ private void buyOrSellAction(Side inSide, suggestion.setIdentifier("Market Data List View Action"); suggestion.setScore(BigDecimal.ONE); suggestion.setOrder(orderSingle); - uiMessageService.post(new MarketDataSuggestionEvent(inSide.name() + " " + marketDataInstrument.getSymbol(), + uiMessageService.post(new SuggestionEvent(inSide.name() + " " + marketDataInstrument.getSymbol(), suggestion)); } /** diff --git a/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataListView.java b/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataListView.java index 645b2d9b42..97e5b9f5e3 100644 --- a/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataListView.java +++ b/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataListView.java @@ -24,9 +24,9 @@ import org.marketcetera.ui.events.NewWindowEvent; import org.marketcetera.ui.events.NotificationEvent; import org.marketcetera.ui.marketdata.event.MarketDataDetailEvent; -import org.marketcetera.ui.marketdata.event.MarketDataSuggestionEvent; import org.marketcetera.ui.marketdata.service.MarketDataClientService; import org.marketcetera.ui.service.trade.TradeClientService; +import org.marketcetera.ui.trade.event.SuggestionEvent; import org.marketcetera.ui.view.AbstractContentView; import org.marketcetera.ui.view.ContentView; import org.marketcetera.util.log.SLF4JLoggerProxy; @@ -425,8 +425,8 @@ private void buyOrSellAction(MarketDataItem inSelectedItem, suggestion.setIdentifier("Market Data List View Action"); suggestion.setScore(BigDecimal.ONE); suggestion.setOrder(orderSingle); - uiMessageService.post(new MarketDataSuggestionEvent(inSide.name() + " " + inSelectedItem.symbolProperty().get(), - suggestion)); + uiMessageService.post(new SuggestionEvent(inSide.name() + " " + inSelectedItem.symbolProperty().get(), + suggestion)); } /** * Prepare a nice, human-readable rendering of the given market data item. diff --git a/photon/src/main/java/org/marketcetera/ui/marketdata/event/MarketDataSuggestionEvent.java b/photon/src/main/java/org/marketcetera/ui/trade/event/SuggestionEvent.java similarity index 81% rename from photon/src/main/java/org/marketcetera/ui/marketdata/event/MarketDataSuggestionEvent.java rename to photon/src/main/java/org/marketcetera/ui/trade/event/SuggestionEvent.java index 9f4e0a4dae..979fc62912 100644 --- a/photon/src/main/java/org/marketcetera/ui/marketdata/event/MarketDataSuggestionEvent.java +++ b/photon/src/main/java/org/marketcetera/ui/trade/event/SuggestionEvent.java @@ -1,4 +1,4 @@ -package org.marketcetera.ui.marketdata.event; +package org.marketcetera.ui.trade.event; import org.marketcetera.trade.HasSuggestion; import org.marketcetera.trade.Suggestion; @@ -9,13 +9,13 @@ /* $License$ */ /** - * Indicates that a trade suggestion has been triggered from market data. + * Indicates that a trade suggestion has been triggered. * * @author Colin DuPlantis * @version $Id$ * @since $Release$ */ -public class MarketDataSuggestionEvent +public class SuggestionEvent implements NewWindowEvent,HasSuggestion { /* (non-Javadoc) @@ -43,13 +43,13 @@ public Class getViewFactoryType() return OrderTicketViewFactory.class; } /** - * Create a new MarketDataSuggestionEvent instance. + * Create a new SuggestionEvent instance. * * @param inTitle a String value * @param inSuggestion a Suggestion value */ - public MarketDataSuggestionEvent(String inTitle, - Suggestion inSuggestion) + public SuggestionEvent(String inTitle, + Suggestion inSuggestion) { title = inTitle; suggestion = inSuggestion; diff --git a/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/DisplaySuggestion.java b/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/DisplaySuggestion.java index e7483b50c9..98715c73b4 100644 --- a/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/DisplaySuggestion.java +++ b/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/DisplaySuggestion.java @@ -36,6 +36,7 @@ public class DisplaySuggestion */ public DisplaySuggestion(Suggestion inSuggestion) { + sourceProperty.set(inSuggestion); identifierProperty.set(inSuggestion.getIdentifier()); scoreProperty.set(inSuggestion.getScore()); if(inSuggestion instanceof OrderSingleSuggestion) { @@ -138,6 +139,23 @@ public ReadOnlyObjectProperty timestampProperty() { return timestampProperty; } + /** + * Get the sourceProperty value. + * + * @return a ReadOnlyObjectProperty value + */ + public ReadOnlyObjectProperty sourceProperty() + { + return sourceProperty; + } + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + return sourceProperty.get().toString(); + } /** * identifier property value */ @@ -170,4 +188,8 @@ public ReadOnlyObjectProperty timestampProperty() * timestamp property value */ private final ObjectProperty timestampProperty = new SimpleObjectProperty<>(); + /** + * original source suggestion property value + */ + private final ObjectProperty sourceProperty = new SimpleObjectProperty<>(); } diff --git a/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java b/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java index f415624242..2b7bdb4cf1 100644 --- a/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java +++ b/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java @@ -5,16 +5,18 @@ import java.util.Date; import java.util.Properties; +import org.marketcetera.core.BigDecimalUtil; import org.marketcetera.core.PlatformServices; import org.marketcetera.trade.Instrument; import org.marketcetera.trade.OrderType; import org.marketcetera.trade.Side; import org.marketcetera.trade.Suggestion; import org.marketcetera.trade.SuggestionListener; +import org.marketcetera.trade.TradePermissions; import org.marketcetera.ui.PhotonServices; import org.marketcetera.ui.events.NewWindowEvent; import org.marketcetera.ui.service.trade.TradeClientService; -import org.marketcetera.ui.strategy.view.DisplayStrategyMessage; +import org.marketcetera.ui.trade.event.SuggestionEvent; import org.marketcetera.ui.view.AbstractContentView; import org.marketcetera.util.log.SLF4JLoggerProxy; import org.nocrala.tools.texttablefmt.BorderStyle; @@ -26,12 +28,18 @@ import org.springframework.stereotype.Component; import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; +import javafx.scene.control.MenuItem; import javafx.scene.control.SelectionMode; +import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; @@ -62,6 +70,7 @@ protected void onStart() mainLayout.getChildren().add(suggestionTable); suggestionTable.prefWidthProperty().bind(mainLayout.widthProperty()); suggestionTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + initializeSuggestionListener(); } /* (non-Javadoc) * @see org.marketcetera.ui.view.ContentView#onClose() @@ -206,7 +215,9 @@ private void initializeSuggestionTableColumns() identifierColumn.setCellValueFactory(new PropertyValueFactory<>("identifier")); scoreColumn = new TableColumn<>("Score"); scoreColumn.setCellValueFactory(new PropertyValueFactory<>("score")); - scoreColumn.setCellFactory(tableColumn -> PhotonServices.renderNumberCell(tableColumn)); + scoreColumn.setCellFactory(tableColumn -> PhotonServices.renderNumberCell(tableColumn, + 4, + 4)); quantityColumn = new TableColumn<>("Quantity"); quantityColumn.setCellValueFactory(new PropertyValueFactory<>("quantity")); quantityColumn.setCellFactory(tableColumn -> PhotonServices.renderNumberCell(tableColumn)); @@ -230,10 +241,27 @@ private void initializeSuggestionTableColumns() suggestionTable.getColumns().add(scoreColumn); suggestionTable.getColumns().add(sideColumn); suggestionTable.getColumns().add(instrumentColumn); + suggestionTable.getColumns().add(quantityColumn); suggestionTable.getColumns().add(priceColumn); suggestionTable.getColumns().add(orderTypeColumn); suggestionTable.getColumns().add(timestampColumn); } + /** + * Enable or disable context menu items based on the given selected row items. + * + * @param inSelectedItems a Collection<DisplaySuggestion> value + */ + protected void enableContextMenuItems(Collection inSelectedItems) + { + if(inSelectedItems == null || inSelectedItems.isEmpty()) { + executeMenuItem.setDisable(true); + deleteMenuItem.setDisable(true); + return; + } + // any suggestion can be executed or deleted, assuming appropriate permissions + executeMenuItem.setDisable(authzHelperService.hasPermission(TradePermissions.SendOrderAction)); + deleteMenuItem.setDisable(false); + } /** * Get the selected suggestions. * @@ -244,44 +272,120 @@ private Collection getSelectedSuggestions() return suggestionTable.getSelectionModel().getSelectedItems(); } /** - * Create a human-readable representation of the given strategy messages. + * Initialize the strategy context menu. + */ + private void initializeSuggestionContextMenu() + { + executeMenuItem = new MenuItem("Execute"); + executeMenuItem.setOnAction(event -> { + Collection selectedItems = getSelectedSuggestions(); + if(selectedItems == null || selectedItems.isEmpty()) { + return; + } + doExecute(selectedItems); + }); + deleteMenuItem = new MenuItem("Delete"); + deleteMenuItem.setOnAction(event -> { + Collection selectedItems = getSelectedSuggestions(); + if(selectedItems == null || selectedItems.isEmpty()) { + return; + } + doDelete(selectedItems); + }); + copyMenuItem = new MenuItem("Copy"); + copyMenuItem.setOnAction(event -> { + Collection selectedItems = getSelectedSuggestions(); + if(selectedItems == null || selectedItems.isEmpty()) { + return; + } + doCopy(selectedItems); + }); + suggestionContextMenu = new ContextMenu(); + suggestionContextMenu.getItems().addAll(executeMenuItem, + deleteMenuItem, + new SeparatorMenuItem(), + copyMenuItem); + suggestionTable.setContextMenu(suggestionContextMenu); + suggestionTable.getSelectionModel().selectedItemProperty().addListener((ChangeListener) (inObservable,inOldValue,inNewValue) -> { + enableContextMenuItems(getSelectedSuggestions()); + }); + } + /** + * Perform the execute operation on the given suggestions. + * + * @param inSuggestions a Collection<DisplaySuggestion> value + */ + private void doExecute(Collection inSuggestions) + { + for(DisplaySuggestion displaySuggestion : inSuggestions) { + uiMessageService.post(new SuggestionEvent(displaySuggestion.sideProperty().get().name() + " " + displaySuggestion.instrumentProperty().get().getFullSymbol(), + displaySuggestion.sourceProperty().get())); + } + } + /** + * Perform the delete operation on the given suggestions. + * + * @param inSuggestions a Collection<DisplaySuggestion> value + */ + private void doDelete(Collection inSuggestions) + { + suggestionTable.getItems().removeAll(inSuggestions); + } + /** + * Perform the copy operation on the given suggestions. + * + * @param inSuggestions a Collection<DisplaySuggestion> value + */ + private void doCopy(Collection inSuggestions) + { + Clipboard clipboard = Clipboard.getSystemClipboard(); + ClipboardContent clipboardContent = new ClipboardContent(); + clipboardContent.putString(renderSuggestions(inSuggestions)); + clipboard.setContent(clipboardContent); + } + /** + * Create a human-readable representation of the given suggestions. * - * @param inStrategyMessages a Collection<DisplayStrategymessage> value + * @param inSuggestions a Collection<DisplaySuggestion> value * @return a String value */ - private String renderStrategyMessages(Collection inStrategyMessages) + private String renderSuggestions(Collection inSuggestions) { - Table table = new Table(4, + Table table = new Table(8, BorderStyle.CLASSIC_COMPATIBLE_WIDE, ShownBorders.ALL, false); - table.addCell("Strategy Messages", + table.addCell("Trade Suggestions", PlatformServices.cellStyle, - 4); - table.addCell("Timestamp", + 8); + table.addCell("Identifier", + PlatformServices.cellStyle); + table.addCell("Score", + PlatformServices.cellStyle); + table.addCell("Side", PlatformServices.cellStyle); - table.addCell("Strategy", + table.addCell("Instrument", PlatformServices.cellStyle); - table.addCell("Severity", + table.addCell("Quantity", PlatformServices.cellStyle); - table.addCell("Message", + table.addCell("Price", + PlatformServices.cellStyle); + table.addCell("Order Type", + PlatformServices.cellStyle); + table.addCell("Timestamp", PlatformServices.cellStyle); - for(DisplayStrategyMessage message : inStrategyMessages) { - table.addCell(String.valueOf(message.timestampProperty().get())); - table.addCell(message.strategyNameProperty().get()); - table.addCell(message.severityProperty().get().name()); - table.addCell(message.messageProperty().get()); + for(DisplaySuggestion suggestion : inSuggestions) { + table.addCell(suggestion.identifierProperty().get()); + table.addCell(BigDecimalUtil.renderDecimal(suggestion.scoreProperty().get(),4,4)); + table.addCell(suggestion.sideProperty().get().name()); + table.addCell(suggestion.instrumentProperty().get().getFullSymbol()); + table.addCell(BigDecimalUtil.render(suggestion.quantityProperty().get())); + table.addCell(BigDecimalUtil.renderCurrency(suggestion.priceProperty().get())); + table.addCell(suggestion.orderTypeProperty().get().name()); + table.addCell(String.valueOf(suggestion.timestampProperty().get())); } return table.render(); } - /** - * Initialize the strategy context menu. - */ - private void initializeSuggestionContextMenu() - { - // execute - // delete - } /** * identifier column */ @@ -316,6 +420,22 @@ private void initializeSuggestionContextMenu() private TableColumn timestampColumn; // TODO suggestion type column? // TODO suggestion owner column? + /** + * context menu for the suggestion table + */ + private ContextMenu suggestionContextMenu; + /** + * execute menu item for the suggestion table + */ + private MenuItem executeMenuItem; + /** + * delete menu item for the suggestion table + */ + private MenuItem deleteMenuItem; + /** + * copy menu item for the suggestion table + */ + private MenuItem copyMenuItem; /** * listens for suggestions */ diff --git a/photon/src/main/resources/log4j2.xml b/photon/src/main/resources/log4j2.xml index 713da27729..40eaaafb99 100644 --- a/photon/src/main/resources/log4j2.xml +++ b/photon/src/main/resources/log4j2.xml @@ -11,6 +11,5 @@ - diff --git a/trade/trade-rpc-client/src/main/java/org/marketcetera/trading/rpc/TradeRpcClient.java b/trade/trade-rpc-client/src/main/java/org/marketcetera/trading/rpc/TradeRpcClient.java index af7f1208eb..1f994fa2b8 100644 --- a/trade/trade-rpc-client/src/main/java/org/marketcetera/trading/rpc/TradeRpcClient.java +++ b/trade/trade-rpc-client/src/main/java/org/marketcetera/trading/rpc/TradeRpcClient.java @@ -1056,6 +1056,8 @@ private static AbstractClientListenerProxy getListenerFor(Object inListen { if(inListener instanceof TradeMessageListener) { return new TradeMessageListenerProxy((TradeMessageListener)inListener); + } else if(inListener instanceof SuggestionListener) { + return new SuggestionListenerProxy((SuggestionListener)inListener); } else { throw new UnsupportedOperationException(); } From 1749a65cec121509dfcbfc9611fd708811c4d477 Mon Sep 17 00:00:00 2001 From: Colin DuPlantis Date: Thu, 13 Apr 2023 13:39:34 -0700 Subject: [PATCH 07/10] MATP-1157 Implement Trade Suggestions --- .../trade/event/AbstractOrderTicketEvent.java | 19 +++++++++++++ .../ui/trade/event/ReplaceOrderEvent.java | 20 ------------- .../ui/trade/event/SuggestionEvent.java | 16 ++++------- .../ui/trade/event/TradeOrderEvent.java | 10 ------- .../view/suggestions/SuggestionsView.java | 17 +++++++---- .../strategy/sample/TestStrategy.java | 28 ++++++++----------- .../src/main/resources/application.properties | 4 +++ 7 files changed, 51 insertions(+), 63 deletions(-) diff --git a/photon/src/main/java/org/marketcetera/ui/trade/event/AbstractOrderTicketEvent.java b/photon/src/main/java/org/marketcetera/ui/trade/event/AbstractOrderTicketEvent.java index fd8e327a87..e0962af6e9 100644 --- a/photon/src/main/java/org/marketcetera/ui/trade/event/AbstractOrderTicketEvent.java +++ b/photon/src/main/java/org/marketcetera/ui/trade/event/AbstractOrderTicketEvent.java @@ -2,8 +2,10 @@ import java.net.URL; +import org.marketcetera.core.Pair; import org.marketcetera.ui.events.NewWindowEvent; import org.marketcetera.ui.trade.view.orderticket.OrderTicketViewFactory; +import org.marketcetera.ui.view.ContentViewFactory; import org.springframework.beans.factory.annotation.Autowired; /* $License$ */ @@ -26,6 +28,23 @@ public URL getWindowIcon() { return orderViewFactory.getMenuIcon(); } + /* (non-Javadoc) + * @see org.marketcetera.ui.events.NewWindowEvent#getWindowSize() + */ + @Override + public Pair getWindowSize() + { + return Pair.create(850.0, + 200.0); + } + /* (non-Javadoc) + * @see org.marketcetera.web.events.NewWindowEvent#getViewFactoryType() + */ + @Override + public Class getViewFactoryType() + { + return OrderTicketViewFactory.class; + } /** * constructs order views */ diff --git a/photon/src/main/java/org/marketcetera/ui/trade/event/ReplaceOrderEvent.java b/photon/src/main/java/org/marketcetera/ui/trade/event/ReplaceOrderEvent.java index 2597b49aaf..39e5ef53c9 100644 --- a/photon/src/main/java/org/marketcetera/ui/trade/event/ReplaceOrderEvent.java +++ b/photon/src/main/java/org/marketcetera/ui/trade/event/ReplaceOrderEvent.java @@ -2,11 +2,8 @@ import java.util.Properties; -import org.marketcetera.core.Pair; import org.marketcetera.trade.ExecutionReport; import org.marketcetera.trade.HasExecutionReport; -import org.marketcetera.ui.trade.view.orderticket.OrderTicketViewFactory; -import org.marketcetera.ui.view.ContentViewFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -42,14 +39,6 @@ public String getWindowTitle() { return "Replace " + executionReport.getOrderID(); } - /* (non-Javadoc) - * @see org.marketcetera.web.events.NewWindowEvent#getViewFactoryType() - */ - @Override - public Class getViewFactoryType() - { - return OrderTicketViewFactory.class; - } /* (non-Javadoc) * @see org.marketcetera.web.events.NewWindowEvent#getProperties() */ @@ -58,15 +47,6 @@ public Properties getProperties() { return windowProperties; } - /* (non-Javadoc) - * @see org.marketcetera.ui.events.NewWindowEvent#getWindowSize() - */ - @Override - public Pair getWindowSize() - { - return Pair.create(850.0, - 200.0); - } /** * Create a new ReplaceOrderEvent instance. * diff --git a/photon/src/main/java/org/marketcetera/ui/trade/event/SuggestionEvent.java b/photon/src/main/java/org/marketcetera/ui/trade/event/SuggestionEvent.java index 979fc62912..ca1ff50c1f 100644 --- a/photon/src/main/java/org/marketcetera/ui/trade/event/SuggestionEvent.java +++ b/photon/src/main/java/org/marketcetera/ui/trade/event/SuggestionEvent.java @@ -3,8 +3,9 @@ import org.marketcetera.trade.HasSuggestion; import org.marketcetera.trade.Suggestion; import org.marketcetera.ui.events.NewWindowEvent; -import org.marketcetera.ui.trade.view.orderticket.OrderTicketViewFactory; -import org.marketcetera.ui.view.ContentViewFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; /* $License$ */ @@ -15,7 +16,10 @@ * @version $Id$ * @since $Release$ */ +@Component +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class SuggestionEvent + extends AbstractOrderTicketEvent implements NewWindowEvent,HasSuggestion { /* (non-Javadoc) @@ -34,14 +38,6 @@ public String getWindowTitle() { return title; } - /* (non-Javadoc) - * @see org.marketcetera.web.events.NewWindowEvent#getViewFactoryType() - */ - @Override - public Class getViewFactoryType() - { - return OrderTicketViewFactory.class; - } /** * Create a new SuggestionEvent instance. * diff --git a/photon/src/main/java/org/marketcetera/ui/trade/event/TradeOrderEvent.java b/photon/src/main/java/org/marketcetera/ui/trade/event/TradeOrderEvent.java index 9bab66cc01..0ed7f55be7 100644 --- a/photon/src/main/java/org/marketcetera/ui/trade/event/TradeOrderEvent.java +++ b/photon/src/main/java/org/marketcetera/ui/trade/event/TradeOrderEvent.java @@ -4,8 +4,6 @@ import org.marketcetera.trade.AverageFillPrice; import org.marketcetera.trade.HasAverageFillPrice; -import org.marketcetera.ui.trade.view.orderticket.OrderTicketViewFactory; -import org.marketcetera.ui.view.ContentViewFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -41,14 +39,6 @@ public String getWindowTitle() { return "Trade " + averageFillPrice.getInstrumentAsString(); } - /* (non-Javadoc) - * @see org.marketcetera.web.events.NewWindowEvent#getViewFactoryType() - */ - @Override - public Class getViewFactoryType() - { - return OrderTicketViewFactory.class; - } /* (non-Javadoc) * @see org.marketcetera.web.events.NewWindowEvent#getProperties() */ diff --git a/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java b/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java index 2b7bdb4cf1..a2630c463f 100644 --- a/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java +++ b/photon/src/main/java/org/marketcetera/ui/trade/view/suggestions/SuggestionsView.java @@ -22,8 +22,10 @@ import org.nocrala.tools.texttablefmt.BorderStyle; import org.nocrala.tools.texttablefmt.ShownBorders; import org.nocrala.tools.texttablefmt.Table; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -208,9 +210,6 @@ protected void updateItem(Instrument inItem, */ private void initializeSuggestionTableColumns() { -// suggestionTable.getSelectionModel().selectedItemProperty().addListener((ChangeListener) (inObservable,inOldValue,inNewValue) -> { -// enableSuggestionContextMenuItems(inNewValue); -// }); identifierColumn = new TableColumn<>("Identifier"); identifierColumn.setCellValueFactory(new PropertyValueFactory<>("identifier")); scoreColumn = new TableColumn<>("Score"); @@ -259,7 +258,7 @@ protected void enableContextMenuItems(Collection inSelectedIt return; } // any suggestion can be executed or deleted, assuming appropriate permissions - executeMenuItem.setDisable(authzHelperService.hasPermission(TradePermissions.SendOrderAction)); + executeMenuItem.setDisable(!authzHelperService.hasPermission(TradePermissions.SendOrderAction)); deleteMenuItem.setDisable(false); } /** @@ -318,8 +317,9 @@ private void initializeSuggestionContextMenu() private void doExecute(Collection inSuggestions) { for(DisplaySuggestion displaySuggestion : inSuggestions) { - uiMessageService.post(new SuggestionEvent(displaySuggestion.sideProperty().get().name() + " " + displaySuggestion.instrumentProperty().get().getFullSymbol(), - displaySuggestion.sourceProperty().get())); + uiMessageService.post(applicationContext.getBean(SuggestionEvent.class, + displaySuggestion.sideProperty().get().name() + " " + displaySuggestion.instrumentProperty().get().getFullSymbol(), + displaySuggestion.sourceProperty().get())); } } /** @@ -386,6 +386,11 @@ private String renderSuggestions(Collection inSuggestions) } return table.render(); } + /** + * provides access to the application context + */ + @Autowired + private ApplicationContext applicationContext; /** * identifier column */ diff --git a/strategy/strategy-sample/src/main/java/org/marketcetera/strategy/sample/TestStrategy.java b/strategy/strategy-sample/src/main/java/org/marketcetera/strategy/sample/TestStrategy.java index a022be9411..d43a1de737 100644 --- a/strategy/strategy-sample/src/main/java/org/marketcetera/strategy/sample/TestStrategy.java +++ b/strategy/strategy-sample/src/main/java/org/marketcetera/strategy/sample/TestStrategy.java @@ -19,7 +19,6 @@ import org.marketcetera.marketdata.MarketDataRequest; import org.marketcetera.marketdata.MarketDataRequestBuilder; import org.marketcetera.strategy.StrategyClient; -import org.marketcetera.trade.Equity; import org.marketcetera.trade.Factory; import org.marketcetera.trade.Instrument; import org.marketcetera.trade.OrderSingle; @@ -28,6 +27,7 @@ import org.marketcetera.trade.Side; import org.marketcetera.trade.client.TradeClient; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; @@ -92,7 +92,6 @@ public void receiveMarketData(Event inEvent) } } }); - sendSuggestion(); } /** * Stop the object. @@ -116,6 +115,9 @@ public void stop() */ private void issueSuggestion(MarketDataCacheElement inCacheElement) { + if(!createSuggestions) { + return; + } TopOfBookEvent topOfBook = (TopOfBookEvent)inCacheElement.getSnapshot(Content.TOP_OF_BOOK); if(topOfBook == null) { return; @@ -142,25 +144,11 @@ private void issueSuggestion(MarketDataCacheElement inCacheElement) orderSingle.setOrderType(OrderType.Limit); orderSingle.setPegToMidpoint(true); orderSingle.setQuantity(new BigDecimal(10*(random.nextInt(10)+1))); + orderSingle.setPrice(quote.getPrice()); orderSingle.setSide(side); orderSingleSuggestion.setOrder(orderSingle); tradeClient.sendOrderSuggestion(orderSingleSuggestion); } - private void sendSuggestion() - { - OrderSingleSuggestion orderSingleSuggestion = Factory.getInstance().createOrderSingleSuggestion(); - orderSingleSuggestion.setIdentifier("Test Strategy"); - orderSingleSuggestion.setScore(new BigDecimal(random.nextDouble())); - OrderSingle orderSingle = Factory.getInstance().createOrderSingle(); - orderSingle.setInstrument(new Equity("AAPL")); - orderSingle.setOrderType(OrderType.Limit); - orderSingle.setPegToMidpoint(true); - orderSingle.setQuantity(new BigDecimal(10*(random.nextInt(10)+1))); - orderSingle.setSide(Side.Buy); - orderSingle.setPrice(new BigDecimal(50.00)); - orderSingleSuggestion.setOrder(orderSingle); - tradeClient.sendOrderSuggestion(orderSingleSuggestion); - } /** * caches market data */ @@ -180,6 +168,11 @@ public MarketDataCacheElement load(Instrument inKey) * holds the market data request id */ private String marketDataRequestId; + /** + * strategy should create suggestions or not + */ + @Value("${metc.strategy.create.suggestions}") + private boolean createSuggestions; /** * provides access to strategy services */ @@ -195,4 +188,5 @@ public MarketDataCacheElement load(Instrument inKey) */ @Autowired private TradeClient tradeClient; + } diff --git a/strategy/strategy-sample/src/main/resources/application.properties b/strategy/strategy-sample/src/main/resources/application.properties index e69de29bb2..cd498e097c 100644 --- a/strategy/strategy-sample/src/main/resources/application.properties +++ b/strategy/strategy-sample/src/main/resources/application.properties @@ -0,0 +1,4 @@ +# +# test strategy settings +# +metc.strategy.create.suggestions=true \ No newline at end of file From ef7edb6dea975da99b0c6457368aa702fe6ad74b Mon Sep 17 00:00:00 2001 From: Colin DuPlantis Date: Thu, 13 Apr 2023 13:41:26 -0700 Subject: [PATCH 08/10] MATP-1157 Implement Trade Suggestions --- .../main/java/org/marketcetera/trade/AbstractSuggestion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/marketcetera/trade/AbstractSuggestion.java b/core/src/main/java/org/marketcetera/trade/AbstractSuggestion.java index 7984c13260..0ce3429b1e 100644 --- a/core/src/main/java/org/marketcetera/trade/AbstractSuggestion.java +++ b/core/src/main/java/org/marketcetera/trade/AbstractSuggestion.java @@ -5,7 +5,7 @@ /* $License$ */ /** - * + * Provides common behavior for Suggestion implementations. * * @author Colin DuPlantis * @version $Id$ From 4342ebede9d46922d1e174168e06121cbd0f0cb9 Mon Sep 17 00:00:00 2001 From: Colin DuPlantis Date: Thu, 13 Apr 2023 13:44:52 -0700 Subject: [PATCH 09/10] MATP-1157 Implement Trade Suggestions --- .../ui/marketdata/view/MarketDataDetailView.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataDetailView.java b/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataDetailView.java index 871a0044d8..023c00fd6b 100644 --- a/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataDetailView.java +++ b/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataDetailView.java @@ -313,8 +313,9 @@ private void buyOrSellAction(Side inSide, suggestion.setIdentifier("Market Data List View Action"); suggestion.setScore(BigDecimal.ONE); suggestion.setOrder(orderSingle); - uiMessageService.post(new SuggestionEvent(inSide.name() + " " + marketDataInstrument.getSymbol(), - suggestion)); + uiMessageService.post(applicationContext.getBean(SuggestionEvent.class, + inSide.name() + " " + marketDataInstrument.getSymbol(), + suggestion)); } /** * Execute the market data request. From 18bc82bd4c4bf76fbb253be30cbebdc76c893318 Mon Sep 17 00:00:00 2001 From: Colin DuPlantis Date: Thu, 13 Apr 2023 13:46:13 -0700 Subject: [PATCH 10/10] MATP-1157 Implement Trade Suggestions --- .../marketcetera/ui/marketdata/view/MarketDataListView.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataListView.java b/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataListView.java index 97e5b9f5e3..51d16e0190 100644 --- a/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataListView.java +++ b/photon/src/main/java/org/marketcetera/ui/marketdata/view/MarketDataListView.java @@ -425,8 +425,9 @@ private void buyOrSellAction(MarketDataItem inSelectedItem, suggestion.setIdentifier("Market Data List View Action"); suggestion.setScore(BigDecimal.ONE); suggestion.setOrder(orderSingle); - uiMessageService.post(new SuggestionEvent(inSide.name() + " " + inSelectedItem.symbolProperty().get(), - suggestion)); + uiMessageService.post(applicationContext.getBean(SuggestionEvent.class, + inSide.name() + " " + inSelectedItem.symbolProperty().get(), + suggestion)); } /** * Prepare a nice, human-readable rendering of the given market data item.