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