pnf-chart-system 0.1.1__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.
@@ -0,0 +1,3 @@
1
+ include README.md
2
+ include VERSION
3
+ include pypnf_dashboard.py
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: pnf-chart-system
3
+ Version: 0.1.1
4
+ Summary: Point and Figure Chart Library - Python bindings
5
+ Home-page: https://github.com/gregorian-09/pnf-chart-system
6
+ Author: Gregorian Rayne
7
+ Author-email: gregorianrayne09@gmail.com
8
+ Project-URL: Documentation, https://github.com/gregorian-09/pnf-chart-system/tree/master/docs
9
+ Project-URL: API Reference, https://github.com/gregorian-09/pnf-chart-system/blob/master/docs/bindings/python.md
10
+ Project-URL: Changelog, https://github.com/gregorian-09/pnf-chart-system/blob/master/CHANGELOG.md
11
+ Project-URL: Issues, https://github.com/gregorian-09/pnf-chart-system/issues
12
+ Project-URL: Source, https://github.com/gregorian-09/pnf-chart-system
13
+ Keywords: point-and-figure,charting,technical-analysis,trading,indicators
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Financial and Insurance Industry
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ Dynamic: author
27
+ Dynamic: author-email
28
+ Dynamic: classifier
29
+ Dynamic: description
30
+ Dynamic: description-content-type
31
+ Dynamic: home-page
32
+ Dynamic: keywords
33
+ Dynamic: project-url
34
+ Dynamic: requires-python
35
+ Dynamic: summary
36
+
37
+ # pnf-chart-system
38
+
39
+ [![PyPI version](https://img.shields.io/pypi/v/pnf-chart-system.svg)](https://pypi.org/project/pnf-chart-system/)
40
+ [![Python versions](https://img.shields.io/pypi/pyversions/pnf-chart-system.svg)](https://pypi.org/project/pnf-chart-system/)
41
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/gregorian-09/pnf-chart-system/blob/master/LICENSE)
42
+
43
+ Production-ready Python bindings for the PnF (Point and Figure) engine.
44
+
45
+ Package name is `pnf-chart-system`; import name is `pypnf`.
46
+
47
+ ## Why This Package
48
+
49
+ `pypnf` is built for real analysis workflows, not only chart construction:
50
+
51
+ | Area | What you get |
52
+ | --- | --- |
53
+ | Chart Engine | Point-and-Figure charting with `Close` and `HighLow` construction |
54
+ | Trend Context | Bullish support / bearish resistance context checks |
55
+ | Indicators | SMA, Bollinger Bands, RSI, OBV, Bullish Percent |
56
+ | Structural Signals | Buy/sell signals and full PnF pattern detection |
57
+ | Market Structure | Support/resistance levels, price objectives, congestion zones |
58
+ | Visualization | Localhost real-time dashboard streaming |
59
+
60
+ ## Installation
61
+
62
+ ```bash
63
+ pip install pnf-chart-system
64
+ ```
65
+
66
+ ## Quick Start
67
+
68
+ ```python
69
+ import pypnf
70
+
71
+ cfg = pypnf.ChartConfig()
72
+ cfg.method = pypnf.ConstructionMethod.HighLow
73
+ cfg.box_size_method = pypnf.BoxSizeMethod.Traditional
74
+ cfg.box_size = 0.0
75
+ cfg.reversal = 3
76
+
77
+ chart = pypnf.Chart(cfg)
78
+
79
+ # high, low, close, timestamp
80
+ chart.add_data(5000.0, 4950.0, 4985.0, 1700000000)
81
+ chart.add_data(5040.0, 4980.0, 5030.0, 1700003600)
82
+ chart.add_data(5065.0, 5010.0, 5055.0, 1700007200)
83
+
84
+ indicators = pypnf.Indicators(pypnf.IndicatorConfig())
85
+ indicators.calculate(chart)
86
+
87
+ print(chart.to_ascii())
88
+ print(indicators.summary())
89
+ ```
90
+
91
+ ## Trendline and Bias Workflow
92
+
93
+ ```python
94
+ last_price = 5055.0
95
+
96
+ print("Bullish bias:", chart.has_bullish_bias())
97
+ print("Bearish bias:", chart.has_bearish_bias())
98
+ print("Above bullish support:", chart.is_above_bullish_support(last_price))
99
+ print("Below bearish resistance:", chart.is_below_bearish_resistance(last_price))
100
+ ```
101
+
102
+ These checks are the normal first gate before acting on breakout or breakdown patterns.
103
+
104
+ ## Indicators and Momentum
105
+
106
+ ```python
107
+ indicators.calculate(chart)
108
+
109
+ sma_short = indicators.sma_short()
110
+ bands = indicators.bollinger()
111
+ rsi = indicators.rsi()
112
+ obv = indicators.obv()
113
+
114
+ col = chart.column_count() - 1
115
+ if col >= 0:
116
+ print("SMA short:", sma_short.value(col))
117
+ print("Bollinger upper:", bands.upper(col))
118
+ print("RSI:", rsi.value(col))
119
+ print("OBV:", obv.value(col))
120
+ ```
121
+
122
+ ## Signals and Pattern Detection
123
+
124
+ ```python
125
+ signals = indicators.signals()
126
+ patterns = indicators.patterns()
127
+
128
+ print("Current signal:", signals.current_signal())
129
+ print("Buy count:", signals.buy_count())
130
+ print("Sell count:", signals.sell_count())
131
+
132
+ print("Pattern count:", patterns.pattern_count())
133
+ print("Bullish patterns:", len(patterns.bullish_patterns()))
134
+ print("Bearish patterns:", len(patterns.bearish_patterns()))
135
+ ```
136
+
137
+ ## Support, Resistance, Objectives, Congestion
138
+
139
+ ```python
140
+ sr = indicators.support_resistance()
141
+ obj = indicators.objectives()
142
+ cong = indicators.congestion()
143
+
144
+ print("Support levels:", sr.support_levels())
145
+ print("Resistance levels:", sr.resistance_levels())
146
+ print("Significant levels (>=3 touches):", sr.significant_levels(3))
147
+
148
+ print("Bullish targets:", obj.bullish_targets())
149
+ print("Bearish targets:", obj.bearish_targets())
150
+
151
+ print("Congestion zones:", cong.zones())
152
+ ```
153
+
154
+ ## Real-Time Dashboard
155
+
156
+ ```python
157
+ from pypnf_dashboard import DashboardServer
158
+
159
+ server = DashboardServer(chart, indicators)
160
+ server.start("127.0.0.1", 8761)
161
+ server.publish()
162
+ print(server.url())
163
+ ```
164
+
165
+ You can call `server.publish()` after each new bar/tick batch to keep the browser in sync.
166
+
167
+ ## API Map
168
+
169
+ Core:
170
+ - `Chart`, `ChartConfig`, `Box`, `Column`
171
+
172
+ Indicators:
173
+ - `Indicators`, `IndicatorConfig`
174
+ - `MovingAverage`, `BollingerBands`, `RSI`, `OnBalanceVolume`, `BullishPercent`
175
+ - `SignalDetector`, `PatternRecognizer`, `SupportResistance`, `PriceObjectiveCalculator`, `CongestionDetector`
176
+
177
+ Data:
178
+ - `OHLC`, `Signal`, `Pattern`, `SupportResistanceLevel`, `PriceObjective`, `CongestionZone`
179
+
180
+ Enums:
181
+ - `BoxType`, `ColumnType`, `ConstructionMethod`, `BoxSizeMethod`, `SignalType`, `PatternType`
182
+
183
+ ## Versioning and Compatibility
184
+
185
+ - Python package version tracks the same release as the core engine.
186
+ - Keep all bindings on the same version when mixing languages in one system.
187
+ - See `CHANGELOG.md` for version-by-version behavior changes.
188
+
189
+ ## Troubleshooting
190
+
191
+ - `ImportError` / native load issues: rebuild and ensure the native library is discoverable.
192
+ - Empty indicator values: verify you have enough columns for the configured lookback periods.
193
+ - Unexpected chart shape: ensure `HighLow` mode receives real high/low values, not close-only values.
194
+
195
+ ## Documentation and Links
196
+
197
+ - Python API reference: `docs/bindings/python.md`
198
+ - Cross-language API index: `docs/reference/api-symbol-index.md`
199
+ - Source: https://github.com/gregorian-09/pnf-chart-system
200
+ - Issues: https://github.com/gregorian-09/pnf-chart-system/issues
201
+ - Changelog: https://github.com/gregorian-09/pnf-chart-system/blob/master/CHANGELOG.md
@@ -0,0 +1,165 @@
1
+ # pnf-chart-system
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/pnf-chart-system.svg)](https://pypi.org/project/pnf-chart-system/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/pnf-chart-system.svg)](https://pypi.org/project/pnf-chart-system/)
5
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/gregorian-09/pnf-chart-system/blob/master/LICENSE)
6
+
7
+ Production-ready Python bindings for the PnF (Point and Figure) engine.
8
+
9
+ Package name is `pnf-chart-system`; import name is `pypnf`.
10
+
11
+ ## Why This Package
12
+
13
+ `pypnf` is built for real analysis workflows, not only chart construction:
14
+
15
+ | Area | What you get |
16
+ | --- | --- |
17
+ | Chart Engine | Point-and-Figure charting with `Close` and `HighLow` construction |
18
+ | Trend Context | Bullish support / bearish resistance context checks |
19
+ | Indicators | SMA, Bollinger Bands, RSI, OBV, Bullish Percent |
20
+ | Structural Signals | Buy/sell signals and full PnF pattern detection |
21
+ | Market Structure | Support/resistance levels, price objectives, congestion zones |
22
+ | Visualization | Localhost real-time dashboard streaming |
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install pnf-chart-system
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ```python
33
+ import pypnf
34
+
35
+ cfg = pypnf.ChartConfig()
36
+ cfg.method = pypnf.ConstructionMethod.HighLow
37
+ cfg.box_size_method = pypnf.BoxSizeMethod.Traditional
38
+ cfg.box_size = 0.0
39
+ cfg.reversal = 3
40
+
41
+ chart = pypnf.Chart(cfg)
42
+
43
+ # high, low, close, timestamp
44
+ chart.add_data(5000.0, 4950.0, 4985.0, 1700000000)
45
+ chart.add_data(5040.0, 4980.0, 5030.0, 1700003600)
46
+ chart.add_data(5065.0, 5010.0, 5055.0, 1700007200)
47
+
48
+ indicators = pypnf.Indicators(pypnf.IndicatorConfig())
49
+ indicators.calculate(chart)
50
+
51
+ print(chart.to_ascii())
52
+ print(indicators.summary())
53
+ ```
54
+
55
+ ## Trendline and Bias Workflow
56
+
57
+ ```python
58
+ last_price = 5055.0
59
+
60
+ print("Bullish bias:", chart.has_bullish_bias())
61
+ print("Bearish bias:", chart.has_bearish_bias())
62
+ print("Above bullish support:", chart.is_above_bullish_support(last_price))
63
+ print("Below bearish resistance:", chart.is_below_bearish_resistance(last_price))
64
+ ```
65
+
66
+ These checks are the normal first gate before acting on breakout or breakdown patterns.
67
+
68
+ ## Indicators and Momentum
69
+
70
+ ```python
71
+ indicators.calculate(chart)
72
+
73
+ sma_short = indicators.sma_short()
74
+ bands = indicators.bollinger()
75
+ rsi = indicators.rsi()
76
+ obv = indicators.obv()
77
+
78
+ col = chart.column_count() - 1
79
+ if col >= 0:
80
+ print("SMA short:", sma_short.value(col))
81
+ print("Bollinger upper:", bands.upper(col))
82
+ print("RSI:", rsi.value(col))
83
+ print("OBV:", obv.value(col))
84
+ ```
85
+
86
+ ## Signals and Pattern Detection
87
+
88
+ ```python
89
+ signals = indicators.signals()
90
+ patterns = indicators.patterns()
91
+
92
+ print("Current signal:", signals.current_signal())
93
+ print("Buy count:", signals.buy_count())
94
+ print("Sell count:", signals.sell_count())
95
+
96
+ print("Pattern count:", patterns.pattern_count())
97
+ print("Bullish patterns:", len(patterns.bullish_patterns()))
98
+ print("Bearish patterns:", len(patterns.bearish_patterns()))
99
+ ```
100
+
101
+ ## Support, Resistance, Objectives, Congestion
102
+
103
+ ```python
104
+ sr = indicators.support_resistance()
105
+ obj = indicators.objectives()
106
+ cong = indicators.congestion()
107
+
108
+ print("Support levels:", sr.support_levels())
109
+ print("Resistance levels:", sr.resistance_levels())
110
+ print("Significant levels (>=3 touches):", sr.significant_levels(3))
111
+
112
+ print("Bullish targets:", obj.bullish_targets())
113
+ print("Bearish targets:", obj.bearish_targets())
114
+
115
+ print("Congestion zones:", cong.zones())
116
+ ```
117
+
118
+ ## Real-Time Dashboard
119
+
120
+ ```python
121
+ from pypnf_dashboard import DashboardServer
122
+
123
+ server = DashboardServer(chart, indicators)
124
+ server.start("127.0.0.1", 8761)
125
+ server.publish()
126
+ print(server.url())
127
+ ```
128
+
129
+ You can call `server.publish()` after each new bar/tick batch to keep the browser in sync.
130
+
131
+ ## API Map
132
+
133
+ Core:
134
+ - `Chart`, `ChartConfig`, `Box`, `Column`
135
+
136
+ Indicators:
137
+ - `Indicators`, `IndicatorConfig`
138
+ - `MovingAverage`, `BollingerBands`, `RSI`, `OnBalanceVolume`, `BullishPercent`
139
+ - `SignalDetector`, `PatternRecognizer`, `SupportResistance`, `PriceObjectiveCalculator`, `CongestionDetector`
140
+
141
+ Data:
142
+ - `OHLC`, `Signal`, `Pattern`, `SupportResistanceLevel`, `PriceObjective`, `CongestionZone`
143
+
144
+ Enums:
145
+ - `BoxType`, `ColumnType`, `ConstructionMethod`, `BoxSizeMethod`, `SignalType`, `PatternType`
146
+
147
+ ## Versioning and Compatibility
148
+
149
+ - Python package version tracks the same release as the core engine.
150
+ - Keep all bindings on the same version when mixing languages in one system.
151
+ - See `CHANGELOG.md` for version-by-version behavior changes.
152
+
153
+ ## Troubleshooting
154
+
155
+ - `ImportError` / native load issues: rebuild and ensure the native library is discoverable.
156
+ - Empty indicator values: verify you have enough columns for the configured lookback periods.
157
+ - Unexpected chart shape: ensure `HighLow` mode receives real high/low values, not close-only values.
158
+
159
+ ## Documentation and Links
160
+
161
+ - Python API reference: `docs/bindings/python.md`
162
+ - Cross-language API index: `docs/reference/api-symbol-index.md`
163
+ - Source: https://github.com/gregorian-09/pnf-chart-system
164
+ - Issues: https://github.com/gregorian-09/pnf-chart-system/issues
165
+ - Changelog: https://github.com/gregorian-09/pnf-chart-system/blob/master/CHANGELOG.md
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: pnf-chart-system
3
+ Version: 0.1.1
4
+ Summary: Point and Figure Chart Library - Python bindings
5
+ Home-page: https://github.com/gregorian-09/pnf-chart-system
6
+ Author: Gregorian Rayne
7
+ Author-email: gregorianrayne09@gmail.com
8
+ Project-URL: Documentation, https://github.com/gregorian-09/pnf-chart-system/tree/master/docs
9
+ Project-URL: API Reference, https://github.com/gregorian-09/pnf-chart-system/blob/master/docs/bindings/python.md
10
+ Project-URL: Changelog, https://github.com/gregorian-09/pnf-chart-system/blob/master/CHANGELOG.md
11
+ Project-URL: Issues, https://github.com/gregorian-09/pnf-chart-system/issues
12
+ Project-URL: Source, https://github.com/gregorian-09/pnf-chart-system
13
+ Keywords: point-and-figure,charting,technical-analysis,trading,indicators
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Financial and Insurance Industry
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ Dynamic: author
27
+ Dynamic: author-email
28
+ Dynamic: classifier
29
+ Dynamic: description
30
+ Dynamic: description-content-type
31
+ Dynamic: home-page
32
+ Dynamic: keywords
33
+ Dynamic: project-url
34
+ Dynamic: requires-python
35
+ Dynamic: summary
36
+
37
+ # pnf-chart-system
38
+
39
+ [![PyPI version](https://img.shields.io/pypi/v/pnf-chart-system.svg)](https://pypi.org/project/pnf-chart-system/)
40
+ [![Python versions](https://img.shields.io/pypi/pyversions/pnf-chart-system.svg)](https://pypi.org/project/pnf-chart-system/)
41
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/gregorian-09/pnf-chart-system/blob/master/LICENSE)
42
+
43
+ Production-ready Python bindings for the PnF (Point and Figure) engine.
44
+
45
+ Package name is `pnf-chart-system`; import name is `pypnf`.
46
+
47
+ ## Why This Package
48
+
49
+ `pypnf` is built for real analysis workflows, not only chart construction:
50
+
51
+ | Area | What you get |
52
+ | --- | --- |
53
+ | Chart Engine | Point-and-Figure charting with `Close` and `HighLow` construction |
54
+ | Trend Context | Bullish support / bearish resistance context checks |
55
+ | Indicators | SMA, Bollinger Bands, RSI, OBV, Bullish Percent |
56
+ | Structural Signals | Buy/sell signals and full PnF pattern detection |
57
+ | Market Structure | Support/resistance levels, price objectives, congestion zones |
58
+ | Visualization | Localhost real-time dashboard streaming |
59
+
60
+ ## Installation
61
+
62
+ ```bash
63
+ pip install pnf-chart-system
64
+ ```
65
+
66
+ ## Quick Start
67
+
68
+ ```python
69
+ import pypnf
70
+
71
+ cfg = pypnf.ChartConfig()
72
+ cfg.method = pypnf.ConstructionMethod.HighLow
73
+ cfg.box_size_method = pypnf.BoxSizeMethod.Traditional
74
+ cfg.box_size = 0.0
75
+ cfg.reversal = 3
76
+
77
+ chart = pypnf.Chart(cfg)
78
+
79
+ # high, low, close, timestamp
80
+ chart.add_data(5000.0, 4950.0, 4985.0, 1700000000)
81
+ chart.add_data(5040.0, 4980.0, 5030.0, 1700003600)
82
+ chart.add_data(5065.0, 5010.0, 5055.0, 1700007200)
83
+
84
+ indicators = pypnf.Indicators(pypnf.IndicatorConfig())
85
+ indicators.calculate(chart)
86
+
87
+ print(chart.to_ascii())
88
+ print(indicators.summary())
89
+ ```
90
+
91
+ ## Trendline and Bias Workflow
92
+
93
+ ```python
94
+ last_price = 5055.0
95
+
96
+ print("Bullish bias:", chart.has_bullish_bias())
97
+ print("Bearish bias:", chart.has_bearish_bias())
98
+ print("Above bullish support:", chart.is_above_bullish_support(last_price))
99
+ print("Below bearish resistance:", chart.is_below_bearish_resistance(last_price))
100
+ ```
101
+
102
+ These checks are the normal first gate before acting on breakout or breakdown patterns.
103
+
104
+ ## Indicators and Momentum
105
+
106
+ ```python
107
+ indicators.calculate(chart)
108
+
109
+ sma_short = indicators.sma_short()
110
+ bands = indicators.bollinger()
111
+ rsi = indicators.rsi()
112
+ obv = indicators.obv()
113
+
114
+ col = chart.column_count() - 1
115
+ if col >= 0:
116
+ print("SMA short:", sma_short.value(col))
117
+ print("Bollinger upper:", bands.upper(col))
118
+ print("RSI:", rsi.value(col))
119
+ print("OBV:", obv.value(col))
120
+ ```
121
+
122
+ ## Signals and Pattern Detection
123
+
124
+ ```python
125
+ signals = indicators.signals()
126
+ patterns = indicators.patterns()
127
+
128
+ print("Current signal:", signals.current_signal())
129
+ print("Buy count:", signals.buy_count())
130
+ print("Sell count:", signals.sell_count())
131
+
132
+ print("Pattern count:", patterns.pattern_count())
133
+ print("Bullish patterns:", len(patterns.bullish_patterns()))
134
+ print("Bearish patterns:", len(patterns.bearish_patterns()))
135
+ ```
136
+
137
+ ## Support, Resistance, Objectives, Congestion
138
+
139
+ ```python
140
+ sr = indicators.support_resistance()
141
+ obj = indicators.objectives()
142
+ cong = indicators.congestion()
143
+
144
+ print("Support levels:", sr.support_levels())
145
+ print("Resistance levels:", sr.resistance_levels())
146
+ print("Significant levels (>=3 touches):", sr.significant_levels(3))
147
+
148
+ print("Bullish targets:", obj.bullish_targets())
149
+ print("Bearish targets:", obj.bearish_targets())
150
+
151
+ print("Congestion zones:", cong.zones())
152
+ ```
153
+
154
+ ## Real-Time Dashboard
155
+
156
+ ```python
157
+ from pypnf_dashboard import DashboardServer
158
+
159
+ server = DashboardServer(chart, indicators)
160
+ server.start("127.0.0.1", 8761)
161
+ server.publish()
162
+ print(server.url())
163
+ ```
164
+
165
+ You can call `server.publish()` after each new bar/tick batch to keep the browser in sync.
166
+
167
+ ## API Map
168
+
169
+ Core:
170
+ - `Chart`, `ChartConfig`, `Box`, `Column`
171
+
172
+ Indicators:
173
+ - `Indicators`, `IndicatorConfig`
174
+ - `MovingAverage`, `BollingerBands`, `RSI`, `OnBalanceVolume`, `BullishPercent`
175
+ - `SignalDetector`, `PatternRecognizer`, `SupportResistance`, `PriceObjectiveCalculator`, `CongestionDetector`
176
+
177
+ Data:
178
+ - `OHLC`, `Signal`, `Pattern`, `SupportResistanceLevel`, `PriceObjective`, `CongestionZone`
179
+
180
+ Enums:
181
+ - `BoxType`, `ColumnType`, `ConstructionMethod`, `BoxSizeMethod`, `SignalType`, `PatternType`
182
+
183
+ ## Versioning and Compatibility
184
+
185
+ - Python package version tracks the same release as the core engine.
186
+ - Keep all bindings on the same version when mixing languages in one system.
187
+ - See `CHANGELOG.md` for version-by-version behavior changes.
188
+
189
+ ## Troubleshooting
190
+
191
+ - `ImportError` / native load issues: rebuild and ensure the native library is discoverable.
192
+ - Empty indicator values: verify you have enough columns for the configured lookback periods.
193
+ - Unexpected chart shape: ensure `HighLow` mode receives real high/low values, not close-only values.
194
+
195
+ ## Documentation and Links
196
+
197
+ - Python API reference: `docs/bindings/python.md`
198
+ - Cross-language API index: `docs/reference/api-symbol-index.md`
199
+ - Source: https://github.com/gregorian-09/pnf-chart-system
200
+ - Issues: https://github.com/gregorian-09/pnf-chart-system/issues
201
+ - Changelog: https://github.com/gregorian-09/pnf-chart-system/blob/master/CHANGELOG.md
@@ -0,0 +1,9 @@
1
+ MANIFEST.in
2
+ README.md
3
+ VERSION
4
+ pypnf_dashboard.py
5
+ setup.py
6
+ pnf_chart_system.egg-info/PKG-INFO
7
+ pnf_chart_system.egg-info/SOURCES.txt
8
+ pnf_chart_system.egg-info/dependency_links.txt
9
+ pnf_chart_system.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ pypnf
2
+ pypnf_dashboard
@@ -0,0 +1,424 @@
1
+ """Localhost real-time dashboard for pypnf."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import hashlib
7
+ import json
8
+ import os
9
+ import socket
10
+ import threading
11
+ import time
12
+ from dataclasses import dataclass
13
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ import pypnf
18
+
19
+ _WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
20
+
21
+
22
+ def _repo_root() -> Path:
23
+ return Path(__file__).resolve().parents[2]
24
+
25
+
26
+ def _asset_path(name: str) -> Path:
27
+ return _repo_root() / "dashboard" / "web" / name
28
+
29
+
30
+ def _enum_name(value: Any) -> str:
31
+ if hasattr(value, "name"):
32
+ return str(value.name)
33
+ return str(value)
34
+
35
+
36
+ def build_snapshot(chart: Any, indicators: Any | None = None, sequence: int = 1) -> dict[str, Any]:
37
+ column_count = int(chart.column_count())
38
+ columns = []
39
+ for idx in range(column_count):
40
+ box_count = int(chart.column_box_count(idx))
41
+ boxes = []
42
+ for box_idx in range(box_count):
43
+ boxes.append(
44
+ {
45
+ "index": box_idx,
46
+ "price": float(chart.box_price(idx, box_idx)),
47
+ "type": _enum_name(chart.box_type(idx, box_idx)),
48
+ "marker": str(chart.box_marker(idx, box_idx) or ""),
49
+ }
50
+ )
51
+ columns.append(
52
+ {
53
+ "index": idx,
54
+ "type": _enum_name(chart.column_type(idx)),
55
+ "box_count": box_count,
56
+ "highest": float(chart.column_high(idx)),
57
+ "lowest": float(chart.column_low(idx)),
58
+ "boxes": boxes,
59
+ }
60
+ )
61
+
62
+ indicator_payload = {
63
+ "summary": "",
64
+ "detailed_summary": "",
65
+ "bullish_percent": 0.0,
66
+ "signal_count": 0,
67
+ "buy_signal_count": 0,
68
+ "sell_signal_count": 0,
69
+ "pattern_count": 0,
70
+ "support_level_count": 0,
71
+ "resistance_level_count": 0,
72
+ "congestion_zone_count": 0,
73
+ "signals": [],
74
+ "patterns": [],
75
+ "support_levels": [],
76
+ "resistance_levels": [],
77
+ "price_objectives": [],
78
+ "congestion_zones": [],
79
+ "series": {
80
+ "sma_short": [],
81
+ "sma_medium": [],
82
+ "sma_long": [],
83
+ "bollinger_middle": [],
84
+ "bollinger_upper": [],
85
+ "bollinger_lower": [],
86
+ "rsi": [],
87
+ },
88
+ }
89
+ if indicators is not None:
90
+ try:
91
+ signal_detector = indicators.signals()
92
+ pattern_recognizer = indicators.patterns()
93
+ support_resistance = indicators.support_resistance()
94
+ objectives = indicators.objectives()
95
+ congestion = indicators.congestion()
96
+ sma_short = [float(v) for v in indicators.sma_short().values_copy()]
97
+ sma_medium = [float(v) for v in indicators.sma_medium().values_copy()]
98
+ sma_long = [float(v) for v in indicators.sma_long().values_copy()]
99
+ bollinger = indicators.bollinger()
100
+ rsi = indicators.rsi()
101
+ signals = [
102
+ {
103
+ "type": _enum_name(signal.type),
104
+ "column_index": int(signal.column_index),
105
+ "price": float(signal.price),
106
+ }
107
+ for signal in signal_detector.signals()
108
+ ]
109
+ patterns = [
110
+ {
111
+ "type": _enum_name(pattern.type),
112
+ "start_column": int(pattern.start_column),
113
+ "end_column": int(pattern.end_column),
114
+ "price": float(pattern.price),
115
+ "is_bullish": bool(pattern.is_bullish()),
116
+ }
117
+ for pattern in pattern_recognizer.patterns()
118
+ ]
119
+ support_levels = [
120
+ {
121
+ "type": "Support",
122
+ "price": float(level.price),
123
+ "touches": int(level.touch_count),
124
+ }
125
+ for level in support_resistance.support_levels()
126
+ ]
127
+ resistance_levels = [
128
+ {
129
+ "type": "Resistance",
130
+ "price": float(level.price),
131
+ "touches": int(level.touch_count),
132
+ }
133
+ for level in support_resistance.resistance_levels()
134
+ ]
135
+ price_objectives = [
136
+ {
137
+ "target": float(objective.target_price),
138
+ "column_index": int(objective.base_column),
139
+ "box_count": int(objective.box_count),
140
+ "is_bullish": bool(objective.is_bullish),
141
+ }
142
+ for objective in objectives.objectives()
143
+ ]
144
+ congestion_zones = [
145
+ {
146
+ "start_column": int(zone.start_column),
147
+ "end_column": int(zone.end_column),
148
+ "high_price": float(zone.high_price),
149
+ "low_price": float(zone.low_price),
150
+ "column_count": int(zone.column_count),
151
+ }
152
+ for zone in congestion.zones()
153
+ ]
154
+ indicator_payload.update(
155
+ {
156
+ "summary": str(indicators.summary()),
157
+ "detailed_summary": str(indicators),
158
+ "bullish_percent": float(indicators.bullish_percent().value()),
159
+ "signal_count": int(signal_detector.buy_count() + signal_detector.sell_count()),
160
+ "buy_signal_count": int(signal_detector.buy_count()),
161
+ "sell_signal_count": int(signal_detector.sell_count()),
162
+ "pattern_count": int(pattern_recognizer.pattern_count()),
163
+ "support_level_count": len(support_levels),
164
+ "resistance_level_count": len(resistance_levels),
165
+ "congestion_zone_count": len(congestion_zones),
166
+ "signals": signals,
167
+ "patterns": patterns,
168
+ "support_levels": support_levels,
169
+ "resistance_levels": resistance_levels,
170
+ "price_objectives": price_objectives,
171
+ "congestion_zones": congestion_zones,
172
+ "series": {
173
+ "sma_short": sma_short,
174
+ "sma_medium": sma_medium,
175
+ "sma_long": sma_long,
176
+ "bollinger_middle": [float(v) for v in bollinger.middle_copy()],
177
+ "bollinger_upper": [float(v) for v in bollinger.upper_copy()],
178
+ "bollinger_lower": [float(v) for v in bollinger.lower_copy()],
179
+ "rsi": [float(v) for v in rsi.values_copy()],
180
+ },
181
+ }
182
+ )
183
+ except Exception:
184
+ indicator_payload["summary"] = "Indicator snapshot unavailable"
185
+ indicator_payload["detailed_summary"] = "Detailed indicator snapshot unavailable"
186
+
187
+ return {
188
+ "type": "dashboard.snapshot",
189
+ "version": 1,
190
+ "timestamp": int(time.time() * 1000),
191
+ "sequence": sequence,
192
+ "payload": {
193
+ "meta": {
194
+ "binding": "python",
195
+ "library_version": pypnf.version(),
196
+ "server_version": "1",
197
+ "stream_state": "running",
198
+ },
199
+ "chart": {
200
+ "box_size": float(chart.current_box_size()),
201
+ "column_count": column_count,
202
+ "x_column_count": int(chart.x_column_count()),
203
+ "o_column_count": int(chart.o_column_count()),
204
+ "has_bullish_bias": bool(chart.has_bullish_bias()),
205
+ "has_bearish_bias": bool(chart.has_bearish_bias()),
206
+ "columns": columns,
207
+ },
208
+ "indicators": indicator_payload,
209
+ },
210
+ }
211
+
212
+
213
+ class _DashboardHandler(BaseHTTPRequestHandler):
214
+ server_version = "PnFPythonDashboard/1.0"
215
+
216
+ def do_GET(self) -> None:
217
+ dashboard = self.server.dashboard_server # type: ignore[attr-defined]
218
+ if self.headers.get("Upgrade", "").lower() == "websocket" and self.path == "/ws":
219
+ self._handle_ws(dashboard)
220
+ return
221
+ if self.path == "/healthz":
222
+ self._send_bytes(200, b"ok", "text/plain; charset=utf-8")
223
+ return
224
+ if self.path == "/snapshot":
225
+ self._send_bytes(200, dashboard.snapshot_json().encode("utf-8"), "application/json; charset=utf-8")
226
+ return
227
+ if self.path == "/app.js":
228
+ self._send_file(_asset_path("app.js"), "application/javascript; charset=utf-8")
229
+ return
230
+ if self.path == "/styles.css":
231
+ self._send_file(_asset_path("styles.css"), "text/css; charset=utf-8")
232
+ return
233
+ if self.path == "/" or self.path == "/index.html":
234
+ html = _asset_path("index.html").read_text(encoding="utf-8")
235
+ ws_url = f"ws://{dashboard.host}:{dashboard.port}/ws"
236
+ html = html.replace(
237
+ "window.PNF_DASHBOARD_CONFIG = window.PNF_DASHBOARD_CONFIG || {};",
238
+ f"window.PNF_DASHBOARD_CONFIG = {{ wsUrl: {json.dumps(ws_url)} }};",
239
+ )
240
+ self._send_bytes(200, html.encode("utf-8"), "text/html; charset=utf-8")
241
+ return
242
+ self._send_bytes(404, b"not found", "text/plain; charset=utf-8")
243
+
244
+ def log_message(self, format: str, *args: Any) -> None: # noqa: A003
245
+ return
246
+
247
+ def _send_file(self, path: Path, content_type: str) -> None:
248
+ self._send_bytes(200, path.read_bytes(), content_type)
249
+
250
+ def _send_bytes(self, status: int, payload: bytes, content_type: str) -> None:
251
+ self.send_response(status)
252
+ self.send_header("Content-Type", content_type)
253
+ self.send_header("Content-Length", str(len(payload)))
254
+ self.send_header("Cache-Control", "no-store")
255
+ self.end_headers()
256
+ self.wfile.write(payload)
257
+
258
+ def _handle_ws(self, dashboard: "DashboardServer") -> None:
259
+ key = self.headers.get("Sec-WebSocket-Key")
260
+ if not key:
261
+ self.send_error(400, "Missing Sec-WebSocket-Key")
262
+ return
263
+ accept = base64.b64encode(hashlib.sha1((key + _WS_GUID).encode("utf-8")).digest()).decode("ascii")
264
+ self.connection.sendall(
265
+ (
266
+ "HTTP/1.1 101 Switching Protocols\r\n"
267
+ "Upgrade: websocket\r\n"
268
+ "Connection: Upgrade\r\n"
269
+ f"Sec-WebSocket-Accept: {accept}\r\n\r\n"
270
+ ).encode("utf-8")
271
+ )
272
+ self.connection.setblocking(True)
273
+ dashboard._register_client(self.connection)
274
+ try:
275
+ while dashboard.is_running:
276
+ time.sleep(1.0)
277
+ finally:
278
+ dashboard._unregister_client(self.connection)
279
+ try:
280
+ self.connection.close()
281
+ except OSError:
282
+ pass
283
+
284
+
285
+ @dataclass
286
+ class DashboardServer:
287
+ chart: Any | None = None
288
+ indicators: Any | None = None
289
+
290
+ def __post_init__(self) -> None:
291
+ self.host = "127.0.0.1"
292
+ self.port = 0
293
+ self._server: ThreadingHTTPServer | None = None
294
+ self._thread: threading.Thread | None = None
295
+ self._clients: list[socket.socket] = []
296
+ self._clients_lock = threading.Lock()
297
+ self._sequence = 0
298
+ self._latest = json.dumps({
299
+ "type": "dashboard.snapshot",
300
+ "version": 1,
301
+ "timestamp": int(time.time() * 1000),
302
+ "sequence": 0,
303
+ "payload": {
304
+ "meta": {"binding": "python", "library_version": pypnf.version(), "server_version": "1", "stream_state": "idle"},
305
+ "chart": {"box_size": 0, "column_count": 0, "x_column_count": 0, "o_column_count": 0, "has_bullish_bias": False, "has_bearish_bias": False, "columns": []},
306
+ "indicators": {"summary": "", "detailed_summary": "", "bullish_percent": 0, "signal_count": 0, "buy_signal_count": 0, "sell_signal_count": 0, "pattern_count": 0, "support_level_count": 0, "resistance_level_count": 0, "congestion_zone_count": 0, "signals": [], "patterns": [], "support_levels": [], "resistance_levels": [], "price_objectives": [], "congestion_zones": [], "series": {"sma_short": [], "sma_medium": [], "sma_long": [], "bollinger_middle": [], "bollinger_upper": [], "bollinger_lower": [], "rsi": []}},
307
+ },
308
+ })
309
+ self._running = threading.Event()
310
+ self._publisher_stop = threading.Event()
311
+ self._publisher_thread: threading.Thread | None = None
312
+
313
+ @property
314
+ def is_running(self) -> bool:
315
+ return self._running.is_set()
316
+
317
+ def set_chart(self, chart: Any) -> None:
318
+ self.chart = chart
319
+
320
+ def set_indicators(self, indicators: Any) -> None:
321
+ self.indicators = indicators
322
+
323
+ def start(self, host: str = "127.0.0.1", port: int = 0) -> str:
324
+ if self._server is not None:
325
+ return self.url()
326
+ self.host = host
327
+ self._server = ThreadingHTTPServer((host, port), _DashboardHandler)
328
+ self._server.dashboard_server = self # type: ignore[attr-defined]
329
+ self.port = int(self._server.server_address[1])
330
+ self._running.set()
331
+ self._thread = threading.Thread(target=self._server.serve_forever, name="pypnf-dashboard", daemon=True)
332
+ self._thread.start()
333
+ return self.url()
334
+
335
+ def url(self) -> str:
336
+ return f"http://{self.host}:{self.port}/"
337
+
338
+ def snapshot(self) -> dict[str, Any]:
339
+ if self.chart is None:
340
+ return json.loads(self._latest)
341
+ self._sequence += 1
342
+ return build_snapshot(self.chart, self.indicators, self._sequence)
343
+
344
+ def snapshot_json(self) -> str:
345
+ return self._latest
346
+
347
+ def publish(self) -> str:
348
+ snap = self.snapshot()
349
+ self._latest = json.dumps(snap)
350
+ self._broadcast(self._latest)
351
+ return self._latest
352
+
353
+ def start_auto_publish(self, interval_ms: int = 250) -> None:
354
+ self.stop_auto_publish()
355
+ self._publisher_stop.clear()
356
+
357
+ def _loop() -> None:
358
+ while not self._publisher_stop.wait(interval_ms / 1000.0):
359
+ if self.is_running and self.chart is not None:
360
+ self.publish()
361
+
362
+ self._publisher_thread = threading.Thread(target=_loop, name="pypnf-dashboard-publisher", daemon=True)
363
+ self._publisher_thread.start()
364
+
365
+ def stop_auto_publish(self) -> None:
366
+ self._publisher_stop.set()
367
+ self._publisher_thread = None
368
+
369
+ def stop(self) -> None:
370
+ self.stop_auto_publish()
371
+ self._running.clear()
372
+ if self._server is not None:
373
+ self._server.shutdown()
374
+ self._server.server_close()
375
+ self._server = None
376
+ with self._clients_lock:
377
+ clients = list(self._clients)
378
+ self._clients.clear()
379
+ for sock in clients:
380
+ try:
381
+ sock.close()
382
+ except OSError:
383
+ pass
384
+
385
+ def _register_client(self, sock: socket.socket) -> None:
386
+ with self._clients_lock:
387
+ self._clients.append(sock)
388
+ if self._latest:
389
+ self._send_ws_text(sock, self._latest)
390
+
391
+ def _unregister_client(self, sock: socket.socket) -> None:
392
+ with self._clients_lock:
393
+ self._clients = [client for client in self._clients if client is not sock]
394
+
395
+ def _broadcast(self, message: str) -> None:
396
+ with self._clients_lock:
397
+ clients = list(self._clients)
398
+ stale = []
399
+ for sock in clients:
400
+ try:
401
+ self._send_ws_text(sock, message)
402
+ except OSError:
403
+ stale.append(sock)
404
+ for sock in stale:
405
+ self._unregister_client(sock)
406
+ try:
407
+ sock.close()
408
+ except OSError:
409
+ pass
410
+
411
+ @staticmethod
412
+ def _send_ws_text(sock: socket.socket, message: str) -> None:
413
+ payload = message.encode("utf-8")
414
+ length = len(payload)
415
+ if length < 126:
416
+ header = bytes([0x81, length])
417
+ elif length < 65536:
418
+ header = bytes([0x81, 126]) + length.to_bytes(2, "big")
419
+ else:
420
+ header = bytes([0x81, 127]) + length.to_bytes(8, "big")
421
+ sock.sendall(header + payload)
422
+
423
+
424
+ __all__ = ["DashboardServer", "build_snapshot"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Setup script for pypnf - Point and Figure Chart Library Python bindings.
4
+
5
+ Build modes:
6
+ 1. Standalone (bundles C++ sources): pip install .
7
+ 2. System library (links to installed libpnf): pip install . --config-settings=--build-option=--use-system-lib
8
+ """
9
+
10
+ import os
11
+ import subprocess
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ from setuptools import setup, Extension
16
+ from setuptools.command.build_ext import build_ext
17
+
18
+
19
+ def read_project_version() -> str:
20
+ """Read the canonical project version from the repo root VERSION file."""
21
+ env_version = os.environ.get("PNF_VERSION", "").strip()
22
+ if env_version:
23
+ return env_version
24
+
25
+ candidates = [
26
+ Path(__file__).resolve().parent / "VERSION",
27
+ Path(__file__).resolve().parents[2] / "VERSION",
28
+ ]
29
+ for version_file in candidates:
30
+ if version_file.exists():
31
+ return version_file.read_text(encoding="utf-8").strip()
32
+
33
+ raise FileNotFoundError(
34
+ "Could not locate VERSION file. Set PNF_VERSION or provide bindings/python/VERSION."
35
+ )
36
+
37
+
38
+ def read_readme() -> str:
39
+ """Read the package README shown on PyPI."""
40
+ return (Path(__file__).resolve().parent / "README.md").read_text(encoding="utf-8")
41
+
42
+
43
+ class CMakeExtension(Extension):
44
+ """Extension that uses CMake for building."""
45
+ def __init__(self, name, sourcedir=""):
46
+ super().__init__(name, sources=[])
47
+ self.sourcedir = os.path.abspath(sourcedir)
48
+
49
+
50
+ class CMakeBuild(build_ext):
51
+ """Build extension using CMake."""
52
+
53
+ def build_extension(self, ext):
54
+ if not isinstance(ext, CMakeExtension):
55
+ super().build_extension(ext)
56
+ return
57
+
58
+ extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
59
+
60
+ # Ensure output directory exists
61
+ if not os.path.exists(extdir):
62
+ os.makedirs(extdir)
63
+
64
+ # CMake configuration
65
+ cmake_args = [
66
+ f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}",
67
+ f"-DPYTHON_EXECUTABLE={sys.executable}",
68
+ "-DPNF_BUILD_PYTHON=ON",
69
+ "-DPNF_BUILD_TESTS=OFF",
70
+ "-DPNF_BUILD_EXAMPLES=OFF",
71
+ "-DPNF_BUILD_VIEWER=OFF",
72
+ ]
73
+
74
+ # Build type
75
+ cfg = "Debug" if self.debug else "Release"
76
+ cmake_args.append(f"-DCMAKE_BUILD_TYPE={cfg}")
77
+
78
+ build_args = ["--config", cfg]
79
+
80
+ # Parallel build
81
+ if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ:
82
+ build_args += ["--", "-j4"]
83
+
84
+ # Build directory
85
+ build_temp = os.path.join(self.build_temp, ext.name)
86
+ if not os.path.exists(build_temp):
87
+ os.makedirs(build_temp)
88
+
89
+ # Run CMake
90
+ subprocess.check_call(["cmake", ext.sourcedir] + cmake_args, cwd=build_temp)
91
+ subprocess.check_call(["cmake", "--build", ".", "--target", "pypnf"] + build_args, cwd=build_temp)
92
+
93
+
94
+ # Alternative: pybind11 direct build (simpler but requires pre-built libpnf)
95
+ def get_pybind11_extension():
96
+ """Create extension using pybind11 directly (requires system libpnf)."""
97
+ try:
98
+ import pybind11
99
+ pybind11_include = pybind11.get_include()
100
+ except ImportError:
101
+ pybind11_include = ""
102
+
103
+ # Find library paths
104
+ root_directory = Path(__file__).parent.parent.parent
105
+ include_dir = root_directory / "include"
106
+
107
+ return Extension(
108
+ "pypnf",
109
+ sources=["pnf_python.cpp"],
110
+ include_dirs=[
111
+ str(include_dir),
112
+ pybind11_include,
113
+ ],
114
+ libraries=["pnf"],
115
+ language="c++",
116
+ extra_compile_args=["-std=c++20", "-O3", "-Wall"],
117
+ )
118
+
119
+
120
+ # Determine build mode
121
+ USE_CMAKE = True # Set to False if you want direct pybind11 build with system library
122
+
123
+ if USE_CMAKE:
124
+ # CMake build bundles everything
125
+ root_dir = str(Path(__file__).parent.parent.parent)
126
+ ext_modules = [CMakeExtension("pypnf", sourcedir=root_dir)]
127
+ cmdclass = {"build_ext": CMakeBuild}
128
+ else:
129
+ # Direct pybind11 build requires pre-installed libpnf
130
+ ext_modules = [get_pybind11_extension()]
131
+ cmdclass = {}
132
+
133
+
134
+ setup(
135
+ name="pnf-chart-system",
136
+ version=read_project_version(),
137
+ author="Gregorian Rayne",
138
+ author_email="gregorianrayne09@gmail.com",
139
+ description="Point and Figure Chart Library - Python bindings",
140
+ long_description=read_readme(),
141
+ long_description_content_type="text/markdown",
142
+ url="https://github.com/gregorian-09/pnf-chart-system",
143
+ project_urls={
144
+ "Documentation": "https://github.com/gregorian-09/pnf-chart-system/tree/master/docs",
145
+ "API Reference": "https://github.com/gregorian-09/pnf-chart-system/blob/master/docs/bindings/python.md",
146
+ "Changelog": "https://github.com/gregorian-09/pnf-chart-system/blob/master/CHANGELOG.md",
147
+ "Issues": "https://github.com/gregorian-09/pnf-chart-system/issues",
148
+ "Source": "https://github.com/gregorian-09/pnf-chart-system",
149
+ },
150
+ ext_modules=ext_modules,
151
+ py_modules=["pypnf_dashboard"],
152
+ cmdclass=cmdclass,
153
+ python_requires=">=3.8",
154
+ keywords="point-and-figure, charting, technical-analysis, trading, indicators",
155
+ classifiers=[
156
+ "Development Status :: 4 - Beta",
157
+ "Intended Audience :: Developers",
158
+ "Intended Audience :: Financial and Insurance Industry",
159
+ "License :: OSI Approved :: MIT License",
160
+ "Programming Language :: Python :: 3",
161
+ "Programming Language :: Python :: 3.8",
162
+ "Programming Language :: Python :: 3.9",
163
+ "Programming Language :: Python :: 3.10",
164
+ "Programming Language :: Python :: 3.11",
165
+ "Programming Language :: Python :: 3.12",
166
+ ],
167
+ )