grynn-fplot 0.3.1__tar.gz → 0.3.2__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 (37) hide show
  1. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/.github/workflows/publish.yml +0 -31
  2. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/PKG-INFO +4 -4
  3. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/grynn_fplot/cli.py +9 -0
  4. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/grynn_fplot/core.py +9 -3
  5. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/grynn_fplot/filter_parser.py +112 -0
  6. grynn_fplot-0.3.2/pyproject.toml +66 -0
  7. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/uv.lock +1 -1
  8. grynn_fplot-0.3.1/pyproject.toml +0 -66
  9. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/.devcontainer/devcontainer.json +0 -0
  10. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/.github/copilot-instructions.md +0 -0
  11. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/.github/dependabot.yml +0 -0
  12. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/.gitignore +0 -0
  13. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/.pre-commit-config.yaml +0 -0
  14. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/.secrets.baseline +0 -0
  15. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/.vscode/settings.json +0 -0
  16. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/FILTER_DESIGN.md +0 -0
  17. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/FILTER_EXAMPLES.sh +0 -0
  18. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/Makefile +0 -0
  19. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/PERFORMANCE_OPTIMIZATION.md +0 -0
  20. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/README.md +0 -0
  21. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/SUMMARY.md +0 -0
  22. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/WEB_REFACTOR.md +0 -0
  23. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/example_usage.sh +0 -0
  24. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/grynn_fplot/__init__.py +0 -0
  25. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/grynn_fplot/drawdowns.py +0 -0
  26. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/grynn_fplot/index.html +0 -0
  27. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/grynn_fplot/plot_option_interactive.py +0 -0
  28. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/grynn_fplot/serve.py +0 -0
  29. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/grynn_fplot/web_api.py +0 -0
  30. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/tests/test_cli_options.py +0 -0
  31. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/tests/test_filter_evaluation.py +0 -0
  32. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/tests/test_filter_parser.py +0 -0
  33. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/tests/test_fplot.py +0 -0
  34. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/tests/test_options.py +0 -0
  35. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/tests/test_web_api.py +0 -0
  36. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/tests/test_web_interface.py +0 -0
  37. {grynn_fplot-0.3.1 → grynn_fplot-0.3.2}/todo.md +0 -0
@@ -7,15 +7,6 @@ on:
7
7
  release:
8
8
  types: [published]
9
9
  workflow_dispatch:
10
- inputs:
11
- target:
12
- description: 'Publish target'
13
- required: true
14
- default: 'testpypi'
15
- type: choice
16
- options:
17
- - testpypi
18
- - pypi
19
10
 
20
11
  jobs:
21
12
  build:
@@ -44,28 +35,6 @@ jobs:
44
35
  name: python-package-distributions
45
36
  path: dist/
46
37
 
47
- publish-testpypi:
48
- name: Publish to TestPyPI
49
- if: github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'testpypi'
50
- needs: build
51
- runs-on: ubuntu-latest
52
- environment:
53
- name: testpypi
54
- url: https://test.pypi.org/p/grynn-fplot
55
- permissions:
56
- id-token: write
57
- steps:
58
- - name: Download distributions
59
- uses: actions/download-artifact@v4
60
- with:
61
- name: python-package-distributions
62
- path: dist/
63
-
64
- - name: Publish to TestPyPI
65
- uses: pypa/gh-action-pypi-publish@release/v1
66
- with:
67
- repository-url: https://test.pypi.org/legacy/
68
-
69
38
  publish-pypi:
70
39
  name: Publish to PyPI
71
40
  if: github.event_name == 'push' || github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'pypi')
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grynn-fplot
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: CLI tool for plotting financial data and analyzing options
5
- Project-URL: Homepage, https://github.com/Grynn/grynn_fplot
6
- Project-URL: Repository, https://github.com/Grynn/grynn_fplot
7
- Project-URL: Issues, https://github.com/Grynn/grynn_fplot/issues
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
8
  Author-email: Grynn <vishal.doshi@gmail.com>
9
9
  License: MIT
10
10
  Keywords: cli,finance,options,plotting,stocks
@@ -52,6 +52,7 @@ except importlib.metadata.PackageNotFoundError:
52
52
  default=None,
53
53
  help="Filter expression (e.g., 'dte>300', 'dte>10, dte<15', 'dte>300 + strike<100')",
54
54
  )
55
+ @click.option("--filter-help", is_flag=True, help="Show help for filter expressions and exit")
55
56
  @click.option("--web", "-w", is_flag=True, help="Launch interactive web interface")
56
57
  @click.option("--port", type=int, default=8000, help="Port for web interface")
57
58
  @click.option("--host", type=str, default="127.0.0.1", help="Host for web interface")
@@ -68,6 +69,7 @@ def display_plot(
68
69
  min_dte,
69
70
  show_all,
70
71
  filter_expr,
72
+ filter_help,
71
73
  web,
72
74
  port,
73
75
  host,
@@ -107,6 +109,13 @@ def display_plot(
107
109
  print(f"fplot {__version__}")
108
110
  return
109
111
 
112
+ # Show filter help if requested
113
+ if filter_help:
114
+ from grynn_fplot.filter_parser import get_filter_help
115
+
116
+ print(get_filter_help())
117
+ return
118
+
110
119
  # Launch web interface if --web flag is used
111
120
  if web:
112
121
  launch_web_interface(ticker, since, interval, port, host, no_browser, debug)
@@ -486,19 +486,25 @@ def format_options_for_display(
486
486
  strike = option.get("strike", 0)
487
487
  volume = option.get("volume", 0) or 0 # Handle None values
488
488
  last_price = option.get("lastPrice", 0) or 0
489
-
489
+
490
490
  # Get last trade date and calculate days since last trade
491
491
  last_trade_date = option.get("lastTradeDate", None)
492
492
  lt_days = None
493
493
  if last_trade_date:
494
494
  try:
495
- from datetime import datetime
495
+ from datetime import datetime, timezone
496
+
496
497
  # lastTradeDate can be a timestamp or datetime
497
498
  if isinstance(last_trade_date, (int, float)):
498
499
  last_trade_dt = datetime.fromtimestamp(last_trade_date)
499
500
  else:
500
501
  last_trade_dt = last_trade_date
501
- lt_days = (datetime.now() - last_trade_dt).days
502
+ # Convert both to timezone-aware UTC for comparison
503
+ now_utc = datetime.now(timezone.utc)
504
+ if last_trade_dt.tzinfo is None:
505
+ # If last_trade_dt is naive, assume UTC
506
+ last_trade_dt = last_trade_dt.replace(tzinfo=timezone.utc)
507
+ lt_days = (now_utc - last_trade_dt).days
502
508
  except Exception:
503
509
  lt_days = None
504
510
 
@@ -26,6 +26,39 @@ from typing import Any, Dict, List, Union
26
26
  from dataclasses import dataclass
27
27
 
28
28
 
29
+ # Registry of available filter fields with descriptions
30
+ FILTER_FIELDS = {
31
+ "dte": {"description": "Days to expiry", "type": "integer", "example": "dte>150"},
32
+ "volume": {"description": "Option trading volume", "type": "integer", "example": "volume>100"},
33
+ "price": {"description": "Last traded price", "type": "float", "example": "price<5.0"},
34
+ "return": {
35
+ "description": "Return metric (CAGR for calls, annualized return for puts)",
36
+ "type": "float",
37
+ "aliases": ["ret", "ar"],
38
+ "example": "return>0.15",
39
+ },
40
+ "ret": {"description": "Alias for 'return'", "type": "float", "alias_of": "return", "example": "ret>0.15"},
41
+ "ar": {
42
+ "description": "Alias for 'return' (annualized return)",
43
+ "type": "float",
44
+ "alias_of": "return",
45
+ "example": "ar>0.15",
46
+ },
47
+ "strike_pct": {
48
+ "description": "Strike percentage relative to spot price (positive = above spot, negative = below spot)",
49
+ "type": "float",
50
+ "aliases": ["sp"],
51
+ "example": "strike_pct>5",
52
+ },
53
+ "sp": {"description": "Alias for 'strike_pct'", "type": "float", "alias_of": "strike_pct", "example": "sp>5"},
54
+ "lt_days": {
55
+ "description": "Days since last trade (useful for filtering stale/illiquid options)",
56
+ "type": "integer",
57
+ "example": "lt_days<7",
58
+ },
59
+ }
60
+
61
+
29
62
  @dataclass
30
63
  class FilterNode:
31
64
  """Represents a single filter condition"""
@@ -451,3 +484,82 @@ def filter_to_string(filter_dict: Dict[str, Any]) -> str:
451
484
  return f"({separator.join(children_strs)})"
452
485
  else:
453
486
  raise ValueError(f"Invalid filter dictionary: {filter_dict}")
487
+
488
+
489
+ def get_filter_help() -> str:
490
+ """Generate formatted help text for available filter fields.
491
+
492
+ Returns:
493
+ Formatted help text describing all filter fields, operators, and syntax
494
+ """
495
+ help_lines = []
496
+
497
+ # Header
498
+ help_lines.append("Filter Expression Reference")
499
+ help_lines.append("=" * 50)
500
+ help_lines.append("")
501
+
502
+ # Available Fields
503
+ help_lines.append("Available Filter Fields:")
504
+ help_lines.append("-" * 50)
505
+
506
+ # Group main fields (exclude aliases)
507
+ main_fields = {k: v for k, v in FILTER_FIELDS.items() if "alias_of" not in v}
508
+
509
+ for field_name in sorted(main_fields.keys()):
510
+ field_info = main_fields[field_name]
511
+ help_lines.append(f"\n {field_name}")
512
+ help_lines.append(f" {field_info['description']}")
513
+ help_lines.append(f" Type: {field_info['type']}")
514
+
515
+ # Show aliases if any
516
+ if "aliases" in field_info:
517
+ aliases_str = ", ".join(field_info["aliases"])
518
+ help_lines.append(f" Aliases: {aliases_str}")
519
+
520
+ help_lines.append(f" Example: {field_info['example']}")
521
+
522
+ # Operators
523
+ help_lines.append("\n")
524
+ help_lines.append("Comparison Operators:")
525
+ help_lines.append("-" * 50)
526
+ help_lines.append(" > Greater than")
527
+ help_lines.append(" < Less than")
528
+ help_lines.append(" >= Greater than or equal")
529
+ help_lines.append(" <= Less than or equal")
530
+ help_lines.append(" = Equal (also ==)")
531
+ help_lines.append(" != Not equal")
532
+
533
+ # Logical Operators
534
+ help_lines.append("\n")
535
+ help_lines.append("Logical Operators:")
536
+ help_lines.append("-" * 50)
537
+ help_lines.append(" , AND - all conditions must be true")
538
+ help_lines.append(" + OR - at least one condition must be true")
539
+ help_lines.append(" () Parentheses for grouping")
540
+
541
+ # Time Value Formats
542
+ help_lines.append("\n")
543
+ help_lines.append("Time Value Formats:")
544
+ help_lines.append("-" * 50)
545
+ help_lines.append(" DTE-style (returns days): 1y, 6m, 2w, 30d")
546
+ help_lines.append(" Duration (returns hours): 2d15h, 30m, 1h")
547
+ help_lines.append(" Plain integers: 5, 150, 300")
548
+
549
+ # Examples
550
+ help_lines.append("\n")
551
+ help_lines.append("Example Filters:")
552
+ help_lines.append("-" * 50)
553
+ help_lines.append(" dte>150")
554
+ help_lines.append(" Options with more than 150 days to expiry")
555
+ help_lines.append("")
556
+ help_lines.append(" dte>150, lt_days<7")
557
+ help_lines.append(" Options with DTE > 150 AND traded in last 7 days")
558
+ help_lines.append("")
559
+ help_lines.append(" (dte>300 + dte<30), volume>100")
560
+ help_lines.append(" Options with (DTE > 300 OR DTE < 30) AND volume > 100")
561
+ help_lines.append("")
562
+ help_lines.append(" return>0.20, sp>5, sp<15")
563
+ help_lines.append(" Options with return > 20%, strike 5-15% above spot")
564
+
565
+ return "\n".join(help_lines)
@@ -0,0 +1,66 @@
1
+ [build-system]
2
+ build-backend = "hatchling.build"
3
+ requires = ["hatchling"]
4
+
5
+ [project]
6
+ authors = [{name = "Grynn", email = "vishal.doshi@gmail.com"}]
7
+ classifiers = [
8
+ "Development Status :: 4 - Beta",
9
+ "Environment :: Console",
10
+ "Intended Audience :: Financial and Insurance Industry",
11
+ "License :: OSI Approved :: MIT License",
12
+ "Programming Language :: Python :: 3.12",
13
+ "Programming Language :: Python :: 3.13",
14
+ "Topic :: Office/Business :: Financial :: Investment",
15
+ ]
16
+ dependencies = [
17
+ "click ~= 8.1.7",
18
+ "matplotlib >= 3.9.2",
19
+ "dateparser ~= 1.2.0",
20
+ "mplcursors ~= 0.5.3",
21
+ "fastapi ~= 0.115.0",
22
+ "uvicorn ~= 0.32.0",
23
+ "scikit-learn >= 1.6.1",
24
+ "grynn-pylib",
25
+ "ipywidgets>=8.1.5",
26
+ "tabulate>=0.9.0",
27
+ "yfinance",
28
+ "pandas>=2.2.3",
29
+ ]
30
+ description = "CLI tool for plotting financial data and analyzing options"
31
+ keywords = ["finance", "stocks", "options", "plotting", "cli"]
32
+ license = {text = "MIT"}
33
+ name = "grynn-fplot"
34
+ readme = "README.md"
35
+ requires-python = ">=3.12"
36
+ version = "0.3.2"
37
+
38
+ [project.scripts]
39
+ fplot = "grynn_fplot.cli:display_plot"
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/Grynn/grynn_cli_fplot"
43
+ Issues = "https://github.com/Grynn/grynn_cli_fplot/issues"
44
+ Repository = "https://github.com/Grynn/grynn_cli_fplot"
45
+
46
+ [project.optional-dependencies]
47
+ dev = [
48
+ "ipykernel>=6.29.5",
49
+ "ipython>=8.18.1",
50
+ "mypy>=1.14.1",
51
+ "pytest>=8.3.4",
52
+ "ruff>=0.9.2",
53
+ ]
54
+
55
+ [tool.uv.sources]
56
+ grynn-pylib = {git = "https://github.com/Grynn/grynn_pylib.git"}
57
+ yfinance = {git = "https://github.com/ranaroussi/yfinance.git"}
58
+
59
+ [dependency-groups]
60
+ dev = [
61
+ "httpx>=0.28.1",
62
+ "pre-commit>=4.1.0",
63
+ ]
64
+
65
+ [tool.ruff]
66
+ line-length = 120
@@ -383,7 +383,7 @@ wheels = [
383
383
 
384
384
  [[package]]
385
385
  name = "grynn-fplot"
386
- version = "0.3.1"
386
+ version = "0.3.2"
387
387
  source = { editable = "." }
388
388
  dependencies = [
389
389
  { name = "click" },
@@ -1,66 +0,0 @@
1
- [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "grynn-fplot"
7
- version = "0.3.1"
8
- description = "CLI tool for plotting financial data and analyzing options"
9
- authors = [{ name = "Grynn", email = "vishal.doshi@gmail.com" }]
10
- requires-python = ">=3.12"
11
- readme = "README.md"
12
- license = { text = "MIT" }
13
- keywords = ["finance", "stocks", "options", "plotting", "cli"]
14
- classifiers = [
15
- "Development Status :: 4 - Beta",
16
- "Environment :: Console",
17
- "Intended Audience :: Financial and Insurance Industry",
18
- "License :: OSI Approved :: MIT License",
19
- "Programming Language :: Python :: 3.12",
20
- "Programming Language :: Python :: 3.13",
21
- "Topic :: Office/Business :: Financial :: Investment",
22
- ]
23
- dependencies = [
24
- "click ~= 8.1.7",
25
- "matplotlib >= 3.9.2",
26
- "dateparser ~= 1.2.0",
27
- "mplcursors ~= 0.5.3",
28
- "fastapi ~= 0.115.0",
29
- "uvicorn ~= 0.32.0",
30
- "scikit-learn >= 1.6.1",
31
- "grynn-pylib",
32
- "ipywidgets>=8.1.5",
33
- "tabulate>=0.9.0",
34
- "yfinance",
35
- "pandas>=2.2.3",
36
- ]
37
-
38
- [project.scripts]
39
- fplot = "grynn_fplot.cli:display_plot"
40
-
41
- [project.urls]
42
- Homepage = "https://github.com/Grynn/grynn_fplot"
43
- Repository = "https://github.com/Grynn/grynn_fplot"
44
- Issues = "https://github.com/Grynn/grynn_fplot/issues"
45
-
46
- [project.optional-dependencies]
47
- dev = [
48
- "ipykernel>=6.29.5",
49
- "ipython>=8.18.1",
50
- "mypy>=1.14.1",
51
- "pytest>=8.3.4",
52
- "ruff>=0.9.2",
53
- ]
54
-
55
- [tool.uv.sources]
56
- grynn-pylib = { git = "https://github.com/Grynn/grynn_pylib.git" }
57
- yfinance = { git = "https://github.com/ranaroussi/yfinance.git" }
58
-
59
- [dependency-groups]
60
- dev = [
61
- "httpx>=0.28.1",
62
- "pre-commit>=4.1.0",
63
- ]
64
-
65
- [tool.ruff]
66
- line-length = 120
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes