File size: 10,837 Bytes
203d1e9
 
 
 
 
 
 
 
 
 
 
 
 
c119a7b
203d1e9
c119a7b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203d1e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c119a7b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203d1e9
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
---
description: Concise coding conventions for the Folio project
alwaysApply: true
---

# Folio Project Conventions

This document outlines the key coding conventions for the Folio project. These conventions are designed to help maintain code quality, readability, and consistency across the codebase.

## Project Tech Stack

- **Web Framework**: Dash (Python)
- **Data Processing**: Pandas, NumPy
- **Financial Data**: Yahoo Finance API
- **Testing**: Pytest
- **Linting**: Ruff

## Principles
1. **Follow the Boy Scout Rule**: Leave the code cleaner than you found it.

2. **Don't Repeat Yourself (DRY)**: Extract repeated code into reusable functions.

3. **You Aren't Gonna Need It (YAGNI)**: Don't add functionality until it's necessary.

4. **Optimize After Measuring**: Profile code to identify actual bottlenecks before optimizing.

5. **Use Consistent Formatting**: Use Black, Flake8, and isort to maintain consistent code style.

6. **Imports at Top**: Always place all imports at the top of the file.

7. **No Unused Code**: Remove commented-out code and unused imports/variables.

8. **Configuration Over Hardcoding**: Use configuration files for values that might change.

9. **Log with Context**: Include relevant information in log messages.

10. **Make Small, Focused Changes**: Don't modify unrelated code when implementing a feature or fixing a bug.

## Core Conventions

### 1. Fail Fast and Transparently

Never hide errors with default values. Financial data must be accurate or explicitly marked as unavailable.

```python
# ❌ Bad: Hiding errors with defaults
def get_beta(ticker):
    try:
        return data_fetcher.get_beta(ticker)
    except Exception:
        return 1.0  # Dangerous default!

# βœ… Good: Transparent failure
def get_beta(ticker):
    try:
        return data_fetcher.get_beta(ticker)
    except Exception as e:
        logger.error(f"Failed to get beta for {ticker}: {e}", exc_info=True)
        raise  # Let the caller handle the error
```

### 2. Use Intention-Revealing Names

Names should clearly communicate what a variable, function, or class is for.

```python
# ❌ Bad: Unclear names
def calc(p, q):
    return p * q * 1.1

# βœ… Good: Clear names
def calculate_total_with_tax(price, quantity):
    return price * quantity * 1.1
```

### 3. Write Small, Focused Functions

Each function should do one thing well and be reasonably small.

```python
# ❌ Bad: Function doing too much
def process_portfolio(portfolio_data):
    # Validate data
    if not portfolio_data:
        raise ValueError("Empty portfolio")

    # Calculate metrics
    total_value = 0
    total_beta_adjusted = 0
    for position in portfolio_data:
        price = position["price"]
        quantity = position["quantity"]
        beta = get_beta(position["ticker"])
        value = price * quantity
        total_value += value
        total_beta_adjusted += value * beta

    # Generate report
    report = {
        "total_value": total_value,
        "portfolio_beta": total_beta_adjusted / total_value if total_value else 0,
        "positions": len(portfolio_data)
    }

    # Save to database
    db.save_portfolio_report(report)

    return report

# βœ… Good: Functions with single responsibilities
def validate_portfolio(portfolio_data):
    if not portfolio_data:
        raise ValueError("Empty portfolio")
    return portfolio_data

def calculate_position_metrics(position):
    price = position["price"]
    quantity = position["quantity"]
    beta = get_beta(position["ticker"])
    value = price * quantity
    beta_adjusted = value * beta
    return {"value": value, "beta_adjusted": value * beta}

def calculate_portfolio_metrics(portfolio_data):
    validated_data = validate_portfolio(portfolio_data)

    position_metrics = [calculate_position_metrics(pos) for pos in validated_data]

    total_value = sum(pos["value"] for pos in position_metrics)
    total_beta_adjusted = sum(pos["beta_adjusted"] for pos in position_metrics)

    return {
        "total_value": total_value,
        "portfolio_beta": total_beta_adjusted / total_value if total_value else 0,
        "positions": len(portfolio_data)
    }

def save_portfolio_report(report):
    db.save_portfolio_report(report)
    return report

def process_portfolio(portfolio_data):
    metrics = calculate_portfolio_metrics(portfolio_data)
    return save_portfolio_report(metrics)
```

### 4. Validate Early, Return Fast

Check inputs at the beginning of functions to avoid deep nesting and keep the happy path clean.

```python
# ❌ Bad: Deeply nested conditionals
def process_data(data):
    if data is not None:
        if "ticker" in data:
            if data["ticker"] != "":
                # Process the data...
                return result
            else:
                return None
        else:
            return None
    else:
        return None

# βœ… Good: Early validation
def process_data(data):
    if data is None:
        raise ValueError("Data cannot be None")
    if "ticker" not in data:
        raise ValueError("Missing required 'ticker' field")
    if data["ticker"] == "":
        raise ValueError("Ticker cannot be empty")

    # Process the data...
    return result
```

### 5. Comment the "Why," Not the "What"

Explain reasoning behind complex code, not obvious operations.

```python
# ❌ Bad: Commenting the obvious
# Calculate the sum of prices
total = sum(item.price for item in items)

# ❌ Bad: Commented-out code
# Old calculation method
# for item in items:
#     total += item.price

# βœ… Good: Explaining the why
# Apply 15% discount for bulk orders (>10 items) per company policy
if len(items) > 10:
    total *= 0.85
```

### 6. Write Minimal, Effective Tests

Focus on testing critical business logic, not framework functionality.

```python
# ❌ Bad: Testing framework functionality
def test_dataframe_creation():
    # This just tests pandas functionality, not our code
    data = {"ticker": ["AAPL"], "price": [150]}
    df = pd.DataFrame(data)
    assert len(df) == 1
    assert "ticker" in df.columns

# βœ… Good: Testing critical business logic
def test_portfolio_beta_calculation():
    # Arrange: Set up test data
    portfolio = Portfolio()
    portfolio.add_position(
        StockPosition(ticker="AAPL", quantity=10, price=150)
    )

    # Mock external dependencies
    data_fetcher = MagicMock()
    data_fetcher.get_beta.return_value = 1.2

    # Act: Call the method under test
    beta = portfolio.calculate_beta(data_fetcher=data_fetcher)

    # Assert: Verify the result
    assert beta == 1.2
    data_fetcher.get_beta.assert_called_once_with("AAPL")
```

### 7. Embrace Pythonic Idioms

Use Python's built-in features to write cleaner, more readable code.

```python
# ❌ Bad: Non-Pythonic code
result = []
for i in range(len(items)):
    if items[i].price > 100:
        result.append(items[i].name)

# βœ… Good: Pythonic code
result = [item.name for item in items if item.price > 100]

# ❌ Bad: Manual resource management
f = open("data.csv", "r")
try:
    data = f.read()
finally:
    f.close()

# βœ… Good: Context manager
with open("data.csv", "r") as f:
    data = f.read()
```

### 8. Use Type Hints

Add type hints to improve readability and enable static analysis.

```python
# ❌ Bad: No type hints
def calculate_position_value(quantity, price):
    return quantity * price

# βœ… Good: With type hints
def calculate_position_value(quantity: float, price: float) -> float:
    return quantity * price

# Even better: With more specific types and docstring
from typing import Dict, List, Optional

def get_positions_by_sector(
    positions: List[Dict[str, any]],
    sector: Optional[str] = None
) -> Dict[str, List[Dict[str, any]]]:
    """
    Group positions by sector.

    Args:
        positions: List of position dictionaries
        sector: Optional sector to filter by

    Returns:
        Dictionary mapping sectors to lists of positions
    """
    result = {}
    for position in positions:
        pos_sector = position.get("sector", "Unknown")
        if sector and pos_sector != sector:
            continue
        if pos_sector not in result:
            result[pos_sector] = []
        result[pos_sector].append(position)
    return result
```

### 9. Handle Errors Gracefully

Use exceptions with context and handle them appropriately.

```python
# ❌ Bad: Using error codes
def divide_stocks(total_value, num_stocks):
    if num_stocks == 0:
        return -1  # Error code
    return total_value / num_stocks

# Usage
result = divide_stocks(1000, 0)
if result == -1:
    print("Error: Cannot divide by zero")

# βœ… Good: Using exceptions
def divide_stocks(total_value: float, num_stocks: int) -> float:
    if num_stocks == 0:
        raise ValueError("Cannot divide by zero stocks")
    return total_value / num_stocks

# Usage
try:
    result = divide_stocks(1000, 0)
except ValueError as e:
    logger.error(f"Portfolio calculation error: {e}")
    # Handle the error appropriately
```

### 10. Keep It Simple (KISS)

Prefer simple, straightforward solutions over complex ones.

```python
# ❌ Bad: Overly complex
def is_valid_ticker(ticker):
    if ticker is not None:
        if isinstance(ticker, str):
            if len(ticker) > 0:
                if len(ticker) <= 5:
                    if ticker.isalpha():
                        return True
    return False

# βœ… Good: Simple and clear
def is_valid_ticker(ticker: str) -> bool:
    return (
        isinstance(ticker, str) and
        1 <= len(ticker) <= 5 and
        ticker.isalpha()
    )
```

### 11. Maintain Separation of Concerns
Business logic MUST reside in the core library (`src/folio/`), not in interface layers (`src/focli/`).
```python
# ❌ Bad: Business logic in CLI layer
# src/focli/utils.py
def calculate_position_value_with_price_change(position_group, price_change):
    # Business logic for calculating position value
    return new_value

# βœ… Good: Business logic in core library
# src/folio/portfolio_value.py
def calculate_position_value_with_price_change(position_group, price_change):
    # Business logic for calculating position value
    return new_value

# src/focli/commands/position.py
def handle_position_command(args):
    # Only handle user interaction and call core library
    result = portfolio_value.calculate_position_value_with_price_change(
        position_group, price_change
    )
    # Format and display result
```

## Benefits of Following These Conventions

- **Readability**: Code is easier to understand at a glance
- **Maintainability**: Simpler structure makes changes easier and safer
- **Testability**: Clear paths make testing more straightforward
- **Reliability**: Proper error handling prevents unexpected behavior
- **Performance**: Well-structured code leads to better performance