| | 4 | |
| | 5 | === executeTrade (точка за влез) === |
| | 6 | '''Опис:''' Методот `executeTrade` во `PortfolioService` е централната точка за влез која ја повикува `TradeRequestController` при одобрување на барање за тргување. Врши рутирање кон `buyStock` или `sellStock` врз основа на типот на трансакцијата. |
| | 7 | |
| | 8 | Целата операција е `@Transactional` - доколку кој било чекор не успее, се откажува целосно. |
| | 9 | |
| | 10 | {{{ |
| | 11 | @Transactional |
| | 12 | public void executeTrade(TradeRequest tr) { |
| | 13 | BigDecimal price = BigDecimal.valueOf(tr.getPricePerUnit()); |
| | 14 | Portfolio portfolio = tr.getPortfolio(); |
| | 15 | |
| | 16 | if ("BUY".equalsIgnoreCase(tr.getType())) { |
| | 17 | buyStock(portfolio.getId(), tr.getStockSymbol(), tr.getQuantity(), price); |
| | 18 | } else if ("SELL".equalsIgnoreCase(tr.getType())) { |
| | 19 | sellStock(portfolio.getId(), tr.getStockSymbol(), tr.getQuantity(), price); |
| | 20 | } else { |
| | 21 | throw new RuntimeException("Unknown trade type: " + tr.getType()); |
| | 22 | } |
| | 23 | } |
| | 24 | }}} |
| | 25 | |
| 14 | | public void buyStock(Long portfolioId, String stockSymbol, int quantity, BigDecimal pricePerUnit) { |
| 15 | | Portfolio portfolio = portfolioRepository.findById(portfolioId) |
| 16 | | .orElseThrow(() -> new RuntimeException("Portfolio not found")); |
| 17 | | |
| 18 | | |
| 19 | | //multiply for how much stocks are bught |
| 20 | | BigDecimal totalCost = pricePerUnit.multiply(BigDecimal.valueOf(quantity)); |
| 21 | | |
| 22 | | // if (portfolio.getBalance().compareTo(totalCost) < 0) { |
| 23 | | // throw new RuntimeException("not enough balance to buy stock"); |
| 24 | | // } |
| 25 | | //TODO - > BRING BACK , JUST FOR TESTING !! |
| 26 | | |
| 27 | | portfolio.setBalance(portfolio.getBalance().subtract(totalCost)); |
| 28 | | |
| 29 | | Stock stock1 = stockRepository.findBySymbol(stockSymbol) |
| 30 | | .orElseThrow(() -> new RuntimeException("Stock not found")); |
| 31 | | |
| 32 | | PortfolioHolding holding = holdingRepository |
| 33 | | .findByPortfolioIdAndStock_Symbol(portfolioId, stockSymbol) |
| 34 | | .orElse(PortfolioHolding.builder() |
| 35 | | .portfolio(portfolio) |
| 36 | | .stock(stock1) |
| 37 | | .quantity(0) |
| 38 | | .avgPrice(BigDecimal.ZERO) |
| 39 | | .build()); |
| 40 | | |
| 41 | | // avg price bought |
| 42 | | // alkaloid 2 x 22.000 + alkaloid 3x 28.000 average od ova |
| 43 | | BigDecimal currentTotalValue = holding.getAvgPrice().multiply(BigDecimal.valueOf(holding.getQuantity())); |
| 44 | | BigDecimal newTotalValue = currentTotalValue.add(totalCost); |
| 45 | | int newQuantity = holding.getQuantity() + quantity; |
| 46 | | BigDecimal newAvgPrice = newTotalValue.divide(BigDecimal.valueOf(newQuantity), BigDecimal.ROUND_HALF_UP); |
| 47 | | |
| 48 | | holding.setQuantity(newQuantity); |
| 49 | | holding.setAvgPrice(newAvgPrice); |
| 50 | | |
| 51 | | holdingRepository.save(holding); |
| 52 | | portfolioRepository.save(portfolio); |
| 53 | | |
| 54 | | |
| 55 | | //sava a transaction |
| 56 | | Transaction transaction = new Transaction(); |
| 57 | | transaction.setUser(portfolio.getUser()); |
| 58 | | |
| 59 | | Stock stock = stockRepository.findBySymbol(stockSymbol) |
| 60 | | .orElseThrow(() -> new RuntimeException("stock not found: " + stockSymbol)); |
| 61 | | transaction.setStock(stock); |
| 62 | | /* transaction.setStock(stockRepository.findBySymbol(stockSymbol) |
| 63 | | .orElseThrow(() -> new RuntimeException("Stock not found")));*/ |
| 64 | | transaction.setType("BUY"); |
| 65 | | transaction.setQuantity(quantity); |
| 66 | | transaction.setPrice(pricePerUnit.doubleValue()); |
| 67 | | transaction.setTimestamp(LocalDateTime.now()); |
| 68 | | |
| 69 | | transactionRepository.save(transaction); |
| 70 | | } |
| | 38 | public void buyStock(Long portfolioId, String stockSymbol, int quantity, BigDecimal pricePerUnit) { |
| | 39 | Portfolio portfolio = portfolioRepository.findById(portfolioId) |
| | 40 | .orElseThrow(() -> new RuntimeException("Portfolio not found")); |
| | 41 | |
| | 42 | BigDecimal totalCost = pricePerUnit.multiply(BigDecimal.valueOf(quantity)); |
| | 43 | |
| | 44 | if (portfolio.getBalance().compareTo(totalCost) < 0) { |
| | 45 | throw new RuntimeException("Insufficient balance to buy stock"); |
| | 46 | } |
| | 47 | |
| | 48 | portfolio.setBalance(portfolio.getBalance().subtract(totalCost)); |
| | 49 | |
| | 50 | Stock stock = stockRepository.findBySymbol(stockSymbol) |
| | 51 | .orElseThrow(() -> new RuntimeException("Stock not found: " + stockSymbol)); |
| | 52 | |
| | 53 | PortfolioHolding holding = holdingRepository |
| | 54 | .findByPortfolioIdAndStock_Symbol(portfolioId, stockSymbol) |
| | 55 | .orElse(PortfolioHolding.builder() |
| | 56 | .portfolio(portfolio) |
| | 57 | .stock(stock) |
| | 58 | .quantity(0) |
| | 59 | .avgPrice(BigDecimal.ZERO) |
| | 60 | .build()); |
| | 61 | |
| | 62 | // Recalculate weighted average price |
| | 63 | BigDecimal currentTotalValue = holding.getAvgPrice().multiply(BigDecimal.valueOf(holding.getQuantity())); |
| | 64 | BigDecimal newTotalValue = currentTotalValue.add(totalCost); |
| | 65 | int newQuantity = holding.getQuantity() + quantity; |
| | 66 | BigDecimal newAvgPrice = newTotalValue.divide(BigDecimal.valueOf(newQuantity), RoundingMode.HALF_UP); |
| | 67 | |
| | 68 | holding.setQuantity(newQuantity); |
| | 69 | holding.setAvgPrice(newAvgPrice); |
| | 70 | |
| | 71 | holdingRepository.save(holding); |
| | 72 | portfolioRepository.save(portfolio); |
| | 73 | |
| | 74 | // Save transaction |
| | 75 | Transaction transaction = new Transaction(); |
| | 76 | transaction.setUser(portfolio.getUser()); |
| | 77 | transaction.setStock(stock); |
| | 78 | transaction.setType("BUY"); |
| | 79 | transaction.setQuantity(quantity); |
| | 80 | transaction.setPrice(pricePerUnit.doubleValue()); |
| | 81 | transaction.setTimestamp(LocalDateTime.now()); |
| | 82 | transactionRepository.save(transaction); |
| | 83 | } |
| 84 | | public void sellStock(Long portfolioId, String stockSymbol, int quantity, BigDecimal pricePerUnit) { |
| 85 | | Portfolio portfolio = portfolioRepository.findById(portfolioId) |
| 86 | | .orElseThrow(() -> new RuntimeException("portfolio not found")); |
| 87 | | |
| 88 | | PortfolioHolding holding = holdingRepository |
| 89 | | .findByPortfolioIdAndStock_Symbol(portfolioId, stockSymbol) |
| 90 | | .orElseThrow(() -> new RuntimeException("stock not found in portfolio")); |
| 91 | | |
| 92 | | |
| 93 | | // checks |
| 94 | | if (holding.getQuantity() < quantity) { |
| 95 | | throw new RuntimeException("not enough shares to sell"); |
| 96 | | } |
| 97 | | |
| 98 | | if (pricePerUnit == null) { |
| 99 | | // fallback: get latest price from stock history or live API |
| 100 | | Stock stock = stockRepository.findBySymbol(stockSymbol) |
| 101 | | .orElseThrow(() -> new RuntimeException("stock not found: " + stockSymbol)); |
| 102 | | pricePerUnit = BigDecimal.valueOf(stock.getCurrentPrice()); |
| 103 | | } |
| 104 | | |
| 105 | | // gain from the sale made |
| 106 | | BigDecimal totalGain = pricePerUnit.multiply(BigDecimal.valueOf(quantity)); |
| 107 | | |
| 108 | | |
| 109 | | holding.setQuantity(holding.getQuantity() - quantity); |
| 110 | | |
| 111 | | // if holding is zero ==== remove it from database |
| 112 | | if (holding.getQuantity() == 0) { |
| 113 | | holdingRepository.delete(holding); |
| 114 | | } else { |
| 115 | | holdingRepository.save(holding); |
| 116 | | } |
| 117 | | |
| 118 | | // update |
| 119 | | portfolio.setBalance(portfolio.getBalance().add(totalGain)); |
| 120 | | portfolioRepository.save(portfolio); |
| 121 | | |
| 122 | | |
| | 97 | public void sellStock(Long portfolioId, String stockSymbol, int quantity, BigDecimal pricePerUnit) { |
| | 98 | Portfolio portfolio = portfolioRepository.findById(portfolioId) |
| | 99 | .orElseThrow(() -> new RuntimeException("Portfolio not found")); |
| | 100 | |
| | 101 | PortfolioHolding holding = holdingRepository |
| | 102 | .findByPortfolioIdAndStock_Symbol(portfolioId, stockSymbol) |
| | 103 | .orElseThrow(() -> new RuntimeException("Stock not found in portfolio")); |
| | 104 | |
| | 105 | if (holding.getQuantity() < quantity) { |
| | 106 | throw new RuntimeException("Not enough shares to sell"); |
| | 107 | } |
| | 108 | |
| | 109 | if (pricePerUnit == null) { |
| | 110 | // fallback: get latest price from stock |
| 124 | | .orElseThrow(() -> new RuntimeException("stock not found: " + stockSymbol)); |
| 125 | | |
| 126 | | Transaction transaction = new Transaction(); |
| 127 | | transaction.setUser(portfolio.getUser()); |
| 128 | | transaction.setStock(stock); |
| 129 | | transaction.setType("SELL"); |
| 130 | | transaction.setQuantity(quantity); |
| 131 | | transaction.setPrice(pricePerUnit.doubleValue()); |
| 132 | | transaction.setTimestamp(LocalDateTime.now()); |
| 133 | | |
| 134 | | transactionRepository.save(transaction); |
| 135 | | |
| 136 | | System.out.println("saved sell"); |
| 137 | | } |
| 138 | | }}} |
| | 112 | .orElseThrow(() -> new RuntimeException("Stock not found: " + stockSymbol)); |
| | 113 | pricePerUnit = BigDecimal.valueOf(stock.getCurrentPrice()); |
| | 114 | } |
| | 115 | |
| | 116 | // gain from the sale made |
| | 117 | BigDecimal totalGain = pricePerUnit.multiply(BigDecimal.valueOf(quantity)); |
| | 118 | |
| | 119 | holding.setQuantity(holding.getQuantity() - quantity); |
| | 120 | |
| | 121 | // if holding reaches zero - remove it from database |
| | 122 | if (holding.getQuantity() == 0) { |
| | 123 | holdingRepository.delete(holding); |
| | 124 | } else { |
| | 125 | holdingRepository.save(holding); |
| | 126 | } |
| | 127 | |
| | 128 | portfolio.setBalance(portfolio.getBalance().add(totalGain)); |
| | 129 | portfolioRepository.save(portfolio); |
| | 130 | |
| | 131 | // Save transaction |
| | 132 | Stock stock = stockRepository.findBySymbol(stockSymbol) |
| | 133 | .orElseThrow(() -> new RuntimeException("Stock not found: " + stockSymbol)); |
| | 134 | |
| | 135 | Transaction transaction = new Transaction(); |
| | 136 | transaction.setUser(portfolio.getUser()); |
| | 137 | transaction.setStock(stock); |
| | 138 | transaction.setType("SELL"); |
| | 139 | transaction.setQuantity(quantity); |
| | 140 | transaction.setPrice(pricePerUnit.doubleValue()); |
| | 141 | transaction.setTimestamp(LocalDateTime.now()); |
| | 142 | transactionRepository.save(transaction); |
| | 143 | } |
| | 144 | }}} |
| | 145 | |