refactor: move business logic from CLI to core library and improve documentation
Browse filesThis commit addresses a critical architectural issue by moving business logic
from the CLI layer to the core library, ensuring proper separation of concerns.
Key changes:
1. Refactored business logic:
- Moved `generate_spy_changes` from `src/focli/utils.py` to `src/folio/simulator.py`
- Moved `calculate_position_value_with_price_change` from `src/focli/utils.py` to `src/folio/portfolio_value.py`
- Moved `simulate_position_with_spy_changes` from `src/focli/utils.py` to `src/folio/simulator.py`
- Updated imports in CLI code to use the functions from the core library
2. Enhanced documentation:
- Added separation of concerns principles to BEST-PRACTICES.md
- Created a dedicated "Separation of Concerns" section in project-design.md
- Added CLI interface documentation to project-design.md
- Added code examples of proper separation in project-conventions.md
3. Verified changes:
- Ran tests to ensure all functionality works correctly
- Manually tested the CLI to verify the refactoring didn't break the user experience
This refactoring improves code organization, increases reusability, and makes
the codebase more maintainable by ensuring business logic is centralized in
the core library while interface layers focus solely on user interaction.
- BEST-PRACTICES.md +15 -247
- docs/ai-rules.md +0 -17
- docs/project-conventions.md +33 -10
- docs/project-design.md +105 -11
- src/focli/commands/position.py +2 -6
- src/focli/commands/simulate.py +5 -2
- src/focli/utils.py +0 -135
- src/folio/portfolio_value.py +43 -0
- src/folio/simulator.py +92 -0
|
@@ -1,249 +1,17 @@
|
|
| 1 |
-
# Best Practices for Folio Project
|
| 2 |
-
|
| 3 |
-
This document is the single source of truth for the Folio project's best practices. Keep it updated as new best practices emerge.
|
| 4 |
-
|
| 5 |
---
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
0. NO FAKE DATA + FAIL FAST: if we hide errors by giving default or fake data, it could mislead hedge fund clients and cost tons of $$$. Stop using default values and fake data when handling errors. FAIL FAST and transparently when the app fails.
|
| 9 |
-
1. **SIMPLICITY** - Prefer simple, focused solutions over complex ones. Question every bit of added complexity.
|
| 10 |
-
2. **PRECISION** - Make the smallest necessary changes. Don't modify unrelated code or remove what you don't understand.
|
| 11 |
-
3. **RELIABILITY** - Debug thoroughly and test rigorously. AVOID SWALLOW EXCEPTIONS or hiding errors
|
| 12 |
-
4. **USABILITY** - Prioritize user experience. Design for clarity and ease of use, not technical elegance alone.
|
| 13 |
-
5. **SAFETY** - Never modify Git history or hide Git commands in scripts. All version control operations must be explicit, visible, and performed manually by the user.
|
| 14 |
-
|
| 15 |
---
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
- **File Management**:
|
| 30 |
-
- **Version Control**: Update .gitignore for new temporary files or directories
|
| 31 |
-
- **Temporary Files**: Store temporary files in the `.tmp` directory
|
| 32 |
-
- **Cache Files**: Use hidden directories (`.cache_*`) for cache files
|
| 33 |
-
|
| 34 |
-
- **Version Control**:
|
| 35 |
-
- **Never Commit Directly**: Let the user handle all git operations
|
| 36 |
-
- **⚠️ NEVER USE GIT IN SCRIPTS**: Do not write scripts that use git commands - this can lead to catastrophic data loss and irreversible history modification
|
| 37 |
-
- **Manual Git Operations**: All git operations must be performed manually by the user, never automated
|
| 38 |
-
- **NEVER CREATE PRs WITHOUT BEING ASKED**: Do not create pull requests unless explicitly requested by the user
|
| 39 |
-
- **Preserve Git History**: Never modify Git history without explicit user consent and understanding of the consequences
|
| 40 |
-
- **Transparent Operations**: All version control suggestions must be explicit, visible, and explained clearly
|
| 41 |
-
- **Preferred Diff Tool**: Use `git aidiff` for code reviews with AI agents - this outputs complete diffs without requiring scrolling
|
| 42 |
-
- If not available, set up with: `git config --global alias.aidiff "!git --no-pager diff --unified=3 --color=never"`
|
| 43 |
-
- This produces AI-friendly output that shows the entire diff at once
|
| 44 |
-
|
| 45 |
-
- **Commit Messages**:
|
| 46 |
-
- **Delivery Format**: When asked to write a commit message, provide it directly as a markdown code block (```...```) in the chat, not as a separate file
|
| 47 |
-
- **Conventional Format**: Follow the conventional commits format with a type prefix
|
| 48 |
-
- **Message Structure**: Use a concise title (50 chars max) followed by a blank line and then a detailed body
|
| 49 |
-
- **Title Format**: `<type>: <concise description in imperative mood>`
|
| 50 |
-
- **Types in Priority Order**: Always use the highest impact prefix when multiple types apply
|
| 51 |
-
1. `feat`: New features or significant enhancements (highest priority)
|
| 52 |
-
2. `fix`: Bug fixes or correcting errors
|
| 53 |
-
3. `security`: Security-related changes
|
| 54 |
-
4. `perf`: Performance improvements
|
| 55 |
-
5. `refactor`: Code restructuring without changing functionality
|
| 56 |
-
6. `test`: Adding or modifying tests
|
| 57 |
-
7. `docs`: Documentation updates only
|
| 58 |
-
8. `style`: Formatting, white-space, etc. (no code change)
|
| 59 |
-
9. `build`: Build system or external dependency changes
|
| 60 |
-
10. `ci`: CI configuration changes
|
| 61 |
-
11. `chore`: Maintenance tasks, no production code change (lowest priority)
|
| 62 |
-
- **Examples**:
|
| 63 |
-
- `feat: add user authentication system`
|
| 64 |
-
- `fix: resolve database connection timeout issue`
|
| 65 |
-
- `docs: update deployment instructions`
|
| 66 |
-
- **Title Guidelines**:
|
| 67 |
-
- Use imperative mood ("Add feature" not "Added feature")
|
| 68 |
-
- Capitalize the first word after the type prefix
|
| 69 |
-
- Don't end with a period
|
| 70 |
-
- Keep under 50 characters
|
| 71 |
-
- Be specific about what changed
|
| 72 |
-
- **Body Guidelines**:
|
| 73 |
-
- Explain the "what" and "why" of the change, not the "how"
|
| 74 |
-
- Wrap text at 72 characters
|
| 75 |
-
- Use bullet points for multiple points
|
| 76 |
-
- Reference issues or tickets where applicable
|
| 77 |
-
- Don't use phrases like "This commit..."
|
| 78 |
-
|
| 79 |
-
### �💻 Implementation
|
| 80 |
-
*Supports SIMPLICITY and RELIABILITY*
|
| 81 |
-
Write code that is clear, maintainable, and robust against edge cases.
|
| 82 |
-
|
| 83 |
-
- **Code Style**:
|
| 84 |
-
- **Imports at Top**: ALWAYS place all imports at the top of the file, never in the middle or at the bottom
|
| 85 |
-
- **Avoid Hardcoding**: Use pattern-based detection instead of hardcoding specific values
|
| 86 |
-
- **Generic Solutions**: Prefer solutions that work for all cases over special-case handling
|
| 87 |
-
- **Readability**: Prioritize readable code over clever optimizations
|
| 88 |
-
- **Code Quality**: Run `make lint` regularly to identify and fix code quality issues
|
| 89 |
-
- **Unused Code**: Avoid unused imports, functions, and variables; prefix intentionally unused variables with underscore
|
| 90 |
-
- **Clean Code**: Remove commented-out code and fix exception handling issues
|
| 91 |
-
|
| 92 |
-
- **Configuration**:
|
| 93 |
-
- **External Config**: Use configuration files for values that might change
|
| 94 |
-
- **Sensible Defaults**: Provide reasonable defaults for all configurable options
|
| 95 |
-
- **Validate Inputs**: Check and validate all external inputs and configuration
|
| 96 |
-
|
| 97 |
-
- **Framework-Specific Patterns**:
|
| 98 |
-
- **Dash Callback Registration**: Always register callbacks in a centralized location in the app creation process
|
| 99 |
-
- **Explicit Registration**: Never rely on side effects of imports for critical functionality like callback registration
|
| 100 |
-
- **Avoid Duplicate Registration**: Be careful about registering callbacks multiple times, which can cause conflicts
|
| 101 |
-
- **Component Documentation**: Document the relationship between components and their callbacks
|
| 102 |
-
|
| 103 |
-
- **Dependency Management**:
|
| 104 |
-
- **Update Requirements**: ALWAYS update `requirements.txt` when adding new imports or dependencies
|
| 105 |
-
- **Deployment Verification**: Test deployments after adding new dependencies to ensure they work in all environments
|
| 106 |
-
- **Minimal Dependencies**: Only add dependencies that are absolutely necessary
|
| 107 |
-
- **Version Pinning**: Pin versions for stability (`==`) or use minimum version constraints (`>=`) as appropriate
|
| 108 |
-
- **Document Dependencies**: Add comments explaining what each dependency is used for
|
| 109 |
-
- **Check Imports**: Regularly audit imports to ensure all are properly listed in requirements
|
| 110 |
-
|
| 111 |
-
### 🛡️ Error Handling and Logging
|
| 112 |
-
*Supports RELIABILITY and PRECISION*
|
| 113 |
-
Handle errors gracefully and log information that helps diagnose issues quickly.
|
| 114 |
-
|
| 115 |
-
- **Exception Handling**:
|
| 116 |
-
- **Use Custom Exceptions**: Use application-specific exceptions from `src/folio/exceptions.py`
|
| 117 |
-
- **Don't Swallow Exceptions**: Never catch exceptions without proper handling or re-raising
|
| 118 |
-
- **Fail Fast**: Fail early and visibly for critical errors rather than continuing with incorrect behavior
|
| 119 |
-
- **Only Handle Errors You Can Handle**: Only catch exceptions that you can meaningfully handle; let others propagate
|
| 120 |
-
- **Specific Exception Types**: Catch specific exception types rather than using broad `except Exception`
|
| 121 |
-
- **Distinguish Error Types**: Treat programming errors (ImportError, NameError, AttributeError, TypeError, SyntaxError) differently from data/operational errors (ValueError, KeyError)
|
| 122 |
-
- **No Default Values for Critical Errors**: Don't use default values (like beta=1.0) for critical calculation errors; fail instead
|
| 123 |
-
- **Context in Exceptions**: Use `raise ... from e` to preserve exception context and chain
|
| 124 |
-
- **User-Friendly Messages**: Provide clear, actionable error messages to users while logging technical details
|
| 125 |
-
- **Use Error Utilities**: Leverage decorators and utilities in `src/folio/error_utils.py`
|
| 126 |
-
|
| 127 |
-
- **Logging Best Practices**:
|
| 128 |
-
- **Structured Messages**: Include context (e.g., "Failed to process AAPL: missing price data")
|
| 129 |
-
- **Include Stack Traces**: For unexpected errors, use `exc_info=True`
|
| 130 |
-
- **Distinguish States vs. Errors**: Log normal states as DEBUG/INFO, not as errors
|
| 131 |
-
- **Verification Logs**: Add logs that verify critical operations like callback registration by checking the app's callback_map
|
| 132 |
-
|
| 133 |
-
- **Log Levels**:
|
| 134 |
-
- **DEBUG**: Detailed flow information ("Processing portfolio entry 5 of 20")
|
| 135 |
-
- **INFO**: Normal application events ("Portfolio loaded successfully")
|
| 136 |
-
- **WARNING**: Potential issues requiring attention ("Using cached data: API unavailable")
|
| 137 |
-
- **ERROR**: Actual errors affecting functionality ("Failed to calculate beta for AAPL")
|
| 138 |
-
- **CRITICAL**: Severe errors preventing operation ("Database connection failed")
|
| 139 |
-
|
| 140 |
-
- **Log Monitoring**:
|
| 141 |
-
- **Check Latest Logs**: Always check the latest logs in the `logs/` directory after running tests or the application
|
| 142 |
-
- **Application Logs**: Review `logs/folio_latest.log` after running the application to identify errors
|
| 143 |
-
- **Test Logs**: Check `logs/test_latest.log` after running tests to catch test failures and errors
|
| 144 |
-
- **Error Investigation**: When errors occur, examine the logs first for detailed error messages and stack traces
|
| 145 |
-
|
| 146 |
-
- **Regression Analysis**:
|
| 147 |
-
- **Root Cause Investigation**: Always use `git blame` to understand what caused a regression
|
| 148 |
-
- **Document Findings**: Record regression analysis in devlogs to prevent repeating mistakes
|
| 149 |
-
- **Follow Process**: See [regression-analysis.md](docs/regression-analysis.md) for the complete process
|
| 150 |
-
|
| 151 |
-
### 🚨 Testing
|
| 152 |
-
*Supports RELIABILITY and PRECISION*
|
| 153 |
-
Thorough testing prevents bugs and ensures code behaves as expected in all scenarios.
|
| 154 |
-
|
| 155 |
-
- **Testing Workflow**:
|
| 156 |
-
- **Run Linter Frequently**: Run `make lint` very frequently - it's cheap, fast, and effective at catching issues early
|
| 157 |
-
- **Always Test Before Completion**: Run `make test` before considering any work complete - this is essential
|
| 158 |
-
- **Check Test Logs**: Always review `logs/test_latest.log` after running tests to identify failures
|
| 159 |
-
- **Fix Linting Issues**: Address linting errors before committing code to maintain code quality
|
| 160 |
-
- **Automated vs. Manual Testing**:
|
| 161 |
-
- For AI assistants: Only run `make test` and `make lint` to verify changes
|
| 162 |
-
- NEVER launch the application with `make folio` or `make portfolio` - leave UI testing to human users
|
| 163 |
-
- Instead, provide detailed instructions on what UI changes to test and how to verify them
|
| 164 |
-
- Add unit tests for new functionality instead of manual testing when possible
|
| 165 |
-
- **Testing Instructions**:
|
| 166 |
-
- Provide clear, step-by-step instructions for the user to test changes
|
| 167 |
-
- Include specific UI elements to check and expected behavior
|
| 168 |
-
- Describe what success looks like and potential issues to watch for
|
| 169 |
-
- Format as a checklist that the user can follow easily
|
| 170 |
-
- **Review App Logs**: Check `logs/test_latest.log` after running tests to catch errors
|
| 171 |
-
- **Test Real Data**: Use `src/lab/portfolio.csv` for testing with real portfolio data
|
| 172 |
-
|
| 173 |
-
- **Test Organization**:
|
| 174 |
-
- **1:1 Test File Mapping**: Tests should be 1:1 with the code file they are testing
|
| 175 |
-
- For each source file (e.g., `util.py`), create a corresponding test file (e.g., `test_util.py`)
|
| 176 |
-
- Maintain the same directory structure in tests as in the source code
|
| 177 |
-
- This makes it easy to find tests for specific functionality
|
| 178 |
-
- **New Functionality, New Tests**: Always write tests for new functionality added to the codebase
|
| 179 |
-
- Tests should be written alongside the implementation, not as an afterthought
|
| 180 |
-
- No new feature is complete without corresponding tests
|
| 181 |
-
|
| 182 |
-
- **Testing Strategy**:
|
| 183 |
-
- **Test Behavior**: Focus on functionality, not implementation details
|
| 184 |
-
- **Edge Cases**: Test boundary conditions (empty inputs, maximum values, etc.)
|
| 185 |
-
- **Regression Tests**: Add tests for bugs to prevent recurrence
|
| 186 |
-
- **Test Coverage**: Aim for high coverage of critical paths and business logic
|
| 187 |
-
- **Test New Functionality**: Always write tests for new features and components
|
| 188 |
-
- **Test Naming**: Name tests specifically to the method/module being tested
|
| 189 |
-
- **Test Public API**: Test only public functions to avoid coupling tests to implementation details
|
| 190 |
-
- **Test Independence**: Each test should be independent and not rely on other tests
|
| 191 |
-
- **Fail-First Testing**: Write tests that fail first to confirm they're testing the right thing
|
| 192 |
-
- **Test Callback Registration**: For Dash apps, always include tests that verify callbacks are properly registered
|
| 193 |
-
|
| 194 |
-
- **Writing Tests**:
|
| 195 |
-
- **Test Structure**: Follow the Arrange-Act-Assert pattern
|
| 196 |
-
- **Mock External Dependencies**: Use mocks for external services, APIs, and databases
|
| 197 |
-
- **Test Edge Cases**: Include tests for error conditions and boundary cases
|
| 198 |
-
- **Parameterized Tests**: Use pytest's parameterize for testing multiple inputs
|
| 199 |
-
- **Fixtures**: Create fixtures for common test setup
|
| 200 |
-
- **Test Isolation**: Reset state between tests to prevent test interdependence
|
| 201 |
-
- **Never Change Test Logic**: Fix implementation to make tests pass, not the other way around
|
| 202 |
-
- **Simple Tests**: Keep tests simple and focused on specific behaviors
|
| 203 |
-
- **Avoid Implementation Details**: Don't test implementation details like DOM structure that might change
|
| 204 |
-
|
| 205 |
-
### 🤖 Agent Interactions
|
| 206 |
-
*Supports PRECISION and USABILITY*
|
| 207 |
-
Effective communication with AI agents ensures efficient development and accurate implementation.
|
| 208 |
-
|
| 209 |
-
- **Question Handling**:
|
| 210 |
-
- **Direct Answers**: When asked a question, answer directly and honestly without writing code
|
| 211 |
-
- **Critical Thinking**: Be critical of assumptions or leading questions in the prompt
|
| 212 |
-
- **Concise Responses**: Provide straightforward responses without unnecessary elaboration
|
| 213 |
-
- **No Automatic Implementation**: Don't jump into implementation unless specifically requested
|
| 214 |
-
- **Clarify Ambiguity**: Ask for clarification when the question is unclear rather than making assumptions
|
| 215 |
-
- **Honest Assessment**: Provide honest feedback about technical feasibility and potential issues
|
| 216 |
-
|
| 217 |
-
- **Implementation Requests**:
|
| 218 |
-
- **Confirm Understanding**: Verify understanding of the request before implementing
|
| 219 |
-
- **Plan First**: Create a detailed plan before making changes
|
| 220 |
-
- **Focused Changes**: Make only the changes requested, not additional "improvements"
|
| 221 |
-
- **Test Verification**: Always suggest testing after implementation
|
| 222 |
-
|
| 223 |
-
### 📝 Documentation
|
| 224 |
-
*Supports USABILITY and RELIABILITY*
|
| 225 |
-
Clear, accurate documentation helps onboard new developers and maintain institutional knowledge.
|
| 226 |
-
|
| 227 |
-
- **Documentation Standards**:
|
| 228 |
-
- **Date Accuracy**: Always use the `date` command for current dates in documents
|
| 229 |
-
- **Consistent Format**: Follow existing formats and naming conventions
|
| 230 |
-
- **Single Source of Truth**: Maintain one authoritative source for each type of documentation
|
| 231 |
-
|
| 232 |
-
- **Documentation Maintenance**:
|
| 233 |
-
- **Keep Updated**: Update documentation when code changes
|
| 234 |
-
- **Code Comments**: Document complex logic and "why" decisions, not obvious code
|
| 235 |
-
- **README Files**: Ensure each major component has a clear, concise README
|
| 236 |
-
- **Component Documentation**: Document component relationships and callback dependencies
|
| 237 |
-
|
| 238 |
-
- **Development Planning**:
|
| 239 |
-
- **Create Devplans**: Document detailed plans in `docs/devplan/` for significant features, design changes, or deployments
|
| 240 |
-
- **Plan Structure**: Include overview, implementation steps, timeline, and considerations
|
| 241 |
-
- **Phased Approach**: Break complex changes into manageable phases with testing checkpoints
|
| 242 |
-
- **Deployment Plans**: For deployment-related changes, include hosting options, infrastructure requirements, and security considerations
|
| 243 |
-
|
| 244 |
-
- **Development Logging**:
|
| 245 |
-
- **Update Devlogs**: Document completed changes in `docs/devlog/` after implementing major features or changes
|
| 246 |
-
- **Devlog Format**: Include date, summary, implementation details, and lessons learned
|
| 247 |
-
- **Technical Details**: Document key technical decisions, challenges overcome, and solutions implemented
|
| 248 |
-
- **Future Considerations**: Note any follow-up tasks or potential improvements identified during implementation
|
| 249 |
-
- **Retrospectives**: Create retrospectives in `docs/retrospective/` for significant issues or challenges to document lessons learned
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
description: Rules to get the AI to behave
|
| 3 |
+
alwaysApply: true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
---
|
| 5 |
+
# General rules for AI
|
| 6 |
+
- Prior to generating any code, carefully read the project conventions
|
| 7 |
+
- Read [project-design.md](docs/project-design.md) to understand the codebase
|
| 8 |
+
- Read [project-conventions.md](docs/project-conventions.md) to understand _how_ to write code for the codebase
|
| 9 |
+
- Run `make lint` and `make test` after every change. `lint` in particular can be run very frequently.
|
| 10 |
+
- When user starts a prompt with `QQ:` or `Question:`, just answer the question or prompt without producing code.
|
| 11 |
+
- Prefer small testable steps, after each step give a summary to the user and summarize the next step
|
| 12 |
+
- **Maintain strict separation of concerns**: Business logic MUST reside in the core library (`src/folio/`), not in interface layers (`src/focli/`). Interface layers should only handle user interaction, command parsing, and result presentation.
|
| 13 |
+
|
| 14 |
+
## Prohibited actions
|
| 15 |
+
|
| 16 |
+
- Do not run `make folio`. This is for the user to run only.
|
| 17 |
+
- Do not use `git` commands unless explicitly asked.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,17 +0,0 @@
|
|
| 1 |
-
---
|
| 2 |
-
description: Miscellaneous rules to get the AI to behave
|
| 3 |
-
globs: *
|
| 4 |
-
alwaysApply: true
|
| 5 |
-
---
|
| 6 |
-
# General rules for AI
|
| 7 |
-
- Prior to generating any code, carefully read the project conventions
|
| 8 |
-
- Read [project-design.md](docs/project-design.md) to understand the codebase
|
| 9 |
-
- Read [project-conventions.md](docs/project-conventions.md) to understand _how_ to write code for the codebase
|
| 10 |
-
- Run `make lint` and `make test` after every change. `lint` in particular can be run very frequently.
|
| 11 |
-
- When user starts a prompt with `QQ:` or `Question:`, just answer the question or prompt without producing code.
|
| 12 |
-
- Prefer small testable steps, after each step give a summary to the user and summarize the next step
|
| 13 |
-
|
| 14 |
-
## Prohibited actions
|
| 15 |
-
|
| 16 |
-
- Do not run `make folio`. This is for the user to run only.
|
| 17 |
-
- Do not use `git` commands unless explicitly asked.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -331,25 +331,48 @@ def is_valid_ticker(ticker: str) -> bool:
|
|
| 331 |
|
| 332 |
## Additional Guidelines
|
| 333 |
|
| 334 |
-
1. **
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
|
| 336 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
|
| 338 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
|
| 340 |
-
|
| 341 |
|
| 342 |
-
|
| 343 |
|
| 344 |
-
|
| 345 |
|
| 346 |
-
|
| 347 |
|
| 348 |
-
|
| 349 |
|
| 350 |
-
|
| 351 |
|
| 352 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
|
| 354 |
## Benefits of Following These Conventions
|
| 355 |
|
|
|
|
| 331 |
|
| 332 |
## Additional Guidelines
|
| 333 |
|
| 334 |
+
1. **Strict Separation of Concerns**: Business logic MUST reside in the core library (`src/folio/`), not in interface layers (`src/focli/`).
|
| 335 |
+
```python
|
| 336 |
+
# ❌ Bad: Business logic in CLI layer
|
| 337 |
+
# src/focli/utils.py
|
| 338 |
+
def calculate_position_value_with_price_change(position_group, price_change):
|
| 339 |
+
# Business logic for calculating position value
|
| 340 |
+
return new_value
|
| 341 |
|
| 342 |
+
# ✅ Good: Business logic in core library
|
| 343 |
+
# src/folio/portfolio_value.py
|
| 344 |
+
def calculate_position_value_with_price_change(position_group, price_change):
|
| 345 |
+
# Business logic for calculating position value
|
| 346 |
+
return new_value
|
| 347 |
|
| 348 |
+
# src/focli/commands/position.py
|
| 349 |
+
def handle_position_command(args):
|
| 350 |
+
# Only handle user interaction and call core library
|
| 351 |
+
result = portfolio_value.calculate_position_value_with_price_change(
|
| 352 |
+
position_group, price_change
|
| 353 |
+
)
|
| 354 |
+
# Format and display result
|
| 355 |
+
```
|
| 356 |
|
| 357 |
+
2. **Follow the Boy Scout Rule**: Leave the code cleaner than you found it.
|
| 358 |
|
| 359 |
+
3. **Don't Repeat Yourself (DRY)**: Extract repeated code into reusable functions.
|
| 360 |
|
| 361 |
+
4. **You Aren't Gonna Need It (YAGNI)**: Don't add functionality until it's necessary.
|
| 362 |
|
| 363 |
+
5. **Optimize After Measuring**: Profile code to identify actual bottlenecks before optimizing.
|
| 364 |
|
| 365 |
+
6. **Use Consistent Formatting**: Use Black, Flake8, and isort to maintain consistent code style.
|
| 366 |
|
| 367 |
+
7. **Imports at Top**: Always place all imports at the top of the file.
|
| 368 |
|
| 369 |
+
8. **No Unused Code**: Remove commented-out code and unused imports/variables.
|
| 370 |
+
|
| 371 |
+
9. **Configuration Over Hardcoding**: Use configuration files for values that might change.
|
| 372 |
+
|
| 373 |
+
10. **Log with Context**: Include relevant information in log messages.
|
| 374 |
+
|
| 375 |
+
11. **Make Small, Focused Changes**: Don't modify unrelated code when implementing a feature or fixing a bug.
|
| 376 |
|
| 377 |
## Benefits of Following These Conventions
|
| 378 |
|
|
@@ -6,11 +6,16 @@ alwaysApply: true
|
|
| 6 |
|
| 7 |
# Folio Project Design
|
| 8 |
|
| 9 |
-
This document outlines how the Folio codebase is structured and how data flows through the application. Folio
|
| 10 |
|
| 11 |
## Application Overview
|
| 12 |
|
| 13 |
-
Folio is a Python-based
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
## Deployment Modes
|
| 16 |
|
|
@@ -90,9 +95,9 @@ Portfolio metrics are calculated in several steps:
|
|
| 90 |
|
| 91 |
The canonical implementations for these calculations are in [portfolio_value.py](src/folio/portfolio_value.py).
|
| 92 |
|
| 93 |
-
## UI Components
|
| 94 |
|
| 95 |
-
The UI is built with Dash and consists of several key components:
|
| 96 |
|
| 97 |
1. **Summary Cards**: Display high-level portfolio metrics
|
| 98 |
2. **Charts**: Visualize portfolio allocation and exposure
|
|
@@ -111,22 +116,64 @@ Components interact through Dash callbacks:
|
|
| 111 |
3. Components subscribe to changes in the stored data and update accordingly
|
| 112 |
4. This pattern allows for a reactive UI without page reloads
|
| 113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
## Key Modules
|
| 115 |
|
| 116 |
-
###
|
|
|
|
|
|
|
| 117 |
|
| 118 |
- **portfolio.py**: Core portfolio processing logic
|
| 119 |
- **portfolio_value.py**: Canonical implementations of portfolio value calculations
|
|
|
|
| 120 |
- **options.py**: Option pricing and Greeks calculations
|
| 121 |
- **cash_detection.py**: Identification of cash-like positions
|
| 122 |
|
| 123 |
-
|
| 124 |
|
| 125 |
- **stockdata.py**: Common interface for data fetchers
|
| 126 |
- **yfinance.py**: Yahoo Finance data fetcher
|
| 127 |
- **fmp.py**: Financial Modeling Prep data fetcher
|
| 128 |
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
- **components/**: UI components for the dashboard
|
| 132 |
- **charts.py**: Portfolio visualization charts
|
|
@@ -135,12 +182,24 @@ Components interact through Dash callbacks:
|
|
| 135 |
- **pnl_chart.py**: Profit/loss visualization
|
| 136 |
- **summary_cards.py**: High-level portfolio metrics
|
| 137 |
|
| 138 |
-
|
| 139 |
|
| 140 |
- **app.py**: Main Dash application setup and callbacks
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
|
| 145 |
## Configuration
|
| 146 |
|
|
@@ -180,13 +239,48 @@ To add new features to Folio:
|
|
| 180 |
3. **Callbacks**: Add new callbacks in `app.py` to handle user interactions
|
| 181 |
4. **Testing**: Add tests for new functionality
|
| 182 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
## Conclusion
|
| 184 |
|
| 185 |
Folio is designed with a clean separation of concerns:
|
| 186 |
|
|
|
|
| 187 |
- Data fetching is abstracted behind interfaces
|
| 188 |
- Data processing is separated from UI components
|
| 189 |
- UI components are modular and reusable
|
| 190 |
- Configuration is externalized for flexibility
|
|
|
|
| 191 |
|
| 192 |
This architecture makes the codebase maintainable, testable, and extensible, allowing for easy addition of new features and improvements.
|
|
|
|
| 6 |
|
| 7 |
# Folio Project Design
|
| 8 |
|
| 9 |
+
This document outlines how the Folio codebase is structured and how data flows through the application. Folio provides tools for analyzing and visualizing investment portfolios, with a focus on stocks and options, through both a web-based dashboard and a command-line interface (CLI).
|
| 10 |
|
| 11 |
## Application Overview
|
| 12 |
|
| 13 |
+
Folio is a Python-based application that provides comprehensive portfolio analysis capabilities through multiple interfaces:
|
| 14 |
+
|
| 15 |
+
1. **Web Interface**: A Dash-based web application for visualizing portfolio data
|
| 16 |
+
2. **CLI Interface (`focli`)**: A command-line interface for portfolio analysis and simulation
|
| 17 |
+
|
| 18 |
+
Both interfaces leverage the same core library (`src/folio/`) for business logic, following our [strict separation of concerns](#separation-of-concerns) principles. The primary domain entities for this app are outlined below. For an authoritative overview of the data model, [data_model.py](src/folio/data_model.py) is the source of truth.
|
| 19 |
|
| 20 |
## Deployment Modes
|
| 21 |
|
|
|
|
| 95 |
|
| 96 |
The canonical implementations for these calculations are in [portfolio_value.py](src/folio/portfolio_value.py).
|
| 97 |
|
| 98 |
+
## Web UI Components
|
| 99 |
|
| 100 |
+
The web UI is built with Dash and consists of several key components:
|
| 101 |
|
| 102 |
1. **Summary Cards**: Display high-level portfolio metrics
|
| 103 |
2. **Charts**: Visualize portfolio allocation and exposure
|
|
|
|
| 116 |
3. Components subscribe to changes in the stored data and update accordingly
|
| 117 |
4. This pattern allows for a reactive UI without page reloads
|
| 118 |
|
| 119 |
+
## CLI Interface
|
| 120 |
+
|
| 121 |
+
The CLI interface (`focli`) provides a command-line tool for portfolio analysis and simulation:
|
| 122 |
+
|
| 123 |
+
### Architecture
|
| 124 |
+
|
| 125 |
+
1. **Shell**: An interactive shell implemented in [shell.py](src/focli/shell.py) using the `cmd` module
|
| 126 |
+
2. **Commands**: Command handlers in the [commands](src/focli/commands) directory
|
| 127 |
+
3. **Formatters**: Output formatting utilities in [formatters.py](src/focli/formatters.py)
|
| 128 |
+
4. **Utils**: CLI-specific utilities in [utils.py](src/focli/utils.py)
|
| 129 |
+
|
| 130 |
+
### Command Structure
|
| 131 |
+
|
| 132 |
+
The CLI follows a command-subcommand structure:
|
| 133 |
+
|
| 134 |
+
```
|
| 135 |
+
folio> command [subcommand] [options]
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
Key commands include:
|
| 139 |
+
- `simulate`: Simulate portfolio performance with SPY changes
|
| 140 |
+
- `position`: Analyze a specific position group
|
| 141 |
+
- `portfolio`: View and analyze portfolio data
|
| 142 |
+
|
| 143 |
+
### Separation of Concerns
|
| 144 |
+
|
| 145 |
+
The CLI strictly adheres to the [separation of concerns](#separation-of-concerns) principles:
|
| 146 |
+
- Command handlers only handle parsing, validation, and presentation
|
| 147 |
+
- All business logic is delegated to the core library
|
| 148 |
+
- No calculation or simulation logic exists in the CLI layer
|
| 149 |
+
|
| 150 |
## Key Modules
|
| 151 |
|
| 152 |
+
### Core Library (src/folio/)
|
| 153 |
+
|
| 154 |
+
#### Data Processing
|
| 155 |
|
| 156 |
- **portfolio.py**: Core portfolio processing logic
|
| 157 |
- **portfolio_value.py**: Canonical implementations of portfolio value calculations
|
| 158 |
+
- **simulator.py**: Portfolio and position simulation logic
|
| 159 |
- **options.py**: Option pricing and Greeks calculations
|
| 160 |
- **cash_detection.py**: Identification of cash-like positions
|
| 161 |
|
| 162 |
+
#### Data Fetching
|
| 163 |
|
| 164 |
- **stockdata.py**: Common interface for data fetchers
|
| 165 |
- **yfinance.py**: Yahoo Finance data fetcher
|
| 166 |
- **fmp.py**: Financial Modeling Prep data fetcher
|
| 167 |
|
| 168 |
+
#### Application Core
|
| 169 |
+
|
| 170 |
+
- **data_model.py**: Core data structures
|
| 171 |
+
- **logger.py**: Logging configuration
|
| 172 |
+
- **security.py**: Security utilities for validating user inputs
|
| 173 |
+
|
| 174 |
+
### Web UI (src/folio/)
|
| 175 |
+
|
| 176 |
+
#### UI Components
|
| 177 |
|
| 178 |
- **components/**: UI components for the dashboard
|
| 179 |
- **charts.py**: Portfolio visualization charts
|
|
|
|
| 182 |
- **pnl_chart.py**: Profit/loss visualization
|
| 183 |
- **summary_cards.py**: High-level portfolio metrics
|
| 184 |
|
| 185 |
+
#### Web Application
|
| 186 |
|
| 187 |
- **app.py**: Main Dash application setup and callbacks
|
| 188 |
+
|
| 189 |
+
### CLI Interface (src/focli/)
|
| 190 |
+
|
| 191 |
+
#### Command Handling
|
| 192 |
+
|
| 193 |
+
- **shell.py**: Interactive shell implementation
|
| 194 |
+
- **commands/**: Command handlers
|
| 195 |
+
- **simulate.py**: Portfolio simulation commands
|
| 196 |
+
- **position.py**: Position analysis commands
|
| 197 |
+
- **portfolio.py**: Portfolio management commands
|
| 198 |
+
|
| 199 |
+
#### Presentation
|
| 200 |
+
|
| 201 |
+
- **formatters.py**: Output formatting utilities
|
| 202 |
+
- **utils.py**: CLI-specific utilities (no business logic)
|
| 203 |
|
| 204 |
## Configuration
|
| 205 |
|
|
|
|
| 239 |
3. **Callbacks**: Add new callbacks in `app.py` to handle user interactions
|
| 240 |
4. **Testing**: Add tests for new functionality
|
| 241 |
|
| 242 |
+
## Separation of Concerns
|
| 243 |
+
|
| 244 |
+
Folio strictly adheres to separation of concerns principles:
|
| 245 |
+
|
| 246 |
+
### Core Library vs Interface Layers
|
| 247 |
+
|
| 248 |
+
1. **Core Library (`src/folio/`)**:
|
| 249 |
+
- Contains ALL business logic, data processing, and calculation functionality
|
| 250 |
+
- Provides a stable API for interface layers to use
|
| 251 |
+
- Should never depend on interface-specific code
|
| 252 |
+
|
| 253 |
+
2. **Interface Layers (`src/focli/`, web UI)**:
|
| 254 |
+
- Handle user interaction, command parsing, and result presentation
|
| 255 |
+
- Call core library functions to perform business operations
|
| 256 |
+
- Should NEVER contain business logic
|
| 257 |
+
- Focus solely on translating user inputs to core library calls and formatting outputs
|
| 258 |
+
|
| 259 |
+
### Business Logic Placement
|
| 260 |
+
|
| 261 |
+
Business logic must ALWAYS reside in the core library, not in interface layers. Examples include:
|
| 262 |
+
|
| 263 |
+
- Calculations and algorithms
|
| 264 |
+
- Data transformations
|
| 265 |
+
- Simulation logic
|
| 266 |
+
- Portfolio analysis
|
| 267 |
+
- Value calculations
|
| 268 |
+
|
| 269 |
+
Interface layers should be thin wrappers around the core library, focusing only on:
|
| 270 |
+
- Parsing user input
|
| 271 |
+
- Calling appropriate core library functions
|
| 272 |
+
- Formatting and presenting results
|
| 273 |
+
- Managing UI state
|
| 274 |
+
|
| 275 |
## Conclusion
|
| 276 |
|
| 277 |
Folio is designed with a clean separation of concerns:
|
| 278 |
|
| 279 |
+
- Business logic is centralized in the core library
|
| 280 |
- Data fetching is abstracted behind interfaces
|
| 281 |
- Data processing is separated from UI components
|
| 282 |
- UI components are modular and reusable
|
| 283 |
- Configuration is externalized for flexibility
|
| 284 |
+
- Interface layers are thin and focused on user interaction
|
| 285 |
|
| 286 |
This architecture makes the codebase maintainable, testable, and extensible, allowing for easy addition of new features and improvements.
|
|
@@ -11,12 +11,8 @@ from src.focli.formatters import (
|
|
| 11 |
display_position_risk_analysis,
|
| 12 |
display_position_simulation,
|
| 13 |
)
|
| 14 |
-
from src.focli.utils import
|
| 15 |
-
|
| 16 |
-
generate_spy_changes,
|
| 17 |
-
parse_args,
|
| 18 |
-
simulate_position_with_spy_changes,
|
| 19 |
-
)
|
| 20 |
|
| 21 |
|
| 22 |
def position_command(args: list[str], state: dict[str, Any], console):
|
|
|
|
| 11 |
display_position_risk_analysis,
|
| 12 |
display_position_simulation,
|
| 13 |
)
|
| 14 |
+
from src.focli.utils import find_position_group, parse_args
|
| 15 |
+
from src.folio.simulator import generate_spy_changes, simulate_position_with_spy_changes
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
|
| 18 |
def position_command(args: list[str], state: dict[str, Any], console):
|
|
@@ -8,8 +8,11 @@ import copy
|
|
| 8 |
from typing import Any
|
| 9 |
|
| 10 |
from src.focli.formatters import display_simulation_results
|
| 11 |
-
from src.focli.utils import filter_portfolio_groups,
|
| 12 |
-
from src.folio.simulator import
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
|
| 15 |
def simulate_command(args: list[str], state: dict[str, Any], console):
|
|
|
|
| 8 |
from typing import Any
|
| 9 |
|
| 10 |
from src.focli.formatters import display_simulation_results
|
| 11 |
+
from src.focli.utils import filter_portfolio_groups, parse_args
|
| 12 |
+
from src.folio.simulator import (
|
| 13 |
+
generate_spy_changes,
|
| 14 |
+
simulate_portfolio_with_spy_changes,
|
| 15 |
+
)
|
| 16 |
|
| 17 |
|
| 18 |
def simulate_command(args: list[str], state: dict[str, Any], console):
|
|
@@ -63,35 +63,6 @@ def load_portfolio(path, state, console=None):
|
|
| 63 |
raise RuntimeError(f"Error loading portfolio: {e!s}") from e
|
| 64 |
|
| 65 |
|
| 66 |
-
def generate_spy_changes(range_pct, steps):
|
| 67 |
-
"""Generate a list of SPY changes for simulation.
|
| 68 |
-
|
| 69 |
-
Args:
|
| 70 |
-
range_pct: Range of SPY changes in percent (e.g., 20.0 for ±20%)
|
| 71 |
-
steps: Number of steps in the simulation
|
| 72 |
-
|
| 73 |
-
Returns:
|
| 74 |
-
List of SPY changes as decimals (e.g., [-0.2, -0.1, 0.0, 0.1, 0.2])
|
| 75 |
-
"""
|
| 76 |
-
# Calculate the step size
|
| 77 |
-
step_size = (2 * range_pct) / (steps - 1) if steps > 1 else 0
|
| 78 |
-
|
| 79 |
-
# Generate the SPY changes
|
| 80 |
-
spy_changes = [-range_pct + i * step_size for i in range(steps)]
|
| 81 |
-
|
| 82 |
-
# Ensure we have a zero point
|
| 83 |
-
if 0.0 not in spy_changes and steps > 2:
|
| 84 |
-
# Find the closest point to zero and replace it with zero
|
| 85 |
-
closest_to_zero = min(spy_changes, key=lambda x: abs(x))
|
| 86 |
-
zero_index = spy_changes.index(closest_to_zero)
|
| 87 |
-
spy_changes[zero_index] = 0.0
|
| 88 |
-
|
| 89 |
-
# Convert to percentages
|
| 90 |
-
spy_changes = [change / 100.0 for change in spy_changes]
|
| 91 |
-
|
| 92 |
-
return spy_changes
|
| 93 |
-
|
| 94 |
-
|
| 95 |
def find_position_group(ticker, portfolio_groups):
|
| 96 |
"""Find a position group by ticker.
|
| 97 |
|
|
@@ -185,112 +156,6 @@ def parse_args(args, arg_specs):
|
|
| 185 |
return result
|
| 186 |
|
| 187 |
|
| 188 |
-
def calculate_position_value_with_price_change(position_group, price_change):
|
| 189 |
-
"""Calculate the value of a position with a given price change.
|
| 190 |
-
|
| 191 |
-
Args:
|
| 192 |
-
position_group: PortfolioGroup to calculate
|
| 193 |
-
price_change: Price change as a decimal (e.g., 0.05 for 5% increase)
|
| 194 |
-
|
| 195 |
-
Returns:
|
| 196 |
-
New position value
|
| 197 |
-
"""
|
| 198 |
-
# Start with current value
|
| 199 |
-
|
| 200 |
-
# For a simple implementation, we'll adjust the value based on the price change
|
| 201 |
-
# This is a simplified approach - in a real implementation, we would recalculate
|
| 202 |
-
# option values based on the new underlying price and delta
|
| 203 |
-
|
| 204 |
-
# Calculate stock value change
|
| 205 |
-
stock_value = (
|
| 206 |
-
position_group.stock_position.market_value
|
| 207 |
-
if position_group.stock_position
|
| 208 |
-
else 0
|
| 209 |
-
)
|
| 210 |
-
new_stock_value = stock_value * (1 + price_change)
|
| 211 |
-
|
| 212 |
-
# Calculate option value change (simplified)
|
| 213 |
-
option_value = (
|
| 214 |
-
sum(op.market_value for op in position_group.option_positions)
|
| 215 |
-
if position_group.option_positions
|
| 216 |
-
else 0
|
| 217 |
-
)
|
| 218 |
-
|
| 219 |
-
# For options, we use delta to approximate the change
|
| 220 |
-
# This is a simplified approach
|
| 221 |
-
option_delta_exposure = (
|
| 222 |
-
position_group.total_delta_exposure
|
| 223 |
-
if hasattr(position_group, "total_delta_exposure")
|
| 224 |
-
else 0
|
| 225 |
-
)
|
| 226 |
-
option_delta_change = option_delta_exposure * price_change
|
| 227 |
-
new_option_value = option_value + option_delta_change
|
| 228 |
-
|
| 229 |
-
# Total new value
|
| 230 |
-
new_value = new_stock_value + new_option_value
|
| 231 |
-
|
| 232 |
-
return new_value
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
def simulate_position_with_spy_changes(position_group, spy_changes):
|
| 236 |
-
"""Simulate a position with SPY changes.
|
| 237 |
-
|
| 238 |
-
Args:
|
| 239 |
-
position_group: PortfolioGroup to simulate
|
| 240 |
-
spy_changes: List of SPY changes as decimals
|
| 241 |
-
|
| 242 |
-
Returns:
|
| 243 |
-
Dictionary with simulation results
|
| 244 |
-
"""
|
| 245 |
-
ticker = position_group.ticker
|
| 246 |
-
beta = position_group.beta
|
| 247 |
-
current_value = position_group.net_exposure
|
| 248 |
-
|
| 249 |
-
# Calculate position values at different SPY changes
|
| 250 |
-
values = []
|
| 251 |
-
for spy_change in spy_changes:
|
| 252 |
-
# Calculate the price change for this position based on beta
|
| 253 |
-
price_change = spy_change * beta
|
| 254 |
-
|
| 255 |
-
# Calculate the new position value
|
| 256 |
-
new_value = calculate_position_value_with_price_change(
|
| 257 |
-
position_group, price_change
|
| 258 |
-
)
|
| 259 |
-
values.append(new_value)
|
| 260 |
-
|
| 261 |
-
# Calculate changes from current value
|
| 262 |
-
changes = [value - current_value for value in values]
|
| 263 |
-
pct_changes = [
|
| 264 |
-
(change / current_value) * 100 if current_value != 0 else 0
|
| 265 |
-
for change in changes
|
| 266 |
-
]
|
| 267 |
-
|
| 268 |
-
# Find min and max values
|
| 269 |
-
min_value = min(values)
|
| 270 |
-
max_value = max(values)
|
| 271 |
-
min_index = values.index(min_value)
|
| 272 |
-
max_index = values.index(max_value)
|
| 273 |
-
min_spy_change = spy_changes[min_index] * 100 # Convert to percentage
|
| 274 |
-
max_spy_change = spy_changes[max_index] * 100 # Convert to percentage
|
| 275 |
-
|
| 276 |
-
# Create results dictionary
|
| 277 |
-
results = {
|
| 278 |
-
"ticker": ticker,
|
| 279 |
-
"beta": beta,
|
| 280 |
-
"current_value": current_value,
|
| 281 |
-
"spy_changes": spy_changes,
|
| 282 |
-
"values": values,
|
| 283 |
-
"changes": changes,
|
| 284 |
-
"pct_changes": pct_changes,
|
| 285 |
-
"min_value": min_value,
|
| 286 |
-
"max_value": max_value,
|
| 287 |
-
"min_spy_change": min_spy_change,
|
| 288 |
-
"max_spy_change": max_spy_change,
|
| 289 |
-
}
|
| 290 |
-
|
| 291 |
-
return results
|
| 292 |
-
|
| 293 |
-
|
| 294 |
def filter_portfolio_groups(portfolio_groups, filter_criteria=None):
|
| 295 |
"""Filter portfolio groups based on criteria.
|
| 296 |
|
|
|
|
| 63 |
raise RuntimeError(f"Error loading portfolio: {e!s}") from e
|
| 64 |
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
def find_position_group(ticker, portfolio_groups):
|
| 67 |
"""Find a position group by ticker.
|
| 68 |
|
|
|
|
| 156 |
return result
|
| 157 |
|
| 158 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
def filter_portfolio_groups(portfolio_groups, filter_criteria=None):
|
| 160 |
"""Filter portfolio groups based on criteria.
|
| 161 |
|
|
@@ -491,3 +491,46 @@ def calculate_component_percentages(
|
|
| 491 |
) # Will be negative
|
| 492 |
|
| 493 |
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 491 |
) # Will be negative
|
| 492 |
|
| 493 |
return result
|
| 494 |
+
|
| 495 |
+
|
| 496 |
+
def calculate_position_value_with_price_change(
|
| 497 |
+
position_group: PortfolioGroup, price_change: float
|
| 498 |
+
) -> float:
|
| 499 |
+
"""Calculate the value of a position with a given price change.
|
| 500 |
+
|
| 501 |
+
Args:
|
| 502 |
+
position_group: PortfolioGroup to calculate
|
| 503 |
+
price_change: Price change as a decimal (e.g., 0.05 for 5% increase)
|
| 504 |
+
|
| 505 |
+
Returns:
|
| 506 |
+
New position value
|
| 507 |
+
"""
|
| 508 |
+
# Calculate stock value change
|
| 509 |
+
stock_value = (
|
| 510 |
+
position_group.stock_position.market_value
|
| 511 |
+
if position_group.stock_position
|
| 512 |
+
else 0
|
| 513 |
+
)
|
| 514 |
+
new_stock_value = stock_value * (1 + price_change)
|
| 515 |
+
|
| 516 |
+
# Calculate option value change (simplified)
|
| 517 |
+
option_value = (
|
| 518 |
+
sum(op.market_value for op in position_group.option_positions)
|
| 519 |
+
if position_group.option_positions
|
| 520 |
+
else 0
|
| 521 |
+
)
|
| 522 |
+
|
| 523 |
+
# For options, we use delta to approximate the change
|
| 524 |
+
# This is a simplified approach
|
| 525 |
+
option_delta_exposure = (
|
| 526 |
+
position_group.total_delta_exposure
|
| 527 |
+
if hasattr(position_group, "total_delta_exposure")
|
| 528 |
+
else 0
|
| 529 |
+
)
|
| 530 |
+
option_delta_change = option_delta_exposure * price_change
|
| 531 |
+
new_option_value = option_value + option_delta_change
|
| 532 |
+
|
| 533 |
+
# Total new value
|
| 534 |
+
new_value = new_stock_value + new_option_value
|
| 535 |
+
|
| 536 |
+
return new_value
|
|
@@ -9,6 +9,7 @@ import numpy as np
|
|
| 9 |
from .data_model import PortfolioGroup
|
| 10 |
from .logger import logger
|
| 11 |
from .portfolio import recalculate_portfolio_with_prices
|
|
|
|
| 12 |
|
| 13 |
|
| 14 |
def simulate_portfolio_with_spy_changes(
|
|
@@ -213,3 +214,94 @@ def calculate_percentage_changes(values: list[float], base_value: float) -> list
|
|
| 213 |
return [0.0] * len(values)
|
| 214 |
|
| 215 |
return [(value / base_value - 1.0) * 100.0 for value in values]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
from .data_model import PortfolioGroup
|
| 10 |
from .logger import logger
|
| 11 |
from .portfolio import recalculate_portfolio_with_prices
|
| 12 |
+
from .portfolio_value import calculate_position_value_with_price_change
|
| 13 |
|
| 14 |
|
| 15 |
def simulate_portfolio_with_spy_changes(
|
|
|
|
| 214 |
return [0.0] * len(values)
|
| 215 |
|
| 216 |
return [(value / base_value - 1.0) * 100.0 for value in values]
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def generate_spy_changes(range_pct: float, steps: int) -> list[float]:
|
| 220 |
+
"""Generate a list of SPY changes for simulation.
|
| 221 |
+
|
| 222 |
+
Args:
|
| 223 |
+
range_pct: Range of SPY changes in percent (e.g., 20.0 for ±20%)
|
| 224 |
+
steps: Number of steps in the simulation
|
| 225 |
+
|
| 226 |
+
Returns:
|
| 227 |
+
List of SPY changes as decimals (e.g., [-0.2, -0.1, 0.0, 0.1, 0.2])
|
| 228 |
+
"""
|
| 229 |
+
# Calculate the step size
|
| 230 |
+
step_size = (2 * range_pct) / (steps - 1) if steps > 1 else 0
|
| 231 |
+
|
| 232 |
+
# Generate the SPY changes
|
| 233 |
+
spy_changes = [-range_pct + i * step_size for i in range(steps)]
|
| 234 |
+
|
| 235 |
+
# Ensure we have a zero point
|
| 236 |
+
if 0.0 not in spy_changes and steps > 2:
|
| 237 |
+
# Find the closest point to zero and replace it with zero
|
| 238 |
+
closest_to_zero = min(spy_changes, key=lambda x: abs(x))
|
| 239 |
+
zero_index = spy_changes.index(closest_to_zero)
|
| 240 |
+
spy_changes[zero_index] = 0.0
|
| 241 |
+
|
| 242 |
+
# Convert to percentages
|
| 243 |
+
spy_changes = [change / 100.0 for change in spy_changes]
|
| 244 |
+
|
| 245 |
+
return spy_changes
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def simulate_position_with_spy_changes(
|
| 249 |
+
position_group: PortfolioGroup, spy_changes: list[float]
|
| 250 |
+
) -> dict:
|
| 251 |
+
"""Simulate a position with SPY changes.
|
| 252 |
+
|
| 253 |
+
Args:
|
| 254 |
+
position_group: PortfolioGroup to simulate
|
| 255 |
+
spy_changes: List of SPY changes as decimals
|
| 256 |
+
|
| 257 |
+
Returns:
|
| 258 |
+
Dictionary with simulation results
|
| 259 |
+
"""
|
| 260 |
+
|
| 261 |
+
ticker = position_group.ticker
|
| 262 |
+
beta = position_group.beta
|
| 263 |
+
current_value = position_group.net_exposure
|
| 264 |
+
|
| 265 |
+
# Calculate position values at different SPY changes
|
| 266 |
+
values = []
|
| 267 |
+
for spy_change in spy_changes:
|
| 268 |
+
# Calculate the price change for this position based on beta
|
| 269 |
+
price_change = spy_change * beta
|
| 270 |
+
|
| 271 |
+
# Calculate the new position value
|
| 272 |
+
new_value = calculate_position_value_with_price_change(
|
| 273 |
+
position_group, price_change
|
| 274 |
+
)
|
| 275 |
+
values.append(new_value)
|
| 276 |
+
|
| 277 |
+
# Calculate changes from current value
|
| 278 |
+
changes = [value - current_value for value in values]
|
| 279 |
+
pct_changes = [
|
| 280 |
+
(change / current_value) * 100 if current_value != 0 else 0
|
| 281 |
+
for change in changes
|
| 282 |
+
]
|
| 283 |
+
|
| 284 |
+
# Find min and max values
|
| 285 |
+
min_value = min(values)
|
| 286 |
+
max_value = max(values)
|
| 287 |
+
min_index = values.index(min_value)
|
| 288 |
+
max_index = values.index(max_value)
|
| 289 |
+
min_spy_change = spy_changes[min_index] * 100 # Convert to percentage
|
| 290 |
+
max_spy_change = spy_changes[max_index] * 100 # Convert to percentage
|
| 291 |
+
|
| 292 |
+
# Create results dictionary
|
| 293 |
+
results = {
|
| 294 |
+
"ticker": ticker,
|
| 295 |
+
"beta": beta,
|
| 296 |
+
"current_value": current_value,
|
| 297 |
+
"spy_changes": spy_changes,
|
| 298 |
+
"values": values,
|
| 299 |
+
"changes": changes,
|
| 300 |
+
"pct_changes": pct_changes,
|
| 301 |
+
"min_value": min_value,
|
| 302 |
+
"max_value": max_value,
|
| 303 |
+
"min_spy_change": min_spy_change,
|
| 304 |
+
"max_spy_change": max_spy_change,
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
return results
|