maidr 1.9.0__py3-none-any.whl → 1.10.0__py3-none-any.whl
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.
- maidr/__init__.py +1 -1
- maidr/core/plot/candlestick.py +59 -90
- maidr/util/datetime_conversion.py +14 -27
- maidr/util/mplfinance_utils.py +5 -303
- {maidr-1.9.0.dist-info → maidr-1.10.0.dist-info}/METADATA +1 -1
- {maidr-1.9.0.dist-info → maidr-1.10.0.dist-info}/RECORD +8 -8
- {maidr-1.9.0.dist-info → maidr-1.10.0.dist-info}/WHEEL +1 -1
- {maidr-1.9.0.dist-info → maidr-1.10.0.dist-info}/licenses/LICENSE +0 -0
maidr/__init__.py
CHANGED
maidr/core/plot/candlestick.py
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import uuid
|
|
4
3
|
from typing import Union, Dict
|
|
5
4
|
from matplotlib.axes import Axes
|
|
6
|
-
|
|
7
|
-
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
8
6
|
|
|
9
7
|
from maidr.core.enum import PlotType
|
|
10
8
|
from maidr.core.plot import MaidrPlot
|
|
11
9
|
from maidr.core.enum.maidr_key import MaidrKey
|
|
12
10
|
from maidr.exception import ExtractionError
|
|
13
|
-
from maidr.util.mplfinance_utils import MplfinanceDataExtractor
|
|
14
11
|
|
|
15
12
|
|
|
16
13
|
class CandlestickPlot(MaidrPlot):
|
|
17
14
|
"""
|
|
18
15
|
Specialized candlestick plot class for mplfinance OHLC data.
|
|
19
16
|
|
|
20
|
-
This class
|
|
21
|
-
|
|
17
|
+
This class extracts candlestick data directly from the original DataFrame
|
|
18
|
+
without any formatting or transformation.
|
|
22
19
|
"""
|
|
23
20
|
|
|
24
21
|
def __init__(self, axes: list[Axes], **kwargs) -> None:
|
|
@@ -34,23 +31,17 @@ class CandlestickPlot(MaidrPlot):
|
|
|
34
31
|
Additional keyword arguments.
|
|
35
32
|
"""
|
|
36
33
|
self.axes = axes
|
|
37
|
-
# Ensure there's at least one axis for the superclass init
|
|
38
34
|
if not axes:
|
|
39
35
|
raise ValueError("Axes list cannot be empty.")
|
|
40
36
|
super().__init__(axes[0], PlotType.CANDLESTICK)
|
|
41
37
|
|
|
42
|
-
# Store
|
|
38
|
+
# Store collections passed from mplfinance patch
|
|
43
39
|
self._maidr_wick_collection = kwargs.get("_maidr_wick_collection", None)
|
|
44
40
|
self._maidr_body_collection = kwargs.get("_maidr_body_collection", None)
|
|
45
|
-
self.
|
|
46
|
-
self._maidr_original_data = kwargs.get(
|
|
47
|
-
"_maidr_original_data", None
|
|
48
|
-
) # Store original data
|
|
49
|
-
self._maidr_datetime_converter = kwargs.get("_maidr_datetime_converter", None)
|
|
41
|
+
self._maidr_original_data = kwargs.get("_maidr_original_data", None)
|
|
50
42
|
|
|
51
|
-
# Store the GID for
|
|
43
|
+
# Store the GID for selector generation
|
|
52
44
|
self._maidr_gid = None
|
|
53
|
-
# Modern-path separate gids for body and wick
|
|
54
45
|
self._maidr_body_gid = None
|
|
55
46
|
self._maidr_wick_gid = None
|
|
56
47
|
if self._maidr_body_collection:
|
|
@@ -62,105 +53,83 @@ class CandlestickPlot(MaidrPlot):
|
|
|
62
53
|
|
|
63
54
|
def _extract_plot_data(self) -> list[dict]:
|
|
64
55
|
"""
|
|
65
|
-
Extract candlestick data from the
|
|
66
|
-
|
|
67
|
-
This method processes candlestick plots from both modern (mplfinance.plot) and
|
|
68
|
-
legacy (original_flavor) pipelines, extracting OHLC data and setting up
|
|
69
|
-
highlighting elements and GIDs.
|
|
56
|
+
Extract candlestick data directly from the original DataFrame.
|
|
70
57
|
|
|
71
58
|
Returns
|
|
72
59
|
-------
|
|
73
60
|
list[dict]
|
|
74
61
|
List of dictionaries containing candlestick data with keys:
|
|
75
|
-
- 'value': Date string
|
|
62
|
+
- 'value': Date string (raw from DataFrame index)
|
|
76
63
|
- 'open': Opening price (float)
|
|
77
64
|
- 'high': High price (float)
|
|
78
65
|
- 'low': Low price (float)
|
|
79
66
|
- 'close': Closing price (float)
|
|
80
|
-
- 'volume': Volume (float
|
|
67
|
+
- 'volume': Volume (float)
|
|
81
68
|
"""
|
|
82
|
-
|
|
83
|
-
# Get the custom collections from kwargs
|
|
84
69
|
body_collection = self._maidr_body_collection
|
|
85
70
|
wick_collection = self._maidr_wick_collection
|
|
86
71
|
|
|
87
72
|
if body_collection and wick_collection:
|
|
88
|
-
# Store the GIDs from the collections
|
|
73
|
+
# Store the GIDs from the collections
|
|
89
74
|
self._maidr_body_gid = body_collection.get_gid()
|
|
90
75
|
self._maidr_wick_gid = wick_collection.get_gid()
|
|
91
|
-
# Keep legacy gid filled for backward compatibility
|
|
92
76
|
self._maidr_gid = self._maidr_body_gid or self._maidr_wick_gid
|
|
93
77
|
|
|
94
78
|
# Use the original collections for highlighting
|
|
95
79
|
self._elements = [body_collection, wick_collection]
|
|
96
80
|
|
|
97
|
-
#
|
|
98
|
-
if self.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
)
|
|
102
|
-
return data
|
|
103
|
-
|
|
104
|
-
# Fallback to original detection method
|
|
105
|
-
if not self.axes:
|
|
106
|
-
return []
|
|
107
|
-
|
|
108
|
-
ax_ohlc = self.axes[0]
|
|
109
|
-
|
|
110
|
-
# Look for Rectangle patches (original_flavor candlestick)
|
|
111
|
-
body_rectangles = []
|
|
112
|
-
for patch in ax_ohlc.patches:
|
|
113
|
-
if isinstance(patch, Rectangle):
|
|
114
|
-
body_rectangles.append(patch)
|
|
115
|
-
|
|
116
|
-
if body_rectangles:
|
|
117
|
-
# Set elements for highlighting
|
|
118
|
-
self._elements = body_rectangles
|
|
119
|
-
|
|
120
|
-
# Generate a GID for highlighting if none exists
|
|
121
|
-
if not self._maidr_gid:
|
|
122
|
-
self._maidr_gid = f"maidr-{uuid.uuid4()}"
|
|
123
|
-
# Set GID on all rectangles
|
|
124
|
-
for rect in body_rectangles:
|
|
125
|
-
rect.set_gid(self._maidr_gid)
|
|
126
|
-
# Keep a dedicated body gid for legacy dict selectors
|
|
127
|
-
self._maidr_body_gid = (
|
|
128
|
-
getattr(self, "_maidr_body_gid", None) or self._maidr_gid
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
# Assign a shared gid to wick Line2D (vertical 2-point lines) on the same axis
|
|
132
|
-
wick_lines = []
|
|
133
|
-
for line in ax_ohlc.get_lines():
|
|
134
|
-
try:
|
|
135
|
-
xydata = line.get_xydata()
|
|
136
|
-
if xydata is None:
|
|
137
|
-
continue
|
|
138
|
-
xy_arr = np.asarray(xydata)
|
|
139
|
-
if (
|
|
140
|
-
xy_arr.ndim == 2
|
|
141
|
-
and xy_arr.shape[0] == 2
|
|
142
|
-
and xy_arr.shape[1] >= 2
|
|
143
|
-
):
|
|
144
|
-
x0 = float(xy_arr[0, 0])
|
|
145
|
-
x1 = float(xy_arr[1, 0])
|
|
146
|
-
if abs(x0 - x1) < 1e-10:
|
|
147
|
-
wick_lines.append(line)
|
|
148
|
-
except Exception:
|
|
149
|
-
continue
|
|
150
|
-
if wick_lines:
|
|
151
|
-
if not getattr(self, "_maidr_wick_gid", None):
|
|
152
|
-
self._maidr_wick_gid = f"maidr-{uuid.uuid4()}"
|
|
153
|
-
for line in wick_lines:
|
|
154
|
-
line.set_gid(self._maidr_wick_gid)
|
|
155
|
-
|
|
156
|
-
# Use the utility class to extract data
|
|
157
|
-
data = MplfinanceDataExtractor.extract_rectangle_candlestick_data(
|
|
158
|
-
body_rectangles, self._maidr_date_nums, self._maidr_original_data
|
|
159
|
-
)
|
|
160
|
-
return data
|
|
81
|
+
# Extract data directly from DataFrame
|
|
82
|
+
if self._maidr_original_data is not None and isinstance(
|
|
83
|
+
self._maidr_original_data, pd.DataFrame
|
|
84
|
+
):
|
|
85
|
+
return self._extract_from_dataframe(self._maidr_original_data)
|
|
161
86
|
|
|
162
87
|
return []
|
|
163
88
|
|
|
89
|
+
def _extract_from_dataframe(self, df: pd.DataFrame) -> list[dict]:
|
|
90
|
+
"""
|
|
91
|
+
Extract candlestick data directly from DataFrame without any formatting.
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
df : pd.DataFrame
|
|
96
|
+
DataFrame with OHLC data and DatetimeIndex.
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
list[dict]
|
|
101
|
+
List of candlestick data dictionaries with raw values.
|
|
102
|
+
"""
|
|
103
|
+
candles = []
|
|
104
|
+
|
|
105
|
+
for i in range(len(df)):
|
|
106
|
+
try:
|
|
107
|
+
# Get date directly from index - raw representation
|
|
108
|
+
date_value = str(df.index[i])
|
|
109
|
+
|
|
110
|
+
# Get OHLC values directly from DataFrame columns
|
|
111
|
+
open_price = float(df.iloc[i]["Open"])
|
|
112
|
+
high_price = float(df.iloc[i]["High"])
|
|
113
|
+
low_price = float(df.iloc[i]["Low"])
|
|
114
|
+
close_price = float(df.iloc[i]["Close"])
|
|
115
|
+
|
|
116
|
+
# Get volume if available, otherwise 0
|
|
117
|
+
volume = float(df.iloc[i].get("Volume", 0.0))
|
|
118
|
+
|
|
119
|
+
candle_data = {
|
|
120
|
+
"value": date_value,
|
|
121
|
+
"open": open_price,
|
|
122
|
+
"high": high_price,
|
|
123
|
+
"low": low_price,
|
|
124
|
+
"close": close_price,
|
|
125
|
+
"volume": volume,
|
|
126
|
+
}
|
|
127
|
+
candles.append(candle_data)
|
|
128
|
+
except (KeyError, IndexError, ValueError, TypeError):
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
return candles
|
|
132
|
+
|
|
164
133
|
def _extract_axes_data(self) -> dict:
|
|
165
134
|
"""
|
|
166
135
|
Extract the plot's axes data including labels.
|
|
@@ -6,18 +6,16 @@ from datetime import datetime
|
|
|
6
6
|
|
|
7
7
|
class DatetimeConverter:
|
|
8
8
|
"""
|
|
9
|
-
|
|
10
|
-
and provides intelligent date/time formatting for mplfinance plots.
|
|
9
|
+
Datetime converter for mplfinance plots.
|
|
11
10
|
|
|
12
|
-
This utility
|
|
13
|
-
datetime values consistently for screen reader accessibility and visual clarity.
|
|
11
|
+
This utility provides datetime value conversion for financial data visualization.
|
|
14
12
|
|
|
15
13
|
Parameters
|
|
16
14
|
----------
|
|
17
15
|
data : pd.DataFrame
|
|
18
16
|
DataFrame with DatetimeIndex containing financial data.
|
|
19
17
|
datetime_format : str, optional
|
|
20
|
-
Custom datetime format string
|
|
18
|
+
Custom datetime format string (currently unused, kept for compatibility).
|
|
21
19
|
|
|
22
20
|
Attributes
|
|
23
21
|
----------
|
|
@@ -49,14 +47,14 @@ class DatetimeConverter:
|
|
|
49
47
|
>>>
|
|
50
48
|
>>> # Get formatted datetime
|
|
51
49
|
>>> formatted = converter.get_formatted_datetime(0)
|
|
52
|
-
>>> print(formatted) # Output: "
|
|
50
|
+
>>> print(formatted) # Output: "2024-01-15 00:00:00"
|
|
53
51
|
>>>
|
|
54
52
|
>>> # For time-based data
|
|
55
53
|
>>> hourly_dates = pd.date_range('2024-01-15 09:00:00', periods=3, freq='H')
|
|
56
54
|
>>> df_hourly = pd.DataFrame({'Open': [3050, 3078, 3080]}, index=hourly_dates)
|
|
57
55
|
>>> converter_hourly = create_datetime_converter(df_hourly)
|
|
58
56
|
>>> formatted_hourly = converter_hourly.get_formatted_datetime(0)
|
|
59
|
-
>>> print(formatted_hourly) # Output: "
|
|
57
|
+
>>> print(formatted_hourly) # Output: "2024-01-15 09:00:00"
|
|
60
58
|
"""
|
|
61
59
|
|
|
62
60
|
def __init__(
|
|
@@ -164,9 +162,7 @@ class DatetimeConverter:
|
|
|
164
162
|
|
|
165
163
|
def get_formatted_datetime(self, index: int) -> Optional[str]:
|
|
166
164
|
"""
|
|
167
|
-
Get
|
|
168
|
-
|
|
169
|
-
Always includes year for screen reader accessibility.
|
|
165
|
+
Get datetime string for given index.
|
|
170
166
|
|
|
171
167
|
Parameters
|
|
172
168
|
----------
|
|
@@ -176,13 +172,13 @@ class DatetimeConverter:
|
|
|
176
172
|
Returns
|
|
177
173
|
-------
|
|
178
174
|
str or None
|
|
179
|
-
|
|
175
|
+
Datetime string or None if index is invalid.
|
|
180
176
|
|
|
181
177
|
Examples
|
|
182
178
|
--------
|
|
183
179
|
>>> converter = create_datetime_converter(df)
|
|
184
180
|
>>> formatted = converter.get_formatted_datetime(0)
|
|
185
|
-
>>> print(formatted) # "
|
|
181
|
+
>>> print(formatted) # "2024-01-15 00:00:00"
|
|
186
182
|
"""
|
|
187
183
|
if index not in self.date_mapping:
|
|
188
184
|
return None
|
|
@@ -192,7 +188,7 @@ class DatetimeConverter:
|
|
|
192
188
|
|
|
193
189
|
def _format_datetime_custom(self, dt: datetime) -> str:
|
|
194
190
|
"""
|
|
195
|
-
|
|
191
|
+
Format datetime as-is using ISO format.
|
|
196
192
|
|
|
197
193
|
Parameters
|
|
198
194
|
----------
|
|
@@ -202,24 +198,15 @@ class DatetimeConverter:
|
|
|
202
198
|
Returns
|
|
203
199
|
-------
|
|
204
200
|
str
|
|
205
|
-
Formatted datetime string
|
|
201
|
+
Formatted datetime string in ISO format.
|
|
206
202
|
|
|
207
203
|
Notes
|
|
208
204
|
-----
|
|
209
|
-
|
|
210
|
-
-
|
|
211
|
-
- Time-based data: "Jan 15 2024 09:00" or "Jan 15 2024 09:00:30"
|
|
212
|
-
- Seconds are only shown when they are non-zero for cleaner display.
|
|
205
|
+
Returns the datetime as a string without smart formatting.
|
|
206
|
+
Output format is "YYYY-MM-DD HH:MM:SS" (e.g., "2024-01-15 00:00:00").
|
|
213
207
|
"""
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if dt.second == 0:
|
|
217
|
-
return dt.strftime("%b %d %Y %H:%M")
|
|
218
|
-
else:
|
|
219
|
-
return dt.strftime("%b %d %Y %H:%M:%S")
|
|
220
|
-
else:
|
|
221
|
-
# Daily/weekly/monthly data: just date
|
|
222
|
-
return dt.strftime("%b %d %Y")
|
|
208
|
+
# Return string representation of datetime
|
|
209
|
+
return str(dt)
|
|
223
210
|
|
|
224
211
|
@property
|
|
225
212
|
def date_nums(self) -> List[float]:
|
maidr/util/mplfinance_utils.py
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
Utility functions for handling mplfinance-specific data extraction and processing.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import re
|
|
5
6
|
import matplotlib.dates as mdates
|
|
6
|
-
import numpy as np
|
|
7
7
|
from matplotlib.patches import Rectangle
|
|
8
|
-
from typing import List, Optional
|
|
9
|
-
import pandas as pd
|
|
8
|
+
from typing import List, Optional
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
class MplfinanceDataExtractor:
|
|
@@ -58,229 +57,6 @@ class MplfinanceDataExtractor:
|
|
|
58
57
|
|
|
59
58
|
return formatted_data
|
|
60
59
|
|
|
61
|
-
@staticmethod
|
|
62
|
-
def extract_candlestick_data(
|
|
63
|
-
body_collection: Any,
|
|
64
|
-
wick_collection: Any,
|
|
65
|
-
date_nums: Optional[List[float]] = None,
|
|
66
|
-
original_data: Optional[Union[pd.DataFrame, pd.Series, dict]] = None,
|
|
67
|
-
) -> List[dict]:
|
|
68
|
-
"""
|
|
69
|
-
Extract candlestick data from mplfinance collections.
|
|
70
|
-
|
|
71
|
-
Parameters
|
|
72
|
-
----------
|
|
73
|
-
body_collection : Any
|
|
74
|
-
PolyCollection containing candlestick bodies
|
|
75
|
-
wick_collection : Any
|
|
76
|
-
LineCollection containing candlestick wicks
|
|
77
|
-
date_nums : Optional[List[float]], default=None
|
|
78
|
-
List of matplotlib date numbers corresponding to the candles
|
|
79
|
-
original_data : Optional[Union[pd.DataFrame, pd.Series, dict]], default=None
|
|
80
|
-
Original DataFrame/Series/dict with OHLC data for accurate bull/bear classification
|
|
81
|
-
|
|
82
|
-
Returns
|
|
83
|
-
-------
|
|
84
|
-
List[dict]
|
|
85
|
-
List of dictionaries with OHLC data
|
|
86
|
-
"""
|
|
87
|
-
if not body_collection or not hasattr(body_collection, "get_paths"):
|
|
88
|
-
return []
|
|
89
|
-
|
|
90
|
-
candles = []
|
|
91
|
-
paths = body_collection.get_paths()
|
|
92
|
-
|
|
93
|
-
for i, path in enumerate(paths):
|
|
94
|
-
if len(path.vertices) >= 4:
|
|
95
|
-
# Extract rectangle coordinates from the path
|
|
96
|
-
vertices = path.vertices
|
|
97
|
-
x_coords = vertices[:, 0]
|
|
98
|
-
y_coords = vertices[:, 1]
|
|
99
|
-
|
|
100
|
-
x_min, x_max = x_coords.min(), x_coords.max()
|
|
101
|
-
y_min, y_max = y_coords.min(), y_coords.max()
|
|
102
|
-
|
|
103
|
-
# Use date mapping if available
|
|
104
|
-
if date_nums is not None and i < len(date_nums):
|
|
105
|
-
date_num = date_nums[i]
|
|
106
|
-
date_str = MplfinanceDataExtractor._convert_date_num_to_string(
|
|
107
|
-
date_num
|
|
108
|
-
)
|
|
109
|
-
else:
|
|
110
|
-
x_center = (x_min + x_max) / 2
|
|
111
|
-
date_str = MplfinanceDataExtractor._convert_date_num_to_string(
|
|
112
|
-
x_center
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
# Determine if this is an up or down candle using original data
|
|
116
|
-
is_up = MplfinanceDataExtractor._determine_bull_bear_from_data(
|
|
117
|
-
original_data, i, date_str
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
# Extract OHLC values
|
|
121
|
-
(
|
|
122
|
-
open_val,
|
|
123
|
-
close_val,
|
|
124
|
-
) = MplfinanceDataExtractor._extract_ohlc_from_rectangle(
|
|
125
|
-
y_min, y_max, is_up
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
# Estimate high and low (these would normally come from wick data)
|
|
129
|
-
high_val = y_max + (y_max - y_min) * 0.1
|
|
130
|
-
low_val = y_min - (y_max - y_min) * 0.1
|
|
131
|
-
|
|
132
|
-
candle_data = {
|
|
133
|
-
"value": date_str,
|
|
134
|
-
"open": round(open_val, 2),
|
|
135
|
-
"high": round(high_val, 2),
|
|
136
|
-
"low": round(low_val, 2),
|
|
137
|
-
"close": round(close_val, 2),
|
|
138
|
-
"volume": 0, # Volume is handled separately
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
candles.append(candle_data)
|
|
142
|
-
|
|
143
|
-
return candles
|
|
144
|
-
|
|
145
|
-
@staticmethod
|
|
146
|
-
def extract_rectangle_candlestick_data(
|
|
147
|
-
body_rectangles: List[Rectangle],
|
|
148
|
-
date_nums: Optional[List[float]] = None,
|
|
149
|
-
original_data: Optional[Union[pd.DataFrame, pd.Series, dict]] = None,
|
|
150
|
-
) -> List[dict]:
|
|
151
|
-
"""
|
|
152
|
-
Extract candlestick data from Rectangle patches (original_flavor).
|
|
153
|
-
|
|
154
|
-
Parameters
|
|
155
|
-
----------
|
|
156
|
-
body_rectangles : List[Rectangle]
|
|
157
|
-
List of Rectangle patches representing candlestick bodies
|
|
158
|
-
date_nums : Optional[List[float]], default=None
|
|
159
|
-
List of matplotlib date numbers corresponding to the candles
|
|
160
|
-
original_data : Optional[Union[pd.DataFrame, pd.Series, dict]], default=None
|
|
161
|
-
Original DataFrame/Series/dict with OHLC data for accurate bull/bear classification
|
|
162
|
-
|
|
163
|
-
Returns
|
|
164
|
-
-------
|
|
165
|
-
List[dict]
|
|
166
|
-
List of dictionaries with OHLC data
|
|
167
|
-
"""
|
|
168
|
-
if not body_rectangles:
|
|
169
|
-
return []
|
|
170
|
-
|
|
171
|
-
candles = []
|
|
172
|
-
|
|
173
|
-
# Sort rectangles by x-coordinate
|
|
174
|
-
body_rectangles.sort(key=lambda r: r.get_x())
|
|
175
|
-
|
|
176
|
-
for i, rect in enumerate(body_rectangles):
|
|
177
|
-
x_left = rect.get_x()
|
|
178
|
-
width = rect.get_width()
|
|
179
|
-
x_center_num = x_left + width / 2.0
|
|
180
|
-
|
|
181
|
-
# Convert x coordinate to date
|
|
182
|
-
if date_nums is not None and i < len(date_nums):
|
|
183
|
-
date_str = MplfinanceDataExtractor._convert_date_num_to_string(
|
|
184
|
-
date_nums[i]
|
|
185
|
-
)
|
|
186
|
-
else:
|
|
187
|
-
date_str = MplfinanceDataExtractor._convert_date_num_to_string(
|
|
188
|
-
x_center_num
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
y_bottom = rect.get_y()
|
|
192
|
-
height = rect.get_height()
|
|
193
|
-
|
|
194
|
-
# Determine if this is an up or down candle using original data
|
|
195
|
-
is_up_candle = MplfinanceDataExtractor._determine_bull_bear_from_data(
|
|
196
|
-
original_data, i, date_str
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
# Extract OHLC values from rectangle
|
|
200
|
-
(
|
|
201
|
-
open_price,
|
|
202
|
-
close_price,
|
|
203
|
-
) = MplfinanceDataExtractor._extract_ohlc_from_rectangle(
|
|
204
|
-
y_bottom, y_bottom + height, is_up_candle
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
# Estimate high and low
|
|
208
|
-
high_price = max(open_price, close_price) + height * 0.1
|
|
209
|
-
low_price = min(open_price, close_price) - height * 0.1
|
|
210
|
-
|
|
211
|
-
# Ensure all values are valid numbers
|
|
212
|
-
open_price = float(open_price) if not np.isnan(open_price) else 0.0
|
|
213
|
-
high_price = float(high_price) if not np.isnan(high_price) else 0.0
|
|
214
|
-
low_price = float(low_price) if not np.isnan(low_price) else 0.0
|
|
215
|
-
close_price = float(close_price) if not np.isnan(close_price) else 0.0
|
|
216
|
-
|
|
217
|
-
candle_data = {
|
|
218
|
-
"value": date_str,
|
|
219
|
-
"open": round(open_price, 2),
|
|
220
|
-
"high": round(high_price, 2),
|
|
221
|
-
"low": round(low_price, 2),
|
|
222
|
-
"close": round(close_price, 2),
|
|
223
|
-
"volume": 0,
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
candles.append(candle_data)
|
|
227
|
-
|
|
228
|
-
return candles
|
|
229
|
-
|
|
230
|
-
@staticmethod
|
|
231
|
-
def _determine_bull_bear_from_data(
|
|
232
|
-
original_data: Optional[Union[pd.DataFrame, pd.Series, dict]],
|
|
233
|
-
index: int,
|
|
234
|
-
date_str: str,
|
|
235
|
-
) -> bool:
|
|
236
|
-
"""
|
|
237
|
-
Determine if a candle is bullish (up) or bearish (down) using original OHLC data.
|
|
238
|
-
|
|
239
|
-
This is the most robust method as it uses the actual data rather than colors.
|
|
240
|
-
|
|
241
|
-
Parameters
|
|
242
|
-
----------
|
|
243
|
-
original_data : Optional[Union[pd.DataFrame, pd.Series, dict]]
|
|
244
|
-
Original DataFrame/Series/dict with OHLC data
|
|
245
|
-
index : int
|
|
246
|
-
Index of the candle
|
|
247
|
-
date_str : str
|
|
248
|
-
Date string for the candle
|
|
249
|
-
|
|
250
|
-
Returns
|
|
251
|
-
-------
|
|
252
|
-
bool
|
|
253
|
-
True if bullish (close > open), False if bearish (close < open)
|
|
254
|
-
"""
|
|
255
|
-
# Default to bullish if no data available
|
|
256
|
-
if original_data is None:
|
|
257
|
-
return True
|
|
258
|
-
|
|
259
|
-
try:
|
|
260
|
-
# Try to access the original data
|
|
261
|
-
if hasattr(original_data, "iloc"):
|
|
262
|
-
# It's a pandas DataFrame/Series
|
|
263
|
-
if index < len(original_data):
|
|
264
|
-
row = original_data.iloc[index]
|
|
265
|
-
if "Close" in row and "Open" in row:
|
|
266
|
-
is_bullish = row["Close"] > row["Open"]
|
|
267
|
-
return is_bullish
|
|
268
|
-
|
|
269
|
-
elif hasattr(original_data, "__getitem__"):
|
|
270
|
-
# It's a dictionary or similar
|
|
271
|
-
if "Close" in original_data and "Open" in original_data:
|
|
272
|
-
closes = original_data["Close"]
|
|
273
|
-
opens = original_data["Open"]
|
|
274
|
-
if index < len(closes) and index < len(opens):
|
|
275
|
-
is_bullish = closes[index] > opens[index]
|
|
276
|
-
return is_bullish
|
|
277
|
-
|
|
278
|
-
except (KeyError, IndexError, AttributeError):
|
|
279
|
-
pass
|
|
280
|
-
|
|
281
|
-
# Fallback to bullish if data access fails
|
|
282
|
-
return True # Default to bullish
|
|
283
|
-
|
|
284
60
|
@staticmethod
|
|
285
61
|
def clean_axis_label(label: str) -> str:
|
|
286
62
|
"""
|
|
@@ -299,8 +75,6 @@ class MplfinanceDataExtractor:
|
|
|
299
75
|
if not label or not isinstance(label, str):
|
|
300
76
|
return label
|
|
301
77
|
|
|
302
|
-
import re
|
|
303
|
-
|
|
304
78
|
# Removes LaTeX-like scientific notation, e.g., "$10^{6}$"
|
|
305
79
|
cleaned_label = re.sub(r"\s*\$.*?\$", "", label).strip()
|
|
306
80
|
return cleaned_label if cleaned_label else label
|
|
@@ -318,7 +92,7 @@ class MplfinanceDataExtractor:
|
|
|
318
92
|
Returns
|
|
319
93
|
-------
|
|
320
94
|
str
|
|
321
|
-
Date string
|
|
95
|
+
Date string representation (e.g., "2024-01-15 00:00:00") or fallback index
|
|
322
96
|
"""
|
|
323
97
|
try:
|
|
324
98
|
# Check if this looks like a matplotlib date number (typically > 700000)
|
|
@@ -326,14 +100,14 @@ class MplfinanceDataExtractor:
|
|
|
326
100
|
date_dt = mdates.num2date(date_num)
|
|
327
101
|
if hasattr(date_dt, "replace"):
|
|
328
102
|
date_dt = date_dt.replace(tzinfo=None)
|
|
329
|
-
return date_dt
|
|
103
|
+
return str(date_dt)
|
|
330
104
|
elif date_num > 1000:
|
|
331
105
|
# Try converting as if it's a pandas timestamp
|
|
332
106
|
try:
|
|
333
107
|
import pandas as pd
|
|
334
108
|
|
|
335
109
|
date_dt = pd.to_datetime(date_num, unit="D")
|
|
336
|
-
return date_dt
|
|
110
|
+
return str(date_dt)
|
|
337
111
|
except (ValueError, TypeError):
|
|
338
112
|
pass
|
|
339
113
|
except (ValueError, TypeError, OverflowError):
|
|
@@ -341,75 +115,3 @@ class MplfinanceDataExtractor:
|
|
|
341
115
|
|
|
342
116
|
# Fallback to index-based date string
|
|
343
117
|
return f"date_{int(date_num):03d}"
|
|
344
|
-
|
|
345
|
-
@staticmethod
|
|
346
|
-
def convert_x_to_date(x_center_num: float, axes: Optional[List] = None) -> str:
|
|
347
|
-
"""
|
|
348
|
-
Convert matplotlib x-coordinate to date string.
|
|
349
|
-
|
|
350
|
-
Parameters
|
|
351
|
-
----------
|
|
352
|
-
x_center_num : float
|
|
353
|
-
X-coordinate value to convert
|
|
354
|
-
axes : Optional[List], optional
|
|
355
|
-
List of matplotlib axes to help with date conversion
|
|
356
|
-
|
|
357
|
-
Returns
|
|
358
|
-
-------
|
|
359
|
-
str
|
|
360
|
-
Date string in YYYY-MM-DD format or fallback
|
|
361
|
-
"""
|
|
362
|
-
# First, try to get the actual dates from the axes x-axis data
|
|
363
|
-
if axes and len(axes) > 0:
|
|
364
|
-
ax = axes[0]
|
|
365
|
-
try:
|
|
366
|
-
# Get x-axis ticks which might contain the actual dates
|
|
367
|
-
x_ticks = ax.get_xticks()
|
|
368
|
-
|
|
369
|
-
# If we have x-axis ticks and they look like dates (large numbers), use them
|
|
370
|
-
if len(x_ticks) > 0 and x_ticks[0] > 1000:
|
|
371
|
-
# Find the closest tick to our x_center_num
|
|
372
|
-
tick_index = int(round(x_center_num))
|
|
373
|
-
if 0 <= tick_index < len(x_ticks):
|
|
374
|
-
actual_date_num = x_ticks[tick_index]
|
|
375
|
-
|
|
376
|
-
# Convert the actual date number
|
|
377
|
-
if actual_date_num > 700000:
|
|
378
|
-
date_dt = mdates.num2date(actual_date_num)
|
|
379
|
-
if hasattr(date_dt, "replace"):
|
|
380
|
-
date_dt = date_dt.replace(tzinfo=None)
|
|
381
|
-
date_str = date_dt.strftime("%Y-%m-%d")
|
|
382
|
-
return date_str
|
|
383
|
-
except Exception:
|
|
384
|
-
pass
|
|
385
|
-
|
|
386
|
-
# Use the utility class for date conversion
|
|
387
|
-
return MplfinanceDataExtractor._convert_date_num_to_string(x_center_num)
|
|
388
|
-
|
|
389
|
-
@staticmethod
|
|
390
|
-
def _extract_ohlc_from_rectangle(
|
|
391
|
-
y_min: float, y_max: float, is_up: bool
|
|
392
|
-
) -> Tuple[float, float]:
|
|
393
|
-
"""
|
|
394
|
-
Extract open and close values from rectangle bounds.
|
|
395
|
-
|
|
396
|
-
Parameters
|
|
397
|
-
----------
|
|
398
|
-
y_min : float
|
|
399
|
-
Minimum y value of rectangle
|
|
400
|
-
y_max : float
|
|
401
|
-
Maximum y value of rectangle
|
|
402
|
-
is_up : bool
|
|
403
|
-
Whether this is an up candle
|
|
404
|
-
|
|
405
|
-
Returns
|
|
406
|
-
-------
|
|
407
|
-
Tuple[float, float]
|
|
408
|
-
(open_price, close_price)
|
|
409
|
-
"""
|
|
410
|
-
if is_up:
|
|
411
|
-
# Up candle: open at bottom, close at top
|
|
412
|
-
return y_min, y_max
|
|
413
|
-
else:
|
|
414
|
-
# Down candle: open at top, close at bottom
|
|
415
|
-
return y_max, y_min
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
maidr/__init__.py,sha256=
|
|
1
|
+
maidr/__init__.py,sha256=BXkAG-4A4pF_IjbBj6cOmPTmQYICXnfPniPJ-4OHnr0,416
|
|
2
2
|
maidr/api.py,sha256=WK7jfQttuPeF1q45RuF_wTciYyiFY6SyjfZVSOVkiUs,4316
|
|
3
3
|
maidr/core/__init__.py,sha256=WgxLpSEYMc4k3OyEOf1shOxfEq0ASzppEIZYmE91ThQ,25
|
|
4
4
|
maidr/core/context_manager.py,sha256=6cT7ZGOApSpC-SLD2XZWWU_H08i-nfv-JUlzXOtvWYw,3374
|
|
@@ -12,7 +12,7 @@ maidr/core/enum/smooth_keywords.py,sha256=z2kVZZ-mETWWh5reWu_hj9WkJD6WFj7_2-6s1e
|
|
|
12
12
|
maidr/core/plot/__init__.py,sha256=xDIpRGM-4DfaSSL3nKcXrjdMecCHJ6en4K4nA_fPefQ,83
|
|
13
13
|
maidr/core/plot/barplot.py,sha256=0hBgp__putezvxXc9G3qmaktmAzze3cN8pQMD9iqktE,2116
|
|
14
14
|
maidr/core/plot/boxplot.py,sha256=i11GdNuz_c-hilmhydu3ah-bzyVdFoBkNvRi5lpMrrY,9946
|
|
15
|
-
maidr/core/plot/candlestick.py,sha256=
|
|
15
|
+
maidr/core/plot/candlestick.py,sha256=qOsYIbn2sRdGE2ES-YJ-Rwhu4NsScHs52mtKe6Bl7_A,8236
|
|
16
16
|
maidr/core/plot/grouped_barplot.py,sha256=odZ52Pl22nb9cWKD3NGsZsyFDxXdBDAEcbOj66HKp9I,4063
|
|
17
17
|
maidr/core/plot/heatmap.py,sha256=yMS-31tS2GW4peds9LtZesMxmmTV_YfqYO5M_t5KasQ,2521
|
|
18
18
|
maidr/core/plot/histogram.py,sha256=QV5W-6ZJQQcZsrM91JJBX-ONktJzH7yg_et5_bBPfQQ,1525
|
|
@@ -40,10 +40,10 @@ maidr/patch/mplfinance.py,sha256=ySD32onanoMgdQkV6XlSAbVd_BQuLWuEQtpkYSEDSzA,949
|
|
|
40
40
|
maidr/patch/regplot.py,sha256=k86ekd0E4XJ_L1u85zObuDnxuXlM83z7tKtyXRTj2rI,3240
|
|
41
41
|
maidr/patch/scatterplot.py,sha256=kln6zZwjVsdQzICalo-RnBOJrx1BnIB2xYUwItHvSNY,525
|
|
42
42
|
maidr/util/__init__.py,sha256=eRJZfRpDX-n7UoV3JXw_9Lbfu_qNl_D0W1UTvLL-Iv4,81
|
|
43
|
-
maidr/util/datetime_conversion.py,sha256=
|
|
43
|
+
maidr/util/datetime_conversion.py,sha256=BF115xweGcrKyDnnjYPeScc0WgeNpCylV0Z-mYKaP4w,13769
|
|
44
44
|
maidr/util/dedup_utils.py,sha256=RpgPL5p-3oULUHaTCZJaQKhPHfyPkvBLHMt8lAGpJ5A,438
|
|
45
45
|
maidr/util/environment.py,sha256=C4VMyB16mqzrFxpJdxFdm40M0IZojxh60UX80680jgo,9403
|
|
46
|
-
maidr/util/mplfinance_utils.py,sha256=
|
|
46
|
+
maidr/util/mplfinance_utils.py,sha256=00YEjrCUbigZZL1j9jzOTamNnwfy5ZZmXJj65AhgNbw,3662
|
|
47
47
|
maidr/util/plot_detection.py,sha256=bgLHoDcHSRwOiyKzUK3EqGwdAIhF44ocHW5ox6xYGZw,3883
|
|
48
48
|
maidr/util/regression_line_utils.py,sha256=yFKr-H0whT_su2YVZwNksBLp5EC5s77sr6HUFgNcsyY,2329
|
|
49
49
|
maidr/util/svg_utils.py,sha256=2gyzBtNKFHs0utrw1iOlxTmznzivOWQMV2aW8zu2c8E,1442
|
|
@@ -52,7 +52,7 @@ maidr/util/mixin/extractor_mixin.py,sha256=j2Rv2vh_gqqcxLV1ka3xsPaPAfWsX94CtKIW2
|
|
|
52
52
|
maidr/util/mixin/merger_mixin.py,sha256=V0qLw_6DUB7X6CQ3BCMpsCQX_ZuwAhoSTm_E4xAJFKM,712
|
|
53
53
|
maidr/widget/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
54
|
maidr/widget/shiny.py,sha256=wrrw2KAIpE_A6CNQGBtNHauR1DjenA_n47qlFXX9_rk,745
|
|
55
|
-
maidr-1.
|
|
56
|
-
maidr-1.
|
|
57
|
-
maidr-1.
|
|
58
|
-
maidr-1.
|
|
55
|
+
maidr-1.10.0.dist-info/METADATA,sha256=6llsnqQI7-gfp62yZJ1ucSXxf9xvrUVNtcItkoxnwnM,3155
|
|
56
|
+
maidr-1.10.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
57
|
+
maidr-1.10.0.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
58
|
+
maidr-1.10.0.dist-info/RECORD,,
|
|
File without changes
|