1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package net.sf.emarket.order.service;
21
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.Comparator;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.logging.Logger;
29
30 import net.sf.emarket.account.domain.Account;
31 import net.sf.emarket.account.domain.AcctPosition;
32 import net.sf.emarket.account.domain.AcctPositionTransfer;
33 import net.sf.emarket.account.domain.CashBalance;
34 import net.sf.emarket.account.domain.CashTransfer;
35 import net.sf.emarket.account.service.AccountNotActiveException;
36 import net.sf.emarket.account.service.IAccountManagerService;
37 import net.sf.emarket.account.service.InSufficientCashBalanceException;
38 import net.sf.emarket.account.service.NotEnoughCashException;
39 import net.sf.emarket.account.service.NotEnoughPositionsException;
40 import net.sf.emarket.account.service.NotValidAccountException;
41 import net.sf.emarket.order.domain.Order;
42 import net.sf.emarket.order.domain.TimeComparator;
43 import net.sf.emarket.order.repository.IOrderDao;
44 import net.sf.emarket.quote.domain.Instrument;
45 import net.sf.emarket.quote.domain.Quote;
46 import net.sf.emarket.quote.domain.Instrument;
47 import net.sf.emarket.quote.service.IQuoteManagerService;
48 import net.sf.emarket.system.service.IMailSenderService;
49 import net.sf.emarket.trade.domain.OrderFill;
50 import net.sf.emarket.trade.domain.OrderMatch;
51 import net.sf.emarket.trade.service.IOrderExecutorService;
52 import net.sf.emarket.user.domain.User;
53 import net.sf.emarket.user.service.IUserManagerService;
54 import net.sf.emarket.user.service.UserNotFoundException;
55
56 import org.springframework.beans.factory.annotation.Autowired;
57 import org.springframework.mail.SimpleMailMessage;
58 import org.springframework.transaction.annotation.Propagation;
59 import org.springframework.transaction.annotation.Transactional;
60
61
62 public class OrderManagerServiceImpl implements IOrderManagerService {
63
64 private static final long serialVersionUID = 6143050250286605772L;
65
66 private static Logger logger = Logger.getLogger( OrderManagerServiceImpl.class.getName() );
67
68 private IOrderDao orderDao = null;
69 private IUserManagerService userMgr =null;
70 private IAccountManagerService acctMgr = null;
71 private IQuoteManagerService quoteMgr = null;
72 private IOrderExecutorService orderExecutor = null;
73 private IMailSenderService mailSender;
74 private SimpleMailMessage templateMessage;
75
76
77 @Autowired
78 public void setOrderDao( IOrderDao dao){
79 orderDao = dao;
80 }
81
82 @Autowired
83 public void setUserManager( IUserManagerService mgr){
84 userMgr = mgr;
85 }
86
87 @Autowired
88 public void setAccountManager( IAccountManagerService acctMgrSvc ){
89 acctMgr = acctMgrSvc;
90 }
91
92 @Autowired
93 public void setQuoteManager( IQuoteManagerService quoteMgrSvr ){
94 quoteMgr = quoteMgrSvr;
95 }
96
97 @Autowired
98 public void setOrderExecutor( IOrderExecutorService executor){
99 orderExecutor = executor;
100 }
101
102 @Autowired
103 public void setMailSender(IMailSenderService mailSender) {
104 this.mailSender = mailSender;
105 }
106
107 @Autowired
108 public void setTemplateMessage(SimpleMailMessage templateMessage) {
109 this.templateMessage = templateMessage;
110 }
111
112
113 @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
114 public Order placeOrder(Order order) throws AccountNotActiveException, InSufficientCashBalanceException, NotEnoughPositionsException, InstrumentCannotTradeException, WrongLimitPriceExeption {
115
116
117 Order verifiedOrder = null;
118 if( order.getId() == null || order.getId().trim().equals("") ){
119 verifiedOrder = verifyOrder(order);
120 }else{
121
122 verifiedOrder =order;
123 }
124
125
126
127
128
129
130 verifiedOrder.setStatus(Order.STATUS_OPEN);
131
132
133 orderDao.updateOrder(verifiedOrder);
134
135
136 orderExecutor.execute(verifiedOrder);
137
138 return verifiedOrder;
139 }
140
141 @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
142 public Order verifyOrder( Order order )
143 throws AccountNotActiveException, InSufficientCashBalanceException, NotEnoughPositionsException, InstrumentCannotTradeException, WrongLimitPriceExeption {
144
145
146
147
148
149 Account account = acctMgr.getAccountById(order.getAcctId());
150
151 String acctStatus = account.getStatus();
152 if( !acctStatus.equals( Account.STATUS_ACTIVE) ){
153 throw new AccountNotActiveException( "Account "+ account.getAcctId() + " not active");
154 }
155
156
157
158
159 Quote quote = quoteMgr.getQuote(order.getSymbol());
160
161
162
163 if( !quote.getInstrument().getStatus().equals( Instrument.STATUS_ACTIVE )){
164 throw new InstrumentCannotTradeException("Symbol "+ quote.getSymbol() + " is not allowed trade" );
165 }
166
167
168 order.setQuoteId(quote.getId());
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187 float cost = calculateCost(order, quote);
188
189
190 float commission = calculateCommision(account, order);
191 order.setCommission(commission);
192
193
194
195 float totalCost = cost + commission;
196 order.setTotalCost(totalCost);
197
198
199
200
201
202 if( order.getOrderType().equals( Order.TYPE_BUY) ){
203 CashBalance balance = acctMgr.getCashBalance(account);
204 if( balance.getAmount() < order.getTotalCost() ){
205 throw new InSufficientCashBalanceException("Account "+ account.getAcctId()
206 + " doesn't have enough cash balance.");
207 }
208
209 float totalCashNeeded = 0f;
210
211 List<Order> openOrders = orderDao.getOrdersForAcctIdWithStatusAndSymbol( account.getAcctId(),
212 Order.STATUS_OPEN,
213 order.getSymbol());
214
215 for( Order o : openOrders ){
216 totalCashNeeded += ( o.getQuantity() * o.getLimitPrice() );
217 }
218
219
220 List<Order> partiallyFilledOrders = orderDao.getOrdersForAcctIdWithStatusAndSymbol( account.getAcctId(),
221 Order.STATUS_PARTIALLY_FILLED ,
222 order.getSymbol());
223
224 for( Order o : partiallyFilledOrders ){
225 totalCashNeeded += ((o.getQuantity() - o.getFilledQuantity())) * o.getLimitPrice();
226 }
227
228 if( balance.getAmount() < ( totalCashNeeded + order.getTotalCost() ) ){
229 throw new InSufficientCashBalanceException("Account "+ account.getAcctId()
230 + " doesn't have enough cash balance, because there are other BUY orders.");
231 }
232
233
234 }else if(order.getOrderType().equals( Order.TYPE_SELL)){
235
236 List<AcctPosition> positions = acctMgr.getAccountPositions(account.getAcctId(), order.getSymbol());
237
238
239 long total = 0;
240 for( AcctPosition pos : positions ){
241 total = total + pos.getQuantity();
242 }
243
244 if( total < order.getQuantity() ){
245 throw new NotEnoughPositionsException("Account "+ account.getAcctId()
246 + " doesn't have enough positions to sell");
247 }
248
249 int totalPositionsOpen = 0;
250
251
252
253 List<Order> openOrders = orderDao.getOrdersForAcctIdWithStatusAndSymbol( account.getAcctId(),
254 Order.STATUS_OPEN ,
255 order.getSymbol());
256
257 for( Order o : openOrders ){
258 totalPositionsOpen += o.getQuantity();
259 }
260
261
262 List<Order> partiallyFilledOrders = orderDao.getOrdersForAcctIdWithStatusAndSymbol( account.getAcctId(),
263 Order.STATUS_PARTIALLY_FILLED ,
264 order.getSymbol());
265
266 for( Order o : partiallyFilledOrders ){
267 totalPositionsOpen += (o.getQuantity() - o.getFilledQuantity());
268 }
269
270 if( total < ( order.getQuantity() + totalPositionsOpen) ){
271 throw new NotEnoughPositionsException("Account "+ account.getAcctId() + " doesn't have enough positions to sell, since "
272 + totalPositionsOpen + " shares are already on sale");
273 }
274
275 }
276
277
278
279 order.setStatus(Order.STATUS_VERIFIED);
280
281
282 long orderId = orderDao.generateOrderId();
283 order.setId( Long.toString(orderId) );
284
285
286 orderDao.addOrder(order);
287
288
289 Order verifiedOrder = orderDao.getOrderById(order.getId());
290
291
292
293
294
295 return verifiedOrder;
296 }
297
298 public float calculateCommision( Account account, Order order ){
299
300 float commission = 0f;
301
302 return commission;
303 }
304
305 public List<Order> getOpenOrdersForAcctId(String acctId) {
306
307 List<Order> openorders =orderDao.getOrdersForAcctIdWithStatus( acctId , Order.STATUS_OPEN);
308 List<Order> partiallFilledOrders = orderDao.getOrdersForAcctIdWithStatus(acctId, Order.STATUS_PARTIALLY_FILLED);
309
310 openorders.addAll(partiallFilledOrders);
311
312
313 Comparator reverseTimeComparator = Collections.reverseOrder( new TimeComparator());
314 Collections.sort(openorders, reverseTimeComparator);
315
316 return openorders;
317 }
318
319 public List<Order> getExecutedOrdersForAcctId(String acctId) {
320
321 List<Order> filledOrders = orderDao.getOrdersForAcctIdWithStatus(acctId, Order.STATUS_FILLED);
322 List<Order> partiallyFilledOrders = orderDao.getOrdersForAcctIdWithStatus(acctId, Order.STATUS_PARTIALLY_FILLED);
323 filledOrders.addAll(partiallyFilledOrders);
324
325
326 Comparator reverseTimeComparator = Collections.reverseOrder( new TimeComparator());
327 Collections.sort( filledOrders, reverseTimeComparator);
328
329 return filledOrders;
330 }
331
332 private float calculateCost( Order order, Quote quote){
333
334 float cost = 0f;
335
336 if( order.getPriceType().equals( Order.PRICE_TYPE_LIMIT) ){
337 cost = order.getQuantity() * order.getLimitPrice();
338 }else if( order.getPriceType().equals( Order.PRICE_TYPE_MARKET) ){
339 if( order.getOrderType().equals( Order.TYPE_BUY )){
340
341 cost = order.getQuantity() * quote.getAskPrice();
342 }
343 if( order.getOrderType().equals(Order.TYPE_SELL)){
344
345 cost = order.getQuantity() * quote.getBidPrice();
346 }
347 }
348
349 return cost;
350 }
351
352 /***
353 * <code>fillOrders</code> .
354 * <OL>
355 <li>determine how many cash transfers are needed</li>
356 <li>collect money from BUY accounts</li>
357 <li>collect positions from SELL accounts</li>
358 <li>Transfer cash from BUY account to SELL account</li>
359 <li>Transfer position from SELL account from BUY account</li>
360 </OL>
361
362 */
363 @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
364 public void fillOrders(List<OrderFill> fills) throws OrderCannotBeFilledException, OrderFillQuantityMismatchException {
365
366
367
368
369
370 Map<String, Order> orders = new HashMap<String,Order>();
371
372 for( OrderFill fill : fills ){
373 Order order = orderDao.getOrderById(fill.getOrderId());
374
375
376 if( !order.getStatus().equalsIgnoreCase(Order.STATUS_OPEN) ||
377 !order.getStatus().equalsIgnoreCase(Order.STATUS_PARTIALLY_FILLED) ){
378 throw new OrderCannotBeFilledException("Order Id : "+ fill.getOrderId() + " cannot be filled");
379 }
380
381 if( fill.getMatchedQuantity() > order.getQuantity() ){
382
383 throw new OrderFillQuantityMismatchException("Order fill quantity exceeds the order quantity for Order Id: "
384 + fill.getOrderId() + ", fillId: "+ fill.getId() );
385 }
386
387 orders.put(fill.getOrderId(), order);
388 }
389
390
391
392
393
394
395
396
397
398
399 int buys = 0;
400 int sells =0;
401 for( OrderFill fill : fills){
402 if( fill.getOrderType().equals(Order.TYPE_BUY) ){
403 buys += 1;
404 }
405 if( fill.getOrderType().equals(Order.TYPE_SELL) ){
406 sells +=1;
407 }
408 }
409
410
411 if( buys == 0 && sells == 0){
412
413 }else if( buys > 1 && sells > 1){
414
415 }
416
417 boolean moreSells = false;
418 if( buys == 1 && sells > 1 ){
419 moreSells = true;
420 }else if( buys > 1 && sells == 1 ){
421 moreSells = false;
422 }
423
424 List<CashTransfer> cashTransfers = new ArrayList<CashTransfer>();
425 List<AcctPositionTransfer> acctPosTransfers = new ArrayList<AcctPositionTransfer>();
426
427 if( moreSells ){
428 for( OrderFill fill : fills ){
429 if( fill.getOrderType().equals(Order.TYPE_SELL ) ){
430
431 Order order = orders.get(fill.getOrderId());
432 CashTransfer cashTransfer = new CashTransfer();
433 cashTransfer.setReceiverAcctId(order.getAcctId());
434 cashTransfer.setAmount(cashTransfer.getAmount());
435 cashTransfers.add(cashTransfer);
436
437 AcctPositionTransfer posTransfer = new AcctPositionTransfer();
438 posTransfer.setSenderAcctId(order.getAcctId());
439 posTransfer.setQuantity(fill.getMatchedQuantity());
440 acctPosTransfers.add(posTransfer);
441 }
442 }
443
444 }else if( !moreSells ){
445
446 for( OrderFill fill : fills ){
447 if( fill.getOrderType().equals(Order.TYPE_BUY ) ){
448
449 Order order = orders.get(fill.getOrderId());
450 CashTransfer cashTransfer = new CashTransfer();
451 cashTransfer.setSenderAcctId(order.getAcctId());
452 cashTransfer.setAmount(cashTransfer.getAmount());
453 cashTransfers.add(cashTransfer);
454
455 AcctPositionTransfer posTransfer = new AcctPositionTransfer();
456 posTransfer.setReceiverAcctId( order.getAcctId() );
457 posTransfer.setQuantity(fill.getMatchedQuantity());
458 acctPosTransfers.add( posTransfer);
459 }
460 }
461 }
462
463
464 }
465
466
467
468 /***
469 *
470 * <code>executeMatches()</code> transfers the positions and cash after order execution.
471 * This method is a short term hack.
472 * This will be replaced by fillOrder() and a corresponding clearing system.
473 * @param matches
474 * @throws OrderCannotBeFilledException
475 * @throws NotValidAccountException
476 * @throws NotEnoughCashException
477 * @throws NotEnoughPositionsException
478 */
479 public void executeMatches( List<OrderMatch> matches)
480 throws OrderCannotBeFilledException, NotEnoughCashException, NotValidAccountException, NotEnoughPositionsException{
481
482
483 for( OrderMatch match : matches ){
484
485 Order buyOrder = orderDao.getOrderById( match.getBuyOrderId() );
486 Order sellOrder = orderDao.getOrderById(match.getSellOrderId());
487
488
489 if( !buyOrder.getStatus().equalsIgnoreCase(Order.STATUS_OPEN) &&
490 !buyOrder.getStatus().equalsIgnoreCase(Order.STATUS_PARTIALLY_FILLED) ){
491 throw new OrderCannotBeFilledException("Order Id : "+ match.getBuyFillId() + " cannot be filled");
492 }
493 if( !sellOrder.getStatus().equalsIgnoreCase(Order.STATUS_OPEN) &&
494 !sellOrder.getStatus().equalsIgnoreCase(Order.STATUS_PARTIALLY_FILLED) ){
495 throw new OrderCannotBeFilledException("Order Id : "+ match.getSellFillId() + " cannot be filled");
496 }
497
498 CashTransfer cashTransfer = new CashTransfer();
499 float amount = match.getMatchPrice() * match.getMatchedQuantity();
500 cashTransfer.setAmount(amount);
501 cashTransfer.setSenderAcctId(buyOrder.getAcctId());
502 cashTransfer.setReceiverAcctId(sellOrder.getAcctId());
503
504 acctMgr.tranferCash(cashTransfer);
505
506 AcctPositionTransfer positionTransfer = new AcctPositionTransfer();
507 positionTransfer.setSenderAcctId( sellOrder.getAcctId());
508 positionTransfer.setReceiverAcctId( buyOrder.getAcctId() );
509 positionTransfer.setQuantity(match.getMatchedQuantity());
510 positionTransfer.setPrice( match.getMatchPrice());
511 positionTransfer.setSymbol(match.getSymbol());
512
513 acctMgr.transferPosition(positionTransfer);
514
515
516 if( match.getBuyMatchType().equals(OrderMatch.TYPE_FULL)){
517 buyOrder.setStatus(Order.STATUS_FILLED);
518 buyOrder.setFilledQuantity(match.getMatchedQuantity());
519 }else if(match.getBuyMatchType().equals(OrderMatch.TYPE_PARTIAL)){
520 buyOrder.setStatus(Order.STATUS_PARTIALLY_FILLED);
521 int matchedQuantity = match.getMatchedQuantity() + buyOrder.getFilledQuantity();
522 buyOrder.setFilledQuantity(matchedQuantity);
523 }
524
525 orderDao.updateOrder(buyOrder);
526
527
528 if(match.getSellMatchType().equals(OrderMatch.TYPE_FULL)){
529 sellOrder.setStatus(Order.STATUS_FILLED);
530 sellOrder.setFilledQuantity(match.getMatchedQuantity());
531 }else if(match.getSellMatchType().equals(OrderMatch.TYPE_PARTIAL)){
532 sellOrder.setStatus(Order.STATUS_PARTIALLY_FILLED);
533 int matchedQuantity = match.getMatchedQuantity() + sellOrder.getFilledQuantity();
534 sellOrder.setFilledQuantity(matchedQuantity);
535 }
536
537 orderDao.updateOrder(sellOrder);
538
539 Account buyerAcct = acctMgr.getAccountById( buyOrder.getAcctId() );
540 Account sellerAcct = acctMgr.getAccountById(sellOrder.getAcctId());
541 try {
542
543 User buyer = userMgr.getUserById( buyerAcct.getId());
544 logger.info("sending mail to buyer");
545 sendMail( buyer.getId(), buyOrder);
546 User seller = userMgr.getUserById( sellerAcct.getId());
547 logger.info("sending mail to seller");
548 sendMail( seller.getId(), sellOrder);
549
550 } catch (UserNotFoundException e) {
551 e.printStackTrace();
552 logger.severe("User not found exception: " + e.getMessage());
553 }
554 }
555 }
556
557 private void sendMail( String email, Order order){
558
559 SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
560
561 msg.setTo(email);
562 msg.setSubject("Re: order no. "+ order.getId());
563
564 StringBuilder sb = new StringBuilder();
565 sb.append("your ");
566
567 if( order.isBuy() ){
568 sb.append("buy ");
569 }else if( order.isSell()){
570 sb.append("sell ");
571 }
572
573 sb.append(" order for "+ order.getQuantity() );
574 sb.append(" shares of " + order.getSymbol());
575 sb.append(" is ");
576
577 if( order.getStatus().equals(Order.STATUS_FILLED) ){
578 sb.append("filled completely");
579 }
580 if( order.getStatus().equals(Order.STATUS_PARTIALLY_FILLED) ){
581 sb.append("filled partially");
582 }
583
584 msg.setText(sb.toString());
585
586 logger.info("sending message : \n"+ msg.toString());
587 this.mailSender.sendMail(msg);
588 }
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615 }