aishare-txt 1.0.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.
- AIShareTxt/__init__.py +63 -0
- AIShareTxt/ai/__init__.py +11 -0
- AIShareTxt/ai/client.py +313 -0
- AIShareTxt/ai/providers/__init__.py +8 -0
- AIShareTxt/core/__init__.py +17 -0
- AIShareTxt/core/analyzer.py +495 -0
- AIShareTxt/core/config.py +324 -0
- AIShareTxt/core/data_fetcher.py +466 -0
- AIShareTxt/core/report_generator.py +783 -0
- AIShareTxt/docs/AI_INTEGRATION.md +212 -0
- AIShareTxt/docs/README_/321/211/320/227/320/235/321/206/320/256/320/224/321/210/320/277/342/224/244/321/206/320/250/320/236.md +202 -0
- AIShareTxt/docs//321/211/320/227/320/235/321/206/320/256/320/224/321/205/320/276/320/234/321/206/320/230/320/240/321/206/320/220/342/225/227/321/207/342/225/227/320/243.md +148 -0
- AIShareTxt/examples/legacy_api.py +259 -0
- AIShareTxt/indicators/__init__.py +15 -0
- AIShareTxt/indicators/technical_indicators.py +507 -0
- AIShareTxt/tests/__init__.py +11 -0
- AIShareTxt/utils/__init__.py +17 -0
- AIShareTxt/utils/stock_list.py +305 -0
- AIShareTxt/utils/utils.py +578 -0
- aishare_txt-1.0.0.dist-info/METADATA +395 -0
- aishare_txt-1.0.0.dist-info/RECORD +25 -0
- aishare_txt-1.0.0.dist-info/WHEEL +5 -0
- aishare_txt-1.0.0.dist-info/entry_points.txt +2 -0
- aishare_txt-1.0.0.dist-info/licenses/LICENSE +194 -0
- aishare_txt-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
技术指标计算模块
|
|
5
|
+
负责计算各种技术指标
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import talib
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import numpy as np
|
|
11
|
+
from ..core.config import IndicatorConfig as Config
|
|
12
|
+
from ..utils.utils import LoggerManager
|
|
13
|
+
import warnings
|
|
14
|
+
warnings.filterwarnings('ignore')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TechnicalIndicators:
|
|
18
|
+
"""技术指标计算器"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.config = Config()
|
|
22
|
+
self.logger = LoggerManager.get_logger('technical_indicators')
|
|
23
|
+
|
|
24
|
+
def calculate_all_indicators(self, data):
|
|
25
|
+
"""
|
|
26
|
+
计算所有技术指标
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
data (pd.DataFrame): 股票数据
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
dict: 包含所有指标的字典
|
|
33
|
+
"""
|
|
34
|
+
if data is None or len(data) == 0:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
# 获取价格数据
|
|
38
|
+
close = data['close'].values
|
|
39
|
+
high = data['high'].values
|
|
40
|
+
low = data['low'].values
|
|
41
|
+
volume = data['volume'].values
|
|
42
|
+
open_price = data['open'].values
|
|
43
|
+
|
|
44
|
+
indicators = {}
|
|
45
|
+
|
|
46
|
+
# 计算各类指标
|
|
47
|
+
indicators.update(self._calculate_moving_averages(close))
|
|
48
|
+
indicators.update(self._calculate_ma_derived_indicators(close))
|
|
49
|
+
indicators.update(self._calculate_volume_price_indicators(high, low, close, volume))
|
|
50
|
+
indicators.update(self._calculate_trend_strength_indicators(high, low, close))
|
|
51
|
+
indicators.update(self._calculate_momentum_oscillators(high, low, close))
|
|
52
|
+
indicators.update(self._calculate_volatility_indicators(high, low, close))
|
|
53
|
+
indicators.update(self._calculate_volume_indicators(volume))
|
|
54
|
+
|
|
55
|
+
# 添加基础数据
|
|
56
|
+
indicators['current_price'] = close[-1]
|
|
57
|
+
# 将日期转换为字符串格式以便JSON序列化
|
|
58
|
+
date_value = data['date'].iloc[-1]
|
|
59
|
+
if hasattr(date_value, 'strftime'):
|
|
60
|
+
indicators['date'] = date_value.strftime('%Y-%m-%d')
|
|
61
|
+
else:
|
|
62
|
+
indicators['date'] = str(date_value)
|
|
63
|
+
|
|
64
|
+
return indicators
|
|
65
|
+
|
|
66
|
+
def _calculate_moving_averages(self, close):
|
|
67
|
+
"""计算移动平均线指标"""
|
|
68
|
+
indicators = {}
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
# MA(移动平均线)
|
|
72
|
+
all_periods = (self.config.MA_PERIODS['short'] +
|
|
73
|
+
self.config.MA_PERIODS['medium'] +
|
|
74
|
+
self.config.MA_PERIODS['long'])
|
|
75
|
+
|
|
76
|
+
for period in all_periods:
|
|
77
|
+
ma = talib.SMA(close, timeperiod=period)
|
|
78
|
+
indicators[f'MA_{period}'] = ma[-1]
|
|
79
|
+
indicators[f'SMA_{period}'] = ma[-1] # SMA与MA相同,保留用于兼容
|
|
80
|
+
|
|
81
|
+
# EMA(指数移动平均线)
|
|
82
|
+
for period in self.config.EMA_PERIODS:
|
|
83
|
+
ema = talib.EMA(close, timeperiod=period)
|
|
84
|
+
indicators[f'EMA_{period}'] = ema[-1]
|
|
85
|
+
|
|
86
|
+
# WMA(加权移动平均线)
|
|
87
|
+
for period in self.config.WMA_PERIODS:
|
|
88
|
+
wma = talib.WMA(close, timeperiod=period)
|
|
89
|
+
indicators[f'WMA_{period}'] = wma[-1]
|
|
90
|
+
|
|
91
|
+
# 均线形态分析
|
|
92
|
+
ma_patterns = self.analyze_ma_patterns(close)
|
|
93
|
+
indicators.update(ma_patterns)
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
self.logger.warning(f"均线指标计算失败:{str(e)}")
|
|
97
|
+
|
|
98
|
+
return indicators
|
|
99
|
+
|
|
100
|
+
def _calculate_ma_derived_indicators(self, close):
|
|
101
|
+
"""计算均线衍生指标"""
|
|
102
|
+
indicators = {}
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
# BIAS 乖离率
|
|
106
|
+
for period in self.config.BIAS_PERIODS:
|
|
107
|
+
bias = self.calculate_bias(close, timeperiod=period)
|
|
108
|
+
if bias is not None:
|
|
109
|
+
indicators[f'BIAS_{period}'] = bias[-1]
|
|
110
|
+
|
|
111
|
+
# MACD
|
|
112
|
+
macd_config = self.config.MACD_CONFIG
|
|
113
|
+
macd, macdsignal, macdhist = talib.MACD(
|
|
114
|
+
close,
|
|
115
|
+
fastperiod=macd_config['fastperiod'],
|
|
116
|
+
slowperiod=macd_config['slowperiod'],
|
|
117
|
+
signalperiod=macd_config['signalperiod']
|
|
118
|
+
)
|
|
119
|
+
indicators['MACD_DIF'] = macd[-1]
|
|
120
|
+
indicators['MACD_DEA'] = macdsignal[-1]
|
|
121
|
+
indicators['MACD_HIST'] = macdhist[-1]
|
|
122
|
+
|
|
123
|
+
# 布林带
|
|
124
|
+
bb_config = self.config.BOLLINGER_BANDS_CONFIG
|
|
125
|
+
upperband, middleband, lowerband = talib.BBANDS(
|
|
126
|
+
close,
|
|
127
|
+
timeperiod=bb_config['timeperiod'],
|
|
128
|
+
nbdevup=bb_config['nbdevup'],
|
|
129
|
+
nbdevdn=bb_config['nbdevdn'],
|
|
130
|
+
matype=int(bb_config['matype']) # type: ignore
|
|
131
|
+
)
|
|
132
|
+
indicators['BB_UPPER'] = upperband[-1]
|
|
133
|
+
indicators['BB_MIDDLE'] = middleband[-1]
|
|
134
|
+
indicators['BB_LOWER'] = lowerband[-1]
|
|
135
|
+
indicators['BB_WIDTH'] = (upperband[-1] - lowerband[-1]) / middleband[-1] * 100
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
self.logger.warning(f"均线衍生指标计算失败:{str(e)}")
|
|
139
|
+
|
|
140
|
+
return indicators
|
|
141
|
+
|
|
142
|
+
def _calculate_volume_price_indicators(self, high, low, close, volume):
|
|
143
|
+
"""计算量价指标"""
|
|
144
|
+
indicators = {}
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
# VWAP (Volume Weighted Average Price)
|
|
148
|
+
typical_price = (high + low + close) / 3
|
|
149
|
+
vwap_period = self.config.VWAP_PERIOD
|
|
150
|
+
if len(typical_price) >= vwap_period:
|
|
151
|
+
price_volume = typical_price[-vwap_period:] * volume[-vwap_period:]
|
|
152
|
+
indicators['VWAP_14'] = np.sum(price_volume) / np.sum(volume[-vwap_period:])
|
|
153
|
+
|
|
154
|
+
# OBV (On Balance Volume)
|
|
155
|
+
close_float = close.astype(np.float64)
|
|
156
|
+
volume_float = volume.astype(np.float64)
|
|
157
|
+
obv = talib.OBV(close_float, volume_float)
|
|
158
|
+
|
|
159
|
+
indicators.update(self._analyze_obv(obv, close))
|
|
160
|
+
|
|
161
|
+
except Exception as e:
|
|
162
|
+
self.logger.warning(f"量价指标计算失败:{str(e)}")
|
|
163
|
+
|
|
164
|
+
return indicators
|
|
165
|
+
|
|
166
|
+
def _calculate_trend_strength_indicators(self, high, low, close):
|
|
167
|
+
"""计算趋势强度指标"""
|
|
168
|
+
indicators = {}
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
# ADX (Average Directional Index) 和 DMI 指标
|
|
172
|
+
adx_period = self.config.ADX_PERIOD
|
|
173
|
+
adx = talib.ADX(high, low, close, timeperiod=adx_period)
|
|
174
|
+
plus_di = talib.PLUS_DI(high, low, close, timeperiod=adx_period)
|
|
175
|
+
minus_di = talib.MINUS_DI(high, low, close, timeperiod=adx_period)
|
|
176
|
+
|
|
177
|
+
indicators['ADX'] = adx[-1]
|
|
178
|
+
indicators['DI_PLUS'] = plus_di[-1]
|
|
179
|
+
indicators['DI_MINUS'] = minus_di[-1]
|
|
180
|
+
indicators['DI_DIFF'] = plus_di[-1] - minus_di[-1]
|
|
181
|
+
|
|
182
|
+
# ADX趋势强度跟踪
|
|
183
|
+
if len(adx) >= 5:
|
|
184
|
+
adx_recent = adx[-5:]
|
|
185
|
+
adx_slope = np.polyfit(range(len(adx_recent)), adx_recent, 1)[0]
|
|
186
|
+
indicators['ADX_TREND'] = adx_slope
|
|
187
|
+
indicators['ADX_5D_CHANGE'] = ((adx[-1] - adx[-5]) / adx[-5] * 100) if adx[-5] != 0 else 0
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
self.logger.warning(f"趋势强度指标计算失败:{str(e)}")
|
|
191
|
+
|
|
192
|
+
return indicators
|
|
193
|
+
|
|
194
|
+
def _calculate_momentum_oscillators(self, high, low, close):
|
|
195
|
+
"""计算动量振荡指标"""
|
|
196
|
+
indicators = {}
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
# RSI (Relative Strength Index)
|
|
200
|
+
for period in self.config.RSI_PERIODS:
|
|
201
|
+
rsi = talib.RSI(close, timeperiod=period)
|
|
202
|
+
indicators[f'RSI_{period}'] = rsi[-1]
|
|
203
|
+
|
|
204
|
+
# Stochastic Oscillator (KD指标)
|
|
205
|
+
stoch_config = self.config.STOCH_CONFIG
|
|
206
|
+
slowk, slowd = talib.STOCH(
|
|
207
|
+
high, low, close,
|
|
208
|
+
fastk_period=stoch_config['fastk_period'],
|
|
209
|
+
slowk_period=stoch_config['slowk_period'],
|
|
210
|
+
slowk_matype=int(stoch_config['slowk_matype']), # type: ignore
|
|
211
|
+
slowd_period=stoch_config['slowd_period'],
|
|
212
|
+
slowd_matype=int(stoch_config['slowd_matype']) # type: ignore
|
|
213
|
+
)
|
|
214
|
+
indicators['STOCH_K'] = slowk[-1]
|
|
215
|
+
indicators['STOCH_D'] = slowd[-1]
|
|
216
|
+
indicators['STOCH_KD_DIFF'] = slowk[-1] - slowd[-1]
|
|
217
|
+
|
|
218
|
+
except Exception as e:
|
|
219
|
+
self.logger.warning(f"动量振荡指标计算失败:{str(e)}")
|
|
220
|
+
|
|
221
|
+
return indicators
|
|
222
|
+
|
|
223
|
+
def _calculate_volatility_indicators(self, high, low, close):
|
|
224
|
+
"""计算波动率指标"""
|
|
225
|
+
indicators = {}
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
# ATR (Average True Range)
|
|
229
|
+
atr_period = self.config.ATR_PERIOD
|
|
230
|
+
atr = talib.ATR(high, low, close, timeperiod=atr_period)
|
|
231
|
+
indicators['ATR_14'] = atr[-1]
|
|
232
|
+
indicators['ATR_RATIO'] = (atr[-1] / close[-1] * 100) if close[-1] != 0 else 0
|
|
233
|
+
|
|
234
|
+
except Exception as e:
|
|
235
|
+
self.logger.warning(f"波动率指标计算失败:{str(e)}")
|
|
236
|
+
|
|
237
|
+
return indicators
|
|
238
|
+
|
|
239
|
+
def _calculate_volume_indicators(self, volume):
|
|
240
|
+
"""计算成交量指标"""
|
|
241
|
+
indicators = {}
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
# 量比指标
|
|
245
|
+
if len(volume) >= 6:
|
|
246
|
+
current_volume = volume[-1]
|
|
247
|
+
avg_volume_5d = np.mean(volume[-6:-1]) # 过去5日平均
|
|
248
|
+
indicators['VOLUME_RATIO'] = current_volume / avg_volume_5d if avg_volume_5d != 0 else 0
|
|
249
|
+
|
|
250
|
+
# 成交量趋势
|
|
251
|
+
if len(volume) >= 5:
|
|
252
|
+
volume_recent = volume[-5:]
|
|
253
|
+
volume_slope = np.polyfit(range(len(volume_recent)), volume_recent, 1)[0]
|
|
254
|
+
indicators['VOLUME_TREND'] = volume_slope
|
|
255
|
+
|
|
256
|
+
except Exception as e:
|
|
257
|
+
self.logger.warning(f"成交量指标计算失败:{str(e)}")
|
|
258
|
+
|
|
259
|
+
return indicators
|
|
260
|
+
|
|
261
|
+
def calculate_bias(self, close, timeperiod=20):
|
|
262
|
+
"""
|
|
263
|
+
计算乖离率 (BIAS)
|
|
264
|
+
BIAS = (收盘价 - N日SMA) / N日SMA * 100
|
|
265
|
+
"""
|
|
266
|
+
sma = talib.SMA(close, timeperiod=timeperiod)
|
|
267
|
+
bias = ((close - sma) / sma) * 100
|
|
268
|
+
return bias
|
|
269
|
+
|
|
270
|
+
def analyze_ma_patterns(self, close):
|
|
271
|
+
"""分析均线形态"""
|
|
272
|
+
patterns = {
|
|
273
|
+
'trend_pattern': '',
|
|
274
|
+
'cross_pattern': '',
|
|
275
|
+
'position_pattern': '',
|
|
276
|
+
'arrangement_pattern': '',
|
|
277
|
+
'support_resistance': ''
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# 计算均线
|
|
281
|
+
ma5 = talib.SMA(close, timeperiod=5)
|
|
282
|
+
ma10 = talib.SMA(close, timeperiod=10)
|
|
283
|
+
ma20 = talib.SMA(close, timeperiod=20)
|
|
284
|
+
ma60 = talib.SMA(close, timeperiod=60)
|
|
285
|
+
|
|
286
|
+
if len(ma5) < 2 or len(ma10) < 2 or len(ma20) < 2:
|
|
287
|
+
return patterns
|
|
288
|
+
|
|
289
|
+
current_price = close[-1]
|
|
290
|
+
current_ma5 = ma5[-1]
|
|
291
|
+
current_ma10 = ma10[-1]
|
|
292
|
+
current_ma20 = ma20[-1]
|
|
293
|
+
current_ma60 = ma60[-1] if len(ma60) > 0 else 0
|
|
294
|
+
|
|
295
|
+
prev_ma5 = ma5[-2]
|
|
296
|
+
prev_ma10 = ma10[-2]
|
|
297
|
+
prev_ma20 = ma20[-2]
|
|
298
|
+
|
|
299
|
+
# 使用配置中的阈值
|
|
300
|
+
thresholds = self.config.MA_THRESHOLDS
|
|
301
|
+
|
|
302
|
+
# 1. 趋势形态分析
|
|
303
|
+
patterns['trend_pattern'] = self._analyze_trend_pattern(
|
|
304
|
+
current_ma5, current_ma10, current_ma20, thresholds
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# 2. 交叉形态分析
|
|
308
|
+
patterns['cross_pattern'] = self._analyze_cross_pattern(
|
|
309
|
+
current_ma5, current_ma10, current_ma20,
|
|
310
|
+
prev_ma5, prev_ma10, prev_ma20
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# 3. 位置形态分析
|
|
314
|
+
patterns['position_pattern'] = self._analyze_position_pattern(
|
|
315
|
+
current_price, current_ma5, current_ma10, current_ma20, current_ma60, thresholds
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
# 4. 排列形态分析
|
|
319
|
+
patterns['arrangement_pattern'] = self._analyze_arrangement_pattern(
|
|
320
|
+
current_ma5, current_ma10, current_ma20, current_ma60
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# 5. 支撑阻力分析
|
|
324
|
+
patterns['support_resistance'] = self._analyze_support_resistance(
|
|
325
|
+
current_price, current_ma5, current_ma10, current_ma20, thresholds
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
return patterns
|
|
329
|
+
|
|
330
|
+
def _analyze_trend_pattern(self, ma5, ma10, ma20, thresholds):
|
|
331
|
+
"""分析趋势形态"""
|
|
332
|
+
if ma5 > ma10 > ma20:
|
|
333
|
+
# 检查发散程度
|
|
334
|
+
ma_diff_5_10 = (ma5 - ma10) / ma10 * 100
|
|
335
|
+
ma_diff_10_20 = (ma10 - ma20) / ma20 * 100
|
|
336
|
+
|
|
337
|
+
if ma_diff_5_10 > thresholds['divergence_strong'] * 100 and ma_diff_10_20 > thresholds['divergence_strong'] * 100:
|
|
338
|
+
return "多头发散"
|
|
339
|
+
elif ma_diff_5_10 < thresholds['divergence_weak'] * 100 and ma_diff_10_20 < thresholds['divergence_weak'] * 100:
|
|
340
|
+
return "多头收敛"
|
|
341
|
+
else:
|
|
342
|
+
return "多头排列"
|
|
343
|
+
|
|
344
|
+
elif ma5 < ma10 < ma20:
|
|
345
|
+
# 检查发散程度
|
|
346
|
+
ma_diff_5_10 = abs(ma5 - ma10) / ma10 * 100
|
|
347
|
+
ma_diff_10_20 = abs(ma10 - ma20) / ma20 * 100
|
|
348
|
+
|
|
349
|
+
if ma_diff_5_10 > thresholds['divergence_strong'] * 100 and ma_diff_10_20 > thresholds['divergence_strong'] * 100:
|
|
350
|
+
return "空头发散"
|
|
351
|
+
elif ma_diff_5_10 < thresholds['divergence_weak'] * 100 and ma_diff_10_20 < thresholds['divergence_weak'] * 100:
|
|
352
|
+
return "空头收敛"
|
|
353
|
+
else:
|
|
354
|
+
return "空头排列"
|
|
355
|
+
else:
|
|
356
|
+
# 检查是否为盘整形态
|
|
357
|
+
ma_diff_5_10 = abs(ma5 - ma10) / ma10 * 100
|
|
358
|
+
ma_diff_10_20 = abs(ma10 - ma20) / ma20 * 100
|
|
359
|
+
ma_diff_5_20 = abs(ma5 - ma20) / ma20 * 100
|
|
360
|
+
|
|
361
|
+
if (ma_diff_5_10 < thresholds['adhesion_loose'] * 100 and
|
|
362
|
+
ma_diff_10_20 < thresholds['adhesion_loose'] * 100 and
|
|
363
|
+
ma_diff_5_20 < thresholds['adhesion_range'] * 100):
|
|
364
|
+
return "三均线粘合"
|
|
365
|
+
elif ma_diff_5_10 < thresholds['adhesion_tight'] * 100:
|
|
366
|
+
return "MA5与MA10粘合"
|
|
367
|
+
elif ma_diff_10_20 < thresholds['adhesion_tight'] * 100:
|
|
368
|
+
return "MA10与MA20粘合"
|
|
369
|
+
else:
|
|
370
|
+
return "均线缠绕"
|
|
371
|
+
|
|
372
|
+
def _analyze_cross_pattern(self, ma5, ma10, ma20, prev_ma5, prev_ma10, prev_ma20):
|
|
373
|
+
"""分析交叉形态"""
|
|
374
|
+
cross_signals = []
|
|
375
|
+
|
|
376
|
+
# 均线交叉检测
|
|
377
|
+
if ma5 > ma10 and prev_ma5 <= prev_ma10:
|
|
378
|
+
cross_signals.append("MA5上穿MA10")
|
|
379
|
+
if ma10 > ma20 and prev_ma10 <= prev_ma20:
|
|
380
|
+
cross_signals.append("MA10上穿MA20")
|
|
381
|
+
|
|
382
|
+
if ma5 < ma10 and prev_ma5 >= prev_ma10:
|
|
383
|
+
cross_signals.append("MA5下穿MA10")
|
|
384
|
+
if ma10 < ma20 and prev_ma10 >= prev_ma20:
|
|
385
|
+
cross_signals.append("MA10下穿MA20")
|
|
386
|
+
|
|
387
|
+
return ",".join(cross_signals) if cross_signals else "无交叉信号"
|
|
388
|
+
|
|
389
|
+
def _analyze_position_pattern(self, price, ma5, ma10, ma20, ma60, thresholds):
|
|
390
|
+
"""分析位置形态"""
|
|
391
|
+
position_signals = []
|
|
392
|
+
support_threshold = thresholds['support_resistance']
|
|
393
|
+
|
|
394
|
+
# 价格与均线关系
|
|
395
|
+
for ma_value, ma_name in [(ma5, "MA5"), (ma10, "MA10"), (ma20, "MA20")]:
|
|
396
|
+
if price > ma_value:
|
|
397
|
+
position_signals.append(f"站上{ma_name}")
|
|
398
|
+
elif abs(price - ma_value) / ma_value < support_threshold:
|
|
399
|
+
position_signals.append(f"接近{ma_name}")
|
|
400
|
+
|
|
401
|
+
if ma60 > 0:
|
|
402
|
+
if price > ma60:
|
|
403
|
+
position_signals.append("站上MA60")
|
|
404
|
+
elif abs(price - ma60) / ma60 < support_threshold:
|
|
405
|
+
position_signals.append("接近MA60")
|
|
406
|
+
|
|
407
|
+
return ",".join(position_signals) if position_signals else "均线下方运行"
|
|
408
|
+
|
|
409
|
+
def _analyze_arrangement_pattern(self, ma5, ma10, ma20, ma60):
|
|
410
|
+
"""分析排列形态"""
|
|
411
|
+
if ma5 > ma10 > ma20:
|
|
412
|
+
if ma60 > 0 and ma20 > ma60:
|
|
413
|
+
return "MA5>MA10>MA20>MA60"
|
|
414
|
+
else:
|
|
415
|
+
return "MA5>MA10>MA20"
|
|
416
|
+
elif ma5 < ma10 < ma20:
|
|
417
|
+
if ma60 > 0 and ma20 < ma60:
|
|
418
|
+
return "MA5<MA10<MA20<MA60"
|
|
419
|
+
else:
|
|
420
|
+
return "MA5<MA10<MA20"
|
|
421
|
+
else:
|
|
422
|
+
return "均线无规律排列"
|
|
423
|
+
|
|
424
|
+
def _analyze_support_resistance(self, price, ma5, ma10, ma20, thresholds):
|
|
425
|
+
"""分析支撑阻力"""
|
|
426
|
+
support_resistance = []
|
|
427
|
+
support_threshold = thresholds['support_resistance']
|
|
428
|
+
|
|
429
|
+
for ma_value, ma_name in [(ma5, "MA5"), (ma10, "MA10"), (ma20, "MA20")]:
|
|
430
|
+
if abs(price - ma_value) / ma_value < support_threshold:
|
|
431
|
+
support_resistance.append(f"接近{ma_name}")
|
|
432
|
+
|
|
433
|
+
return ",".join(support_resistance) if support_resistance else "无接近均线"
|
|
434
|
+
|
|
435
|
+
def _analyze_obv(self, obv, close):
|
|
436
|
+
"""分析OBV指标"""
|
|
437
|
+
indicators = {}
|
|
438
|
+
obv_config = self.config.OBV_CONFIG
|
|
439
|
+
|
|
440
|
+
# 计算OBV基础指标
|
|
441
|
+
indicators['OBV_current'] = obv[-1]
|
|
442
|
+
indicators['OBV_5d_ago'] = obv[-6] if len(obv) >= 6 else obv[0]
|
|
443
|
+
indicators['OBV_20d_ago'] = obv[-21] if len(obv) >= 21 else obv[0]
|
|
444
|
+
|
|
445
|
+
# 计算OBV变化率
|
|
446
|
+
obv_5d_change = ((obv[-1] - obv[-6]) / abs(obv[-6]) * 100) if len(obv) >= 6 and obv[-6] != 0 else 0
|
|
447
|
+
obv_20d_change = ((obv[-1] - obv[-21]) / abs(obv[-21]) * 100) if len(obv) >= 21 and obv[-21] != 0 else 0
|
|
448
|
+
|
|
449
|
+
indicators['OBV_5d_change'] = obv_5d_change
|
|
450
|
+
indicators['OBV_20d_change'] = obv_20d_change
|
|
451
|
+
|
|
452
|
+
# 计算OBV趋势(最近5日斜率)
|
|
453
|
+
if len(obv) >= 5:
|
|
454
|
+
recent_obv = obv[-5:]
|
|
455
|
+
x = np.arange(len(recent_obv))
|
|
456
|
+
slope = np.polyfit(x, recent_obv, 1)[0]
|
|
457
|
+
indicators['OBV_trend'] = slope
|
|
458
|
+
|
|
459
|
+
# OBV背离验证
|
|
460
|
+
if len(obv) >= 20 and len(close) >= 20:
|
|
461
|
+
try:
|
|
462
|
+
from scipy.signal import argrelextrema
|
|
463
|
+
indicators['OBV_DIVERGENCE'] = self._detect_obv_divergence(obv, close, obv_config)
|
|
464
|
+
except ImportError:
|
|
465
|
+
indicators['OBV_DIVERGENCE'] = "需要scipy库进行背离分析"
|
|
466
|
+
|
|
467
|
+
return indicators
|
|
468
|
+
|
|
469
|
+
def _detect_obv_divergence(self, obv, close, config):
|
|
470
|
+
"""检测OBV背离"""
|
|
471
|
+
try:
|
|
472
|
+
from scipy.signal import argrelextrema
|
|
473
|
+
|
|
474
|
+
recent_close = close[-20:]
|
|
475
|
+
recent_obv = obv[-20:]
|
|
476
|
+
|
|
477
|
+
# 找局部高点和低点
|
|
478
|
+
order = config['extrema_order']
|
|
479
|
+
price_highs = argrelextrema(recent_close, np.greater, order=order)[0]
|
|
480
|
+
price_lows = argrelextrema(recent_close, np.less, order=order)[0]
|
|
481
|
+
obv_highs = argrelextrema(recent_obv, np.greater, order=order)[0]
|
|
482
|
+
obv_lows = argrelextrema(recent_obv, np.less, order=order)[0]
|
|
483
|
+
|
|
484
|
+
# 顶背离:价格创新高,OBV未创新高
|
|
485
|
+
if len(price_highs) >= 2 and len(obv_highs) >= 2:
|
|
486
|
+
latest_price_high_idx = price_highs[-1]
|
|
487
|
+
prev_price_high_idx = price_highs[-2]
|
|
488
|
+
|
|
489
|
+
if latest_price_high_idx < len(obv_highs) and prev_price_high_idx < len(obv_highs):
|
|
490
|
+
if (recent_close[latest_price_high_idx] > recent_close[prev_price_high_idx] and
|
|
491
|
+
recent_obv[latest_price_high_idx] < recent_obv[prev_price_high_idx]):
|
|
492
|
+
return "顶背离"
|
|
493
|
+
|
|
494
|
+
# 底背离:价格创新低,OBV未创新低
|
|
495
|
+
if len(price_lows) >= 2 and len(obv_lows) >= 2:
|
|
496
|
+
latest_price_low_idx = price_lows[-1]
|
|
497
|
+
prev_price_low_idx = price_lows[-2]
|
|
498
|
+
|
|
499
|
+
if latest_price_low_idx < len(obv_lows) and prev_price_low_idx < len(obv_lows):
|
|
500
|
+
if (recent_close[latest_price_low_idx] < recent_close[prev_price_low_idx] and
|
|
501
|
+
recent_obv[latest_price_low_idx] > recent_obv[prev_price_low_idx]):
|
|
502
|
+
return "底背离"
|
|
503
|
+
|
|
504
|
+
return "无明显背离"
|
|
505
|
+
|
|
506
|
+
except Exception:
|
|
507
|
+
return "背离检测失败"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIShareTxt工具模块
|
|
3
|
+
|
|
4
|
+
包含日志管理、股票列表获取等辅助功能。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .utils import Logger
|
|
8
|
+
from .stock_list import get_stock_list
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"Logger",
|
|
12
|
+
"get_stock_list",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
# 这里可以添加更多工具函数
|
|
16
|
+
# from .data_validator import DataValidator
|
|
17
|
+
# __all__.append("DataValidator")
|