Skip to content

Commit 3622e6b

Browse files
refactor: logic improved for better readability and performance
1 parent daf0368 commit 3622e6b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+992
-588
lines changed

.gitignore

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,20 @@ __pycache__/
2222

2323
*.DS_Store
2424
Thumbs.db
25-
bankmanaging.db
25+
bankmanaging.db
26+
# Nifty Stock Analyzer specific
27+
nifty50_analysis_*.csv
28+
*.egg-info/
29+
build/
30+
dist/
31+
32+
# Python testing
33+
.pytest_cache/
34+
.coverage
35+
htmlcov/
36+
.tox/
37+
38+
# IDE
39+
*.swp
40+
*.swo
41+
*~
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
# Design Document: Nifty 50 Stock Analyzer
2+
3+
## Overview
4+
5+
The Nifty 50 Stock Analyzer is a Python CLI application that provides real-time technical analysis of India's top 50 stocks. The tool leverages the yfinance library to fetch market data, performs moving average calculations, and identifies momentum opportunities by highlighting stocks trading significantly above their 20-day moving average. Results are exported to CSV format for further analysis.
6+
7+
The application follows a simple pipeline architecture: data retrieval → calculation → filtering → export, with robust error handling at each stage.
8+
9+
## Architecture
10+
11+
The application uses a modular, pipeline-based architecture:
12+
13+
```
14+
┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐
15+
│ CLI │────▶│ Data │────▶│ Analysis │────▶│ Export │
16+
│ Interface │ │ Fetcher │ │ Engine │ │ Handler │
17+
└─────────────┘ └──────────────┘ └─────────────┘ └──────────────┘
18+
│ │ │
19+
▼ ▼ ▼
20+
┌──────────────┐ ┌─────────────┐ ┌──────────────┐
21+
│ yfinance │ │ Calculator │ │ CSV Writer │
22+
│ API │ │ Module │ │ │
23+
└──────────────┘ └─────────────┘ └──────────────┘
24+
```
25+
26+
### Key Design Principles
27+
28+
1. **Separation of Concerns**: Each module has a single, well-defined responsibility
29+
2. **Fail-Safe Operation**: Errors in processing individual stocks don't halt the entire analysis
30+
3. **User Feedback**: Progress indicators and clear messaging throughout execution
31+
4. **Data Integrity**: Validation at each pipeline stage ensures reliable results
32+
33+
## Components and Interfaces
34+
35+
### 1. CLI Interface Module (`cli.py`)
36+
37+
**Responsibility**: Entry point for the application, handles user interaction and orchestrates the pipeline.
38+
39+
**Key Functions**:
40+
- `main()`: Entry point that coordinates the entire workflow
41+
- `display_progress(current, total, stock_symbol)`: Shows progress during data fetching
42+
- `display_summary(results)`: Prints analysis summary to console
43+
44+
**Interface**:
45+
```python
46+
def main() -> int:
47+
"""
48+
Main entry point for the CLI tool.
49+
Returns: Exit code (0 for success, 1 for failure)
50+
"""
51+
```
52+
53+
### 2. Stock Symbol Provider (`symbols.py`)
54+
55+
**Responsibility**: Provides the list of Nifty 50 stock symbols with proper Yahoo Finance formatting.
56+
57+
**Key Functions**:
58+
- `get_nifty50_symbols()`: Returns list of all Nifty 50 stock symbols
59+
60+
**Interface**:
61+
```python
62+
def get_nifty50_symbols() -> List[str]:
63+
"""
64+
Returns the list of Nifty 50 stock symbols in Yahoo Finance format.
65+
Returns: List of stock symbols (e.g., ['RELIANCE.NS', 'TCS.NS', ...])
66+
"""
67+
```
68+
69+
**Note**: Nifty 50 symbols require the `.NS` suffix for Yahoo Finance (National Stock Exchange of India).
70+
71+
### 3. Data Fetcher Module (`fetcher.py`)
72+
73+
**Responsibility**: Retrieves stock data from Yahoo Finance with retry logic and error handling.
74+
75+
**Key Functions**:
76+
- `fetch_stock_data(symbol, days=30)`: Fetches historical data for a single stock
77+
- `fetch_all_stocks(symbols)`: Fetches data for all provided symbols with progress tracking
78+
79+
**Interface**:
80+
```python
81+
@dataclass
82+
class StockData:
83+
symbol: str
84+
current_price: float
85+
historical_prices: List[float] # Last 30 days of closing prices
86+
fetch_success: bool
87+
error_message: Optional[str] = None
88+
89+
def fetch_stock_data(symbol: str, days: int = 30, retries: int = 3) -> StockData:
90+
"""
91+
Fetches stock data with retry logic.
92+
Args:
93+
symbol: Stock symbol (e.g., 'RELIANCE.NS')
94+
days: Number of days of historical data to fetch
95+
retries: Number of retry attempts on failure
96+
Returns: StockData object with fetched information
97+
"""
98+
```
99+
100+
### 4. Analysis Engine (`analyzer.py`)
101+
102+
**Responsibility**: Performs moving average calculations and identifies stocks meeting the threshold criteria.
103+
104+
**Key Functions**:
105+
- `calculate_moving_average(prices, period=20)`: Computes moving average
106+
- `calculate_percentage_difference(current, average)`: Computes percentage difference
107+
- `analyze_stock(stock_data)`: Performs complete analysis on a single stock
108+
109+
**Interface**:
110+
```python
111+
@dataclass
112+
class AnalysisResult:
113+
symbol: str
114+
current_price: float
115+
moving_average_20d: Optional[float]
116+
percentage_difference: Optional[float]
117+
is_highlighted: bool # True if >= 5% above MA
118+
error_message: Optional[str] = None
119+
120+
def calculate_moving_average(prices: List[float], period: int = 20) -> Optional[float]:
121+
"""
122+
Calculates simple moving average.
123+
Args:
124+
prices: List of closing prices (most recent last)
125+
period: Number of periods for moving average
126+
Returns: Moving average value or None if insufficient data
127+
"""
128+
129+
def analyze_stock(stock_data: StockData) -> AnalysisResult:
130+
"""
131+
Performs complete analysis on stock data.
132+
Args:
133+
stock_data: StockData object from fetcher
134+
Returns: AnalysisResult with all calculated metrics
135+
"""
136+
```
137+
138+
### 5. Export Handler (`exporter.py`)
139+
140+
**Responsibility**: Writes analysis results to CSV file with proper formatting.
141+
142+
**Key Functions**:
143+
- `export_to_csv(results, filename)`: Writes results to CSV file
144+
- `generate_filename()`: Creates timestamped filename
145+
146+
**Interface**:
147+
```python
148+
def export_to_csv(results: List[AnalysisResult], filename: Optional[str] = None) -> str:
149+
"""
150+
Exports analysis results to CSV file.
151+
Args:
152+
results: List of AnalysisResult objects
153+
filename: Optional custom filename (generates timestamped name if None)
154+
Returns: Path to created CSV file
155+
"""
156+
```
157+
158+
**CSV Format**:
159+
```
160+
Symbol,Current Price,20-Day MA,Percentage Difference,Highlighted
161+
RELIANCE.NS,2450.50,2350.25,4.26,No
162+
TCS.NS,3500.00,3300.00,6.06,Yes
163+
...
164+
```
165+
166+
## Data Models
167+
168+
### StockData
169+
Represents raw data fetched from Yahoo Finance.
170+
171+
```python
172+
@dataclass
173+
class StockData:
174+
symbol: str # Stock symbol (e.g., 'RELIANCE.NS')
175+
current_price: float # Most recent closing price
176+
historical_prices: List[float] # Last 30 days of closing prices
177+
fetch_success: bool # Whether data fetch succeeded
178+
error_message: Optional[str] = None # Error details if fetch failed
179+
```
180+
181+
### AnalysisResult
182+
Represents analyzed stock with calculated metrics.
183+
184+
```python
185+
@dataclass
186+
class AnalysisResult:
187+
symbol: str # Stock symbol
188+
current_price: float # Current trading price
189+
moving_average_20d: Optional[float] # 20-day moving average (None if insufficient data)
190+
percentage_difference: Optional[float] # Percentage above/below MA
191+
is_highlighted: bool # True if >= 5% above MA
192+
error_message: Optional[str] = None # Error details if analysis failed
193+
```
194+
195+
## Correctness Properties
196+
197+
*A property is a characteristic or behavior that should hold true across all valid executions of a system—essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
198+
199+
200+
### Property 1: Error isolation
201+
*For any* list of stock symbols containing both valid and invalid symbols, processing failures for invalid symbols should not prevent the successful processing of valid symbols, and all failed symbols should be marked with error information.
202+
**Validates: Requirements 1.4, 6.2**
203+
204+
### Property 2: Current price inclusion
205+
*For any* stock that is successfully fetched, the resulting StockData object should contain a valid current_price value (non-negative float).
206+
**Validates: Requirements 1.5**
207+
208+
### Property 3: Sufficient historical data
209+
*For any* stock that is successfully fetched, the historical_prices list should contain at least 20 data points to enable moving average calculation.
210+
**Validates: Requirements 2.1**
211+
212+
### Property 4: Moving average correctness
213+
*For any* list of 20 or more closing prices, the calculated 20-day moving average should equal the arithmetic mean of the most recent 20 prices: sum(prices[-20:]) / 20.
214+
**Validates: Requirements 2.2**
215+
216+
### Property 5: Percentage difference formula
217+
*For any* current price and moving average pair, the calculated percentage difference should equal ((current_price - moving_average) / moving_average) * 100.
218+
**Validates: Requirements 3.1, 3.4**
219+
220+
### Property 6: Highlight threshold accuracy
221+
*For any* analyzed stock, is_highlighted should be True if and only if percentage_difference >= 5.0.
222+
**Validates: Requirements 3.2**
223+
224+
### Property 7: CSV column completeness
225+
*For any* exported CSV file, parsing the header row should reveal exactly these columns in order: Symbol, Current Price, 20-Day MA, Percentage Difference, Highlighted.
226+
**Validates: Requirements 4.2**
227+
228+
### Property 8: Filename timestamp format
229+
*For any* auto-generated CSV filename, it should match the pattern "nifty50_analysis_YYYYMMDD_HHMMSS.csv" where the timestamp represents a valid date and time.
230+
**Validates: Requirements 4.4**
231+
232+
### Property 9: Failed stock tracking
233+
*For any* analysis execution where some stocks fail to fetch, the final results should include entries for failed stocks with error_message populated and is_highlighted set to False.
234+
**Validates: Requirements 6.4**
235+
236+
## Error Handling
237+
238+
### Network Errors
239+
- **Retry Strategy**: Exponential backoff with 3 retry attempts (delays: 1s, 2s, 4s)
240+
- **Timeout**: 10-second timeout per request to prevent hanging
241+
- **Graceful Degradation**: Failed stocks are logged but don't halt execution
242+
243+
### Data Validation Errors
244+
- **Insufficient Historical Data**: If fewer than 20 days available, set moving_average_20d to None
245+
- **Invalid Price Data**: If current price is negative or zero, mark as error
246+
- **Missing Data**: If yfinance returns empty dataset, mark as fetch failure
247+
248+
### File System Errors
249+
- **Write Permissions**: Check write permissions before attempting CSV export
250+
- **Disk Space**: Basic validation that output directory is writable
251+
- **Path Errors**: Use absolute paths and validate directory existence
252+
253+
### Error Reporting
254+
All errors are captured in the respective data structures (StockData.error_message, AnalysisResult.error_message) and reported in:
255+
1. Console output during execution
256+
2. Final summary statistics
257+
3. CSV output (error stocks included with error indicators)
258+
259+
## Testing Strategy
260+
261+
### Unit Testing Framework
262+
- **Framework**: pytest
263+
- **Coverage Target**: 80% code coverage minimum
264+
- **Test Organization**: Mirror source structure in `tests/` directory
265+
266+
### Unit Tests
267+
Unit tests will cover:
268+
- **Moving Average Calculation**: Test with known price sequences (e.g., [100, 110, 120, ...] should yield predictable MA)
269+
- **Percentage Difference**: Test with edge cases (zero MA, negative differences, exact 5% threshold)
270+
- **CSV Export**: Verify file creation, header format, and data row structure
271+
- **Symbol Provider**: Verify exactly 50 symbols returned with .NS suffix
272+
- **Error Handling**: Test retry logic with mocked network failures
273+
274+
### Property-Based Testing Framework
275+
- **Framework**: Hypothesis (Python property-based testing library)
276+
- **Iterations**: Minimum 100 test cases per property
277+
- **Strategy**: Generate random but valid test data to verify universal properties
278+
279+
### Property-Based Tests
280+
Each property-based test will:
281+
1. Generate random valid inputs (stock prices, symbol lists, etc.)
282+
2. Execute the function under test
283+
3. Verify the correctness property holds
284+
4. Be tagged with format: `# Feature: nifty-stock-analyzer, Property X: [property description]`
285+
286+
Property tests will verify:
287+
- **Property 1**: Error isolation with randomly generated valid/invalid symbol mixes
288+
- **Property 2**: Current price inclusion across random successful fetches
289+
- **Property 3**: Historical data sufficiency across random fetch results
290+
- **Property 4**: Moving average mathematical correctness with random price sequences
291+
- **Property 5**: Percentage formula correctness with random price/MA pairs
292+
- **Property 6**: Highlight threshold accuracy with random percentage differences
293+
- **Property 7**: CSV column structure across random result sets
294+
- **Property 8**: Filename timestamp format across random execution times
295+
- **Property 9**: Failed stock tracking with random failure scenarios
296+
297+
### Integration Testing
298+
- **End-to-End Test**: Run against a small subset of real Nifty 50 symbols (3-5 stocks)
299+
- **Mock Testing**: Use mocked yfinance responses for predictable testing
300+
- **CSV Validation**: Parse generated CSV files to verify data integrity
301+
302+
### Test Data Strategy
303+
- **Mock Data**: Create realistic StockData and AnalysisResult fixtures
304+
- **Price Generators**: Generate valid price sequences (positive floats with realistic ranges)
305+
- **Symbol Generators**: Generate valid NSE symbol formats
306+
- **Edge Cases**: Empty lists, single-element lists, exactly 20 elements, boundary values
307+
308+
## Dependencies
309+
310+
### External Libraries
311+
```
312+
yfinance>=0.2.28 # Yahoo Finance data retrieval
313+
pandas>=2.0.0 # Data manipulation (used by yfinance)
314+
hypothesis>=6.90.0 # Property-based testing
315+
pytest>=7.4.0 # Unit testing framework
316+
```
317+
318+
### Python Version
319+
- **Minimum**: Python 3.9
320+
- **Recommended**: Python 3.11+
321+
322+
## Performance Considerations
323+
324+
### Expected Performance
325+
- **Data Fetching**: ~2-3 seconds per stock (network dependent)
326+
- **Total Execution**: ~2-3 minutes for all 50 stocks
327+
- **Memory Usage**: < 100MB for typical execution
328+
329+
### Optimization Strategies
330+
- **Sequential Processing**: Avoid rate limiting from Yahoo Finance
331+
- **Minimal Data Storage**: Keep only necessary historical data (30 days)
332+
- **Efficient CSV Writing**: Use pandas for optimized CSV export
333+
334+
### Scalability
335+
The current design is optimized for the fixed set of 50 Nifty stocks. For larger datasets:
336+
- Consider parallel fetching with rate limiting
337+
- Implement caching for historical data
338+
- Use database storage instead of CSV for large result sets
339+
340+
## Future Enhancements
341+
342+
Potential future features (out of scope for initial implementation):
343+
1. **Configurable Thresholds**: Allow users to specify custom percentage thresholds
344+
2. **Multiple Moving Averages**: Support 50-day, 100-day, 200-day MAs
345+
3. **Technical Indicators**: Add RSI, MACD, Bollinger Bands
346+
4. **Historical Analysis**: Compare current signals against historical patterns
347+
5. **Alerts**: Email/SMS notifications when stocks meet criteria
348+
6. **Web Dashboard**: Interactive visualization of results
349+
7. **Database Storage**: Persist historical analysis results for trend tracking

0 commit comments

Comments
 (0)