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
|