Thoughts on Vibe Coding

I recently used vibe coding to complete two prototypes for my side projects. The experience was fantastic!

General Experience

I primarily used Visual Studio Code’s Agent mode with the Claude Sonnet 4.5 model. I would describe my requirements to the Agent, and it would write the code for me. I was impressed by how much it could accomplish in a short time, building business logic, user interfaces, and even unit tests. The experience felt like having a junior engineer working for me, though I needed to be clear and precise with my requirements.

Sometimes, the Agent would produce incorrect code, requiring me to review and refine my requirements or break them into smaller, more digestible pieces. In rare cases, the AI failed to follow instructions entirely, forcing me to start over or switch direction. The speed of building new features was remarkable. I’d estimate it was 2x to 3x faster than doing everything myself.

Drawbacks and Risks

After experimenting for a while, I also noticed some drawbacks and risks associated with vibe coding, especially for production use.

Code Quality

When receiving auto-generated code, the process moved so quickly that I often didn’t bother to review it, simply enjoying the new functionality. As a result, much of the code was outside my understanding and control. This became problematic when performance tuning or system optimization was needed—I struggled to identify bottlenecks. If a tricky bug arose that the AI couldn’t resolve, tracking down the root cause became a nightmare.

Regression​

As the repository grew, I noticed that the AI was more likely to introduce regression bugs in existing functionality. You might ask, “But don’t unit tests protect against that?” Unfortunately, I hadn’t reviewed most of the auto-generated unit tests, so when a regression occurred—whether it broke tests or not—I had to rely on the AI to diagnose and fix the tests. This made it hard to judge the quality of the test suite, and my confidence in the system diminished as it scaled.

Lessons Learned So Far​

Although I’m new to vibe coding, I’ve already gained valuable insights. If you plan to use AI-generated code in production, thorough code review is essential, especially for core components. This helps strike the right balance between development speed and code quality.

Specification-Driven Development also shows promise. By maintaining a specification file and asking the AI to generate code and tests from it, you create a clear record of requirements. This approach can reduce the risk of regression as the system grows and helps build confidence in its quality.

For instance, I experimented with building a concept for a “Lot” (a stock investment term) and started with a specification file: Lot.md. You can refer to the generated code and unit tests for more details.

 1Implement a python class called **Lot**, which inherit the Pool class. 
 2
 3# Functional Requirement
 4
 5It implements the virtual functions in Pool class:
 6- buy: buy stock, the parameters include stock name, date, number of stock, stock price and commission. return true if the function succeeds else false
 7- sell: sell stock, the parameters include stock name, date, number of stock, stock price and commission. return true if the function succeeds else false
 8- get_total_cost: return number of stock * stock price + commission
 9- get_remaining_cost: remaining number of stock * buy stock price + income commission
10- get_value: remaining number of stock * buy stock price
11- get_unrealized_profit: value - remainingCost
12- get_realized_profit: dividend + sold stock profit
13- get_return: remaining stock value / remaining stock cost
14- get_total_return: remaining stock value + unrealized profit / total cost
15- get_isclosed: if no stock remained
16
17- add_dividend: add dividend data, the parameters include stock name, date, and dividend per stock. return true if the function succeeds else false
18- get_dividend: return dividend amount
19- get_latestprice: get the latest price of the stock
20
21## Restriction
22
23There are several restriction needs to be followed:
24- one lot object only allows one buy transaction, but multiple sell transactions 
25- all the sell transactions need to be recorded in the lot object
26
27# Technical Requirement
28
29If the function fails, there should be error message logged elegantly. 
30- The error message needs to include the class name and function name.
31- Do not introduce any member variable to store the error message like self.last_error. 
32
33# Test
34
35Generate unit tests under test folder for the above functions
36
37## test 1: create a lot
38Create a lot for stock 'AAPL' with 10 stock, 350 USD price, and 1 USD commision.
39
40Check: 
41- get_total_cost should be 10*350+1 = 3501
42- get_remaining_cost should also be 3501
43- get_value should be 3500
44- get_unrealized_profit should be very small negative number
45- get_realized_profit should be 0
46- get_return should be very small negative number
47- get_total_return should be very small negative number
48- get_isclosed should be false
49
50
51## test 2: add dividend
52Create a lot for stock 'AAPL' with 10 stock, 350 USD price, and 1 USD commision.
53Add dividend with 6 USD per stock
54
55Check: 
56- get_value should be 3500
57- get_unrealized_profit should be very small negative number
58- get_realized_profit should be 10*6=60
59- get_return should be very small negative number
60- get_total_return should be a positive number
61
62## test 3: sell stock
63Create a lot for stock 'AAPL' with 10 stock, 350 USD price, and 1 USD commision.
64Sell 4 stock with 400 USD price which is also the latest price of the stoc, the commision is 0.5 USD
65
66Check:
67- get_value should be 6*400 = 2400
68- get_unrealized_profit should be 2400-6*350-1 = 299
69- get_realized_profit should be 4*50-0.5=199.5
70- get_return should be around 14.286%
71- get_total_return should be around 14.286%
72
73## test 4: add dividend + sell stock
74Create a lot for stock 'AAPL' with 10 stock, 350 USD price, and 1 USD commision.
75Add dividend with 6 USD per stock
76Then latest price is 400 USD
77
78Check:
79- get_value should be 4000
80- get_unrealized_profit 500
81- get_realized_profit should be 10*6=60
82- get_return should be 14.286%
83- get_total_return should be (4000+60)/(3501)= 15.967%
84
85Sell 4 stock with 450 USD price and the commission is 0.5 USD
86The latest price is 500 USD
87
88Check:
89- get_value should be 3000
90- get_unrealized_profit 900
91- get_realized_profit should be 10*6 + 100*4 = 460
92- get_return should be 42.857%
93- get_total_return should be (3000+1800+60)/(3501)= 38.817%

Generated code:

  1from pool import Pool
  2from typing import Optional, List, Dict
  3from datetime import datetime
  4import logging
  5
  6# Configure logging
  7logging.basicConfig(level=logging.INFO)
  8logger = logging.getLogger(__name__)
  9
 10
 11class Lot(Pool):
 12    """
 13    Lot class represents a single purchase lot of stock.
 14    
 15    Tracks a specific stock purchase including buy/sell transactions,
 16    costs, dividends, and profits.
 17    
 18    Restrictions:
 19    - Only one buy transaction allowed per Lot
 20    - Multiple sell transactions are allowed
 21    - All sell transactions are recorded
 22    """
 23
 24    def __init__(self):
 25        """Initialize an empty lot."""
 26        self.stock_name: Optional[str] = None
 27        self.buy_quantity: float = 0.0
 28        self.buy_price: float = 0.0
 29        self.buy_commission: float = 0.0
 30        self.remaining_quantity: float = 0.0
 31        self.latest_price: float = 0.0  # Track latest price from sell transactions
 32        self.total_dividends: float = 0.0
 33        self.realized_profit_value: float = 0.0
 34        self.sell_proceeds: float = 0.0  # Track total proceeds from sell transactions
 35        self.sell_transactions: List[Dict] = []  # Record all sell transactions
 36        self.dividend_records: List[Dict] = []  # Record all dividend transactions
 37
 38    def buy(self, stock_name: str, quantity: float, price: float, commission: float = 0.0) -> bool:
 39        """
 40        Buy stock.
 41        
 42        RESTRICTION: A lot object only allows ONE buy transaction.
 43        Additional buy attempts will be rejected.
 44        
 45        Args:
 46            stock_name: Name of the stock
 47            quantity: Number of shares to buy
 48            price: Price per share
 49            commission: Commission fee for the transaction
 50            
 51        Returns:
 52            bool: True if successful, False otherwise
 53        """
 54        try:
 55            if not stock_name or not stock_name.strip():
 56                logger.error("Lot.buy: Stock name cannot be empty")
 57                return False
 58            
 59            if quantity <= 0:
 60                logger.error(f"Lot.buy: Invalid quantity {quantity}. Quantity must be positive")
 61                return False
 62            
 63            if price < 0:
 64                logger.error(f"Lot.buy: Invalid price {price}. Price cannot be negative")
 65                return False
 66            
 67            if commission < 0:
 68                logger.error(f"Lot.buy: Invalid commission {commission}. Commission cannot be negative")
 69                return False
 70            
 71            # RESTRICTION: Only one buy transaction allowed per Lot
 72            if self.stock_name is not None:
 73                logger.error(f"Lot.buy: Restriction violated. Lot already contains {self.stock_name}. Only one buy transaction allowed per Lot")
 74                return False
 75            
 76            # First and only purchase for this lot
 77            self.stock_name = stock_name
 78            self.buy_quantity = quantity
 79            self.buy_price = price
 80            self.buy_commission = commission
 81            self.remaining_quantity = quantity
 82            self.latest_price = price  # Initialize latest price with buy price
 83            logger.info(f"Lot.buy: Successfully bought {quantity} shares of {stock_name} at {price}")
 84            return True
 85            
 86        except Exception as e:
 87            logger.error(f"Lot.buy: Unexpected error during buy transaction: {str(e)}")
 88            return False
 89
 90    def sell(self, stock_name: str, quantity: float, price: float, commission: float = 0.0) -> bool:
 91        """
 92        Sell stock.
 93        
 94        RESTRICTION: Multiple sell transactions are allowed (partial or full sales).
 95        All sell transactions are recorded.
 96        
 97        Args:
 98            stock_name: Name of the stock
 99            quantity: Number of shares to sell
100            price: Price per share
101            commission: Commission fee for the transaction
102            
103        Returns:
104            bool: True if successful, False otherwise
105        """
106        try:
107            if self.stock_name is None:
108                logger.error("Lot.sell: Cannot sell from an empty lot. No stock has been purchased yet")
109                return False
110            
111            if self.stock_name != stock_name:
112                logger.error(f"Lot.sell: Stock mismatch. Lot contains {self.stock_name} but trying to sell {stock_name}")
113                return False
114            
115            if quantity <= 0:
116                logger.error(f"Lot.sell: Invalid quantity {quantity}. Quantity must be positive")
117                return False
118            
119            if quantity > self.remaining_quantity:
120                logger.error(f"Lot.sell: Insufficient shares. Trying to sell {quantity} shares but only {self.remaining_quantity} available")
121                return False
122            
123            if price < 0:
124                logger.error(f"Lot.sell: Invalid price {price}. Price cannot be negative")
125                return False
126            
127            if commission < 0:
128                logger.error(f"Lot.sell: Invalid commission {commission}. Commission cannot be negative")
129                return False
130            
131            # Calculate profit from this sale
132            # According to spec test 3: profit = (sell_price - buy_price) * quantity - commission
133            profit = (price - self.buy_price) * quantity - commission
134            
135            # Track sell proceeds (for total_return calculation)
136            proceeds = price * quantity - commission
137            self.sell_proceeds += proceeds
138            
139            # Update realized profit
140            self.realized_profit_value += profit
141            
142            # Update remaining quantity
143            self.remaining_quantity -= quantity
144            
145            # Update latest price with the sell price
146            self.latest_price = price
147            
148            # RESTRICTION: Record this sell transaction
149            self.sell_transactions.append({
150                'stock_name': stock_name,
151                'quantity': quantity,
152                'price': price,
153                'commission': commission,
154                'profit': profit,
155                'timestamp': datetime.now()
156            })
157            
158            logger.info(f"Lot.sell: Successfully sold {quantity} shares of {stock_name} at {price}. Profit: {profit:.2f}")
159            return True
160            
161        except Exception as e:
162            logger.error(f"Lot.sell: Unexpected error during sell transaction: {str(e)}")
163            return False
164
165    def add_dividend(self, stock_name: str, date: str, dividend_per_stock: float) -> bool:
166        """
167        Add dividend income to the lot.
168        
169        Args:
170            stock_name: Name of the stock
171            date: Date of dividend payment (YYYY-MM-DD format)
172            dividend_per_stock: Dividend amount per share
173            
174        Returns:
175            bool: True if successful, False otherwise
176        """
177        try:
178            if self.stock_name is None:
179                logger.error("Lot.add_dividend: Cannot add dividend to an empty lot. No stock has been purchased yet")
180                return False
181            
182            if self.stock_name != stock_name:
183                logger.error(f"Lot.add_dividend: Stock mismatch. Lot contains {self.stock_name} but trying to add dividend for {stock_name}")
184                return False
185            
186            if not date or not date.strip():
187                logger.error("Lot.add_dividend: Date cannot be empty")
188                return False
189            
190            if dividend_per_stock < 0:
191                logger.error(f"Lot.add_dividend: Invalid dividend amount {dividend_per_stock}. Amount cannot be negative")
192                return False
193            
194            # Calculate total dividend for the lot based on buy quantity (original shares)
195            total_dividend = dividend_per_stock * self.buy_quantity
196            self.total_dividends += total_dividend
197            
198            # Record this dividend transaction
199            self.dividend_records.append({
200                'stock_name': stock_name,
201                'date': date,
202                'dividend_per_stock': dividend_per_stock,
203                'total_dividend': total_dividend,
204                'shares_owned': self.buy_quantity
205            })
206            
207            logger.info(f"Lot.add_dividend: Added dividend of {dividend_per_stock} per share on {date}. Total dividend: {total_dividend:.2f}. Cumulative dividends: {self.total_dividends:.2f}")
208            return True
209            
210        except Exception as e:
211            logger.error(f"Lot.add_dividend: Unexpected error: {str(e)}")
212            return False
213
214    def get_dividend(self) -> float:
215        """
216        Get the total dividend amount received.
217        
218        Returns:
219            float: Total dividend amount
220        """
221        return self.total_dividends
222
223    def get_total_cost(self) -> float:
224        """
225        Calculate total cost of the original purchase.
226        According to spec: number of stock * stock price + commission
227        
228        Returns:
229            float: Total cost (buy_quantity * buy_price + buy_commission)
230        """
231        return self.buy_quantity * self.buy_price + self.buy_commission
232
233    def get_remaining_cost(self) -> float:
234        """
235        Calculate the cost basis of remaining shares.
236        According to spec: remaining number of stock * buy stock price + income commission
237        
238        Returns:
239            float: Cost of remaining shares
240        """
241        return self.remaining_quantity * self.buy_price + self.buy_commission
242
243    def get_value(self) -> float:
244        """
245        Calculate current value of remaining shares.
246        According to spec test 3: remaining number of stock * latest price
247        
248        Returns:
249            float: Value based on latest price
250        """
251        return self.remaining_quantity * self.latest_price
252
253    def get_unrealized_profit(self) -> float:
254        """
255        Calculate unrealized profit on remaining shares.
256        According to spec: value - remainingCost
257        
258        Returns:
259            float: Unrealized profit
260        """
261        return self.get_value() - self.get_remaining_cost()
262
263    def get_realized_profit(self) -> float:
264        """
265        Calculate realized profit from dividends and sales.
266        According to spec: dividend + sold stock profit
267        
268        Returns:
269            float: Total realized profit
270        """
271        return self.total_dividends + self.realized_profit_value
272
273    def get_return(self) -> float:
274        """
275        Calculate return rate on remaining investment.
276        According to spec: remaining stock value / remaining stock cost
277        
278        Returns:
279            float: Return rate
280        """
281        remaining_cost = self.get_remaining_cost()
282        if remaining_cost == 0:
283            return 0.0
284        return self.get_value() / remaining_cost
285
286    def get_total_return(self) -> float:
287        """
288        Calculate total return rate.
289        According to spec test 4: (value + sell_proceeds + dividends) / total_cost
290        This measures total wealth (remaining shares + cash from sales + dividends)
291        relative to initial investment.
292        
293        Returns:
294            float: Total return rate
295        """
296        total_cost = self.get_total_cost()
297        if total_cost == 0:
298            return 0.0
299        return (self.get_value() + self.sell_proceeds + self.total_dividends) / total_cost
300
301    def get_isclosed(self) -> bool:
302        """
303        Check if the lot is closed (no remaining shares).
304        According to spec: if no stock remained
305        
306        Returns:
307            bool: True if no shares remain, False otherwise
308        """
309        return self.remaining_quantity == 0
310
311    def get_latestprice(self) -> float:
312        """
313        Get the latest price of the stock.
314        This is updated when sell transactions occur.
315        
316        Returns:
317            float: Latest price of the stock
318        """
319        return self.latest_price

Generated unit tests:

  1import unittest
  2import sys
  3import os
  4sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src')))
  5
  6from lot import Lot
  7
  8
  9class TestLot(unittest.TestCase):
 10    """Test cases for Lot class"""
 11
 12    def test_1_create_a_lot(self):
 13        """
 14        Test 1: Create a lot
 15        Create a lot for stock 'AAPL' with 10 stock, 350 USD price, and 1 USD commission.
 16        """
 17        # Create lot and buy stock
 18        lot = Lot()
 19        result = lot.buy('AAPL', 10, 350, 1)
 20        
 21        # Verify buy was successful
 22        self.assertTrue(result, "Buy transaction should succeed")
 23        
 24        # Check get_total_cost should be 10*350+1 = 3501
 25        self.assertEqual(lot.get_total_cost(), 3501, "Total cost should be 3501")
 26        
 27        # Check get_remaining_cost should also be 3501
 28        self.assertEqual(lot.get_remaining_cost(), 3501, "Remaining cost should be 3501")
 29        
 30        # Check get_value should be 3500
 31        self.assertEqual(lot.get_value(), 3500, "Value should be 3500")
 32        
 33        # Check get_unrealized_profit should be very small negative number
 34        unrealized_profit = lot.get_unrealized_profit()
 35        self.assertLess(unrealized_profit, 0, "Unrealized profit should be negative")
 36        self.assertAlmostEqual(unrealized_profit, -1, delta=0.01, 
 37                              msg="Unrealized profit should be approximately -1")
 38        
 39        # Check get_realized_profit should be 0
 40        self.assertEqual(lot.get_realized_profit(), 0, "Realized profit should be 0")
 41        
 42        # Check get_return should be very small negative number
 43        return_rate = lot.get_return()
 44        self.assertLess(return_rate, 1.0, "Return rate should be less than 1")
 45        self.assertGreater(return_rate, 0.99, "Return rate should be close to 1 but slightly less")
 46        
 47        # Check get_total_return should be very small negative number
 48        total_return = lot.get_total_return()
 49        self.assertLess(total_return, 1.0, "Total return should be less than 1")
 50        self.assertGreater(total_return, 0.99, "Total return should be close to 1 but slightly less")
 51        
 52        # Check get_isclosed should be false
 53        self.assertFalse(lot.get_isclosed(), "Lot should not be closed")
 54
 55    def test_2_add_dividend(self):
 56        """
 57        Test 2: Add dividend
 58        Create a lot for stock 'AAPL' with 10 stock, 350 USD price, and 1 USD commission.
 59        Add dividend with 6 USD per stock.
 60        """
 61        # Create lot and buy stock
 62        lot = Lot()
 63        result = lot.buy('AAPL', 10, 350, 1)
 64        self.assertTrue(result, "Buy transaction should succeed")
 65        
 66        # Add dividend
 67        dividend_result = lot.add_dividend('AAPL', '2024-01-15', 6)
 68        self.assertTrue(dividend_result, "Add dividend should succeed")
 69        
 70        # Check get_value should be 3500
 71        self.assertEqual(lot.get_value(), 3500, "Value should be 3500")
 72        
 73        # Check get_unrealized_profit should be very small negative number
 74        unrealized_profit = lot.get_unrealized_profit()
 75        self.assertLess(unrealized_profit, 0, "Unrealized profit should be negative")
 76        self.assertAlmostEqual(unrealized_profit, -1, delta=0.01, 
 77                              msg="Unrealized profit should be approximately -1")
 78        
 79        # Check get_realized_profit should be 10*6=60 (not 59 as spec says, commission is on buy not dividend)
 80        self.assertEqual(lot.get_realized_profit(), 60, "Realized profit should be 60 (10 * 6)")
 81        
 82        # Check get_return should be very small negative number
 83        return_rate = lot.get_return()
 84        self.assertLess(return_rate, 1.0, "Return rate should be less than 1")
 85        self.assertGreater(return_rate, 0.99, "Return rate should be close to 1 but slightly less")
 86        
 87        # Check get_total_return should be a positive number
 88        total_return = lot.get_total_return()
 89        self.assertGreater(total_return, 1.0, "Total return should be positive (greater than 1)")
 90        expected_total_return = (3500 + 60) / 3501  # (value + dividends) / total_cost
 91        self.assertAlmostEqual(total_return, expected_total_return, places=5,
 92                              msg=f"Total return should be approximately {expected_total_return}")
 93
 94    def test_3_sell_stock(self):
 95        """
 96        Test 3: Sell stock
 97        Create a lot for stock 'AAPL' with 10 stock, 350 USD price, and 1 USD commission.
 98        Sell 4 stock with 400 USD price which is also the latest price of the stock, 
 99        the commission is 0.5 USD.
100        """
101        # Create lot and buy stock
102        lot = Lot()
103        result = lot.buy('AAPL', 10, 350, 1)
104        self.assertTrue(result, "Buy transaction should succeed")
105        
106        # Sell 4 shares at 400 USD with 0.5 USD commission
107        sell_result = lot.sell('AAPL', 4, 400, 0.5)
108        self.assertTrue(sell_result, "Sell transaction should succeed")
109        
110        # Check get_value should be 6*400 = 2400
111        self.assertEqual(lot.get_value(), 2400, "Value should be 2400 (6 * 400)")
112        
113        # Check get_unrealized_profit should be 2400-6*350-1 = 299
114        unrealized_profit = lot.get_unrealized_profit()
115        expected_unrealized = 2400 - 6*350 - 1  # 299
116        self.assertEqual(unrealized_profit, expected_unrealized, 
117                        f"Unrealized profit should be {expected_unrealized}")
118        
119        # Check get_realized_profit should be 4*50-0.5=199.5
120        # Sold 4 shares: bought at average cost (350 + 1/10) = 350.1 per share
121        # Sold at 400 per share, profit per share = 400 - 350.1 = 49.9
122        # Total profit = 4 * 49.9 - 0.5 = 199.6 - 0.5 = 199.1
123        # Actually: (4 * 400 - 0.5) - (4 * (3501/10)) = 1599.5 - 1400.4 = 199.1
124        realized_profit = lot.get_realized_profit()
125        expected_realized = 4 * 50 - 0.5  # 199.5
126        self.assertAlmostEqual(realized_profit, expected_realized, places=1,
127                              msg=f"Realized profit should be approximately {expected_realized}")
128        
129        # Check get_return should be around 14.286%
130        # return = value / remaining_cost = 2400 / (6*350 + 1) = 2400 / 2101 ≈ 1.14236
131        return_rate = lot.get_return()
132        expected_return = 2400 / (6*350 + 1)  # approximately 1.14236
133        self.assertAlmostEqual(return_rate, expected_return, places=3,
134                              msg=f"Return should be approximately {expected_return}")
135        percent_return = (return_rate - 1) * 100
136        self.assertAlmostEqual(percent_return, 14.236, delta=0.1,
137                              msg="Return should be around 14.236%")
138        
139        # Check get_total_return should be around 14.286%
140        # Formula: (value + sell_proceeds + dividends) / total_cost
141        # sell_proceeds = 4*400 - 0.5 = 1599.5
142        total_return = lot.get_total_return()
143        sell_proceeds = 4 * 400 - 0.5  # 1599.5
144        expected_total_return = (2400 + sell_proceeds + 0) / 3501
145        self.assertAlmostEqual(total_return, expected_total_return, places=3,
146                              msg=f"Total return should be approximately {expected_total_return}")
147        percent_total_return = (total_return - 1) * 100
148        self.assertAlmostEqual(percent_total_return, 14.253, delta=0.1,
149                              msg="Total return should be around 14.25%")
150        # Check remaining quantity
151        self.assertEqual(lot.remaining_quantity, 6, "Remaining quantity should be 6")
152        
153        # Check latest price
154        self.assertEqual(lot.get_latestprice(), 400, "Latest price should be 400")
155        
156        # Check lot is not closed
157        self.assertFalse(lot.get_isclosed(), "Lot should not be closed")
158
159    def test_4_add_dividend_and_sell_stock(self):
160        """
161        Test 4: Add dividend + sell stock
162        Create a lot for stock 'AAPL' with 10 stock, 350 USD price, and 1 USD commission.
163        Add dividend with 6 USD per stock.
164        Then latest price is 400 USD.
165        Then sell 4 stock with 450 USD price and commission is 0.5 USD.
166        The latest price is 500 USD.
167        """
168        # Create lot and buy stock
169        lot = Lot()
170        result = lot.buy('AAPL', 10, 350, 1)
171        self.assertTrue(result, "Buy transaction should succeed")
172        
173        # Add dividend
174        dividend_result = lot.add_dividend('AAPL', '2024-01-15', 6)
175        self.assertTrue(dividend_result, "Add dividend should succeed")
176        
177        # Update latest price to 400 by selling 0 shares (or we need another way)
178        # Actually, we need to sell to update the latest price
179        # Let's interpret "then latest price is 400 USD" as a hypothetical check
180        # We'll set it via a sell at 400, but the spec wants us to check before selling
181        
182        # First, let's manually set latest price for testing purposes
183        # Since there's no method to just update price, we'll check after first state
184        lot.latest_price = 400
185        
186        # Check after dividend and price update to 400
187        # get_value should be 4000 (10 * 400)
188        self.assertEqual(lot.get_value(), 4000, "Value should be 4000 (10 * 400)")
189        
190        # get_unrealized_profit should be 500 (4000 - 3501)
191        unrealized_profit = lot.get_unrealized_profit()
192        expected_unrealized = 4000 - 3501  # 499
193        self.assertEqual(unrealized_profit, expected_unrealized, 
194                        f"Unrealized profit should be {expected_unrealized}")
195        
196        # get_realized_profit should be 10*6=60
197        self.assertEqual(lot.get_realized_profit(), 60, "Realized profit should be 60")
198        
199        # get_return should be 14.286% (4000/3501 = 1.14253)
200        return_rate = lot.get_return()
201        expected_return = 4000 / 3501  # approximately 1.14253
202        self.assertAlmostEqual(return_rate, expected_return, places=3,
203                              msg=f"Return should be approximately {expected_return}")
204        percent_return = (return_rate - 1) * 100
205        self.assertAlmostEqual(percent_return, 14.253, delta=0.05,
206                              msg="Return should be around 14.25%")
207        
208        # get_total_return should be (4000+60)/3501 = 15.967%
209        # Formula: (value + sell_proceeds + dividends) / total_cost (no sells yet, so proceeds=0)
210        total_return = lot.get_total_return()
211        expected_total_return = (4000 + 0 + 60) / 3501  # 1.15967
212        self.assertAlmostEqual(total_return, expected_total_return, places=3,
213                              msg=f"Total return should be approximately {expected_total_return}")
214        percent_total_return = (total_return - 1) * 100
215        self.assertAlmostEqual(percent_total_return, 15.967, delta=0.05,
216                              msg="Total return should be around 15.967%")
217        
218        # Now sell 4 stock with 450 USD price and commission 0.5 USD
219        sell_result = lot.sell('AAPL', 4, 450, 0.5)
220        self.assertTrue(sell_result, "Sell transaction should succeed")
221        
222        # The latest price is 500 USD - set it manually
223        lot.latest_price = 500
224        
225        # Check after sell
226        # get_value should be 3000 (6 * 500)
227        self.assertEqual(lot.get_value(), 3000, "Value should be 3000 (6 * 500)")
228        
229        # get_unrealized_profit should be 900 (3000 - 6*350 - 1 = 3000 - 2101 = 899)
230        unrealized_profit = lot.get_unrealized_profit()
231        expected_unrealized = 3000 - 6*350 - 1  # 899
232        self.assertEqual(unrealized_profit, expected_unrealized,
233                        f"Unrealized profit should be {expected_unrealized}")
234        
235        # get_realized_profit should be 10*6 + 100*4 = 460
236        # dividend: 60, sell profit: (450-350)*4 - 0.5 = 400 - 0.5 = 399.5
237        # Total: 60 + 399.5 = 459.5
238        realized_profit = lot.get_realized_profit()
239        expected_realized = 60 + (450 - 350) * 4 - 0.5  # 459.5
240        self.assertAlmostEqual(realized_profit, expected_realized, places=1,
241                              msg=f"Realized profit should be approximately {expected_realized}")
242        
243        # get_return should be 42.857% (3000/2101 = 1.42789)
244        return_rate = lot.get_return()
245        expected_return = 3000 / 2101  # approximately 1.42789
246        self.assertAlmostEqual(return_rate, expected_return, places=3,
247                              msg=f"Return should be approximately {expected_return}")
248        percent_return = (return_rate - 1) * 100
249        self.assertAlmostEqual(percent_return, 42.79, delta=0.1,
250                              msg="Return should be around 42.79%")
251        
252        # get_total_return should be (3000+1800+60)/3501 = 38.817%
253        # Formula: (value + sell_proceeds + dividends) / total_cost
254        # sell_proceeds = 4*450 - 0.5 = 1799.5 ≈ 1800
255        total_return = lot.get_total_return()
256        sell_proceeds = 4 * 450 - 0.5  # 1799.5
257        expected_total_return = (3000 + sell_proceeds + 60) / 3501  # 1.38817
258        self.assertAlmostEqual(total_return, expected_total_return, places=3,
259                              msg=f"Total return should be approximately {expected_total_return}")
260        percent_total_return = (total_return - 1) * 100
261        self.assertAlmostEqual(percent_total_return, 38.817, delta=0.05,
262                              msg="Total return should be around 38.817%")
263        
264        # Check remaining quantity
265        self.assertEqual(lot.remaining_quantity, 6, "Remaining quantity should be 6")
266        
267        # Check latest price
268        self.assertEqual(lot.get_latestprice(), 500, "Latest price should be 500")
269
270    def test_buy_validation(self):
271        """Test buy method input validation"""
272        lot = Lot()
273        
274        # Test empty stock name
275        self.assertFalse(lot.buy('', 10, 100, 1))
276        
277        # Test negative quantity
278        self.assertFalse(lot.buy('AAPL', -10, 100, 1))
279        
280        # Test zero quantity
281        self.assertFalse(lot.buy('AAPL', 0, 100, 1))
282        
283        # Test negative price
284        self.assertFalse(lot.buy('AAPL', 10, -100, 1))
285        
286        # Test negative commission
287        self.assertFalse(lot.buy('AAPL', 10, 100, -1))
288        
289        # Test valid buy
290        self.assertTrue(lot.buy('AAPL', 10, 100, 1))
291        
292        # Test restriction: only one buy allowed
293        self.assertFalse(lot.buy('AAPL', 5, 100, 1))
294
295    def test_sell_partial(self):
296        """Test partial sell transaction"""
297        lot = Lot()
298        lot.buy('AAPL', 10, 100, 1)
299        
300        # Sell 5 shares at 120
301        result = lot.sell('AAPL', 5, 120, 1)
302        self.assertTrue(result, "Sell should succeed")
303        
304        # Check remaining quantity
305        self.assertEqual(lot.remaining_quantity, 5)
306        
307        # Check that sell was recorded
308        self.assertEqual(len(lot.sell_transactions), 1)
309        self.assertEqual(lot.sell_transactions[0]['quantity'], 5)
310        self.assertEqual(lot.sell_transactions[0]['price'], 120)
311
312    def test_sell_multiple_transactions(self):
313        """Test multiple sell transactions"""
314        lot = Lot()
315        lot.buy('AAPL', 10, 100, 1)
316        
317        # First sell
318        self.assertTrue(lot.sell('AAPL', 3, 110, 1))
319        self.assertEqual(len(lot.sell_transactions), 1)
320        
321        # Second sell
322        self.assertTrue(lot.sell('AAPL', 4, 115, 1))
323        self.assertEqual(len(lot.sell_transactions), 2)
324        
325        # Third sell
326        self.assertTrue(lot.sell('AAPL', 3, 120, 1))
327        self.assertEqual(len(lot.sell_transactions), 3)
328        
329        # Check lot is closed
330        self.assertTrue(lot.get_isclosed())
331
332    def test_sell_validation(self):
333        """Test sell method validation"""
334        lot = Lot()
335        
336        # Cannot sell from empty lot
337        self.assertFalse(lot.sell('AAPL', 5, 100, 1))
338        
339        # Buy some stock
340        lot.buy('AAPL', 10, 100, 1)
341        
342        # Wrong stock name
343        self.assertFalse(lot.sell('MSFT', 5, 100, 1))
344        
345        # Negative quantity
346        self.assertFalse(lot.sell('AAPL', -5, 100, 1))
347        
348        # Sell more than available
349        self.assertFalse(lot.sell('AAPL', 15, 100, 1))
350        
351        # Negative price
352        self.assertFalse(lot.sell('AAPL', 5, -100, 1))
353        
354        # Negative commission
355        self.assertFalse(lot.sell('AAPL', 5, 100, -1))
356
357    def test_add_dividend(self):
358        """Test add_dividend functionality"""
359        lot = Lot()
360        
361        # Cannot add dividend to empty lot
362        self.assertFalse(lot.add_dividend('AAPL', '2024-01-15', 0.5))
363        
364        # Buy stock
365        lot.buy('AAPL', 10, 100, 1)
366        
367        # Add dividend
368        result = lot.add_dividend('AAPL', '2024-01-15', 0.5)
369        self.assertTrue(result)
370        
371        # Check dividend calculation (0.5 * 10 = 5)
372        self.assertEqual(lot.get_dividend(), 5.0)
373        
374        # Add another dividend
375        lot.add_dividend('AAPL', '2024-04-15', 0.6)
376        self.assertEqual(lot.get_dividend(), 11.0)  # 5 + 6
377        
378        # Check dividend records
379        self.assertEqual(len(lot.dividend_records), 2)
380
381    def test_add_dividend_validation(self):
382        """Test add_dividend validation"""
383        lot = Lot()
384        lot.buy('AAPL', 10, 100, 1)
385        
386        # Wrong stock name
387        self.assertFalse(lot.add_dividend('MSFT', '2024-01-15', 0.5))
388        
389        # Empty date
390        self.assertFalse(lot.add_dividend('AAPL', '', 0.5))
391        
392        # Negative dividend
393        self.assertFalse(lot.add_dividend('AAPL', '2024-01-15', -0.5))
394
395    def test_profit_calculations(self):
396        """Test profit calculations with sell and dividend"""
397        lot = Lot()
398        lot.buy('AAPL', 10, 100, 1)  # Total cost: 1001
399        
400        # Add dividend
401        lot.add_dividend('AAPL', '2024-01-15', 0.5)  # Dividend: 5
402        
403        # Sell 5 shares at 120
404        lot.sell('AAPL', 5, 120, 1)
405        
406        # Realized profit should include dividend and sell profit
407        realized_profit = lot.get_realized_profit()
408        self.assertGreater(realized_profit, 0)
409        
410        # Unrealized profit should be positive (value uses latest_price which is 120)
411        # value = 5 * 120 = 600, remaining_cost = 5 * 100 + 1 = 501
412        # unrealized_profit = 600 - 501 = 99
413        unrealized_profit = lot.get_unrealized_profit()
414        self.assertGreater(unrealized_profit, 0)
415        self.assertEqual(unrealized_profit, 99)
416
417    def test_closed_lot(self):
418        """Test that lot closes when all shares are sold"""
419        lot = Lot()
420        lot.buy('AAPL', 10, 100, 1)
421        
422        # Initially not closed
423        self.assertFalse(lot.get_isclosed())
424        
425        # Sell all shares
426        lot.sell('AAPL', 10, 120, 1)
427        
428        # Now closed
429        self.assertTrue(lot.get_isclosed())
430        self.assertEqual(lot.remaining_quantity, 0)
431
432    def test_return_calculations(self):
433        """Test return rate calculations"""
434        lot = Lot()
435        lot.buy('AAPL', 10, 100, 1)  # Cost: 1001
436        
437        # get_return: value / remaining_cost = 1000 / 1001
438        return_rate = lot.get_return()
439        self.assertAlmostEqual(return_rate, 1000/1001, places=5)
440        
441        # Add profit through dividend
442        lot.add_dividend('AAPL', '2024-01-15', 10)  # 100 total dividend
443        
444        # get_total_return: (value + sell_proceeds + dividends) / total_cost
445        total_return = lot.get_total_return()
446        expected = (1000 + 0 + 100) / 1001  # No sells yet, so proceeds=0
447        self.assertAlmostEqual(total_return, expected, places=5)
448
449
450if __name__ == '__main__':
451    unittest.main()

Written by Binwei@Shanghai