grynn-fplot 0.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. grynn_fplot-0.3.4/.devcontainer/devcontainer.json +28 -0
  2. grynn_fplot-0.3.4/.github/copilot-instructions.md +141 -0
  3. grynn_fplot-0.3.4/.github/dependabot.yml +12 -0
  4. grynn_fplot-0.3.4/.github/workflows/publish.yml +56 -0
  5. grynn_fplot-0.3.4/.gitignore +5 -0
  6. grynn_fplot-0.3.4/.pre-commit-config.yaml +35 -0
  7. grynn_fplot-0.3.4/.secrets.baseline +127 -0
  8. grynn_fplot-0.3.4/.vscode/settings.json +7 -0
  9. grynn_fplot-0.3.4/Makefile +70 -0
  10. grynn_fplot-0.3.4/PKG-INFO +188 -0
  11. grynn_fplot-0.3.4/README.md +149 -0
  12. grynn_fplot-0.3.4/docs/FILTER_DESIGN.md +316 -0
  13. grynn_fplot-0.3.4/docs/FILTER_EXAMPLES.sh +110 -0
  14. grynn_fplot-0.3.4/docs/PERFORMANCE_OPTIMIZATION.md +170 -0
  15. grynn_fplot-0.3.4/docs/WEB_REFACTOR.md +175 -0
  16. grynn_fplot-0.3.4/example_usage.sh +52 -0
  17. grynn_fplot-0.3.4/grynn_fplot/__init__.py +1 -0
  18. grynn_fplot-0.3.4/grynn_fplot/cli.py +607 -0
  19. grynn_fplot-0.3.4/grynn_fplot/core.py +844 -0
  20. grynn_fplot-0.3.4/grynn_fplot/drawdowns.py +80 -0
  21. grynn_fplot-0.3.4/grynn_fplot/filter_parser.py +579 -0
  22. grynn_fplot-0.3.4/grynn_fplot/index.html +967 -0
  23. grynn_fplot-0.3.4/grynn_fplot/plot_option_interactive.py +57 -0
  24. grynn_fplot-0.3.4/grynn_fplot/serve.py +221 -0
  25. grynn_fplot-0.3.4/grynn_fplot/web_api.py +292 -0
  26. grynn_fplot-0.3.4/pyproject.toml +67 -0
  27. grynn_fplot-0.3.4/tests/test_candlestick.py +143 -0
  28. grynn_fplot-0.3.4/tests/test_cli_integration.py +123 -0
  29. grynn_fplot-0.3.4/tests/test_cli_options.py +124 -0
  30. grynn_fplot-0.3.4/tests/test_filter_evaluation.py +174 -0
  31. grynn_fplot-0.3.4/tests/test_filter_parser.py +320 -0
  32. grynn_fplot-0.3.4/tests/test_fplot.py +68 -0
  33. grynn_fplot-0.3.4/tests/test_input_parsing.py +151 -0
  34. grynn_fplot-0.3.4/tests/test_leverage.py +167 -0
  35. grynn_fplot-0.3.4/tests/test_options.py +127 -0
  36. grynn_fplot-0.3.4/tests/test_web_api.py +274 -0
  37. grynn_fplot-0.3.4/tests/test_web_interface.py +301 -0
  38. grynn_fplot-0.3.4/uv.lock +1776 -0
@@ -0,0 +1,28 @@
1
+ // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2
+ // README at: https://github.com/devcontainers/templates/tree/main/src/python
3
+ {
4
+ "name": "Python 3",
5
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6
+ "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye",
7
+ "features": {
8
+ "ghcr.io/devcontainers-extra/features/uv:1": {}
9
+ },
10
+ // Mount .venv to a named volume to separate container and host environments
11
+ // Copy git config from host (not synced, just copy)
12
+ "mounts": [
13
+ "source=grynn_fplot_venv,target=${containerWorkspaceFolder}/.venv,type=volume",
14
+ "source=${localEnv:HOME}/.gitconfig,target=/tmp/host-gitconfig,type=bind,readonly"
15
+ ],
16
+ // Use 'postCreateCommand' to run commands after the container is created.
17
+ "postCreateCommand": "sudo chown -R vscode:vscode ${containerWorkspaceFolder}/.venv && cp /tmp/host-gitconfig ~/.gitconfig || true",
18
+ // Configure tool-specific properties.
19
+ "customizations": {
20
+ "vscode": {
21
+ "settings": {
22
+ "chat.tools.global.autoApprove": true
23
+ }
24
+ }
25
+ }
26
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
27
+ // "remoteUser": "root"
28
+ }
@@ -0,0 +1,141 @@
1
+ # Financial Plotting CLI Tool
2
+ Financial plotting CLI tool (fplot) is a Python application that provides both a command-line interface and a web interface for plotting stock price data, drawdowns, and financial analysis. Built with Python 3.12+, matplotlib, FastAPI, and managed with uv package manager.
3
+
4
+ Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
5
+
6
+ ## Working Effectively
7
+
8
+ ### Bootstrap and Setup
9
+ - Install uv package manager: `pip install uv`
10
+ - Bootstrap the development environment: `make dev`
11
+ - Takes ~3 minutes for fresh setup. NEVER CANCEL. Set timeout to 10+ minutes.
12
+ - Creates virtual environment and installs all dependencies including scipy, matplotlib, pandas
13
+ - Subsequent runs take <1 second due to caching
14
+ - Install CLI tool system-wide: `make install`
15
+ - Takes ~3 minutes for fresh install. NEVER CANCEL. Set timeout to 10+ minutes.
16
+ - Makes `fplot` command available globally via uv tool install
17
+
18
+ ### Testing and Quality
19
+ - Run tests: `make test` or `uv run pytest`
20
+ - Takes ~6 seconds. Set timeout to 30+ seconds.
21
+ - All tests must pass before committing changes
22
+ - Run linter: `make lint` or `uvx ruff check`
23
+ - Takes <1 second. Set timeout to 30+ seconds.
24
+ - Code must pass linting before committing changes
25
+ - Clean build artifacts: `make clean`
26
+
27
+ ### Build and Development
28
+ - Development setup: `make dev`
29
+ - NEVER CANCEL: Takes up to 3 minutes for fresh setup. Set timeout to 10+ minutes.
30
+ - Downloads and builds many packages including scipy (35MB+), matplotlib, pandas
31
+ - Subsequent runs are nearly instantaneous due to uv caching
32
+ - The project has no separate "build" step - it's a Python package installed directly
33
+
34
+ ## Running the Application
35
+
36
+ ### CLI Tool
37
+ - Basic usage: `fplot <ticker> [options]`
38
+ - Examples:
39
+ - `fplot AAPL` - Plot Apple stock (last 1 year default)
40
+ - `fplot AAPL --since "last 30 days"` - Plot Apple stock for last 30 days
41
+ - `fplot AAPL,TSLA --since "mar 2023"` - Compare multiple tickers
42
+ - `fplot --version` - Show version
43
+ - `fplot --help` - Show help
44
+
45
+ ### Web Interface
46
+ - Start web server: `uv run python grynn_fplot/serve.py`
47
+ - Runs on http://0.0.0.0:8000
48
+ - Provides interactive HTML interface with Plotly charts
49
+ - Hot reloads on code changes in development
50
+
51
+ ### Network Requirements
52
+ - CLI and web server require internet access to download financial data from Yahoo Finance
53
+ - Without internet, commands will fail with DNS resolution errors (expected behavior)
54
+ - All core functionality works offline with sample data
55
+
56
+ ## Validation
57
+
58
+ ### Always Test After Making Changes
59
+ 1. **Run the full test suite**: `make test` - must pass completely
60
+ 2. **Run the linter**: `make lint` - must pass without issues
61
+ 3. **Test CLI functionality**:
62
+ - `fplot --version` should show current version
63
+ - `fplot --help` should show usage information
64
+ - If internet is available, test with real ticker: `fplot AAPL --since "last 7 days"`
65
+ 4. **Test web server**:
66
+ - Start server: `uv run python grynn_fplot/serve.py`
67
+ - Verify http://localhost:8000 loads the HTML interface
68
+ - Check that page contains "Stock Chart" and plotly references
69
+ 5. **Test core functionality with sample data**:
70
+ - All core functions (normalize_prices, calculate_drawdowns, calculate_cagr, etc.) work correctly
71
+ - Date parsing handles various formats correctly
72
+
73
+ ### Manual Validation Scenarios
74
+ - **CLI Workflow**: `fplot --version` → `fplot --help` → verify output is correct
75
+ - **Web Workflow**: Start server → verify homepage loads → check HTML contains expected UI elements
76
+ - **Date Parsing**: Test with "last 30 days", "YTD", "last 6 months", "2 years ago"
77
+
78
+ ## Common Tasks
79
+
80
+ ### Key Project Structure
81
+ ```
82
+ /home/runner/work/grynn_cli_fplot/grynn_cli_fplot/
83
+ ├── grynn_fplot/ # Main package
84
+ │ ├── cli.py # CLI command implementation
85
+ │ ├── core.py # Core financial functions
86
+ │ ├── serve.py # FastAPI web server
87
+ │ └── index.html # Web UI template
88
+ ├── tests/ # Test suite
89
+ ├── Makefile # Build automation
90
+ ├── pyproject.toml # Package configuration
91
+ └── uv.lock # Dependency lock file
92
+ ```
93
+
94
+ ### Dependencies and External Libraries
95
+ - **Package Manager**: uv (modern Python package manager)
96
+ - **Core Dependencies**: click, matplotlib, pandas, yfinance, scikit-learn
97
+ - **Web Dependencies**: fastapi, uvicorn
98
+ - **Dev Dependencies**: pytest, ruff, mypy
99
+ - **External Git Dependencies**:
100
+ - grynn-pylib from https://github.com/Grynn/grynn_pylib.git
101
+ - yfinance from https://github.com/ranaroussi/yfinance.git
102
+
103
+ ### Timing Expectations and Timeouts
104
+ - **CRITICAL**: NEVER CANCEL builds or dependency installations - they may take 3+ minutes
105
+ - **make dev**: 3 minutes fresh, <1 second cached. Timeout: 10+ minutes.
106
+ - **make install**: 3 minutes fresh, <1 second cached. Timeout: 10+ minutes.
107
+ - **make test**: 6 seconds. Timeout: 30+ seconds.
108
+ - **make lint**: <1 second. Timeout: 30+ seconds.
109
+ - **CLI commands**: <5 seconds (plus network time for data). Timeout: 60+ seconds.
110
+ - **Web server startup**: 3 seconds. Timeout: 30+ seconds.
111
+
112
+ ### Key Files to Monitor
113
+ - Always check `pyproject.toml` when changing dependencies
114
+ - Always check `grynn_fplot/core.py` when modifying financial calculations
115
+ - Always check `grynn_fplot/cli.py` when changing CLI behavior
116
+ - Always check `tests/test_fplot.py` when adding new functionality
117
+
118
+ ### Development Environment Details
119
+ - **Python Version**: 3.12+ required
120
+ - **Virtual Environment**: Managed by uv in `.venv/`
121
+ - **Package Installation**: Uses `uv tool install` for global CLI access
122
+ - **Code Style**: Enforced by ruff linter with 120 character line length
123
+ - **Testing**: pytest-based with unittest-style test classes
124
+
125
+ ## Troubleshooting
126
+
127
+ ### Common Issues
128
+ - **ModuleNotFoundError**: Run `make dev` to ensure all dependencies are installed
129
+ - **DNS/Network Errors**: Expected when internet is limited - test with sample data instead
130
+ - **Build timeouts**: Increase timeout values - builds legitimately take 3+ minutes fresh
131
+ - **Import errors**: Use `uv run` prefix for all Python commands to use virtual environment
132
+
133
+ ### Build Problems
134
+ - Clean and rebuild: `make clean && make dev`
135
+ - Check uv version: `uv --version` (should be 0.8+)
136
+ - Verify Python version: `python3 --version` (should be 3.12+)
137
+
138
+ ### Testing Without Network
139
+ - CLI will show DNS errors but this is expected behavior
140
+ - Use core functionality tests with sample data to verify logic
141
+ - Web server will start but data endpoints will fail (expected)
@@ -0,0 +1,12 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for more information:
4
+ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
+ # https://containers.dev/guide/dependabot
6
+
7
+ version: 2
8
+ updates:
9
+ - package-ecosystem: "devcontainers"
10
+ directory: "/"
11
+ schedule:
12
+ interval: weekly
@@ -0,0 +1,56 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+ release:
8
+ types: [published]
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ build:
13
+ name: Build distribution
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v4
20
+ with:
21
+ version: "latest"
22
+
23
+ - name: Set up Python
24
+ run: uv python install 3.13
25
+
26
+ - name: Sync dependencies
27
+ run: uv sync
28
+
29
+ - name: Build package
30
+ run: uv build
31
+
32
+ - name: Store distribution packages
33
+ uses: actions/upload-artifact@v4
34
+ with:
35
+ name: python-package-distributions
36
+ path: dist/
37
+
38
+ publish-pypi:
39
+ name: Publish to PyPI
40
+ if: github.event_name == 'push' || github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'pypi')
41
+ needs: build
42
+ runs-on: ubuntu-latest
43
+ environment:
44
+ name: pypi
45
+ url: https://pypi.org/p/grynn-fplot
46
+ permissions:
47
+ id-token: write
48
+ steps:
49
+ - name: Download distributions
50
+ uses: actions/download-artifact@v4
51
+ with:
52
+ name: python-package-distributions
53
+ path: dist/
54
+
55
+ - name: Publish to PyPI
56
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,5 @@
1
+ .venv/
2
+ __pycache__/
3
+ build/
4
+ dist/
5
+ *.egg-info/
@@ -0,0 +1,35 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v6.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-added-large-files
9
+ - id: check-toml
10
+
11
+ - repo: https://github.com/astral-sh/ruff-pre-commit
12
+ rev: v0.9.2
13
+ hooks:
14
+ - id: ruff
15
+ name: ruff-lint
16
+ args: ["--fix"]
17
+ - id: ruff-format
18
+ name: ruff-format
19
+
20
+ - repo: https://github.com/Yelp/detect-secrets
21
+ rev: v1.5.0
22
+ hooks:
23
+ - id: detect-secrets
24
+ name: detect-secrets
25
+ args: ['--baseline', '.secrets.baseline']
26
+ exclude: package.lock.json
27
+
28
+ - repo: local
29
+ hooks:
30
+ - id: pytest
31
+ name: Run tests
32
+ entry: uv run pytest
33
+ language: system
34
+ pass_filenames: false
35
+ always_run: true
@@ -0,0 +1,127 @@
1
+ {
2
+ "version": "1.5.0",
3
+ "plugins_used": [
4
+ {
5
+ "name": "ArtifactoryDetector"
6
+ },
7
+ {
8
+ "name": "AWSKeyDetector"
9
+ },
10
+ {
11
+ "name": "AzureStorageKeyDetector"
12
+ },
13
+ {
14
+ "name": "Base64HighEntropyString",
15
+ "limit": 4.5
16
+ },
17
+ {
18
+ "name": "BasicAuthDetector"
19
+ },
20
+ {
21
+ "name": "CloudantDetector"
22
+ },
23
+ {
24
+ "name": "DiscordBotTokenDetector"
25
+ },
26
+ {
27
+ "name": "GitHubTokenDetector"
28
+ },
29
+ {
30
+ "name": "GitLabTokenDetector"
31
+ },
32
+ {
33
+ "name": "HexHighEntropyString",
34
+ "limit": 3.0
35
+ },
36
+ {
37
+ "name": "IbmCloudIamDetector"
38
+ },
39
+ {
40
+ "name": "IbmCosHmacDetector"
41
+ },
42
+ {
43
+ "name": "IPPublicDetector"
44
+ },
45
+ {
46
+ "name": "JwtTokenDetector"
47
+ },
48
+ {
49
+ "name": "KeywordDetector",
50
+ "keyword_exclude": ""
51
+ },
52
+ {
53
+ "name": "MailchimpDetector"
54
+ },
55
+ {
56
+ "name": "NpmDetector"
57
+ },
58
+ {
59
+ "name": "OpenAIDetector"
60
+ },
61
+ {
62
+ "name": "PrivateKeyDetector"
63
+ },
64
+ {
65
+ "name": "PypiTokenDetector"
66
+ },
67
+ {
68
+ "name": "SendGridDetector"
69
+ },
70
+ {
71
+ "name": "SlackDetector"
72
+ },
73
+ {
74
+ "name": "SoftlayerDetector"
75
+ },
76
+ {
77
+ "name": "SquareOAuthDetector"
78
+ },
79
+ {
80
+ "name": "StripeDetector"
81
+ },
82
+ {
83
+ "name": "TelegramBotTokenDetector"
84
+ },
85
+ {
86
+ "name": "TwilioKeyDetector"
87
+ }
88
+ ],
89
+ "filters_used": [
90
+ {
91
+ "path": "detect_secrets.filters.allowlist.is_line_allowlisted"
92
+ },
93
+ {
94
+ "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
95
+ "min_level": 2
96
+ },
97
+ {
98
+ "path": "detect_secrets.filters.heuristic.is_indirect_reference"
99
+ },
100
+ {
101
+ "path": "detect_secrets.filters.heuristic.is_likely_id_string"
102
+ },
103
+ {
104
+ "path": "detect_secrets.filters.heuristic.is_lock_file"
105
+ },
106
+ {
107
+ "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
108
+ },
109
+ {
110
+ "path": "detect_secrets.filters.heuristic.is_potential_uuid"
111
+ },
112
+ {
113
+ "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
114
+ },
115
+ {
116
+ "path": "detect_secrets.filters.heuristic.is_sequential_string"
117
+ },
118
+ {
119
+ "path": "detect_secrets.filters.heuristic.is_swagger_file"
120
+ },
121
+ {
122
+ "path": "detect_secrets.filters.heuristic.is_templated_secret"
123
+ }
124
+ ],
125
+ "results": {},
126
+ "generated_at": "2025-09-19T21:03:14Z"
127
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "python.testing.pytestArgs": [
3
+ "."
4
+ ],
5
+ "python.testing.unittestEnabled": false,
6
+ "python.testing.pytestEnabled": true
7
+ }
@@ -0,0 +1,70 @@
1
+ .PHONY: install clean test lint format coverage dev bump pre-commit pre-commit-install build publish
2
+
3
+ test:
4
+ uv run pytest
5
+
6
+ pre-commit: dev
7
+ uvx pre-commit run --all-files
8
+
9
+ pre-commit-install:
10
+ uvx pre-commit install
11
+
12
+ work-tree-is-clean:
13
+ @# Check if the working directory is clean
14
+ @if [ -n "$$(git status --porcelain)" ]; then \
15
+ echo "Error: Working directory is not clean. Please commit or stash your changes first."; \
16
+ git status --short; \
17
+ exit 1; \
18
+ else \
19
+ echo "Working directory is clean."; \
20
+ fi
21
+
22
+ bump: test pre-commit-install work-tree-is-clean
23
+ @# 1. Bump version using uv
24
+ @uv version --bump patch
25
+ @# 2. Commit changes (work tree was clean before, so only version files changed)
26
+ @new_version=$$(uv version --short) && \
27
+ git commit -am "Bump version => v$$new_version" && \
28
+ git tag "v$$new_version" && \
29
+ echo "Created git commit and tag v$$new_version"
30
+
31
+ install:
32
+ uv tool install --upgrade-package "grynn_fplot" "grynn_fplot @ $$PWD"
33
+
34
+ dev:
35
+ uv sync --all-extras
36
+
37
+ coverage: dev
38
+ uv run pytest --cov=src/grynn_cli_fplot --cov-report=term-missing
39
+
40
+ lint: dev
41
+ uvx ruff check
42
+
43
+ format: dev
44
+ uvx ruff format
45
+
46
+ clean:
47
+ find . -type d -name "__pycache__" -exec rm -rf {} +
48
+ find . -type f -name "*.pyc" -delete
49
+ find . -type f -name "*.pyo" -delete
50
+ find . -type f -name "*.pyd" -delete
51
+ find . -type f -name ".coverage" -delete
52
+ find . -type d -name "*.egg-info" -exec rm -rf {} +
53
+ find . -type d -name "*.egg" -exec rm -rf {} +
54
+ find . -type d -name ".pytest_cache" -exec rm -rf {} +
55
+ find . -type d -name ".mypy_cache" -exec rm -rf {} +
56
+ find . -type d -name ".ruff_cache" -exec rm -rf {} +
57
+ rm -rf ./dist
58
+ rm -rf ./build
59
+ rm -rf ./venv
60
+ rm -rf ./.venv
61
+ rm -rf .coverage
62
+ rm -rf htmlcov
63
+
64
+ build: clean
65
+ uv build
66
+
67
+ publish: build
68
+ @echo "Publishing to PyPI..."
69
+ @echo "Note: For CI/CD, use GitHub Actions with trusted publishing instead."
70
+ uv publish
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: grynn-fplot
3
+ Version: 0.3.4
4
+ Summary: CLI tool for plotting financial data and analyzing options
5
+ Project-URL: Homepage, https://github.com/Grynn/grynn_cli_fplot
6
+ Project-URL: Issues, https://github.com/Grynn/grynn_cli_fplot/issues
7
+ Project-URL: Repository, https://github.com/Grynn/grynn_cli_fplot
8
+ Author-email: Grynn <vishal.doshi@gmail.com>
9
+ License: MIT
10
+ Keywords: cli,finance,options,plotting,stocks
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Financial and Insurance Industry
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Office/Business :: Financial :: Investment
18
+ Requires-Python: >=3.12
19
+ Requires-Dist: click~=8.1.7
20
+ Requires-Dist: dateparser~=1.2.0
21
+ Requires-Dist: fastapi~=0.115.0
22
+ Requires-Dist: grynn-pylib
23
+ Requires-Dist: ipywidgets>=8.1.5
24
+ Requires-Dist: matplotlib>=3.9.2
25
+ Requires-Dist: mplcursors~=0.5.3
26
+ Requires-Dist: mplfinance>=0.12.9
27
+ Requires-Dist: pandas>=2.2.3
28
+ Requires-Dist: scikit-learn>=1.6.1
29
+ Requires-Dist: tabulate>=0.9.0
30
+ Requires-Dist: uvicorn~=0.32.0
31
+ Requires-Dist: yfinance
32
+ Provides-Extra: dev
33
+ Requires-Dist: ipykernel>=6.29.5; extra == 'dev'
34
+ Requires-Dist: ipython>=8.18.1; extra == 'dev'
35
+ Requires-Dist: mypy>=1.14.1; extra == 'dev'
36
+ Requires-Dist: pytest>=8.3.4; extra == 'dev'
37
+ Requires-Dist: ruff>=0.9.2; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # fplot - Financial Plotting & Options Analysis CLI
41
+
42
+ [![PyPI version](https://img.shields.io/pypi/v/grynn-fplot.svg)](https://pypi.org/project/grynn-fplot/)
43
+ [![Python versions](https://img.shields.io/pypi/pyversions/grynn-fplot.svg)](https://pypi.org/project/grynn-fplot/)
44
+
45
+ A command-line tool for plotting comparative stock price history and analyzing options contracts.
46
+
47
+ ## Installation
48
+
49
+ ### From PyPI
50
+
51
+ ```shell
52
+ pip install grynn-fplot
53
+ ```
54
+
55
+ Or with uv:
56
+
57
+ ```shell
58
+ uv tool install grynn-fplot
59
+ ```
60
+
61
+ ### From Source
62
+
63
+ For development, install the package in editable mode:
64
+
65
+ ```shell
66
+ make dev
67
+ ```
68
+
69
+ Or install locally:
70
+
71
+ ```shell
72
+ make install # Uses uv tool install .
73
+ ```
74
+
75
+ ## Usage
76
+
77
+ ### Stock Plotting
78
+
79
+ ```shell
80
+ fplot <ticker> [--since <date>] [--interval <interval>]
81
+ ```
82
+
83
+ Examples:
84
+
85
+ - `fplot AAPL`
86
+ - `fplot AAPL --since 2020`
87
+ - `fplot AAPL,TSLA --since "mar 2023"`
88
+
89
+ ### Options Listing
90
+
91
+ ```shell
92
+ fplot <ticker> --call # List call options (default: 6 months max)
93
+ fplot <ticker> --put # List put options (default: 6 months max)
94
+ fplot <ticker> --call --max 3m # List calls with 3 month max expiry
95
+ fplot <ticker> --put --all # List all available put options
96
+ fplot <ticker> --call --min-dte 1y # List long-dated calls (min 1 year)
97
+ fplot <ticker> --call --filter "dte>1y" # Filter using time expressions
98
+ ```
99
+
100
+ Examples:
101
+
102
+ - `fplot AAPL --call`
103
+ - `fplot TSLA --put --max 3m`
104
+ - `fplot AAPL --call --all`
105
+ - `fplot AAPL --call --min-dte 1y` # Long-dated calls (1+ year)
106
+ - `fplot AAPL --call --min-dte 6m` # Calls with 6+ months to expiry
107
+ - `fplot AAPL --call --filter "dte>10, dte<50"` # 10-50 days to expiry
108
+ - `fplot AAPL --call --filter "dte>1y"` # Options with 1+ year to expiry
109
+
110
+ The options output includes pricing, return metrics, implied leverage, and efficiency:
111
+ ```
112
+ AAPL 225C 35DTE ($5.25, 18.5%, 19.0x, eff:45)
113
+ AAPL 230C 35DTE ($3.10, 25.2%, 32.3x, eff:78)
114
+ AAPL 235C 35DTE ($1.85, 35.1%, 54.1x, eff:92)
115
+ ```
116
+
117
+ Format: `TICKER STRIKE[C|P] DAYS_TO_EXPIRY (price, return_metric, leverage, eff:percentile)`
118
+ - For calls: return_metric is CAGR to breakeven
119
+ - For puts: return_metric is annualized return
120
+ - Leverage: Implied leverage (Ω = Δ × S/O) where Δ is Black-Scholes delta, S is spot price, O is option price
121
+ - Delta calculated using actual implied volatility from Yahoo Finance
122
+ - Shows "N/A" if implied volatility is not available
123
+ - Shows the percentage change in option value for a 1% change in stock price
124
+ - Example: 10x leverage means a 1% stock move results in ~10% option move
125
+ - Efficiency: Percentile rank (0-100) of leverage/CAGR ratio
126
+ - Higher = better (high leverage with low required stock movement)
127
+ - 80+ = top 20% most efficient options
128
+ - Shows "N/A" if leverage or return unavailable
129
+
130
+ **Expiry Filtering Options:**
131
+ - `--max <time>`: Filter to show only options expiring within the specified time
132
+ - Examples: `3m` (3 months), `6m` (6 months), `1y` (1 year), `2w` (2 weeks), `30d` (30 days)
133
+ - Default: `6m` (6 months)
134
+ - `--min-dte <time>`: Minimum days to expiry (useful for long-dated options)
135
+ - Accepts plain days or time expressions: `300`, `1y`, `1.5y`, `6m`, `2w`
136
+ - Examples: `--min-dte 1y` (1+ year), `--min-dte 6m` (6+ months)
137
+ - Note: Using `--min-dte` automatically enables `--all` behavior
138
+ - `--all`: Show all available expiries (overrides `--max`)
139
+
140
+ **Advanced Filtering with `--filter`:**
141
+
142
+ The `--filter` option supports complex filter expressions with logical operators:
143
+
144
+ - **Syntax:**
145
+ - Comma (`,`) represents AND operation
146
+ - Plus (`+`) represents OR operation
147
+ - Comparison operators: `>`, `<`, `>=`, `<=`, `=`, `!=`
148
+ - Parentheses for grouping: `(expr1 + expr2), expr3`
149
+
150
+ - **Filter Fields:**
151
+ - `dte`: Days to expiry
152
+ - `volume`: Option volume
153
+ - `price`: Last price
154
+ - `return`, `ret`, `ar`: Return metric (CAGR for calls, annualized return for puts) - all aliases work
155
+ - `strike_pct`, `sp`: Strike percentage above/below spot (positive = above spot, negative = below spot)
156
+ - `lt_days`: Days since last trade (useful for filtering stale options)
157
+ - `leverage`, `lev`: Implied leverage (Ω = Δ × S/O, using Black-Scholes delta)
158
+ - `efficiency`, `eff`: Efficiency percentile (leverage/CAGR ratio, 0-100 scale)
159
+
160
+ - **Examples:**
161
+ - `--filter "dte>300"` - Options with more than 300 days to expiry
162
+ - `--filter "dte>10, dte<50"` - Options between 10-50 days (AND operation)
163
+ - `--filter "dte<30 + dte>300"` - Short-term OR long-dated (OR operation)
164
+ - `--filter "sp>5, sp<15"` - Strikes 5-15% above current spot price
165
+ - `--filter "(dte>300 + dte<30), sp>5"` - Complex nested filters
166
+ - `--filter "volume>=100"` - High volume options
167
+ - `--filter "lt_days<=7"` - Options traded within last 7 days
168
+ - `--filter "ar>50"` - Annualized return > 50%
169
+ - `--filter "leverage>10"` - High leverage options (10x or more)
170
+ - `--filter "lev>5, lev<20"` - Moderate leverage options (5x-20x)
171
+ - `--filter "efficiency>80"` - Top 20% most efficient options
172
+ - `--filter "eff>60, dte<60"` - Above-average efficiency with <60 days to expiry
173
+
174
+ - **Time Values:**
175
+ - DTE-style expressions: `1y` (365 days), `6m` (180 days), `2w` (14 days)
176
+ - Duration expressions: `2d15h`, `30m`, `1d` (converted to hours for duration fields)
177
+ - Examples:
178
+ - `--filter "dte>1y"` - Options with more than 1 year to expiry
179
+ - `--filter "dte>6m"` - Options with more than 6 months to expiry
180
+ - `--filter "lt_days<=7"` - Options traded in the last week
181
+
182
+ Options data is cached for 1 hour to improve performance and reduce API calls.
183
+
184
+ ## TODO
185
+
186
+ - fplot --call | fzf lets you select a call; once a call is selected it should be possible to plot a chart for it (yfinance provides price history for option identifiers)
187
+ - need more examples of usage with fzf to pick a call
188
+ - use grynn_pylib to download options (does it have identifiers that can be used to fetch price history?)