View Javadoc

1   /*
2    *  AccountManagerService
3    *
4    *  author nambi sankaran
5    *  copyright (C) 2009 nambi sankaran
6    *
7    *  This program is free software: you can redistribute it and/or modify
8    *  it under the terms of the GNU General Public License as published by
9    *  the Free Software Foundation, either version 3 of the License, or
10   *  (at your option) any later version.
11   *
12   *  This program is distributed in the hope that it will be useful,
13   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   *  GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License
18   *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19   *
20   */
21  package net.sf.emarket.account.service;
22  
23  import java.sql.Timestamp;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.Comparator;
27  import java.util.Date;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  
34  import net.sf.emarket.account.domain.Account;
35  import net.sf.emarket.account.domain.AcctPosition;
36  import net.sf.emarket.account.domain.AcctPositionSummary;
37  import net.sf.emarket.account.domain.AcctPositionTransfer;
38  import net.sf.emarket.account.domain.AcctValue;
39  import net.sf.emarket.account.domain.CashBalance;
40  import net.sf.emarket.account.domain.CashTransfer;
41  import net.sf.emarket.account.repository.IAccountDao;
42  import net.sf.emarket.account.repository.IAccountPositionDao;
43  import net.sf.emarket.account.repository.IAcctPositionTransferDao;
44  import net.sf.emarket.account.repository.ICashBalanceDao;
45  import net.sf.emarket.account.repository.ICashTransferDao;
46  import net.sf.emarket.quote.domain.Quote;
47  import net.sf.emarket.quote.service.IQuoteManagerService;
48  import net.sf.emarket.quote.service.SymbolsCannotBeEmptyException;
49  import net.sf.emarket.user.domain.User;
50  import net.sf.emarket.user.service.IUserManagerService;
51  import net.sf.emarket.user.service.UserNotFoundException;
52  
53  import org.springframework.beans.factory.annotation.Autowired;
54  import org.springframework.dao.EmptyResultDataAccessException;
55  import org.springframework.transaction.annotation.Propagation;
56  import org.springframework.transaction.annotation.Transactional;
57  
58  // Due to a bug in spring, we need to define a bean in applicationContext.xml for AccountManagerService
59  //@Service
60  public  class AccountManagerServiceImpl implements IAccountManagerService {
61  	
62  	private static final long serialVersionUID = -5752914821716454464L;
63  	//private static Logger logger = Logger.getLogger(AccountManagerServiceImpl.class );
64  	
65  	private IAccountDao accountDao;
66  	private IAccountPositionDao accountPositionDao;
67  	private IAcctPositionTransferDao acctPositionTransferDao;
68  	private ICashBalanceDao cashBalanceDao;
69  	private ICashTransferDao cashTransferDao;
70  
71      private IQuoteManagerService quoteMgr;
72      private IUserManagerService userMgr;
73  	
74  	@Autowired
75  	public void setAccountDao( IAccountDao dao){
76  		accountDao = dao;
77  	}
78  	
79  	@Autowired
80  	public void setAccountPositionDao( IAccountPositionDao dao){
81  		accountPositionDao = dao;
82  	}	
83  	
84  	@Autowired
85  	public void setAcctPositionTransferDao( IAcctPositionTransferDao dao){
86  		acctPositionTransferDao = dao;
87  	}
88  	
89  	@Autowired
90  	public void setCashBalanceDao( ICashBalanceDao dao){
91  		cashBalanceDao = dao;
92  	}
93  	
94  	@Autowired
95  	public void setCashTransferDao( ICashTransferDao dao){
96  		cashTransferDao = dao;
97  	}
98  
99      @Autowired
100     public void setQuoteManager( IQuoteManagerService mgr ){
101         quoteMgr = mgr;
102     }
103     
104     @Autowired
105     public void setUserManager( IUserManagerService mgr ){
106     	userMgr = mgr;
107     }
108 
109 	public void addAccount(Account account) {
110 		
111 		long acctId = accountDao.generateAccountId();
112 		account.setAcctId( Long.toString(acctId) );
113 		account.setStatus(Account.STATUS_ACTIVE);
114 		
115 		accountDao.addAccount(account);
116 	}
117 
118 	public int deleteAccountById(String acctId) {
119 		return accountDao.deleteAccountById(acctId);
120 	}
121 
122 	public Account getAccountById(String acctId) {
123 		return accountDao.getAccountById(acctId);
124 	}
125 
126 	public List<Account> getAccountsForUser(User user) {
127 		return accountDao.getAccountsForUser(user)	;
128 	}
129 	
130 	public List<Account> getAccountsOfType( String acctType ){
131 		return accountDao.getAccountsOfType(acctType);
132 	}
133 
134 	public int updateAccount(Account account) {
135 		return accountDao.updateAccount(account);
136 	}
137 
138 	public CashBalance getCashBalance(Account account) {
139 		
140 		CashBalance balance = new CashBalance();
141 		
142 		try{
143 			balance = cashBalanceDao.getCashBalance(account);
144 		}catch( EmptyResultDataAccessException ere){
145 			balance.setAmount(0l);
146 		}
147 		
148 		return balance;
149 	}
150 
151     @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
152     public void tranferCash(CashTransfer transfer) throws NotEnoughCashException, NotValidAccountException {
153 	
154 		// first check whether the sender has enough money to send
155 		CashBalance senderBalance = cashBalanceDao.getCashBalance(transfer.getSenderAcctId());
156 		if( senderBalance.getAmount() < transfer.getAmount() ){
157 			String message = "Account "+ transfer.getSenderAcctId() + " does not have cash to transfer";
158 			throw new NotEnoughCashException( message );
159 		}
160 		
161 		// check whether receiver account exists and active
162 		Account receiverAccount = accountDao.getAccountById(transfer.getReceiverAcctId());
163 		
164 		if( receiverAccount != null && 
165 				( receiverAccount.getStatus().equals( Account.STATUS_INACTIVE) ||
166 				  receiverAccount.getStatus().equals( Account.STATUS_DISABLED) )
167 			){
168 			
169 			String message = "Receiver account "+ transfer.getReceiverAcctId() + " is not a valid account";
170 			throw new NotValidAccountException(message);
171 		}
172 		
173 		// subtract the transfer amount from sender balance
174 		float remaining = senderBalance.getAmount() - transfer.getAmount();
175 		senderBalance.setAmount(remaining);
176 		senderBalance.setAcctId(transfer.getSenderAcctId());
177 		cashBalanceDao.updateCashBalance(senderBalance);
178 	
179 		//get receiver balance
180 		CashBalance receiverBalance = new CashBalance();
181 		boolean insertFlag = false;
182 		
183 		try{
184 			receiverBalance = cashBalanceDao.getCashBalance(transfer.getReceiverAcctId());
185 		}catch(EmptyResultDataAccessException ere){
186 			insertFlag = true;
187 			receiverBalance.setAmount(0f);
188 		}
189 		
190 		float addition = receiverBalance.getAmount() + transfer.getAmount();
191 		receiverBalance.setAmount(addition);
192 		receiverBalance.setAcctId(transfer.getReceiverAcctId());
193 		
194 		// update receiver balance
195 		if( insertFlag ){
196 			cashBalanceDao.addCashBalance(receiverBalance);
197 		}else{
198 			cashBalanceDao.updateCashBalance(receiverBalance);
199 		}
200 		
201 		// generate transfer Id
202 		long transferId = cashTransferDao.generateTransferId();
203 		transfer.setTransferId(transferId);
204 		
205 		Date date = new Date();
206 		Timestamp createdTime = new Timestamp( date.getTime());	
207 		transfer.setCreatedTime(createdTime);
208 		
209 		// record CashTransfer
210 		transfer.setTransferStatus(CashTransfer.STATUS_COMPLETED);
211 		cashTransferDao.addCashTransfer(transfer);
212     }
213     
214     @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
215     public void transferPosition( AcctPositionTransfer transfer ) throws NotEnoughPositionsException, NotValidAccountException{
216     	
217     	// first check the sender has enough positions to send
218     	List<AcctPosition> positions = accountPositionDao.getAccountPositions(transfer.getSenderAcctId(), transfer.getSymbol());
219     	int totalQuantity = 0;
220     	
221     	for( AcctPosition position : positions ){
222     		totalQuantity += position.getQuantity();
223     	}
224     	
225     	if( totalQuantity < transfer.getQuantity() ){
226     		// the sender cannot transfer positions, that he doesn't have.
227     		String message = "Account: "+ transfer.getSenderAcctId() +" does not have "+ 
228     											transfer.getQuantity() + " positions of "+ transfer.getSymbol();
229     		
230     		throw new NotEnoughPositionsException(message );
231     	}
232     	
233     	// check whether receiver account exists and valid
234 		Account receiverAccount = accountDao.getAccountById(transfer.getReceiverAcctId());
235 		
236 		if( receiverAccount != null && 
237 				( receiverAccount.getStatus().equals( Account.STATUS_INACTIVE) ||
238 				  receiverAccount.getStatus().equals( Account.STATUS_DISABLED) )
239 			){		
240 			String message = "Receiver account "+ transfer.getReceiverAcctId() + " is not a valid account";
241 			throw new NotValidAccountException(message);
242 		}
243 		
244 		
245 		// delete or subtract  the positions from sender		
246 		int remaining = transfer.getQuantity();
247 		for( AcctPosition senderPosition : positions){
248 			
249 			if( senderPosition.getQuantity() <= remaining ){
250 				remaining -= senderPosition.getQuantity();
251 				accountPositionDao.deleteAccountPosition(senderPosition);
252 			}else if( senderPosition.getQuantity() > remaining ){
253 				long updateQuantity = senderPosition.getQuantity() - remaining;
254 				remaining =0;
255 				senderPosition.setQuantity(updateQuantity);
256 				accountPositionDao.updateAccountPosition(senderPosition);
257 			}
258 
259 			if( remaining == 0){
260 				break;
261 			}else if( remaining < 0){
262 				// error condition. should never reach here.
263 				// we never know
264 			}
265 		}
266 
267 		// add positions to the receiver
268 		AcctPosition receiverPosition = new AcctPosition();
269 		receiverPosition.setAcctId(transfer.getReceiverAcctId());
270 		receiverPosition.setQuantity(transfer.getQuantity());
271 		receiverPosition.setSymbol(transfer.getSymbol());
272         receiverPosition.setPurchasePrice( transfer.getPrice());
273 
274         // calculate totalPrice
275         float totalPrice = receiverPosition.getPurchasePrice() * receiverPosition.getQuantity();
276         // TODO : total price should include commissions, fees etc
277         receiverPosition.setTotalPrice(totalPrice);
278 		
279 		long posId = accountPositionDao.generateAcctPositionId();
280 		receiverPosition.setId(posId);
281 		
282 		accountPositionDao.addAccountPosition(receiverPosition);
283 
284 
285 		// generate transfer Id
286 		long transferId = acctPositionTransferDao.generateTransferId();
287 		transfer.setTransferId(transferId);
288 		
289 		Date date = new Date();
290 		Timestamp createdTime = new Timestamp( date.getTime());	
291 		transfer.setCreatedTime(createdTime);
292 		
293 		// record CashTransfer
294 		transfer.setTransferStatus(CashTransfer.STATUS_COMPLETED);
295 		acctPositionTransferDao.addAcctPositionTransfer( transfer);		
296 		
297     }
298 
299     @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
300 	public AcctPosition addAccountPosition(AcctPosition position) {
301 		long acctPosId = accountPositionDao.generateAcctPositionId();
302 		position.setId(acctPosId);
303 		return accountPositionDao.addAccountPosition(position);
304 	}
305 
306     @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
307 	public void deleteAccountPosition(AcctPosition position) {
308 		accountPositionDao.deleteAccountPosition(position);
309 	}
310 
311 	public AcctPosition getAccountPosition(long acctPositionId) {
312 		return accountPositionDao.getAccountPosition(acctPositionId);
313 	}
314 
315 	public List<AcctPosition> getAccountPositions(String acctId) {
316 		return accountPositionDao.getAccountPositions(acctId);
317 	}
318 
319 	public List<AcctPosition> getAccountPositions(String acctId, String symbol) {
320 		return accountPositionDao.getAccountPositions(acctId, symbol);
321 	}
322 
323     public float getTotalValueOfPositions(String acctId) {
324 
325         List<AcctPosition> positions =  getAccountPositions( acctId );
326 
327         List<String> symbols = new ArrayList<String>();
328         for( AcctPosition pos : positions){
329             symbols.add( pos.getSymbol());
330         }
331 
332         float total = 0f;
333         List<Quote> quotes;
334         
335 		try {
336 			
337 			quotes = quoteMgr.getQuotes(symbols);
338 	        // calculate portfolio value based on last price
339 		     
340 	        for( AcctPosition pos : positions ){
341 	            String symbol = pos.getSymbol();
342 	            Quote quote=null;
343 	            for( Quote q : quotes ){
344 	                if( q.getSymbol().equals(symbol)){
345 	                    quote = q;
346 	                    break;
347 	                }
348 	            }
349 
350 	            if( quote != null ){
351 	                float value = pos.getQuantity() * quote.getLastPrice();
352 	                total += value;
353 	            }
354 	        }
355 	        
356 		} catch (SymbolsCannotBeEmptyException e) {
357 			e.printStackTrace();
358 		} 
359 
360 
361 
362         return total;
363     }
364 
365     @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
366 	public AcctPosition updateAccountPosition(AcctPosition position) {
367 		return accountPositionDao.updateAccountPosition(position);
368 	}
369 
370 	public List<AcctPositionSummary> getAccountPositionsSummary(String acctId) {
371 
372         List<AcctPosition> positions =  getAccountPositions( acctId );
373 
374         List<String> symbols = new ArrayList<String>();
375         for( AcctPosition pos : positions){
376             symbols.add( pos.getSymbol());
377         }
378 
379         float total = 0f;
380         List<Quote> quotes;
381         
382         List<AcctPositionSummary> acctSummaries = new ArrayList<AcctPositionSummary>();
383         
384 		try {
385 			
386 			quotes = quoteMgr.getQuotes(symbols);
387 	        // calculate portfolio value based on last price
388 			
389 			for( Quote q : quotes ){
390 				
391 					String symbol = q.getSymbol();
392 					int totalShares = 0;
393 					float totalPrice = 0;
394 					float totalCurrentPrice =0;
395 					
396 					// calculate the average price for each symbol
397 					for( AcctPosition pos : positions ){
398 						if( !q.getSymbol().equals(pos.getSymbol())){
399 							continue;
400 						}
401 						
402 						totalShares += pos.getQuantity();
403 						totalPrice += pos.getQuantity() * pos.getPurchasePrice();
404 						totalCurrentPrice += pos.getQuantity() * q.getLastPrice();
405 					}
406 					
407 					AcctPositionSummary acctPos = new AcctPositionSummary();
408 					
409 					acctPos.setAcctId(acctId);
410 					acctPos.setSymbol(symbol);
411 					acctPos.setQuantity(totalShares);
412 					float avgPurchasePrice = totalPrice / totalShares;
413 					acctPos.setAveragePurchasePrice(avgPurchasePrice);
414 					acctPos.setTotalPurchasePrice(totalPrice);
415 					
416 					acctPos.setCurrentPrice(q.getLastPrice());
417 					acctPos.setCurrentValue(totalCurrentPrice);
418 					
419 					acctSummaries.add(acctPos);
420 					
421 			}
422 		     
423 	        
424 		} catch (SymbolsCannotBeEmptyException e) {
425 			e.printStackTrace();
426 		} 
427 
428 		return acctSummaries;
429 	}
430 
431 	public List<AcctValue> getTopAccounts(int count) {
432 
433 		// get the list of all accounts that are not system accounts
434 		List<Account> accounts = getAccountsOfType( Account.TYPE_CASH);
435 		
436 		// get the total value of positions in batches
437 		List<AcctValue> acctValues = new ArrayList<AcctValue>();
438 		for( Account acct : accounts ){
439 			
440 			float totalValue = getTotalValueOfPositions( acct.getAcctId());
441 			
442 			AcctValue v  = new AcctValue();
443 			v.setAccount(acct);
444 			v.setValue( new Float(totalValue) );
445 			acctValues.add(v);
446 		}
447 		
448 		
449 		for( AcctValue value : acctValues ){
450 			CashBalance balance = getCashBalance( value.getAccount() );
451 			float total = value.getValue() + balance.getAmount();
452 			value.setValue(total);
453 		}
454 		
455 		// we need to sort in descending order
456 		Collections.sort(acctValues, new Comparator<AcctValue>(){
457 			public int compare(AcctValue o1, AcctValue o2) {
458 				int diff =  (int) (o1.getValue() - o2.getValue());
459 				return (-diff);
460 			}
461 		});
462 		
463 		// did we get 'count' number of records?
464 		int sortcount =0;
465 		if( count <= acctValues.size()){
466 			sortcount = count;
467 		}else{
468 			sortcount = acctValues.size();
469 		}
470 		
471 		//get Users for Account, for the first 'count' accounts that have highest value
472 		List<AcctValue> sortedList = new ArrayList<AcctValue>(sortcount);
473 		for( int i=0; i<sortcount; i++ ){
474 			AcctValue acctValue = acctValues.get(i);
475 			try {
476 				User user = userMgr.getUserById( acctValue.getAccount().getId() );
477 				acctValue.setUser(user);
478 				sortedList.add(acctValue);
479 			} catch (UserNotFoundException e) {
480 				e.printStackTrace();			
481 			}
482 		}
483 				
484 		return sortedList;
485 	}
486 	
487 	private Map<String,Float> sortMapByValue(Map<String,Float> map){
488 		
489 		  List<CustomEntry> list = new ArrayList<CustomEntry>();
490 		  Set<Map.Entry<String,Float>> entrySet = map.entrySet();
491 		  Iterator<Map.Entry<String,Float>>  iterator = entrySet.iterator();
492 		  
493 		  while (iterator.hasNext()) {
494 			Map.Entry<String,Float> entry = iterator.next();
495 		    CustomEntry customEntry = new CustomEntry(entry);
496 		    list.add(customEntry);
497 		  }
498 		  
499 		  Collections.sort(list);
500 		  
501 		  // now construct the map again
502 		  Map<String,Float> sortedMap = new HashMap<String,Float>();
503 		  for(CustomEntry entry : list ){
504 			  Map.Entry<String,Float> mEntry = entry.getEntry();
505 			  sortedMap.put(mEntry.getKey(), mEntry.getValue());
506 		  }
507 
508 		return sortedMap;
509 	}
510 	
511 	public class CustomEntry implements Comparable{
512 		  private Map.Entry<String,Float> entry;
513 
514 		  public CustomEntry(Map.Entry<String,Float> entry) {
515 		   this.entry = entry;
516 		  }
517 
518 		  public Map.Entry<String,Float> getEntry() {
519 		    return this.entry;
520 		  }
521 
522 		  public int compareTo(CustomEntry anotherEntry) {
523 		    Float thisFloatVal = this.getEntry().getValue();
524 		    float thisVal = thisFloatVal.floatValue();
525 		    Float anotherFloatVal = anotherEntry.getEntry().getValue();
526 		    float anotherVal = anotherFloatVal.floatValue();
527 		    return (thisVal > anotherVal ? 1 : (thisVal==anotherVal ? 0 : -1));
528 		  }
529 
530 		  public int compareTo(Object o) {
531 		    return compareTo((CustomEntry)o);
532 		  }
533 
534 		}
535 }