siat 3.10.130__py3-none-any.whl → 3.10.132__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.
Files changed (217) hide show
  1. build/lib/build/lib/siat/__init__.py +75 -0
  2. build/lib/build/lib/siat/allin.py +137 -0
  3. build/lib/build/lib/siat/assets_liquidity.py +915 -0
  4. build/lib/build/lib/siat/beta_adjustment.py +1058 -0
  5. build/lib/build/lib/siat/beta_adjustment_china.py +548 -0
  6. build/lib/build/lib/siat/blockchain.py +143 -0
  7. build/lib/build/lib/siat/bond.py +2900 -0
  8. build/lib/build/lib/siat/bond_base.py +992 -0
  9. build/lib/build/lib/siat/bond_china.py +100 -0
  10. build/lib/build/lib/siat/bond_zh_sina.py +143 -0
  11. build/lib/build/lib/siat/capm_beta.py +783 -0
  12. build/lib/build/lib/siat/capm_beta2.py +887 -0
  13. build/lib/build/lib/siat/common.py +5360 -0
  14. build/lib/build/lib/siat/compare_cross.py +642 -0
  15. build/lib/build/lib/siat/copyrights.py +18 -0
  16. build/lib/build/lib/siat/cryptocurrency.py +667 -0
  17. build/lib/build/lib/siat/economy.py +1471 -0
  18. build/lib/build/lib/siat/economy2.py +1853 -0
  19. build/lib/build/lib/siat/esg.py +536 -0
  20. build/lib/build/lib/siat/event_study.py +815 -0
  21. build/lib/build/lib/siat/fama_french.py +1521 -0
  22. build/lib/build/lib/siat/fin_stmt2_yahoo.py +982 -0
  23. build/lib/build/lib/siat/financial_base.py +1160 -0
  24. build/lib/build/lib/siat/financial_statements.py +598 -0
  25. build/lib/build/lib/siat/financials.py +2339 -0
  26. build/lib/build/lib/siat/financials2.py +1278 -0
  27. build/lib/build/lib/siat/financials_china.py +4433 -0
  28. build/lib/build/lib/siat/financials_china2.py +2212 -0
  29. build/lib/build/lib/siat/fund.py +629 -0
  30. build/lib/build/lib/siat/fund_china.py +3307 -0
  31. build/lib/build/lib/siat/future_china.py +551 -0
  32. build/lib/build/lib/siat/google_authenticator.py +47 -0
  33. build/lib/build/lib/siat/grafix.py +3636 -0
  34. build/lib/build/lib/siat/holding_risk.py +867 -0
  35. build/lib/build/lib/siat/luchy_draw.py +638 -0
  36. build/lib/build/lib/siat/market_china.py +1168 -0
  37. build/lib/build/lib/siat/markowitz.py +2363 -0
  38. build/lib/build/lib/siat/markowitz2.py +3150 -0
  39. build/lib/build/lib/siat/markowitz2_20250704.py +2969 -0
  40. build/lib/build/lib/siat/markowitz2_20250705.py +3158 -0
  41. build/lib/build/lib/siat/markowitz_simple.py +373 -0
  42. build/lib/build/lib/siat/ml_cases.py +2291 -0
  43. build/lib/build/lib/siat/ml_cases_example.py +60 -0
  44. build/lib/build/lib/siat/option_china.py +3069 -0
  45. build/lib/build/lib/siat/option_pricing.py +1925 -0
  46. build/lib/build/lib/siat/other_indexes.py +409 -0
  47. build/lib/build/lib/siat/risk_adjusted_return.py +1576 -0
  48. build/lib/build/lib/siat/risk_adjusted_return2.py +1900 -0
  49. build/lib/build/lib/siat/risk_evaluation.py +2218 -0
  50. build/lib/build/lib/siat/risk_free_rate.py +351 -0
  51. build/lib/build/lib/siat/sector_china.py +4140 -0
  52. build/lib/build/lib/siat/security_price2.py +727 -0
  53. build/lib/build/lib/siat/security_prices.py +3408 -0
  54. build/lib/build/lib/siat/security_trend.py +402 -0
  55. build/lib/build/lib/siat/security_trend2.py +646 -0
  56. build/lib/build/lib/siat/stock.py +4284 -0
  57. build/lib/build/lib/siat/stock_advice_linear.py +934 -0
  58. build/lib/build/lib/siat/stock_base.py +26 -0
  59. build/lib/build/lib/siat/stock_china.py +2095 -0
  60. build/lib/build/lib/siat/stock_prices_kneighbors.py +910 -0
  61. build/lib/build/lib/siat/stock_prices_linear.py +386 -0
  62. build/lib/build/lib/siat/stock_profile.py +707 -0
  63. build/lib/build/lib/siat/stock_technical.py +3305 -0
  64. build/lib/build/lib/siat/stooq.py +74 -0
  65. build/lib/build/lib/siat/transaction.py +347 -0
  66. build/lib/build/lib/siat/translate.py +5183 -0
  67. build/lib/build/lib/siat/valuation.py +1378 -0
  68. build/lib/build/lib/siat/valuation_china.py +2076 -0
  69. build/lib/build/lib/siat/var_model_validation.py +444 -0
  70. build/lib/build/lib/siat/yf_name.py +811 -0
  71. build/lib/siat/__init__.py +75 -0
  72. build/lib/siat/allin.py +137 -0
  73. build/lib/siat/assets_liquidity.py +915 -0
  74. build/lib/siat/beta_adjustment.py +1058 -0
  75. build/lib/siat/beta_adjustment_china.py +548 -0
  76. build/lib/siat/blockchain.py +143 -0
  77. build/lib/siat/bond.py +2900 -0
  78. build/lib/siat/bond_base.py +992 -0
  79. build/lib/siat/bond_china.py +100 -0
  80. build/lib/siat/bond_zh_sina.py +143 -0
  81. build/lib/siat/capm_beta.py +783 -0
  82. build/lib/siat/capm_beta2.py +887 -0
  83. build/lib/siat/common.py +5360 -0
  84. build/lib/siat/compare_cross.py +642 -0
  85. build/lib/siat/copyrights.py +18 -0
  86. build/lib/siat/cryptocurrency.py +667 -0
  87. build/lib/siat/economy.py +1471 -0
  88. build/lib/siat/economy2.py +1853 -0
  89. build/lib/siat/esg.py +536 -0
  90. build/lib/siat/event_study.py +815 -0
  91. build/lib/siat/fama_french.py +1521 -0
  92. build/lib/siat/fin_stmt2_yahoo.py +982 -0
  93. build/lib/siat/financial_base.py +1160 -0
  94. build/lib/siat/financial_statements.py +598 -0
  95. build/lib/siat/financials.py +2339 -0
  96. build/lib/siat/financials2.py +1278 -0
  97. build/lib/siat/financials_china.py +4433 -0
  98. build/lib/siat/financials_china2.py +2212 -0
  99. build/lib/siat/fund.py +629 -0
  100. build/lib/siat/fund_china.py +3307 -0
  101. build/lib/siat/future_china.py +551 -0
  102. build/lib/siat/google_authenticator.py +47 -0
  103. build/lib/siat/grafix.py +3636 -0
  104. build/lib/siat/holding_risk.py +867 -0
  105. build/lib/siat/luchy_draw.py +638 -0
  106. build/lib/siat/market_china.py +1168 -0
  107. build/lib/siat/markowitz.py +2363 -0
  108. build/lib/siat/markowitz2.py +3150 -0
  109. build/lib/siat/markowitz2_20250704.py +2969 -0
  110. build/lib/siat/markowitz2_20250705.py +3158 -0
  111. build/lib/siat/markowitz_simple.py +373 -0
  112. build/lib/siat/ml_cases.py +2291 -0
  113. build/lib/siat/ml_cases_example.py +60 -0
  114. build/lib/siat/option_china.py +3069 -0
  115. build/lib/siat/option_pricing.py +1925 -0
  116. build/lib/siat/other_indexes.py +409 -0
  117. build/lib/siat/risk_adjusted_return.py +1576 -0
  118. build/lib/siat/risk_adjusted_return2.py +1900 -0
  119. build/lib/siat/risk_evaluation.py +2218 -0
  120. build/lib/siat/risk_free_rate.py +351 -0
  121. build/lib/siat/sector_china.py +4140 -0
  122. build/lib/siat/security_price2.py +727 -0
  123. build/lib/siat/security_prices.py +3408 -0
  124. build/lib/siat/security_trend.py +402 -0
  125. build/lib/siat/security_trend2.py +646 -0
  126. build/lib/siat/stock.py +4284 -0
  127. build/lib/siat/stock_advice_linear.py +934 -0
  128. build/lib/siat/stock_base.py +26 -0
  129. build/lib/siat/stock_china.py +2095 -0
  130. build/lib/siat/stock_prices_kneighbors.py +910 -0
  131. build/lib/siat/stock_prices_linear.py +386 -0
  132. build/lib/siat/stock_profile.py +707 -0
  133. build/lib/siat/stock_technical.py +3305 -0
  134. build/lib/siat/stooq.py +74 -0
  135. build/lib/siat/transaction.py +347 -0
  136. build/lib/siat/translate.py +5183 -0
  137. build/lib/siat/valuation.py +1378 -0
  138. build/lib/siat/valuation_china.py +2076 -0
  139. build/lib/siat/var_model_validation.py +444 -0
  140. build/lib/siat/yf_name.py +811 -0
  141. siat/__init__.py +0 -0
  142. siat/allin.py +0 -0
  143. siat/assets_liquidity.py +0 -0
  144. siat/beta_adjustment.py +0 -0
  145. siat/beta_adjustment_china.py +0 -0
  146. siat/blockchain.py +0 -0
  147. siat/bond.py +0 -0
  148. siat/bond_base.py +0 -0
  149. siat/bond_china.py +0 -0
  150. siat/bond_zh_sina.py +0 -0
  151. siat/capm_beta.py +0 -0
  152. siat/capm_beta2.py +0 -0
  153. siat/common.py +94 -30
  154. siat/compare_cross.py +0 -0
  155. siat/copyrights.py +0 -0
  156. siat/cryptocurrency.py +0 -0
  157. siat/economy.py +0 -0
  158. siat/economy2.py +0 -0
  159. siat/esg.py +0 -0
  160. siat/event_study.py +0 -0
  161. siat/fama_french.py +0 -0
  162. siat/fin_stmt2_yahoo.py +0 -0
  163. siat/financial_base.py +0 -0
  164. siat/financial_statements.py +0 -0
  165. siat/financials.py +0 -0
  166. siat/financials2.py +0 -0
  167. siat/financials_china.py +0 -0
  168. siat/financials_china2.py +0 -0
  169. siat/fund.py +0 -0
  170. siat/fund_china.py +0 -0
  171. siat/future_china.py +0 -0
  172. siat/google_authenticator.py +0 -0
  173. siat/grafix.py +1 -1
  174. siat/holding_risk.py +0 -0
  175. siat/luchy_draw.py +0 -0
  176. siat/market_china.py +7 -1
  177. siat/markowitz.py +0 -0
  178. siat/markowitz2.py +240 -39
  179. siat/markowitz2_20250704.py +2969 -0
  180. siat/markowitz2_20250705.py +3158 -0
  181. siat/markowitz_simple.py +0 -0
  182. siat/ml_cases.py +0 -0
  183. siat/ml_cases_example.py +0 -0
  184. siat/option_china.py +0 -0
  185. siat/option_pricing.py +0 -0
  186. siat/other_indexes.py +0 -0
  187. siat/risk_adjusted_return.py +0 -0
  188. siat/risk_adjusted_return2.py +0 -0
  189. siat/risk_evaluation.py +0 -0
  190. siat/risk_free_rate.py +0 -0
  191. siat/sector_china.py +0 -0
  192. siat/security_price2.py +0 -0
  193. siat/security_prices.py +3 -1
  194. siat/security_trend.py +0 -0
  195. siat/security_trend2.py +1 -1
  196. siat/stock.py +4 -2
  197. siat/stock_advice_linear.py +0 -0
  198. siat/stock_base.py +0 -0
  199. siat/stock_china.py +0 -0
  200. siat/stock_prices_kneighbors.py +0 -0
  201. siat/stock_prices_linear.py +0 -0
  202. siat/stock_profile.py +0 -0
  203. siat/stock_technical.py +0 -0
  204. siat/stooq.py +0 -0
  205. siat/transaction.py +0 -0
  206. siat/translate.py +11 -11
  207. siat/valuation.py +0 -0
  208. siat/valuation_china.py +0 -0
  209. siat/var_model_validation.py +0 -0
  210. siat/yf_name.py +0 -0
  211. {siat-3.10.130.dist-info → siat-3.10.132.dist-info}/METADATA +11 -11
  212. siat-3.10.132.dist-info/RECORD +218 -0
  213. siat-3.10.132.dist-info/top_level.txt +4 -0
  214. siat-3.10.130.dist-info/RECORD +0 -76
  215. siat-3.10.130.dist-info/top_level.txt +0 -1
  216. {siat-3.10.130.dist-info → siat-3.10.132.dist-info}/WHEEL +0 -0
  217. {siat-3.10.130.dist-info → siat-3.10.132.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,3305 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 本模块功能:股票技术分析 technical analysis
4
+ 所属工具包:证券投资分析工具SIAT
5
+ SIAT:Security Investment Analysis Tool
6
+ 创建日期:2023年1月27日
7
+ 最新修订日期:2025年1月31日
8
+ 作者:王德宏 (WANG Dehong, Peter)
9
+ 作者单位:北京外国语大学国际商学院
10
+ 作者邮件:wdehong2000@163.com
11
+ 版权所有:王德宏
12
+ 用途限制:仅限研究与教学使用,不可商用!商用需要额外授权。
13
+ 特别声明:作者不对使用本工具进行证券投资导致的任何损益负责!
14
+ """
15
+ #==============================================================================
16
+ #关闭所有警告
17
+ import warnings; warnings.filterwarnings('ignore')
18
+
19
+ from siat.common import *
20
+ from siat.translate import *
21
+ from siat.grafix import *
22
+ from siat.security_prices import *
23
+ from siat.security_price2 import *
24
+ from siat.stock import *
25
+ from siat.valuation import *
26
+ #==============================================================================
27
+ import matplotlib.pyplot as plt
28
+ #plt.rcParams['figure.figsize']=(12.8,7.2)
29
+ plt.rcParams['figure.figsize']=(12.8,6.4)
30
+ plt.rcParams['figure.dpi']=300
31
+ plt.rcParams['font.size'] = 13
32
+ plt.rcParams['xtick.labelsize']=11 #横轴字体大小
33
+ plt.rcParams['ytick.labelsize']=11 #纵轴字体大小
34
+
35
+ title_txt_size=16
36
+ ylabel_txt_size=14
37
+ xlabel_txt_size=14
38
+ legend_txt_size=14
39
+
40
+ import mplfinance as mpf
41
+
42
+ #处理绘图汉字乱码问题
43
+ import sys; czxt=sys.platform
44
+ if czxt in ['win32','win64']:
45
+ plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置默认字体
46
+ mpfrc={'font.family': 'SimHei'}
47
+
48
+ if czxt in ['darwin']: #MacOSX
49
+ plt.rcParams['font.family']= ['Heiti TC']
50
+ mpfrc={'font.family': 'Heiti TC'}
51
+
52
+ if czxt in ['linux']: #website Jupyter
53
+ plt.rcParams['font.family']= ['Heiti TC']
54
+ mpfrc={'font.family':'Heiti TC'}
55
+
56
+ # 解决保存图像时'-'显示为方块的问题
57
+ plt.rcParams['axes.unicode_minus'] = False
58
+
59
+ #设置绘图风格:关闭网格虚线
60
+ plt.rcParams['axes.grid']=False
61
+
62
+ #==============================================================================
63
+ #==============================================================================
64
+ #==============================================================================
65
+
66
+ if __name__ =="__main__":
67
+ RSI_days=[6,24]
68
+
69
+ OBV_days=5
70
+
71
+ MA_days=[5,20]; MACD_fastperiod=12; MACD_slowperiod=26; MACD_signalperiod=9
72
+
73
+ KDJ_fastk_period=5; KDJ_slowk_period=3; KDJ_slowk_matype=0; KDJ_slowd_period=3
74
+ KDJ_slowd_matype=0
75
+
76
+ VOL_fastperiod=5; VOL_slowperiod=10
77
+
78
+ PSY_days=12
79
+
80
+ ARBR_days=26
81
+
82
+ CR_day=16; CR_madays=[5,10,20]
83
+
84
+ EMV_day=14; EMV_madays=9
85
+
86
+ BULL_days=20; BULL_nbdevup=2; BULL_nbdevdn=2; BULL_matype=0
87
+
88
+ TRIX_day=12; TRIX_madays=20
89
+
90
+ DMA_fastperiod=10; DMA_slowperiod=50; DMA_madays=10
91
+
92
+ BIAS_days=[6,12,24]
93
+
94
+ CCI_days=[6,12]
95
+
96
+ WR_days=[10,6]
97
+
98
+ ROC_day=12; ROC_madays=6
99
+
100
+ DMI_DIdays=14; DMI_ADXdays=6
101
+
102
+ ticker='AAPL'
103
+
104
+ start='2024-3-1'; end='2024-4-12'; ahead_days=30*3
105
+ start1=date_adjust(start,adjust=-ahead_days)
106
+
107
+ df=get_price(ticker,start1,end)
108
+ ta=calc_technical(df,start,end)
109
+
110
+ df1,found=get_price_1ticker_mixed(ticker=ticker,fromdate=start1,todate=end)
111
+ ta=calc_technical(df1,start,end)
112
+
113
+ ta=calc_technical(df,start,end)
114
+
115
+ #OBV: 纵轴数量级大(千万),判断是否加零线(有正有负),如加零线则不加均值线,不加零线时判断是否可加均值线
116
+ df_ta[['obv','obv_ma']].plot(title=ticker_name(ticker)+': '+'能量潮OBV')
117
+
118
+ #SAR: 纵轴数量级不大
119
+ df_ta[['sar']].plot(title=ticker_name(ticker)+': '+'抛物转向SAR')
120
+
121
+ #VOL: 纵轴数量级大,无零线,可加均值线
122
+ df_ta[['vol5','vol10']].plot(title=ticker_name(ticker)+': '+'成交量VOL')
123
+
124
+ #ARBR: 纵轴数量级不大,无零线,无均值线
125
+ df_ta[['ar','br']].plot(title=ticker_name(ticker)+': '+'人气与意愿ARBR')
126
+
127
+ #CR: 纵轴数量级不大,无零线
128
+ df_ta[['cr','crma5','crma10','crma20']].plot(title=ticker_name(ticker)+': '+'能力与意愿CR')
129
+ df_ta[['cr','crma5','crma20']].plot(title=ticker_name(ticker)+': '+'能力与意愿CR')
130
+ df_ta[['cr']].plot(title=ticker_name(ticker)+': '+'能力与意愿CR')
131
+
132
+ #EMV: 纵轴数量级大
133
+ df_ta[['em','emv','emva']].plot(title=ticker_name(ticker)+': '+'简易波动EMV')
134
+ df_ta[['em','emv']].plot(title=ticker_name(ticker)+': '+'简易波动EMV')
135
+
136
+ #TRIX: 纵轴数量级不大
137
+ df_ta[['trix','trma']].plot(title=ticker_name(ticker)+': '+'三重指数平滑TRIX')
138
+
139
+ #DMA: 纵轴数量级不大
140
+ df_ta[['dma']].plot(title=ticker_name(ticker)+': '+'均线差DMA')
141
+
142
+ #BIAS: 纵轴数量级不大
143
+ df_ta[['bias','bias2','bias3']].plot(title=ticker_name(ticker)+': '+'乖离率BIAS')
144
+ df_ta[['bias']].plot(title=ticker_name(ticker)+': '+'乖离率BIAS')
145
+
146
+ #CCI: 纵轴数量级不大
147
+ df_ta[['cci']].plot(title=ticker_name(ticker)+': '+'顺势CCI')
148
+
149
+ #W%R: 纵轴数量级不大
150
+ df_ta[['wr6','wr10']].plot(title=ticker_name(ticker)+': '+'威廉W%R')
151
+
152
+ #ROC: 纵轴数量级不大
153
+ df_ta[['roc','rocma']].plot(title=ticker_name(ticker)+': '+'变动速率ROC')
154
+
155
+ #DMI: 纵轴数量级不大
156
+ df_ta[['pdi','mdi','adx','adxr']].plot(title=ticker_name(ticker)+': '+'趋向DMI')
157
+ df_ta[['pdi','mdi']].plot(title=ticker_name(ticker)+': '+'趋向DMI')
158
+ df_ta[['adx','adxr']].plot(title=ticker_name(ticker)+': '+'趋向DMI')
159
+
160
+ #PSY: 纵轴数量级不大
161
+ df_ta[['psy']].plot(title=ticker_name(ticker)+': '+'心理线PSY')
162
+
163
+
164
+
165
+ def calc_technical(df,start,end,technical='MACD', \
166
+
167
+ RSI_days=[14], \
168
+ OBV_days=[5], \
169
+
170
+ MA_days=[5,20],EMA_days=[5,20], \
171
+ MACD_fastperiod=12,MACD_slowperiod=26,MACD_signalperiod=9, \
172
+
173
+ KDJ_fastk_period=9,KDJ_slowk_period=5,KDJ_slowk_matype=1,KDJ_slowd_period=5,KDJ_slowd_matype=1, \
174
+
175
+ VOL_fastperiod=5,VOL_slowperiod=10, \
176
+ PSY_days=[12], \
177
+ ARBR_days=[26], \
178
+ CR_day=16,CR_madays=[5,10,20], \
179
+ EMV_day=14,EMV_madays=[9], \
180
+
181
+ BULL_day=20,BULL_nbdevup=2,BULL_nbdevdn=2,BULL_matype=0, \
182
+
183
+ DMA_fastperiod=10,DMA_slowperiod=50,DMA_madays=[10], \
184
+
185
+ TRIX_day=12,TRIX_madays=[20], \
186
+ BIAS_days=[6,12,24], \
187
+ CCI_days=[6,12], \
188
+ WR_days=[10,6], \
189
+ ROC_day=12,ROC_madays=[6], \
190
+ DMI_DIdays=[14],DMI_ADXdays=[6], \
191
+
192
+ MFI_day=14,MFI_madays=[6], \
193
+
194
+ #提出选择12日或25日,移动均线为10日
195
+ MOM_day=12,MOM_madays=6, \
196
+
197
+ #需要显示SAR
198
+ SAR_day=4,SAR_madays=[5,20], \
199
+
200
+ #需要显示BETA
201
+ BETA_day=5,BETA_madays=[5,20], \
202
+
203
+ #需要显示TSF
204
+ TSF_day=14,TSF_madays=[5,20], \
205
+
206
+ #需要显示AD
207
+ AD_madays=[5], \
208
+
209
+ #不建议使用复权价,因为最高最低价和开盘价没有复权价!
210
+ indicator='Close', \
211
+ more_details=False):
212
+ """
213
+ 功能:计算股票的技术分析指标
214
+ 输入:df,四种股价Open/Close/High/Low,成交量Volume
215
+ 输出:df
216
+ 支持的指标:
217
+ RSI、OBV、MACD、 KDJ、 SAR、 VOL、 PSY、 ARBR、 CR、 EMV、
218
+ BOLL、 TRIX、 DMA、 BIAS、 CCI、 W%R、 ROC、 DMI
219
+
220
+ 注意:indicator='Close'为不使用复权价,'Adj Close'为前复权价(需要指定source='yahoo')
221
+ """
222
+ if indicator not in ['Close','Adj Close']:
223
+ print(" #Error(calc_technical): unsupported indicator",indicator)
224
+ print(" Supported indicator: Close, Adj Close")
225
+
226
+ return None
227
+
228
+ # 导入需要的包
229
+ try:
230
+ import talib
231
+ except:
232
+ print(" #Error(calc_technical): lack of necessary package - talib")
233
+ talib_install_method()
234
+ return None
235
+
236
+ #用于对比,检查是否有计算结果的指标加入
237
+ dfcols_original=list(df)
238
+
239
+ #=========== RSI,相对强弱指标Relative Strength Index
240
+ """
241
+ 计算公式:RSI有两种计算方法:
242
+ 第一种方法:
243
+ 假设A为N日内收盘价涨幅的正数之和,B为N日内收盘价涨幅的负数之和再乘以(-1),
244
+ 这样,A和B均为正,将A,B代入RSI计算公式,则:
245
+ RSI(N) = A ÷ (A + B) × 100
246
+ 第二种方法:
247
+ RS(相对强度) = N日内收盘价涨数和之均值 ÷ N日内收盘价跌数和之均值
248
+ RSI = 100 - 100 ÷ (1+RS)
249
+
250
+ 指标解读:
251
+ 80-100 极强 卖出
252
+ 50-80 强 观望,谨慎卖出
253
+ 30-50 弱 观望,谨慎买入
254
+ 0-30 极弱 买入
255
+ """
256
+ if technical=='RSI':
257
+
258
+ if not isinstance(RSI_days,list):
259
+ RSI_days=[RSI_days]
260
+ for d in RSI_days:
261
+ df['rsi'+str(d)] = talib.RSI(df[indicator], timeperiod=d)
262
+ #注意:rsi1没有意义
263
+
264
+ #=========== OBV:能量潮
265
+ """
266
+ OBV的英文全称是:On Balance Volume,是由美国的投资分析家Joe Granville所创。
267
+ 该指标通过统计成交量变动的趋势来推测股价趋势。
268
+ OBV指标所谓股市人气,指投资者活跃在股市上的程度。
269
+ 如果买卖双方交易热情高,股价、成交量就上升,股市气氛则热烈。
270
+ 因此,利用股价和股票成交量的指标来反映人气的兴衰,就形成了OBV指标。
271
+
272
+ OBV = 前一天的OBV ± 当日成交量
273
+ 说明:(当日收盘价高于前日收盘价,成交量定位为正值,取加号;
274
+ 当日收盘价低于前日收盘价,成交量定义为负值,取减号;二者相等计为0)
275
+
276
+ 指标解读:
277
+ 1、当股价上升而OBV线下降,表示买盘无力,股价可能会回跌。
278
+ 2、股价下降时而OBV线上升,表示买盘旺盛,逢低接手强股,股价可能会止跌回升。
279
+ 3、OBV线缓慢上升,表示买气逐渐加强,为买进信号。
280
+ 4、OBV线急速上升时,表示力量将用尽为卖出信号。
281
+ 5、OBV线从正的累积数转为负数时,为下跌趋势,应该卖出持有股票。反之,OBV线从负的累积数转为正数时,应该买进股票。
282
+ 6、OBV线最大的用处,在于观察股市盘局整理后,何时会脱离盘局以及突破后的未来走势,OBV线变动方向是重要参考指数,其具体的数值并无实际意义。
283
+
284
+ 缺点
285
+ OBV指标是建立在国外成熟市场上的经验总结。
286
+ 用在内地股市坐庄的股票上就不灵了,这时股价涨得越高成交量反而越少。
287
+ 这是因为主力控盘较重,股价在上涨过程中没有获利筹码加以兑现,所以此时股票会涨得很“疯”,但成交量并不增加,OBV自然就无法发挥作用。
288
+ 另外,涨跌停板的股票也会导致指标失真。
289
+ 由于内地股市采用了涨跌停板的限制,很多股票在连续涨停的时候,由于股民预期后市会继续大涨,往往会持股观望,导致出现越涨越无量的现象。
290
+ 因此,对于那些达到涨跌停板的股票,OBV指标也无法正常发挥作用。
291
+
292
+ """
293
+ if technical=='OBV':
294
+
295
+ df['obv'] = talib.OBV(df[indicator],df['Volume'])
296
+
297
+ if not isinstance(OBV_days,list):
298
+ OBV_days=[OBV_days]
299
+ for d in OBV_days:
300
+ df['obv_ma'+str(d)] = talib.MA(df['obv'],timeperiod=d)
301
+
302
+ if not more_details:
303
+ df.drop(columns = ['obv'],inplace=True)
304
+
305
+ #=========== MA: 简单、加权移动平均
306
+ """
307
+ MA,又称移动平均线,是借助统计处理方式将若干天的股票价格加以平均,然后连接成一条线,用以观察股价趋势。
308
+ 移动平均线通常有3日、6日、10日、12日、24日、30日、72日、200日、288日、13周、26周、52周等等,不一而足,
309
+ 其目的在取得某一段期间的平均成本,而以此平均成本的移动曲线配合每日收盘价的线路变化分析某一期间多空的优劣形势,
310
+ 以研判股价的可能变化。
311
+ 一般来说,现行价格在平均价之上,意味着市场买力(需求)较大,行情看好;
312
+ 反之,行情价在平均价之下,则意味着供过于求,卖压显然较重,行情看淡。
313
+ """
314
+ if technical=='MA':
315
+
316
+ if not isinstance(MA_days,list):
317
+ MA_days=[MA_days]
318
+
319
+ for d in MA_days:
320
+ df['ma'+str(d)] = talib.MA(df[indicator],timeperiod=d)
321
+
322
+ if technical=='EMA':
323
+
324
+ if not isinstance(EMA_days,list):
325
+ EMA_days=[EMA_days]
326
+
327
+ for d in EMA_days:
328
+ df['ema'+str(d)] = talib.EMA(df[indicator],timeperiod=d)
329
+
330
+ #=========== MACD:指数平滑异同平均线
331
+ """
332
+ 计算方法:快速时间窗口设为12日,慢速时间窗口设为26日,DIF参数设为9日
333
+ 3.1) 计算指数平滑移动平均值(EMA)
334
+ 12日EMA的计算公式为:
335
+ EMA(12) = 昨日EMA(12) × 11 ÷ 13 + 今日收盘价 × 2 ÷ 13
336
+ 26日EMA的计算公式为:
337
+ EMA(26) = 昨日EMA(26) × 25 ÷ 27 + 今日收盘价 × 2 ÷ 27
338
+
339
+ 3.2) 计算离差值(DIF)
340
+ DIF = 今日EMA(12) – 今日EMA(26)
341
+
342
+ 3.3) 计算DIF的9日DEA
343
+ 根据差值计算其9日的DEA,即差值平均
344
+ 今日DEA = 昨日DEA × 8 ÷ 10 + 今日DIF × 2 ÷ 10
345
+
346
+ 形态解读:
347
+ 1.DIF、DEA均为正,DIF向上突破DEA,买入信号。
348
+ 2.DIF、DEA均为负,DIF向下跌破DEA,卖出信号。
349
+ 3.DEA线与K线发生背离,行情反转信号。
350
+ 4.分析MACD柱状线,由红变绿(正变负),卖出信号;由绿变红,买入信号。
351
+
352
+ MACD一则去掉移动平均线频繁的假讯号缺陷,二则能确保移动平均线最大的战果。
353
+ 1. MACD金叉:DIF由下向上突破DEM,为买入信号。
354
+ 2. MACD死叉:DIF由上向下突破DEM,为卖出信号。
355
+ 3. MACD绿转红:MACD值由负变正,市场由空头转为多头。
356
+ 4. MACD红转绿:MACD值由正变负,市场由多头转为空头。
357
+ """
358
+ if technical=='MACD':
359
+
360
+ df['DIF'],df['DEA'],df['MACD']=talib.MACD(df[indicator], \
361
+ fastperiod=MACD_fastperiod, \
362
+ slowperiod=MACD_slowperiod, \
363
+ signalperiod=MACD_signalperiod)
364
+
365
+ #=========== KDJ: 随机指标
366
+ """
367
+ 计算公式:
368
+ 1) 以日KDJ数值的计算为例
369
+ N日RSV = (CN – LN)÷(HN-LN) ×100
370
+ 说明:CN为第N日收盘价;LN为N日内的最低价;HN为N日内的最高价,RSV值始终在1~100间波动
371
+ 2) 计算K值与D值
372
+ 当日K值 = 2/3 × 前一日K值 + 1/3 × 当日RSV
373
+ 当日D值 = 2/3 × 前一日D值 + 1/3 × 当日K值
374
+ 如果没有前一日K值与D值,则可分别用50来代替
375
+ 3) 计算J值
376
+ J = 3D – 2K
377
+
378
+ 指标解读:
379
+ 多头区域,空头区域
380
+ 金叉,死叉
381
+ """
382
+ if technical=='KDJ':
383
+
384
+ df['kdj_k'],df['kdj_d'] = talib.STOCH(df['High'],df['Low'],df[indicator], \
385
+ fastk_period=KDJ_fastk_period,
386
+ slowk_period=KDJ_slowk_period,
387
+ slowk_matype=KDJ_slowk_matype,
388
+ slowd_period=KDJ_slowd_period,
389
+ slowd_matype=KDJ_slowd_matype)
390
+ df['kdj_j'] = 3*df['kdj_k'] - 2*df['kdj_d']
391
+
392
+ #=========== SAR: 抛物转向
393
+ """
394
+ 计算过程:
395
+ 1)先选定一段时间判断为上涨或下跌
396
+ 2)如果是看涨,则第一天的SAR值必须是近期内的最低价;
397
+ 如果是看跌,则第一天的SAR值必须是近期的最高价。
398
+ 3)第二天的SAR值,则为第一天的最高价(看涨时)或是最低价(看跌时)与第一天的SAR值的差距乘上加速因子,
399
+ 再加上第一天的SAR值就可以求得。
400
+ 4)每日的SAR值都可用上述方法类推,公式归纳如下:
401
+ SAR(N) = SAR(N-1) + AF × [(EP(N-1) – SAR(N-1))]
402
+ SAR(N) = 第N日的SAR值
403
+ SAR(N-1) = 第(N-1)日的SAR值
404
+ 说明:AF表示加速因子;EP表示极点价,如果是看涨一段期间,则EP为这段时间的最高价,
405
+ 如果是看跌一段期间,则EP为这段时间的最低价;EP(N-1)等于第(N-1)日的极点价
406
+ 5)加速因子第一次取0.02,假若第一天的最高价比前一天的最高价还高,则加速因子增加0.02,
407
+ 如无新高则加速因子沿用前一天的数值,但加速因子最高不能超过0.2。反之,下跌也类推
408
+ 6)如果是看涨期间,计算出某日的SAR值比当日或前一日的最低价高,则应以当日或前一日的最低价为某日之SAR值;
409
+ 如果是看跌期间,计算某日的SAR值比当日或前一日的最高价低,则应以当日或前一日的最高价为某日的SAR值。
410
+ 7)SAR指标基准周期的参数为2,如2日、2周、2月等,其计算周期的参数变动范围为2~8
411
+ 8)SAR指标在股价分析系统的主图上显示为“O”形点状图。
412
+ """
413
+ if technical=='SAR':
414
+
415
+ df['sar'] = talib.SAR(df['High'],df['Low'])
416
+
417
+ if not isinstance(SAR_madays,list):
418
+ SAR_madays=[SAR_madays]
419
+ for d in SAR_madays:
420
+ df['sar_ma'+str(d)] = df['sar'].rolling(window=d).mean()
421
+
422
+ #需要显示SAR?
423
+ """
424
+ if not more_details:
425
+ #不保留指标本身
426
+ df.drop(columns = ['sar'],inplace=True)
427
+ """
428
+ #=========== VOL: 成交量
429
+ """
430
+ 柱状图是成交量,两条曲线是成交量的移动平均
431
+ """
432
+ if technical=='VOL':
433
+
434
+ df['vol'+str(VOL_fastperiod)] = talib.MA(df['Volume'],timeperiod=VOL_fastperiod)
435
+ df['vol'+str(VOL_slowperiod)] = talib.MA(df['Volume'],timeperiod=VOL_slowperiod)
436
+
437
+ #=========== PSY: 心理线
438
+ """
439
+ 计算公式:
440
+ PSY(N) = A/N × 100
441
+ 说明:N为天数,A为在这N天之中股价上涨的天数
442
+ """
443
+ if technical=='PSY':
444
+
445
+ df['ext_0'] = df[indicator]-df[indicator].shift(1)
446
+ df['ext_1'] = 0
447
+ df.loc[df['ext_0']>0,'ext_1'] = 1
448
+
449
+ if not isinstance(PSY_days,list):
450
+ PSY_days=[PSY_days]
451
+ for d in PSY_days:
452
+ df['ext_2'] = df['ext_1'].rolling(window=d).sum()
453
+ df['psy'+str(d)] = (df['ext_2']/float(d))*100
454
+
455
+ df.drop(columns = ['ext_0','ext_1','ext_2'],inplace=True)
456
+
457
+ #=========== ARBR: 人气和意愿指标, AR为人气指标,BR为买卖意愿指标
458
+ """
459
+ 计算公式:
460
+ AR(N) = N日内(H-O)之和 ÷ N日内(O-L)之和 × 100
461
+ 说明:H表示当天最高价;L表示当天最低价;O表示当天开盘价;N表示设定的时间参数,一般原始参数日缺省值为26日
462
+ BR(N) = N日内(H-CY)之和 ÷ N日内(CY-L)之和 × 100
463
+ 说明:H表示当天最高价;L表示当天最低价;CY表示前一交易日的收盘价,N表示设定的时间参数,一般原始参数缺省值为26日
464
+ """
465
+ if technical=='ARBR':
466
+
467
+ df['h_o'] = df['High'] - df['Open']
468
+ df['o_l'] = df['Open'] - df['Low']
469
+
470
+ if not isinstance(ARBR_days,list):
471
+ ARBR_days=[ARBR_days]
472
+ for d in ARBR_days:
473
+ df['h_o_sum'] = df['h_o'].rolling(window=d).sum()
474
+ df['o_l_sum'] = df['o_l'].rolling(window=d).sum()
475
+ df['ar'+str(d)] = (df['h_o_sum']/df['o_l_sum'])*100
476
+
477
+
478
+ df['h_c'] = df['High'] - df[indicator]
479
+ df['c_l'] = df[indicator] - df['Low']
480
+ for d in ARBR_days:
481
+ df['h_c_sum'] = df['h_c'].rolling(window=d).sum()
482
+ df['c_l_sum'] = df['c_l'].rolling(window=d).sum()
483
+ df['br'+str(d)] = (df['h_c_sum']/df['c_l_sum'])*100
484
+
485
+ df.drop(columns = ['h_o','o_l','h_o_sum','o_l_sum','h_c','c_l','h_c_sum','c_l_sum'],inplace=True)
486
+
487
+ #=========== CR: 带状能力线或中间意愿指标
488
+ """
489
+ 计算过程:
490
+ 1)计算中间价,取以下四种中一种,任选:
491
+ 中间价 = (最高价 + 最低价)÷2
492
+ 中间价 = (最高价 + 最低价 + 收盘价)÷3
493
+ 中间价 = (最高价 + 最低价 + 开盘价 + 收盘价)÷4
494
+ 中间价 = (2倍的开盘价 + 最高价 + 最低价)÷4
495
+ 2)计算CR:
496
+ CR = N日内(当日最高价 – 上个交易日的中间价)之和 ÷ N日内(上个交易日的中间价 – 当日最低价)之和
497
+ 说明:N为设定的时间周期参数,一般原始参数日设定为26日
498
+ 3)计算CR值在不同时间周期内的移动平均值:这三条移动平均曲线分别为MA1 MA2 MA3,时间周期分别为5日 10日 20日
499
+ """
500
+ if technical=='CR':
501
+
502
+ df['m_price'] = (df['High'] + df['Low'])/2
503
+ df['h_m'] = df['High']-df['m_price'].shift(1)
504
+ df['m_l'] = df['m_price'].shift(1)-df['Low']
505
+
506
+ df['h_m_sum'] = df['h_m'].rolling(window=CR_day).sum()
507
+ df['m_l_sum'] = df['m_l'].rolling(window=CR_day).sum()
508
+ df['cr'] = (df['h_m_sum']/df['m_l_sum'])*100
509
+
510
+ for d in CR_madays:
511
+ df['cr_ma'+str(d)] = talib.MA(df['cr'],timeperiod=d)
512
+
513
+ df.drop(columns = ['m_price','h_m','m_l','h_m_sum','m_l_sum'],inplace=True)
514
+ if not more_details:
515
+ df.drop(columns = ['cr'],inplace=True)
516
+
517
+ #=========== EMV: 简易波动指标
518
+ """
519
+ 计算方法:
520
+ 1)先计算出三个因子A B C的数值。
521
+ A = (当日最高价 + 当日最低价)÷2
522
+ B = (上个交易日最高价 + 上个交易日最低价) ÷2
523
+ C = 当日最高价 – 当日最低价
524
+ 2)求出EM数值
525
+ EM = (A-B) ×C÷当日成交额
526
+ 3)求出EMV数值
527
+ EMV = EM数值的N个交易日之和,N为时间周期,一般设为14日
528
+ 4)求出EMV的移动平均值EMVA
529
+ EMVA = EMV的M日移动平均值,M一般设置9日
530
+ """
531
+ if technical=='EMV':
532
+
533
+ df['a'] = (df['High']+df['Low'])/2
534
+ df['b'] = (df['High'].shift(1)+df['Low'].shift(1))/2
535
+ df['c'] = df['High'] - df['Low']
536
+ df['Amount']=df[indicator]*df['Volume']
537
+ df['em'] = (df['a']-df['b'])*df['c']/df['Amount']
538
+
539
+ df['emv'] = df['em'].rolling(window=EMV_day).sum()
540
+
541
+ if not isinstance(EMV_madays,list):
542
+ EMV_madays=[EMV_madays]
543
+ for d in EMV_madays:
544
+ df['emv_ma'+str(d)] = talib.MA(df['emv'],timeperiod=d)
545
+
546
+ df.drop(columns = ['a','b','c','em'],inplace=True)
547
+
548
+ #=========== BOLL: 布林线指标
549
+ """
550
+ 计算公式:
551
+ 中轨线 = N日的移动平均线
552
+ 上轨线 = 中轨线 + 两倍的标准差
553
+ 下轨线 = 中轨线 – 两倍的标准差
554
+ 计算过程:
555
+ 1)先计算出移动平均值MA
556
+ MA = N日内的收盘价之和÷N
557
+ 2)计算出标准差MD的平方
558
+ MD的平方 = 每个交易日的(收盘价-MA)的N日累加之和的两次方 ÷ N
559
+ 3)求出MD
560
+ MD = (MD的平方)的平方根
561
+ 4)计算MID、UPPER、LOWER的数值
562
+ MID = (N-1)日的MA
563
+ UPPER = MID + 2×MD
564
+ LOWER = MID – 2×MD
565
+ 说明:N一般原始参数日缺省值为20日
566
+
567
+ 指标解读:
568
+ BOLL指标即布林线指标,其利用统计原理,求出股价的标准差及其信赖区间,
569
+ 从而确定股价的波动范围及未来走势,利用波带显示股价的安全高低价位,因而也被称为布林带。
570
+ 其上下限范围不固定,随股价的滚动而变化。布林指标股价波动在上限和下限的区间之内,
571
+ 这条带状区的宽窄,随着股价波动幅度的大小而变化,股价涨跌幅度加大时,带状区变宽,
572
+ 涨跌幅度狭小盘整时,带状区则变窄。
573
+ """
574
+ if technical=='Bollinger':
575
+
576
+ df['upper'],df['mid'],df['lower'] = talib.BBANDS(df[indicator], \
577
+ timeperiod=BULL_day, \
578
+ nbdevup=BULL_nbdevup,nbdevdn=BULL_nbdevdn,matype=BULL_matype)
579
+
580
+ #=========== TRIX:三重指数平滑移动平均指标
581
+ """
582
+
583
+ """
584
+ if technical=='TRIX':
585
+
586
+ df['trix'] = talib.TRIX(df[indicator],timeperiod=TRIX_day)
587
+
588
+ if not isinstance(TRIX_madays,list):
589
+ TRIX_madays=[TRIX_madays]
590
+ for d in TRIX_madays:
591
+ df['trix_ma'+str(d)] = talib.MA(df['trix'],timeperiod=d)
592
+ """
593
+ if not more_details:
594
+ #不保留TRIX
595
+ df.drop(columns = ['trix'],inplace=True)
596
+ """
597
+ #=========== DMA: 平均线差
598
+ """
599
+ 计算公式:
600
+ DDD(N) = N日短期平均值 – M日长期平均值
601
+ AMA(N) = DDD的N日短期平均值
602
+ 计算过程:
603
+ 以求10日、50日为基准周期的DMA指标为例
604
+ 1)求出周期不等的两条移动平均线MA之间的差值
605
+ DDD(10) = MA10 – MA50
606
+ 2)求DDD的10日移动平均数值
607
+ DMA(10) = DDD(10)÷10
608
+ """
609
+ if technical=='DMA':
610
+
611
+ df['ma_shortperiod'] = talib.MA(df[indicator],timeperiod=DMA_fastperiod)
612
+ df['ma_longperiod'] = talib.MA(df[indicator],timeperiod=DMA_slowperiod)
613
+ df['ddd'] = df['ma_shortperiod'] - df['ma_longperiod']
614
+
615
+ if not isinstance(DMA_madays,list):
616
+ DMA_madays=[DMA_madays]
617
+ for d in DMA_madays:
618
+ df['dma_ma'+str(d)] = talib.MA(df['ddd'],timeperiod=d)
619
+ #注意:dma1似乎没有意义
620
+
621
+ df.drop(columns = ['ma_shortperiod','ma_longperiod','ddd'],inplace=True)
622
+
623
+ #=========== BIAS: 乖离率
624
+ """
625
+ N日BIAS = (当日收盘价 – N日移动平均价)÷N日移动平均价×100
626
+
627
+ 指标解读:
628
+ 6日BIAS>+5%,是卖出时机;<-5%,为买入时机。
629
+ 12日BIAS>+6%是卖出时机;<-5.5%,为买入时机。
630
+ 24日BIAS>+9%是卖出时机;<-8%,为买入时机。
631
+ """
632
+ if technical=='BIAS':
633
+
634
+ if not isinstance(BIAS_days,list):
635
+ BIAS_days=[BIAS_days]
636
+ for d in BIAS_days:
637
+ df['ma'] = talib.MA(df[indicator],timeperiod=d)
638
+ df['bias'+str(d)] = ((df[indicator]-df['ma'])/df['ma'])*100
639
+
640
+ df.drop(columns = ['ma'],inplace=True)
641
+
642
+ #=========== CCI: 顺势指标
643
+ """
644
+ 计算过程:
645
+ CCI(N日) = (TP-MA)÷MD÷0.015
646
+ 说明:TP = (最高价+最低价+收盘价)÷3;
647
+ MA=最近N日收盘价的累计之和÷N;
648
+ MD=最近N日(MA-收盘价)的累计之和÷N;
649
+ 0.015为计算系数;N为计算周期,默认为14天
650
+ """
651
+ if technical=='CCI':
652
+ """
653
+ df['cci'] = talib.CCI(df['High'],df['Low'],df[indicator],timeperiod=CCI_days)
654
+
655
+ if not isinstance(CCI_madays,list):
656
+ CCI_madays=[CCI_madays]
657
+ for d in CCI_madays:
658
+ df['cci_ma'+str(d)] = df['cci'].rolling(window=d).mean()
659
+
660
+ #需要显示CCI?
661
+ if not more_details:
662
+ #不保留指标本身
663
+ df.drop(columns = ['cci'],inplace=True)
664
+ """
665
+ if not isinstance(CCI_days,list):
666
+ CCI_days=[CCI_days]
667
+
668
+ for d in CCI_days:
669
+ df['cci'+str(d)] = talib.CCI(df['High'],df['Low'],df[indicator],timeperiod=d)
670
+
671
+
672
+ #=========== W%R: 威廉指标
673
+ """
674
+ N日W%R = [(Hn-Ct)/(Hn-Ln)]*100
675
+ Ct是计算日的收盘价;Hn/Ln为包括计算日当天的N周期内的最高(低)价
676
+
677
+ 指标解读:
678
+ 威廉指标(William's %R) 原理:用当日收盘价在最近一段时间股价分布的相对位置来描述超买和超卖程度。
679
+ 算法:N日内最高价与当日收盘价的差,除以N日内最高价与最低价的差,结果放大100倍。
680
+ 参数:N 统计天数 一般取14天
681
+ 用法:
682
+ 1.低于30,超买,即将见顶,应及时卖出
683
+ 2.高于80,超卖,即将见底,应伺机买进
684
+ 3.与RSI、MTM指标配合使用,效果更好
685
+ """
686
+ if technical=='W%R':
687
+
688
+ if not isinstance(WR_days,list):
689
+ WR_days=[WR_days]
690
+ for d in WR_days:
691
+ df['h_10'] = df['High'].rolling(window=d).max()
692
+ df['l_10'] = df['Low'].rolling(window=d).min()
693
+ df['wr'+str(d)] = ((df['h_10']-df[indicator])/(df['h_10']-df['l_10']))*100
694
+
695
+ df.drop(columns = ['h_10','l_10'],inplace=True)
696
+
697
+ #=========== ROC: 变动速率指标
698
+ """
699
+ 计算过程:
700
+ 1)计算出ROC数值
701
+ ROC = (当日收盘价 – N日前收盘价)÷N日前收盘价×100
702
+ 说明:N一般取值为12日
703
+ 2)计算ROC移动平均线(ROCMA)数值
704
+ ROCMA = ROC的M日数值之和÷M
705
+ 说明:M一般取值为6日
706
+ """
707
+ if technical=='ROC':
708
+
709
+ df['roc'] = talib.ROC(df[indicator],timeperiod=ROC_day)
710
+
711
+ if not isinstance(ROC_madays,list):
712
+ ROC_madays=[ROC_madays]
713
+ for d in ROC_madays:
714
+ df['roc_ma'+str(d)] = talib.MA(df['roc'],timeperiod=d)
715
+
716
+ if not more_details:
717
+ #不保留roc
718
+ df.drop(columns = ['roc'],inplace=True)
719
+
720
+ #=========== DMI: 趋向指标
721
+ """
722
+ pdi: 正向DI
723
+ mdi: 负向DI
724
+ """
725
+ if technical=='DMI':
726
+
727
+ if not isinstance(DMI_DIdays,list):
728
+ DMI_DIdays=[DMI_DIdays]
729
+ for d in DMI_DIdays:
730
+ df['pdi'+str(d)] = talib.PLUS_DI(df['High'],df['Low'],df[indicator],timeperiod=d)
731
+ df['mdi'+str(d)] = talib.MINUS_DI(df['High'],df['Low'],df[indicator],timeperiod=d)
732
+
733
+ if not isinstance(DMI_ADXdays,list):
734
+ DMI_ADXdays=[DMI_ADXdays]
735
+ for d in DMI_ADXdays:
736
+ df['adx'+str(d)] = talib.ADX(df['High'],df['Low'],df[indicator],timeperiod=d)
737
+ df['adxr'+str(d)] = talib.ADXR(df['High'],df['Low'],df[indicator],timeperiod=d)
738
+
739
+ #=========== MFI:资金流动指标
740
+ """
741
+
742
+ """
743
+ if technical=='MFI':
744
+
745
+ df['mfi'] = talib.MFI(df['High'],df['Low'],df[indicator],df['Volume'],timeperiod=MFI_day)
746
+ if not isinstance(MFI_madays,list):
747
+ MFI_madays=[MFI_madays]
748
+ for d in MFI_madays:
749
+ df['mfi_ma'+str(d)] = df['mfi'].rolling(window=d).mean()
750
+ """
751
+ if not more_details:
752
+ #不保留指标本身
753
+ df.drop(columns = ['mfi'],inplace=True)
754
+ """
755
+ #=========== MOM:动量指标
756
+ """
757
+
758
+ """
759
+ if technical=='MOM':
760
+
761
+ df['mom'] = talib.MOM(df[indicator],timeperiod=MOM_day)
762
+ if not isinstance(MOM_madays,list):
763
+ MOM_madays=[MOM_madays]
764
+ for d in MOM_madays:
765
+ df['mom_ma'+str(d)] = df['mom'].rolling(window=d).mean()
766
+ """
767
+ if not more_details:
768
+ #不保留指标本身
769
+ df.drop(columns = ['mom'],inplace=True)
770
+ """
771
+ #=========== BETA:贝塔系数
772
+ """
773
+ 动态贝塔系数?
774
+ """
775
+ if technical=='BETA':
776
+
777
+ df['beta'] = talib.BETA(df['High'],df['Low'],timeperiod=BETA_day)
778
+
779
+ if not isinstance(BETA_madays,list):
780
+ BETA_madays=[BETA_madays]
781
+ for d in BETA_madays:
782
+ df['beta_ma'+str(d)] = df['beta'].rolling(window=d).mean()
783
+
784
+ if not more_details:
785
+ #不保留指标本身
786
+ df.drop(columns = ['beta'],inplace=True)
787
+
788
+ #=========== TSF:时间序列预测
789
+ """
790
+
791
+ """
792
+ if technical=='TSF':
793
+
794
+ df['tsf'] = talib.TSF(df[indicator],timeperiod=TSF_day)
795
+
796
+ if not isinstance(TSF_madays,list):
797
+ TSF_madays=[TSF_madays]
798
+ for d in TSF_madays:
799
+ df['tsf_ma'+str(d)] = df['tsf'].rolling(window=d).mean()
800
+
801
+ if not more_details:
802
+ #不保留指标本身
803
+ df.drop(columns = ['tsf'],inplace=True)
804
+
805
+ #=========== AD:量价指标
806
+ """
807
+
808
+ """
809
+ if technical=='AD':
810
+
811
+ df['ad'] = talib.AD(df['High'],df['Low'],df[indicator],df['Volume'])
812
+
813
+ if not isinstance(AD_madays,list):
814
+ AD_madays=[AD_madays]
815
+ for d in AD_madays:
816
+ df['ad_ma'+str(d)] = df['ad'].rolling(window=d).mean()
817
+ """
818
+ if not more_details:
819
+ #不保留指标本身
820
+ df.drop(columns = ['ad'],inplace=True)
821
+ """
822
+
823
+
824
+ #过滤日期===================================================================
825
+ _,startpd,endpd=check_period(start,end)
826
+ df1=df[(df.index >= startpd) & (df.index <= endpd)]
827
+
828
+ #检查是否加入了计算结果的指标
829
+ dfcols=list(df)
830
+ if len(dfcols) > len(dfcols_original):
831
+ calculated=True
832
+ else:
833
+ calculated=False
834
+
835
+ return df1,calculated
836
+
837
+
838
+ #==============================================================================
839
+ #==============================================================================
840
+ #==============================================================================
841
+
842
+ if __name__ =="__main__":
843
+ ticker='600519.SS'
844
+ start='2022-6-1'
845
+ end='2022-6-30'
846
+
847
+ MA_days=[5,20]
848
+ EMA_days=[5,20]
849
+
850
+ MACD_fastperiod=12
851
+ MACD_slowperiod=26
852
+ MACD_signalperiod=9
853
+
854
+ loc1='upper left'
855
+ loc2='center right'
856
+
857
+ resample_freq='H'
858
+ smooth=True
859
+ linewidth=1.5
860
+ graph=['MA']
861
+ graph=['EMA']
862
+ graph=['MACD']
863
+ printout=True
864
+
865
+ def security_MACD(ticker,start='default',end='default', \
866
+ MA_days=[5,20],EMA_days=[5,20], \
867
+ MACD_fastperiod=12,MACD_slowperiod=26,MACD_signalperiod=9, \
868
+ resample_freq='6H',smooth=True,linewidth=1.5, \
869
+ loc1='lower left',loc2='lower right', \
870
+ graph=['ALL'],printout=True,ticker_type='auto',source='auto', \
871
+ price_line_color='red',facecolor='k'):
872
+ """
873
+ 套壳函数:可用于股票、交易所债券、交易所基金、部分期货期权(限美股)
874
+ """
875
+ df=stock_MACD(ticker=ticker,start=start,end=end, \
876
+ MA_days=MA_days,EMA_days=EMA_days, \
877
+ MACD_fastperiod=MACD_fastperiod,MACD_slowperiod=MACD_slowperiod, \
878
+ MACD_signalperiod=MACD_signalperiod, \
879
+ resample_freq=resample_freq,smooth=smooth,linewidth=linewidth, \
880
+ loc1=loc1,loc2=loc2, \
881
+ graph=graph,printout=printout,ticker_type=ticker_type,source=source, \
882
+ price_line_color=price_line_color,facecolor=facecolor)
883
+ return df
884
+
885
+
886
+ def stock_MACD(ticker,start='default',end='default', \
887
+ MA_days=[5,20],EMA_days=[5,20], \
888
+ MACD_fastperiod=12,MACD_slowperiod=26,MACD_signalperiod=9, \
889
+ resample_freq='H',smooth=True,linewidth=1.5, \
890
+ loc1='lower left',loc2='lower right', \
891
+ graph=['ALL'],printout=True,ticker_type='auto',source='auto', \
892
+ price_line_color='red',facecolor='k'):
893
+ """
894
+ 功能:计算股票的技术分析指标MACD
895
+ 输入:df,四种股价Open/Close/High/Low,成交量Volume
896
+ 输出:df
897
+ 含有指标:
898
+ MA5、MA20、MACD_fastperiod、 MACD_slowperiod、MACD_signalperiod
899
+ """
900
+
901
+ #=========== 导入需要的包
902
+ try:
903
+ import talib
904
+ except:
905
+ print(" #Error(stock_MACD): lack of necessary module - ta-lib")
906
+ talib_install_method()
907
+ return None
908
+
909
+ #=========== 日期转换与检查
910
+ # 检查日期:截至日期
911
+ import datetime as dt; today=dt.date.today()
912
+ if end in ['default','today']:
913
+ end=today
914
+ else:
915
+ validdate,end=check_date2(end)
916
+ if not validdate:
917
+ print(" #Warning(stock_MACD): invalid date for",end)
918
+ end=today
919
+
920
+ # 检查日期:开始日期
921
+ if start in ['default']:
922
+ start=date_adjust(end,adjust=-31)
923
+ else:
924
+ validdate,start=check_date2(start)
925
+ if not validdate:
926
+ print(" #Warning(stock_MACD): invalid date for",start)
927
+ start=date_adjust(todate,adjust=-31)
928
+
929
+ #=========== 获取股价和成交量数据
930
+ result,startpd,endpd=check_period(start,end)
931
+
932
+ days_list=MA_days+EMA_days+[MACD_fastperiod]+[MACD_slowperiod]+[MACD_signalperiod]
933
+ max_days=max(days_list)
934
+ start1=date_adjust(start,adjust=-max_days * 3)
935
+
936
+ #df=get_price(ticker,start1,end)
937
+ df,found=get_price_1ticker_mixed(ticker=ticker,fromdate=start1,todate=end,ticker_type=ticker_type,source=source)
938
+ if df is None:
939
+ print(" #Error(stock_MACD): no info found for",ticker,"from",start,"to",end)
940
+ return None
941
+ if len(df)==0:
942
+ print(" #Error(stock_MACD): zero record found for",ticker,"from",start,"to",end)
943
+ return None
944
+
945
+ #=========== MA: 简单、加权移动平均
946
+ """
947
+ MA,又称移动平均线,是借助统计处理方式将若干天的股票价格加以平均,然后连接成一条线,用以观察股价趋势。
948
+ 移动平均线通常有3日、6日、10日、12日、24日、30日、72日、200日、288日、13周、26周、52周等等,不一而足,
949
+ 其目的在取得某一段期间的平均成本,而以此平均成本的移动曲线配合每日收盘价的线路变化分析某一期间多空的优劣形势,
950
+ 以研判股价的可能变化。
951
+ 一般来说,现行价格在平均价之上,意味着市场买力(需求)较大,行情看好;
952
+ 反之,行情价在平均价之下,则意味着供过于求,卖压显然较重,行情看淡。
953
+ """
954
+ #if ('MA' in graph) or ('ALL' in graph):
955
+ if (graph in ['MA',['MA']]) or ('ALL' in graph):
956
+ MA_cols=[]
957
+ for mad in MA_days:
958
+ col='MA'+str(mad)+text_lang('简单移动均线',' Simple MA Line')
959
+ MA_cols=MA_cols+[col]
960
+ df[col] = talib.MA(df['Close'],timeperiod=mad)
961
+
962
+ # MA快慢线交叉
963
+ dft=df.copy()
964
+ dft['datepd']=dft.index
965
+ dft['日期']=dft['datepd'].apply(lambda x: x.strftime("%Y-%m-%d"))
966
+
967
+ dft['short-long']=dft[MA_cols[0]]-dft[MA_cols[1]]
968
+ dft['sign']=dft['short-long'].apply(lambda x: 1 if x>0 else -1)
969
+ dft['sign_cross']=dft['sign']-dft['sign'].shift(1)
970
+ dft['交叉类型']=dft['sign_cross'].apply(lambda x: '上穿' if x > 0 else '下穿' if x < 0 else '')
971
+ dft2=dft[dft['sign_cross'] != 0]
972
+ dft3=dft2[(dft2.index >= startpd) & (dft2.index <= endpd)]
973
+ dft3.dropna(inplace=True)
974
+
975
+ # 限定日期范围
976
+ df1=df[(df.index >= startpd) & (df.index <= endpd)]
977
+
978
+ y_label=text_lang("价格","Price")
979
+ import datetime as dt; today=dt.date.today()
980
+ source=text_lang("数据来源:Sina/Yahoo/Stooq,","Data source: Sina/Yahoo/Stooq, ")+str(today)
981
+ footnote=text_lang("MA参数:","MA days=")+str(MA_days)
982
+ x_label=footnote+'\n'+source
983
+
984
+ axhline_value=0
985
+ axhline_label=''
986
+
987
+ # 简单移动均线MA绘图:moving average
988
+ df2=df1[['Close']+MA_cols]
989
+ df2.rename(columns={'Close':text_lang('收盘价','Close')},inplace=True)
990
+
991
+ title_txt=text_lang("证券技术分析:","Technical Analysis: ")+ticker_name(ticker,ticker_type)+text_lang(",简单移动均线",", Simple MA Line")
992
+
993
+ print(" Rendering graphics ...")
994
+ draw_lines(df2,y_label,x_label,axhline_value,axhline_label,title_txt, \
995
+ data_label=False,resample_freq=resample_freq,smooth=smooth,linewidth=linewidth*2,facecolor=facecolor)
996
+
997
+ if printout:
998
+ if len(dft3)!=0:
999
+ print(text_lang("\n== 简单移动均线交叉 ==","\n== Simple MA Line =="))
1000
+ alignlist=['left','center']
1001
+ print(dft3[['日期','交叉类型']].to_markdown(index=False,tablefmt='plain',colalign=alignlist))
1002
+ else:
1003
+ print(" Note: no cross of lines incurred for",ticker,"from",start,"to",end)
1004
+
1005
+ # 指数移动均线EMA绘图:exponential moving average
1006
+ """
1007
+ MA是用每天的收盘价来计算简单的平均值,
1008
+ 而EMA是需要给每天的最高最低等价位数值做一个权重处理后,再平均计算,
1009
+ 所以,EMA更具有平均价值一些。
1010
+ EMA最大特点也是由于它的计算方式导致的,因为后期的k线价格在计算均价时,比重更大,
1011
+ 所以相同参数EMA比MA更加贴近行情,更加平滑,产生的变盘信号、交易信号也更加激进?
1012
+ EMA的变盘信号:熊市转牛市,比MA迟钝?牛市转熊市,比MA敏感?
1013
+ """
1014
+ if ('EMA' in graph) or ('ALL' in graph):
1015
+ EMA_cols=[]
1016
+ for mad in EMA_days:
1017
+ col='EMA'+str(mad)+text_lang('指数移动均线',' Exponential MA Line')
1018
+ EMA_cols=EMA_cols+[col]
1019
+ df[col] = talib.EMA(df['Close'],timeperiod=mad)
1020
+
1021
+ # EMA快慢线交叉
1022
+ dft=df.copy()
1023
+ dft['datepd']=dft.index
1024
+ dft['日期']=dft['datepd'].apply(lambda x: x.strftime("%Y-%m-%d"))
1025
+
1026
+ dft['short-long']=dft[EMA_cols[0]]-dft[EMA_cols[1]]
1027
+ dft['sign']=dft['short-long'].apply(lambda x: 1 if x>0 else -1)
1028
+ dft['sign_cross']=dft['sign']-dft['sign'].shift(1)
1029
+ dft['交叉类型']=dft['sign_cross'].apply(lambda x: '上穿' if x > 0 else '下穿' if x < 0 else '')
1030
+ dft2=dft[dft['sign_cross'] != 0]
1031
+ dft3=dft2[(dft2.index >= startpd) & (dft2.index <= endpd)]
1032
+ dft3.dropna(inplace=True)
1033
+
1034
+ # 限定日期范围
1035
+ df1=df[(df.index >= startpd) & (df.index <= endpd)]
1036
+
1037
+ y_label=text_lang("价格","Price")
1038
+ import datetime as dt; today=dt.date.today()
1039
+ source=text_lang("数据来源:Sina/Yahoo/Stooq,","Data source: Sina/Yahoo/Stooq, ")+str(today)
1040
+ footnote=text_lang("EMA参数:","EMA days=")+str(EMA_days)
1041
+ x_label=footnote+'\n'+source
1042
+
1043
+ axhline_value=0
1044
+ axhline_label=''
1045
+
1046
+ df3=df1[['Close']+EMA_cols]
1047
+ df3.rename(columns={'Close':text_lang('收盘价','Close')},inplace=True)
1048
+ title_txt=text_lang("证券技术分析:","Technical Analysis: ")+ticker_name(ticker,ticker_type)+text_lang(",指数移动平均线","Exponential MA Line")
1049
+ draw_lines(df3,y_label,x_label,axhline_value,axhline_label,title_txt, \
1050
+ data_label=False,resample_freq=resample_freq,smooth=smooth,linewidth=linewidth*2,facecolor=facecolor)
1051
+
1052
+ if printout:
1053
+ if len(dft3)!=0:
1054
+ print(text_lang("\n== 指数移动平均线交叉 ==","\n== Exponential MA Line Cross =="))
1055
+ alignlist=['left','center']
1056
+ print(dft3[['日期','交叉类型']].to_markdown(index=False,tablefmt='plain',colalign=alignlist))
1057
+ else:
1058
+ print(" Note: no cross of lines incurred for",ticker,"from",start,"to",end)
1059
+
1060
+ #=========== MACD:指数平滑异同平均线
1061
+ """
1062
+ 计算方法:快速时间窗口设为12日,慢速时间窗口设为26日,DIF参数设为9日
1063
+ 3.1) 计算指数平滑移动平均值(EMA)
1064
+ 12日EMA的计算公式为:
1065
+ EMA(12) = 昨日EMA(12) × 11 ÷ 13 + 今日收盘价 × 2 ÷ 13
1066
+ 26日EMA的计算公式为:
1067
+ EMA(26) = 昨日EMA(26) × 25 ÷ 27 + 今日收盘价 × 2 ÷ 27
1068
+
1069
+ 3.2) 计算离差值(DIF)
1070
+ DIF = 今日EMA(12) – 今日EMA(26)
1071
+
1072
+ 3.3) 计算DIF的9日DEA
1073
+ 根据差值计算其9日的DEA,即差值平均
1074
+ 今日DEA = 昨日DEA × 8 ÷ 10 + 今日DIF × 2 ÷ 10
1075
+
1076
+ 形态解读:
1077
+ 1.DIF、DEA均为正,DIF向上突破DEA,买入信号。
1078
+ 2.DIF、DEA均为负,DIF向下跌破DEA,卖出信号。
1079
+ 3.DEA线与K线发生背离,行情反转信号。
1080
+ 4.分析MACD柱状线,由红变绿(正变负),卖出信号;由绿变红,买入信号。
1081
+
1082
+ MACD一则去掉移动平均线频繁的假讯号缺陷,二则能确保移动平均线最大的战果。
1083
+ 1. MACD金叉:DIF由下向上突破DEM,为买入信号。
1084
+ 2. MACD死叉:DIF由上向下突破DEM,为卖出信号。
1085
+ 3. MACD绿转红:MACD值由负变正,市场由空头转为多头。
1086
+ 4. MACD红转绿:MACD值由正变负,市场由多头转为空头。
1087
+ """
1088
+ if ('MACD' in graph) or ('ALL' in graph):
1089
+ df['DIF'],df['DEA'],df['MACD']=talib.MACD(df['Close'], \
1090
+ fastperiod=MACD_fastperiod, \
1091
+ slowperiod=MACD_slowperiod, \
1092
+ signalperiod=MACD_signalperiod)
1093
+
1094
+ # DIF/DEA快慢线交叉
1095
+ dft=df.copy()
1096
+ dft['datepd']=dft.index
1097
+ dft['日期']=dft['datepd'].apply(lambda x: x.strftime("%Y-%m-%d"))
1098
+
1099
+ dft['short-long']=dft['DIF']-dft['DEA']
1100
+ dft['sign']=dft['short-long'].apply(lambda x: 1 if x>0 else -1)
1101
+ dft['sign_cross']=dft['sign']-dft['sign'].shift(1)
1102
+ dft['交叉类型']=dft['sign_cross'].apply(lambda x: '上穿' if x > 0 else '下穿' if x < 0 else '')
1103
+ dft2=dft[dft['sign_cross'] != 0]
1104
+ dft3=dft2[(dft2.index >= startpd) & (dft2.index <= endpd)]
1105
+ dft3.dropna(inplace=True)
1106
+
1107
+ # 限定日期范围
1108
+ df1=df[(df.index >= startpd) & (df.index <= endpd)]
1109
+
1110
+ # MACD绘图
1111
+ print('') #空一行
1112
+
1113
+ df4=df1[['Close','DIF','DEA','MACD']]
1114
+ df4.rename(columns={'Close':'收盘价','DIF':'快线DIF','DEA':'慢线DEA','MACD':'柱线MACD'},inplace=True)
1115
+ title_txt=text_lang("证券技术分析:","Technical Analysis: ")+ticker_name(ticker,ticker_type)+",MACD"
1116
+
1117
+ import datetime as dt; today=dt.date.today()
1118
+ source=text_lang("数据来源:Sina/Yahoo/Stooq,","Data source: Sina/Yahoo/Stooq, ")+str(today)
1119
+
1120
+ #设置绘图风格:关闭网格虚线
1121
+ plt.rcParams['axes.grid']=False
1122
+
1123
+ # 设置绘图区的背景颜色为黑色
1124
+ fig=plt.figure()
1125
+ ax=fig.add_subplot(111)
1126
+ #ax.patch.set_facecolor('black')
1127
+ ax.patch.set_facecolor(color=facecolor)
1128
+
1129
+ # 绘制曲线
1130
+ ax.plot(df4['快线DIF'],label=text_lang('快线DIF','DIF(Fast line)'),linewidth=linewidth*2,color='white')
1131
+ ax.plot(df4['慢线DEA'],label=text_lang('慢线DEA','DEA(Slow line)'),linewidth=linewidth*2,color='orange')
1132
+
1133
+ # 绘制红绿柱子
1134
+ """
1135
+ MACD指标由三部分组成,分别是:DIF (差离值)、 DEA (差离值平均数)和BAR(柱状线),
1136
+ DIF在图中用白线表示,称为“快线”,DEA在图中用黄线表示,称为“慢线”,
1137
+ 最早的MACD只有这两条快慢线,通过两条曲线的聚合和分离来判断市场状况。
1138
+ 后来随着MACD的广泛运用,又引入了柱状线(BAR),俗称“红绿柱”。
1139
+ 红绿柱表示的是快线与慢线之间的距离,对指标实质没有影响,只是为了更便于观察和使用指标。
1140
+ """
1141
+ macd_plus=df4[df4['柱线MACD']>=0]
1142
+ macd_minus=df4[df4['柱线MACD']<=0]
1143
+ ax.bar(macd_plus.index,macd_plus['柱线MACD'],color='red',alpha=0.5)
1144
+ ax.bar(macd_minus.index,macd_minus['柱线MACD'],color='green',label=text_lang('柱线MACD','MACD(Bar chart)'),alpha=0.5)
1145
+
1146
+ # 绘制水平辅助线
1147
+ #plt.axhline(y=0,label='指标零线',color='cyan',linestyle=':',linewidth=linewidth*2)
1148
+ plt.axhline(y=0,label='',color='cyan',linestyle=':',linewidth=linewidth*2)
1149
+
1150
+ # 设置左侧坐标轴
1151
+ ax.set_ylabel(text_lang('DIF/DEA/MACD指标','DIF/DEA/MACD'),fontsize=ylabel_txt_size)
1152
+
1153
+ footnote="MACD days="+str([MACD_fastperiod,MACD_slowperiod,MACD_signalperiod])
1154
+ x_label=footnote+'\n'+source
1155
+ ax.set_xlabel(x_label,fontsize=xlabel_txt_size)
1156
+ ax.legend(loc=loc1,fontsize=legend_txt_size)
1157
+
1158
+ # 绘制股价在右侧
1159
+ #绘证券2:建立第二y轴
1160
+
1161
+ #插值平滑
1162
+ if smooth:
1163
+ try:
1164
+ df5=df_smooth_manual(df4,resample_freq=resample_freq)
1165
+ except:
1166
+ df5=df4
1167
+ else:
1168
+ df5=df4
1169
+
1170
+ ax2 = ax.twinx()
1171
+ ax2.plot(df5['收盘价'],label=text_lang('收盘价','Close'),linewidth=linewidth,color=price_line_color,ls='--')
1172
+
1173
+ # 右侧坐标轴标记
1174
+ ax2.set_ylabel(text_lang('收盘价','Close'),fontsize=ylabel_txt_size)
1175
+ ax2.legend(loc=loc2,fontsize=legend_txt_size)
1176
+
1177
+ # 图示标题
1178
+ plt.title(title_txt,fontweight='bold',fontsize=title_txt_size)
1179
+ #plt.xticks(rotation=30)
1180
+ plt.gcf().autofmt_xdate() # 优化标注(自动倾斜)
1181
+ #plt.legend(loc='best',fontsize=legend_txt_size)
1182
+ plt.show()
1183
+
1184
+ if printout:
1185
+ if len(dft3)!=0:
1186
+ print(text_lang("\n== DIF与DEA交叉 ==","\n== Cross of DIF and DEA"))
1187
+ alignlist=['left','center']
1188
+ print(dft3[['日期','交叉类型']].to_markdown(index=False,tablefmt='plain',colalign=alignlist))
1189
+ else:
1190
+ print(" Note: no cross of lines incurred for",ticker,"from",start,"to",end)
1191
+
1192
+ return df1
1193
+
1194
+ #==============================================================================
1195
+ #==============================================================================
1196
+ #==============================================================================
1197
+ if __name__ =="__main__":
1198
+ talib_install_method()
1199
+
1200
+
1201
+ def is_64bit_os():
1202
+ import platform
1203
+ if platform.machine().endswith('64'):
1204
+ bits='64'
1205
+ else:
1206
+ bits='32'
1207
+
1208
+ return bits+'-bit'
1209
+
1210
+ def talib_install_method():
1211
+ """
1212
+ 功能:提示必需的talib安装方法
1213
+ """
1214
+ print(" Warning: the classical method may not work properly:")
1215
+ print(" pip install TA-Lib\n")
1216
+ print(" Installation method: for Windows")
1217
+ print(" Step1. Check your Python version and your OS")
1218
+ print(" Your Python version:",check_python_version(),"\b, your OS:",is_64bit_os(),check_os())
1219
+
1220
+ print(" Step2. Search TA_lib whl file for your OS and Python version")
1221
+ print(" e.g. Find the one free of charge from CSDN, ... ...\n")
1222
+ print(" Step3. Download the file to a local folder in your computer")
1223
+ print(" Step4. Directly install the .whl file from the local folder by:")
1224
+ print(" pip install ta_lib_whl_file_name")
1225
+
1226
+ print("\n Installation method: for Mac, not tested for M chip")
1227
+ print(" Step1. brew install ta-lib")
1228
+ print(" Step2. pip install ta-lib")
1229
+
1230
+ print("\n Important: after installing ta-lib, RESTART your Jupyter.")
1231
+
1232
+ return
1233
+ #==============================================================================
1234
+
1235
+ if __name__ =="__main__":
1236
+ ticker='600519.SS'
1237
+ start='2022-1-1'
1238
+ end='2022-12-31'
1239
+
1240
+ RSI_days=[6,12,24]
1241
+ RSI_lines=[20,50,80]
1242
+
1243
+ loc1='upper left'
1244
+ loc2='center right'
1245
+
1246
+ resample_freq='H'
1247
+ smooth=True
1248
+ linewidth=1.5
1249
+ graph=['ALL']
1250
+ printout=True
1251
+
1252
+ def security_RSI(ticker,start='default',end='default', \
1253
+ RSI_days=[6,12,24],RSI_lines=[20,50,80], \
1254
+ resample_freq='6H',smooth=True,linewidth=1.5, \
1255
+ loc1='lower left',loc2='lower right', \
1256
+ graph=['ALL'],printout=True,ticker_type='auto',source='auto', \
1257
+ price_line_color='red',facecolor='k'):
1258
+ """
1259
+ 套壳函数,除了股票,还可用于交易所债券和交易所基金(如ETF和REITS)
1260
+ """
1261
+ df=stock_RSI(ticker=ticker,start=start,end=end, \
1262
+ RSI_days=RSI_days,RSI_lines=RSI_lines, \
1263
+ resample_freq=resample_freq,smooth=smooth,linewidth=linewidth, \
1264
+ loc1=loc1,loc2=loc2, \
1265
+ graph=graph,printout=printout,ticker_type=ticker_type,source=source, \
1266
+ price_line_color=price_line_color,facecolor=facecolor)
1267
+ return df
1268
+
1269
+
1270
+ def stock_RSI(ticker,start='default',end='default', \
1271
+ RSI_days=[6,12,24],RSI_lines=[20,50,80], \
1272
+ resample_freq='H',smooth=True,linewidth=1.5, \
1273
+ loc1='lower left',loc2='lower right', \
1274
+ graph=['ALL'],printout=True,ticker_type='auto',source='auto', \
1275
+ price_line_color='red',facecolor='k'):
1276
+ """
1277
+ 功能:计算股票的技术分析指标RSI
1278
+ 输入:df,四种股价Open/Close/High/Low,成交量Volume
1279
+ 输出:df
1280
+ 含有指标:
1281
+ RSI1(快速线,周线,黄色)、RSI2(中速线,双周线,紫色)、RSI3(慢速线,月线,白色)
1282
+ """
1283
+
1284
+ #=========== 导入需要的包
1285
+ try:
1286
+ import talib
1287
+ except:
1288
+ print(" #Error(stock_RSI): lack of necessary module - ta-lib")
1289
+ talib_install_method()
1290
+ return None
1291
+
1292
+ #=========== 日期转换与检查
1293
+ # 检查日期:截至日期
1294
+ import datetime as dt; today=dt.date.today()
1295
+ if end in ['default','today']:
1296
+ end=today
1297
+ else:
1298
+ validdate,end=check_date2(end)
1299
+ if not validdate:
1300
+ print(" #Warning(stock_RSI): invalid date for",end)
1301
+ end=today
1302
+
1303
+ # 检查日期:开始日期
1304
+ if start in ['default']:
1305
+ start=date_adjust(end,adjust=-31)
1306
+ else:
1307
+ validdate,start=check_date2(start)
1308
+ if not validdate:
1309
+ print(" #Warning(stock_RSI): invalid date for",start)
1310
+ start=date_adjust(todate,adjust=-31)
1311
+
1312
+ #=========== 获取股价和成交量数据
1313
+ result,startpd,endpd=check_period(start,end)
1314
+
1315
+ days_list=RSI_days
1316
+ max_days=max(days_list)
1317
+ start1=date_adjust(start,adjust=-max_days * 3)
1318
+
1319
+ #df=get_price(ticker,start1,end)
1320
+ df,found=get_price_1ticker_mixed(ticker=ticker,fromdate=start1,todate=end,ticker_type=ticker_type,source=source)
1321
+ if df is None:
1322
+ print(" #Error(stock_RSI): no info found for",ticker,"from",start,"to",end)
1323
+ return None
1324
+ if len(df)==0:
1325
+ print(" #Error(stock_RSI): zero record found for",ticker,"from",start,"to",end)
1326
+ return None
1327
+
1328
+ #============ 计算RSI
1329
+ if len(RSI_days) < 3:
1330
+ print(" #Warning(stock_RSI): RSI_days parameter needs 3 different days")
1331
+ return None
1332
+
1333
+ #df['rsi'] = talib.RSI(df['Close'], timeperiod=RSI_days)
1334
+ RSI_cols=[]
1335
+ RSI_seq=1
1336
+ for mad in RSI_days:
1337
+ col='RSI'+str(RSI_seq)
1338
+ RSI_cols=RSI_cols+[col]
1339
+ df[col] = talib.RSI(df['Close'],timeperiod=mad)
1340
+ RSI_seq=RSI_seq+1
1341
+
1342
+ df['datepd']=df.index
1343
+ df['日期']=df['datepd'].apply(lambda x: x.strftime("%Y-%m-%d"))
1344
+ del df['datepd']
1345
+
1346
+ # RSI1/RSI3快慢线交叉
1347
+ dft1=df.copy()
1348
+ dft1['short-long']=dft1['RSI1']-dft1['RSI3']
1349
+ dft1['sign']=dft1['short-long'].apply(lambda x: 1 if x>0 else -1)
1350
+ dft1['sign_cross']=dft1['sign']-dft1['sign'].shift(1)
1351
+ dft1['RSI1/3交叉类型']=dft1['sign_cross'].apply(lambda x: '上穿' if x > 0 else '下穿' if x < 0 else '')
1352
+ dft1b=dft1[dft1['sign_cross'] != 0]
1353
+ dft1c=dft1b[(dft1b.index >= startpd) & (dft1b.index <= endpd)]
1354
+ dft1c.dropna(inplace=True)
1355
+
1356
+ # RSI2/RSI3快慢线交叉
1357
+ dft2=df.copy()
1358
+ dft2['short-long']=dft2['RSI2']-dft2['RSI3']
1359
+ dft2['sign']=dft2['short-long'].apply(lambda x: 1 if x>0 else -1)
1360
+ dft2['sign_cross']=dft2['sign']-dft2['sign'].shift(1)
1361
+ dft2['RSI2/3交叉类型']=dft2['sign_cross'].apply(lambda x: '上穿' if x > 0 else '下穿' if x < 0 else '')
1362
+ dft2b=dft2[dft2['sign_cross'] != 0]
1363
+ dft2c=dft2b[(dft2b.index >= startpd) & (dft2b.index <= endpd)]
1364
+ dft2c.dropna(inplace=True)
1365
+
1366
+ # RSI限定日期范围
1367
+ df1=df[(df.index >= startpd) & (df.index <= endpd)]
1368
+
1369
+ if len(graph) != 0:
1370
+
1371
+ # RSI绘图
1372
+ df4=df1[['Close']+RSI_cols]
1373
+ df4.rename(columns={'Close':'收盘价'},inplace=True)
1374
+ title_txt=text_lang("证券技术分析:","Technical Analysis: ")+ticker_name(ticker,ticker_type)+text_lang(",RSI",", RSI")
1375
+
1376
+ import datetime as dt; today=dt.date.today()
1377
+ source=text_lang("数据来源:Sina/Yahoo/Stooq,","Data source: Sina/Yahoo/Stooq, ")+str(today)
1378
+ footnote=text_lang("RSI参数:","RSI days=")+str(RSI_days)
1379
+ x_label=footnote+'\n'+source
1380
+
1381
+ # 设置绘图区的背景颜色为黑色
1382
+ fig=plt.figure()
1383
+ ax=fig.add_subplot(111)
1384
+ ax.patch.set_facecolor(color=facecolor)
1385
+
1386
+ # 绘制曲线
1387
+ if ('RSI1' in graph) or ('ALL' in graph):
1388
+ ax.plot(df4['RSI1'],label=text_lang('快速线RSI1','RSI1(Fast line)'),linewidth=linewidth*2,color='orange')
1389
+ if ('RSI2' in graph) or ('ALL' in graph):
1390
+ ax.plot(df4['RSI2'],label=text_lang('中速线RSI2','RSI2(Mid line)'),linewidth=linewidth*2,color='purple')
1391
+ if ('RSI3' in graph) or ('ALL' in graph):
1392
+ ax.plot(df4['RSI3'],label=text_lang('慢速线RSI3','RSI3(Slow line)'),linewidth=linewidth*2,color='white')
1393
+
1394
+ # 绘制水平辅助线
1395
+ #hl_linestyle_list=['dashed','-.','dotted']
1396
+ hl_linestyle_list=['dotted','dotted','dotted','dotted','dotted','dotted']
1397
+ #plt.axhline(y=0,label='指标零线',color='cyan',linestyle=':',linewidth=linewidth*2)
1398
+ for hl in RSI_lines:
1399
+ pos=RSI_lines.index(hl)
1400
+ hl_ls=hl_linestyle_list[pos]
1401
+ plt.axhline(y=hl,label='',color='cyan',linestyle=hl_ls,linewidth=linewidth)
1402
+
1403
+ # 设置左侧坐标轴
1404
+ ax.set_ylabel(text_lang('RSI指标','RSI'),fontsize=ylabel_txt_size)
1405
+ ax.set_xlabel(x_label,fontsize=xlabel_txt_size)
1406
+ ax.legend(loc=loc1,fontsize=legend_txt_size)
1407
+
1408
+ # 绘制股价在右侧
1409
+ #绘证券2:建立第二y轴
1410
+
1411
+ #插值平滑
1412
+ if smooth:
1413
+ print(" Smoothening curves directly ...")
1414
+ try:
1415
+ df5=df_smooth_manual(df4,resample_freq=resample_freq)
1416
+ except:
1417
+ df5=df4
1418
+ else:
1419
+ df5=df4
1420
+
1421
+ ax2 = ax.twinx()
1422
+ ax2.plot(df5['收盘价'],label=text_lang('收盘价','Close'),linewidth=linewidth,color=price_line_color,ls='--')
1423
+
1424
+ # 右侧坐标轴标记
1425
+ ax2.set_ylabel(text_lang('收盘价','Close'),fontsize=ylabel_txt_size)
1426
+ ax2.legend(loc=loc2,fontsize=legend_txt_size)
1427
+
1428
+ # 图示标题
1429
+ plt.title(title_txt,fontweight='bold',fontsize=title_txt_size)
1430
+ #plt.xticks(rotation=30)
1431
+ plt.gcf().autofmt_xdate() # 优化标注(自动倾斜)
1432
+ #plt.legend(loc='best',fontsize=legend_txt_size)
1433
+ plt.show()
1434
+
1435
+ if printout:
1436
+ if (('RSI1' in graph) & ('RSI3' in graph)) or ('ALL' in graph):
1437
+ if len(dft1c)!=0:
1438
+ print(text_lang("\n=== RSI1与RSI3的交叉点 ===","\n=== Cross of RSI1 & RSI3"))
1439
+ alignlist=['left','center']
1440
+ print(dft1c[['日期','RSI1/3交叉类型']].to_markdown(index=False,tablefmt='plain',colalign=alignlist))
1441
+ else:
1442
+ print(" Note: no RSI1/3 cross of lines incurred for",ticker,"from",start,"to",end)
1443
+ if printout:
1444
+ if (('RSI2' in graph) & ('RSI3' in graph)) or ('ALL' in graph):
1445
+ if len(dft2c)!=0:
1446
+ print(text_lang("\n=== RSI2与RSI3的交叉点 ===","\n=== Cross of RSI2 & RSI3"))
1447
+ alignlist=['left','center']
1448
+ print(dft2c[['日期','RSI2/3交叉类型']].to_markdown(index=False,tablefmt='plain',colalign=alignlist))
1449
+ else:
1450
+ print(" Note: no RSI2/3 cross of lines incurred for",ticker,"from",start,"to",end)
1451
+
1452
+ return df1
1453
+ #==============================================================================
1454
+ #==============================================================================
1455
+ #==============================================================================
1456
+
1457
+ def findout_cross(df,start,end,fast_line,slow_line):
1458
+ """
1459
+ 功能:技术分析,找出fast_line与slow_line两条线交叉的记录,并给出交叉类型:上穿,下穿
1460
+ """
1461
+
1462
+ dft1=df.copy()
1463
+ dft1['short-long']=dft1[fast_line]-dft1[slow_line]
1464
+ dft1['sign']=dft1['short-long'].apply(lambda x: 1 if x>0 else -1)
1465
+ dft1['sign_cross']=dft1['sign']-dft1['sign'].shift(1)
1466
+ dft1['交叉类型']=dft1['sign_cross'].apply(lambda x: '上穿' if x > 0 else '下穿' if x < 0 else '')
1467
+ dft1b=dft1[dft1['sign_cross'] != 0]
1468
+
1469
+ result,startpd,endpd=check_period(start,end)
1470
+ dft1c=dft1b[(dft1b.index >= startpd) & (dft1b.index <= endpd)]
1471
+
1472
+ dft1c.dropna(inplace=True)
1473
+
1474
+ return dft1c
1475
+
1476
+ #==============================================================================
1477
+ #==============================================================================
1478
+ #==============================================================================
1479
+
1480
+
1481
+ if __name__ =="__main__":
1482
+ ticker='600519.SS'
1483
+ start='2010-1-1'
1484
+ end='2022-12-31'
1485
+
1486
+ KDJ_days=[9,3,3]
1487
+ matypes=[0,0]
1488
+
1489
+ loc1='upper left'
1490
+ loc2='center right'
1491
+
1492
+ resample_freq='H'
1493
+ smooth=True
1494
+ linewidth=1.5
1495
+
1496
+ graph=['ALL']
1497
+ printout=True
1498
+
1499
+ graph=False
1500
+
1501
+ def security_KDJ(ticker,start='default',end='default', \
1502
+ KDJ_days=[9,3,3],matypes=[0,0],KDJ_lines=[20,50,80], \
1503
+ resample_freq='6H',smooth=True,linewidth=1.5, \
1504
+ loc1='lower left',loc2='lower right', \
1505
+ graph=['ALL'],printout=True,ticker_type='auto',source='auto', \
1506
+ price_line_color='red',facecolor='k'):
1507
+ """
1508
+ 套壳函数
1509
+ """
1510
+ df=stock_KDJ(ticker=ticker,start=start,end=end, \
1511
+ KDJ_days=KDJ_days,matypes=matypes,KDJ_lines=KDJ_lines, \
1512
+ resample_freq=resample_freq,smooth=smooth,linewidth=linewidth, \
1513
+ loc1=loc1,loc2=loc2, \
1514
+ graph=graph,printout=printout,ticker_type=ticker_type,source=source, \
1515
+ price_line_color=price_line_color,facecolor=facecolor)
1516
+ return df
1517
+
1518
+
1519
+ def stock_KDJ(ticker,start='default',end='default', \
1520
+ KDJ_days=[9,3,3],matypes=[0,0],KDJ_lines=[20,50,80], \
1521
+ resample_freq='H',smooth=True,linewidth=1.5, \
1522
+ loc1='lower left',loc2='lower right', \
1523
+ graph=['ALL'],printout=True,ticker_type='auto',source='auto', \
1524
+ price_line_color='red',facecolor='k'):
1525
+ """
1526
+ 功能:计算股票的技术分析指标KDJ
1527
+ 输入:df,四种股价Open/Close/High/Low,成交量Volume
1528
+ 输出:df
1529
+ 含有指标:
1530
+ K线(快速线,黄色)、D线(慢速线,绿色)、J线(差额,紫色)
1531
+ """
1532
+
1533
+ #=========== 导入需要的包
1534
+ try:
1535
+ import talib
1536
+ except:
1537
+ print(" #Error(stock_KDJ): lack of necessary module - talib")
1538
+ talib_install_method()
1539
+ return None
1540
+
1541
+ #=========== 日期转换与检查
1542
+ # 检查日期:截至日期
1543
+ import datetime as dt; today=dt.date.today()
1544
+ if end in ['default','today']:
1545
+ end=today
1546
+ else:
1547
+ validdate,end=check_date2(end)
1548
+ if not validdate:
1549
+ print(" #Warning(stock_KDJ): invalid date for",end)
1550
+ end=today
1551
+
1552
+ # 检查日期:开始日期
1553
+ if start in ['default']:
1554
+ start=date_adjust(end,adjust=-31)
1555
+ else:
1556
+ validdate,start=check_date2(start)
1557
+ if not validdate:
1558
+ print(" #Warning(stock_KDJ): invalid date for",start)
1559
+ start=date_adjust(todate,adjust=-31)
1560
+
1561
+ #=========== 获取股价和成交量数据
1562
+ result,startpd,endpd=check_period(start,end)
1563
+
1564
+ days_list=KDJ_days
1565
+ max_days=max(days_list)
1566
+ start1=date_adjust(start,adjust=-max_days * 3)
1567
+
1568
+ #df=get_price(ticker,start1,end)
1569
+ df,found=get_price_1ticker_mixed(ticker=ticker,fromdate=start1,todate=end,ticker_type=ticker_type,source=source)
1570
+ if df is None:
1571
+ print(" #Error(stock_RSI): no info found for",ticker,"from",start,"to",end)
1572
+ return None
1573
+ if len(df)==0:
1574
+ print(" #Error(stock_RSI): zero record found for",ticker,"from",start,"to",end)
1575
+ return None
1576
+
1577
+ df['datepd']=df.index
1578
+ df['日期']=df['datepd'].apply(lambda x: x.strftime("%Y-%m-%d"))
1579
+ del df['datepd']
1580
+
1581
+ #============ 计算KDJ
1582
+ """
1583
+ df['kdj_k'],df['kdj_d'] = talib.STOCH(df['High'],df['Low'],df['Close'], \
1584
+ fastk_period=KDJ_fastk_period,
1585
+ slowk_period=KDJ_slowk_period,
1586
+ slowk_matype=KDJ_slowk_matype,
1587
+ slowd_period=KDJ_slowd_period,
1588
+ slowd_matype=KDJ_slowd_matype)
1589
+ df['kdj_j'] = 3*df['kdj_k'] - 2*df['kdj_d']
1590
+ """
1591
+ KDJ_cols=['K','D','J']
1592
+ df['K'],df['D'] = talib.STOCH(df['High'],df['Low'],df['Close'], \
1593
+ fastk_period=KDJ_days[0],
1594
+ slowk_period=KDJ_days[1],
1595
+ slowk_matype=matypes[0],
1596
+ slowd_period=KDJ_days[2],
1597
+ slowd_matype=matypes[1])
1598
+ df['J'] = 3*df['K'] - 2*df['D']
1599
+
1600
+ # 限制J线在0-100之间
1601
+ if graph:
1602
+ df['J']=df['J'].apply(lambda x:100 if x>100 else 0 if x<0 else x)
1603
+
1604
+ if not graph:
1605
+ return df
1606
+
1607
+ # J/K线交叉
1608
+ """
1609
+ dft1=df.copy()
1610
+ dft1['short-long']=dft1['J']-dft1['K']
1611
+ dft1['sign']=dft1['short-long'].apply(lambda x: 1 if x>0 else -1)
1612
+ dft1['sign_cross']=dft1['sign']-dft1['sign'].shift(1)
1613
+ dft1['J/K线交叉类型']=dft1['sign_cross'].apply(lambda x: '上穿' if x > 0 else '下穿' if x < 0 else '')
1614
+ dft1b=dft1[dft1['sign_cross'] != 0]
1615
+ dft1c=dft1b[(dft1b.index >= startpd) & (dft1b.index <= endpd)]
1616
+ dft1c.dropna(inplace=True)
1617
+ """
1618
+ dft1c=findout_cross(df,start,end,'J','K')
1619
+
1620
+ # J/D线交叉
1621
+ """
1622
+ dft2=df.copy()
1623
+ dft2['short-long']=dft2['J']-dft2['D']
1624
+ dft2['sign']=dft2['short-long'].apply(lambda x: 1 if x>0 else -1)
1625
+ dft2['sign_cross']=dft2['sign']-dft2['sign'].shift(1)
1626
+ dft2['J/D线交叉类型']=dft2['sign_cross'].apply(lambda x: '上穿' if x > 0 else '下穿' if x < 0 else '')
1627
+ dft2b=dft2[dft2['sign_cross'] != 0]
1628
+ dft2c=dft2b[(dft2b.index >= startpd) & (dft2b.index <= endpd)]
1629
+ dft2c.dropna(inplace=True)
1630
+ """
1631
+ dft2c=findout_cross(df,start,end,'J','D')
1632
+
1633
+ # K/D线交叉
1634
+ dft3c=findout_cross(df,start,end,'K','D')
1635
+
1636
+ # 限定日期范围
1637
+ df1=df[(df.index >= startpd) & (df.index <= endpd)]
1638
+
1639
+ if len(graph) != 0:
1640
+
1641
+ # 绘图
1642
+ df4=df1[['Close']+KDJ_cols]
1643
+ df4.rename(columns={'Close':'收盘价'},inplace=True)
1644
+ title_txt=text_lang("证券技术分析:","Technical Analysis: ")+ticker_name(ticker,ticker_type)+text_lang(",KDJ",", KDJ")
1645
+
1646
+ import datetime as dt; today=dt.date.today()
1647
+ source=text_lang("数据来源:Sina/Yahoo/Stooq,","Data source: Sina/Yahoo/Stooq, ")+str(today)
1648
+ footnote=text_lang("KDJ参数:","KDJ days=")+str(KDJ_days)
1649
+ x_label=footnote+'\n'+source
1650
+
1651
+ # 设置绘图区的背景颜色为黑色
1652
+ fig=plt.figure()
1653
+ ax=fig.add_subplot(111)
1654
+ ax.patch.set_facecolor(color=facecolor)
1655
+
1656
+ # 绘制曲线
1657
+ if ('K' in graph) or ('ALL' in graph):
1658
+ ax.plot(df4['K'],label=text_lang('快速线K','K(Fast line)'),linewidth=linewidth*2,color='orange')
1659
+ if ('D' in graph) or ('ALL' in graph):
1660
+ ax.plot(df4['D'],label=text_lang('慢速线D','D(Slow line)'),linewidth=linewidth*2,color='green')
1661
+ if ('J' in graph) or ('ALL' in graph):
1662
+ ax.plot(df4['J'],label=text_lang('超快线J','J(Faster line)'),linewidth=linewidth*2,color='purple')
1663
+
1664
+ # 绘制水平辅助线: 某些情况下不绘制,以便展现KDJ线细节
1665
+ maxK=df4['K'].max()
1666
+ maxD=df4['D'].max()
1667
+ maxJ=df4['J'].max()
1668
+ maxKDJ=max(maxK,maxD,maxJ)
1669
+
1670
+ minK=df4['K'].min()
1671
+ minD=df4['D'].min()
1672
+ minJ=df4['J'].min()
1673
+ minKDJ=min(minK,minD,minJ)
1674
+
1675
+ """
1676
+ hl_linestyle_list=['dashed','dashed','-.','dotted','dotted']
1677
+ hl_color_list=['red','red','white','cyan','cyan']
1678
+ """
1679
+ hl_linestyle_list=['dotted','dotted','dotted','dotted','dotted']
1680
+ hl_color_list=['cyan','cyan','cyan','cyan','cyan']
1681
+
1682
+ #plt.axhline(y=0,label='指标零线',color='cyan',linestyle=':',linewidth=linewidth*2)
1683
+ for hl in KDJ_lines:
1684
+ draw=True
1685
+ if (hl > maxKDJ):
1686
+ draw=False
1687
+ if (hl < minKDJ):
1688
+ draw=False
1689
+
1690
+ if draw:
1691
+ pos=KDJ_lines.index(hl)
1692
+ hl_ls=hl_linestyle_list[pos]
1693
+ hl_color=hl_color_list[pos]
1694
+ plt.axhline(y=hl,label='',color=hl_color,linestyle=hl_ls,linewidth=linewidth)
1695
+
1696
+ # 设置左侧坐标轴
1697
+ ax.set_ylabel(text_lang('KDJ指标','KDJ'),fontsize=ylabel_txt_size)
1698
+ ax.set_xlabel(x_label,fontsize=xlabel_txt_size)
1699
+ ax.legend(loc=loc1,fontsize=legend_txt_size)
1700
+
1701
+ # 设置纵轴刻度
1702
+ from matplotlib.pyplot import MultipleLocator
1703
+ y_major_locator=MultipleLocator(10)
1704
+ ax.yaxis.set_major_locator(y_major_locator)
1705
+ #ax.set_ylim(-5,105)
1706
+
1707
+ # 绘制股价在右侧
1708
+ #绘证券2:建立第二y轴
1709
+
1710
+ #插值平滑
1711
+ if smooth:
1712
+ print(" Smoothening curves directly ...")
1713
+ try:
1714
+ df5=df_smooth_manual(df4,resample_freq=resample_freq)
1715
+ except:
1716
+ df5=df4
1717
+ else:
1718
+ df5=df4
1719
+
1720
+ ax2 = ax.twinx()
1721
+ ax2.plot(df5['收盘价'],label=text_lang('收盘价','Close'),linewidth=linewidth,color=price_line_color,ls='--')
1722
+
1723
+ # 右侧坐标轴标记
1724
+ ax2.set_ylabel(text_lang('收盘价','Close'),fontsize=ylabel_txt_size)
1725
+ ax2.legend(loc=loc2,fontsize=legend_txt_size)
1726
+
1727
+ # 图示标题
1728
+ plt.title(title_txt,fontweight='bold',fontsize=title_txt_size)
1729
+ #plt.xticks(rotation=30)
1730
+ plt.gcf().autofmt_xdate() # 优化标注(自动倾斜)
1731
+ #plt.legend(loc='best',fontsize=legend_txt_size)
1732
+ plt.show()
1733
+
1734
+ if printout:
1735
+ alignlist=['left','center']
1736
+ if (('J' in graph) & ('K' in graph)) or ('ALL' in graph):
1737
+ if len(dft1c)!=0:
1738
+ print(text_lang("\n**** J线与K线的交叉点","\n**** Cross of J & K Lines"))
1739
+ print(dft1c[['日期','交叉类型']].to_markdown(index=False,tablefmt='plain',colalign=alignlist))
1740
+ else:
1741
+ print(" Note: no J/K cross of lines incurred for",ticker,"from",start,"to",end)
1742
+
1743
+ if (('J' in graph) & ('D' in graph)) or ('ALL' in graph):
1744
+ if len(dft2c)!=0:
1745
+ print(text_lang("\n**** J线与D线的交叉点","\n**** Cross of J & D Lines"))
1746
+ alignlist=['left','center']
1747
+ print(dft2c[['日期','交叉类型']].to_markdown(index=False,tablefmt='plain',colalign=alignlist))
1748
+ else:
1749
+ print(" Note: no J/D cross of lines incurred for",ticker,"from",start,"to",end)
1750
+
1751
+ if (('K' in graph) & ('D' in graph)) or ('ALL' in graph):
1752
+ if len(dft3c)!=0:
1753
+ print(text_lang("\n**** K线与D线的交叉点","\n**** Cross of K & D Lines"))
1754
+ alignlist=['left','center']
1755
+ print(dft3c[['日期','交叉类型']].to_markdown(index=False,tablefmt='plain',colalign=alignlist))
1756
+ else:
1757
+ print(" Note: no K/D cross of lines incurred for",ticker,"from",start,"to",end)
1758
+
1759
+ return df1
1760
+ #==============================================================================
1761
+ #==============================================================================
1762
+ #==============================================================================
1763
+
1764
+ if __name__ =="__main__":
1765
+ ticker='600519.SS'
1766
+ start='2020-1-1'
1767
+ end='2022-12-31'
1768
+
1769
+ past_days=1
1770
+ momemtum_days=0
1771
+ future_days=1
1772
+
1773
+ threshhold=0.001
1774
+
1775
+ df=stock_KDJ(ticker,start,end,graph=False)
1776
+
1777
+ def price_trend_technical(df, \
1778
+ past_days=1,momemtum_days=1,future_days=1, \
1779
+ threshhold=0.001):
1780
+ """
1781
+ 功能:检查技术结果中每日股价的变化趋势:上涨,下跌,振荡,转折
1782
+ past_days: 当前日期前几个交易日的算术收益率天数
1783
+ momemtum_days: 股价发生变化的惯性天数,从当前日期开始计算
1784
+ future_days: 当前日期未来几个交易日的算术收益率天数,从当前日期+惯性天数开始计算
1785
+ threshhold: 股价算术收益率变化的最低门槛,
1786
+ 不小于threshhold:上涨,+;
1787
+ 小于-threshhold:下跌,-;
1788
+ 其余:振荡,~
1789
+
1790
+ 注意:可能存在尚不明了的问题!!!
1791
+ """
1792
+
1793
+ cols=list(df)
1794
+ cols.remove('Close')
1795
+ cols1=cols+['Close']
1796
+ df1=df[cols1]
1797
+
1798
+ df1['Close_past']=df1['Close'].shift(past_days)
1799
+ df1['ret_past']=df1['Close'] - df1['Close_past']
1800
+
1801
+ # +=上涨,~=振荡,-=下跌
1802
+ df1['trend_past']=df1['ret_past'].apply(lambda x:'+' if x >= threshhold else
1803
+ '-' if x <= -threshhold
1804
+ else '~' if -threshhold < x < threshhold
1805
+ else '')
1806
+ if momemtum_days != 0:
1807
+ df1['Close_mom']=df1['Close'].shift(-momemtum_days)
1808
+ else:
1809
+ df1['Close_mom']=df1['Close']
1810
+
1811
+ df1['Close_future']=df1['Close'].shift(-momemtum_days-future_days)
1812
+ df1['ret_future']=df1['Close_future'] - df1['Close_mom']
1813
+ df1['trend_future']=df1['ret_future'].apply(lambda x:'+' if x >= threshhold
1814
+ else '-' if x <= -threshhold
1815
+ else '~' if -threshhold < x < threshhold
1816
+ else '')
1817
+
1818
+ df1['trend_past2future']=''
1819
+ df1['trend_past2future']=df1.apply(lambda x:
1820
+ '+2+' if (x['trend_past']=='+') & (x['trend_future']=='+')
1821
+ else x['trend_past2future'],axis=1)
1822
+ df1['trend_past2future']=df1.apply(lambda x:
1823
+ '-2-' if (x['trend_past']=='-') & (x['trend_future']=='-')
1824
+ else x['trend_past2future'],axis=1)
1825
+ df1['trend_past2future']=df1.apply(lambda x:
1826
+ '~2~' if (x['trend_past']=='~') & (x['trend_future']=='~')
1827
+ else x['trend_past2future'],axis=1)
1828
+
1829
+ df1['trend_past2future']=df1.apply(lambda x:
1830
+ '+2-' if (x['trend_past']=='+') & (x['trend_future']=='-')
1831
+ else x['trend_past2future'],axis=1)
1832
+ df1['trend_past2future']=df1.apply(lambda x:
1833
+ '+2~' if (x['trend_past']=='+') & (x['trend_future']=='~')
1834
+ else x['trend_past2future'],axis=1)
1835
+
1836
+ df1['trend_past2future']=df1.apply(lambda x:
1837
+ '-2+' if (x['trend_past']=='-') & (x['trend_future']=='+')
1838
+ else x['trend_past2future'],axis=1)
1839
+ df1['trend_past2future']=df1.apply(lambda x:
1840
+ '-2~' if (x['trend_past']=='-') & (x['trend_future']=='~')
1841
+ else x['trend_past2future'],axis=1)
1842
+
1843
+ df1['trend_past2future']=df1.apply(lambda x:
1844
+ '~2+' if (x['trend_past']=='~') & (x['trend_future']=='+')
1845
+ else x['trend_past2future'],axis=1)
1846
+ df1['trend_past2future']=df1.apply(lambda x:
1847
+ '~2-' if (x['trend_past']=='~') & (x['trend_future']=='-')
1848
+ else x['trend_past2future'],axis=1)
1849
+
1850
+
1851
+ tempcols=['Close_past','ret_past','Close_mom','Close_future','ret_future']
1852
+ df1.drop(tempcols, axis=1, inplace=True)
1853
+
1854
+ # 记录参数
1855
+ df1['past_days']=past_days
1856
+ df1['momemtum_days']=momemtum_days
1857
+ df1['future_days']=future_days
1858
+ df1['threshhold']=threshhold
1859
+
1860
+ return df1
1861
+
1862
+
1863
+ #==============================================================================
1864
+ #==============================================================================
1865
+
1866
+ if __name__ =="__main__":
1867
+
1868
+ ticker='600519.SS'
1869
+ start='2020-1-1'
1870
+ end='2022-12-31'
1871
+ df=stock_KDJ(ticker,start,end,graph=False)
1872
+
1873
+ df1=price_trend_technical(df)
1874
+ KDJ_values=[20,20,10]
1875
+ J_days=3
1876
+ JvsK='above'
1877
+ JKcross='bottom up'
1878
+ value_type='ceiling'
1879
+
1880
+ otc1,dfs1=check_KDJ_market(df1,start,end,KDJ_values=[50,50,50],value_type='floor')
1881
+ otc2,dfs2=check_KDJ_market(df1,start,end,KDJ_values=[80,80,100],J_days=3,value_type='floor')
1882
+ dfs_down=dfs2[dfs2['trend_future']=='-']
1883
+
1884
+ otp1,dfs1=check_KDJ_market(df1,start,end,KDJ_values=[50,50,50],value_type='ceiling')
1885
+ otp2,dfs2=check_KDJ_market(df1,start,end,KDJ_values=[20,20,0],J_days=3,value_type='ceiling')
1886
+ dfs_up=dfs2[dfs2['trend_future']=='+']
1887
+
1888
+
1889
+ def check_KDJ_market(df1,start,end,KDJ_values=[50,50,50],J_days=1,JvsK='any',JKcross='any',
1890
+ value_type='ceiling'):
1891
+ """
1892
+ 功能:检查KDJ计算结果中,多头/空头市场的股价趋势比例
1893
+ start,end:日期范围
1894
+ value_type='ceiling':K、D、J值位于KDJ_values=[50,50,50]以下,假设为空头市场
1895
+ value_type='floor':K、D、J值位于KDJ_values=[50,50,50]以上,假设为多头市场
1896
+
1897
+ 注意:可能存在尚不明了的问题!!!
1898
+ """
1899
+ import pandas as pd
1900
+
1901
+ ticker=df1['ticker'].values[0]
1902
+ past_days=df1['past_days'].values[0]
1903
+ momemtum_days=df1['momemtum_days'].values[0]
1904
+ future_days=df1['future_days'].values[0]
1905
+ threshhold=df1['threshhold'].values[0]
1906
+
1907
+ # J值的持续天数
1908
+ if J_days > 1:
1909
+ for jj in range(1,J_days):
1910
+ df1['J_'+str(jj)]=df1['J'].shift(jj)
1911
+
1912
+ result,startpd,endpd=check_period(start,end)
1913
+ df2=df1[(df1.index >= startpd) & (df1.index <=endpd)]
1914
+ df2=df2[df2['trend_future'] != '']
1915
+
1916
+ # 多头市场:KDJ值的下限floor;空头市场:KDJ值的上限ceiling
1917
+ if value_type == 'floor':
1918
+ df3=df2[(df2['K'] > KDJ_values[0]) &
1919
+ (df2['D'] > KDJ_values[1]) &
1920
+ (df2['J'] > KDJ_values[2])]
1921
+ if J_days > 1:
1922
+ for jj in range(1,J_days):
1923
+ #print('jj=',jj)
1924
+ df3=df3[df3['J_'+str(jj)] > KDJ_values[2]]
1925
+ elif value_type == 'ceiling':
1926
+ df3=df2[(df2['K'] < KDJ_values[0]) &
1927
+ (df2['D'] < KDJ_values[1]) &
1928
+ (df2['J'] < KDJ_values[2])]
1929
+ if J_days > 1:
1930
+ for jj in range(1,J_days):
1931
+ #print('jj=',jj)
1932
+ df3=df3[df3['J_'+str(jj)] < KDJ_values[2]]
1933
+ else:
1934
+ pass
1935
+
1936
+ # J/K线上下位置
1937
+ if JvsK=='above':
1938
+ df3=df3[df3['J'] > df3['K']]
1939
+ elif JvsK=='below':
1940
+ df3=df3[df3['J'] < df3['K']]
1941
+ else:
1942
+ pass
1943
+
1944
+ # J/K线交叉
1945
+ if JKcross in ['bottom up','top down']:
1946
+ df3cross=findout_cross(df3,start,end,'J','K')
1947
+ df3cross2=pd.DataFrame(df3cross['交叉类型'])
1948
+
1949
+ import pandas as pd
1950
+ df3=pd.merge(df3,df3cross2,how='inner',left_index=True,right_index=True)
1951
+
1952
+ if JKcross == 'bottom up':
1953
+ df3=df3[df3['交叉类型']=='上穿']
1954
+ elif JKcross == 'top down':
1955
+ df3=df3[df3['交叉类型']=='下穿']
1956
+ else:
1957
+ pass
1958
+
1959
+ if len(df3)==0:
1960
+ print(" #Warning(check_KDJ_market): no designated signals found for",ticker,"from",start,'to',end)
1961
+ return None,None
1962
+
1963
+ # 未来上涨、下跌、振荡的比例
1964
+ df4a=df3[df3['trend_future'] == '+']
1965
+ pcta=len(df4a)/len(df3)
1966
+ df4b=df3[df3['trend_future'] == '-']
1967
+ pctb=len(df4b)/len(df3)
1968
+ df4c=df3[df3['trend_future'] == '~']
1969
+ pctc=len(df4c)/len(df3)
1970
+
1971
+ # 未来转为下跌、上涨的比例
1972
+ df4d=df3[df3['trend_past2future'].isin(['+2-','~2-'])]
1973
+ pctd=len(df4d)/len(df3)
1974
+ df4e=df3[df3['trend_past2future'].isin(['-2+','~2+'])]
1975
+ pcte=len(df4e)/len(df3)
1976
+
1977
+ import pandas as pd
1978
+ outcome=pd.DataFrame()
1979
+
1980
+ row=pd.Series({'ticker':ticker,'start':start,'end':end, \
1981
+ 'K value':KDJ_values[0],'D value':KDJ_values[1],'J value':KDJ_values[2],
1982
+ 'J days':J_days,'JvsK':JvsK,'JKcross':JKcross,
1983
+ 'value type':value_type, \
1984
+ 'past_days':past_days,'momemtum_days':momemtum_days,'future_days':future_days,
1985
+ 'threshhold':threshhold, \
1986
+ 'future up ratio':pcta,'future down ratio':pctb,'future vibrate ratio':pctc, \
1987
+ 't2- ratio':pctd,'t2+ ratio':pcte})
1988
+ try:
1989
+ outcome=outcome.append(row,ignore_index=True)
1990
+ except:
1991
+ outcome=outcome._append(row,ignore_index=True)
1992
+
1993
+ return outcome,df3
1994
+
1995
+ #==============================================================================
1996
+ #==============================================================================
1997
+ if __name__ =="__main__":
1998
+ ticker='600518.SS'
1999
+ start='2013-1-1'
2000
+ end='2022-12-31'
2001
+
2002
+ KDJ_days=[9,3,3]
2003
+ matypes=[0,0]
2004
+
2005
+ past_days=1
2006
+ momemtum_days=0
2007
+ future_days=1
2008
+ threshhold=0.001
2009
+
2010
+ KDJ_values=[50,50,50]
2011
+ J_days=1
2012
+ JvsK='above'
2013
+ JKcross='bottom up'
2014
+
2015
+ value_type='floor'
2016
+
2017
+ # 多头市场与空头市场
2018
+ dfs1=backtest_KDJ(ticker,start,end,KDJ_values=[50,50,50],value_type='floor')
2019
+ dfs2=backtest_KDJ(ticker,start,end,KDJ_values=[50,50,50],value_type='ceiling')
2020
+
2021
+ # 超买区:茅台买家众多,即使在超买区,下跌趋势也比较有限
2022
+ dfs3=backtest_KDJ(ticker,start,end,KDJ_values=[80,80,80],value_type='floor')
2023
+ dfs4=backtest_KDJ(ticker,start,end,KDJ_values=[80,80,90],value_type='floor')
2024
+ dfs4=backtest_KDJ(ticker,start,end,KDJ_values=[80,80,90],J_days=3,value_type='floor')
2025
+
2026
+ # 超卖区
2027
+ dfs12=backtest_KDJ(ticker,start,end,KDJ_values=[20,20,10],value_type='ceiling')
2028
+ dfs15=backtest_KDJ(ticker,start,end,KDJ_values=[20,20,0],J_days=3,value_type='ceiling')
2029
+
2030
+ # K线在D线上方/下方
2031
+ dfs21=backtest_KDJ(ticker,start,end,KDJ_values=[50,50,50],JvsK='above',value_type='floor')
2032
+ dfs22=backtest_KDJ(ticker,start,end,KDJ_values=[50,50,50],JvsK='below',value_type='floor')
2033
+
2034
+ # K/D线交叉
2035
+ dfs31=backtest_KDJ(ticker,start,end,KDJ_values=[50,50,50],JKcross='top down',value_type='floor')
2036
+ dfs32=backtest_KDJ(ticker,start,end,KDJ_values=[50,50,50],JKcross='bottom up',value_type='floor')
2037
+ dfs32=backtest_KDJ(ticker,start,end,KDJ_values=[20,20,10],JKcross='bottom up',value_type='ceiling')
2038
+
2039
+ # 高位死叉
2040
+ dfs33=backtest_KDJ(ticker,start,end,KDJ_values=[80,80,90],JKcross='top down',value_type='floor')
2041
+
2042
+ # 低位金叉
2043
+ dfs34=backtest_KDJ(ticker,start,end,KDJ_values=[20,20,10],JKcross='bottom up',value_type='ceiling')
2044
+
2045
+ def backtest_KDJ(ticker,start,end,
2046
+ KDJ_days=[9,3,3],matypes=[0,0],
2047
+ past_days=3,momemtum_days=0,future_days=3,threshhold=0.005,
2048
+ KDJ_values=[50,50,50],J_days=1,JvsK='any',JKcross='any',
2049
+ value_type='floor'):
2050
+ """
2051
+ 功能:验证KDJ指标对股价未来走势判断的准确度概率
2052
+
2053
+ 注意:可能存在尚不明了的问题!!!
2054
+ """
2055
+ # 计算KDJ指标
2056
+ df=stock_KDJ(ticker,start,end,KDJ_days=KDJ_days,matypes=matypes,graph=False)
2057
+ total_obs=len(df)
2058
+
2059
+ # 计算股价波动率,约等于1,无法作为threshhold使用
2060
+ #=df['Close'].std() / df['Close'].mean()
2061
+
2062
+ # 计算某一日期前后的股价走势及其变化
2063
+ df1=price_trend_technical(df,
2064
+ past_days=past_days,
2065
+ momemtum_days=momemtum_days,future_days=future_days,
2066
+ threshhold=threshhold)
2067
+
2068
+ # KDJ特定情景:股价走势情形概率
2069
+ checkdf,dfs=check_KDJ_market(df1,start,end,
2070
+ KDJ_values=KDJ_values,J_days=J_days,
2071
+ JvsK=JvsK,JKcross=JKcross,
2072
+ value_type=value_type)
2073
+ if checkdf is None:
2074
+ #print(" #Warning(backtest_KDJ): no KDJ signals found for",ticker)
2075
+ print(" Possible reasons: conditions are too strict, or period is too short")
2076
+ return None
2077
+
2078
+ if value_type == 'floor':
2079
+ rel=' > '
2080
+ elif value_type == 'ceiling':
2081
+ rel=' < '
2082
+ else:
2083
+ rel=' ? '
2084
+
2085
+ signals1="K"+rel+str(KDJ_values[0])+', D'+rel+str(KDJ_values[1])+', J'+rel+str(KDJ_values[2])
2086
+
2087
+ signals2=''
2088
+ if J_days > 1:
2089
+ signals2='\n J值条件至少持续'+str(J_days)+'个交易日'
2090
+
2091
+ signals3=''
2092
+ if JvsK=='above':
2093
+ signals3='\n J线在K线上方'
2094
+ elif JvsK=='below':
2095
+ signals3='\n J线在K线下方'
2096
+ else:
2097
+ pass
2098
+
2099
+ signals4=''
2100
+ if JKcross=='bottom up':
2101
+ signals4='\n J/K线出现金叉'
2102
+ elif JKcross=='top down':
2103
+ signals4='\n J/K线出现死叉'
2104
+ else:
2105
+ pass
2106
+
2107
+ signals=signals1 +signals2 +signals3 +signals4
2108
+ print_KDJ_result(checkdf,dfs,signals,ticker,start,end,total_obs,JKcross,
2109
+ past_days,momemtum_days,future_days,threshhold)
2110
+ """
2111
+ market_upward=dfs[dfs['trend_future']=='+']
2112
+ market_downward=dfs[dfs['trend_future']=='-']
2113
+ market_vibrate=dfs[dfs['trend_future']=='~']
2114
+ market_upturn=dfs[dfs['trend_past2future'].isin(['~2+','-2+'])]
2115
+ market_downturn=dfs[dfs['trend_past2future'].isin(['~2-','+2-'])]
2116
+ """
2117
+ return dfs
2118
+
2119
+
2120
+
2121
+ #==============================================================================
2122
+ def print_KDJ_result(checkdf,dfs,KDJ_signals,
2123
+ ticker,start,end,total_obs,JKcross,
2124
+ past_days,momemtum_days,future_days,threshhold):
2125
+ """
2126
+ 功能:打印KDJ验证结果
2127
+ 注意:KDJ_signals需要提前编辑条件字符串输入,例如"K > 80, D > 80, J > 100且至少持续3个交易日"
2128
+ """
2129
+
2130
+ print("\n*** KDJ研判价格趋势:回溯验证,"+ticker_name(ticker))
2131
+ print(" "+start+'至'+end+',共计'+str(total_obs)+'个样本')
2132
+
2133
+ print(" KDJ信号:期间内出现"+str(len(dfs))+'次')
2134
+ print(" "+KDJ_signals)
2135
+
2136
+ print(" 验证参数:")
2137
+ print(" 之前趋势期间:"+str(past_days)+'个交易日')
2138
+
2139
+ if momemtum_days > 0:
2140
+ print(" 滞后变化/提前反应:滞后"+str(momemtum_days)+'个交易日')
2141
+ elif momemtum_days == 0:
2142
+ print(" 滞后变化/提前反应:无,立即变化")
2143
+ else:
2144
+ print(" 滞后变化/提前反应:提前"+str(momemtum_days)+'个交易日')
2145
+
2146
+ print(" 之后趋势期间:"+str(future_days)+'个交易日')
2147
+ print(" 股价变化过滤门限:"+str(threshhold*100)+'%')
2148
+
2149
+ print(" 回溯检验结果:")
2150
+ print(" 之后出现价格上涨趋势的比例:",round(checkdf['future up ratio'].values[0]*100,1),'\b%')
2151
+ print(" 之后出现价格下跌趋势的比例:",round(checkdf['future down ratio'].values[0]*100,1),'\b%')
2152
+ print(" 之后出现价格振荡情形的比例:",round(checkdf['future vibrate ratio'].values[0]*100,1),'\b%')
2153
+
2154
+ """
2155
+ if JKcross =='bottom up':
2156
+ print(" 出现JD金叉后价格上涨的比例:",round(checkdf['t2+ ratio'].values[0]*100,1),'\b%')
2157
+ elif JKcross =='top down':
2158
+ print(" 出现JK死叉后价格下跌的比例:",round(checkdf['t2- ratio'].values[0]*100,1),'\b%')
2159
+ else:
2160
+ pass
2161
+ """
2162
+
2163
+ return
2164
+ #==============================================================================
2165
+ #==============================================================================
2166
+ if __name__ =="__main__":
2167
+ ticker='600519.SS'
2168
+ fromdate='2023-1-1'
2169
+ todate='2023-6-25'
2170
+ boll_days=20
2171
+ graph=True
2172
+ smooth=False
2173
+ loc='best'
2174
+ date_range=False
2175
+ date_freq=False
2176
+ annotate=False
2177
+
2178
+ def security_Bollinger(ticker,start='default',end='default',boll_days=20, \
2179
+ graph=True,smooth=True,loc='best', \
2180
+ date_range=False,date_freq=False,annotate=False, \
2181
+ ticker_type='auto',source='auto',facecolor='white'):
2182
+ """
2183
+ 套壳函数,为了与security_MACD/RSI/KDJ保持相似
2184
+ """
2185
+ df=stock_Bollinger(ticker=ticker,start=start,end=end,boll_days=boll_days, \
2186
+ graph=graph,smooth=smooth,loc=loc, \
2187
+ date_range=date_range,date_freq=date_freq, \
2188
+ annotate=annotate,ticker_type=ticker_type,source=source,facecolor=facecolor)
2189
+ return df
2190
+
2191
+ def stock_Bollinger(ticker,start='default',end='default',boll_days=20, \
2192
+ graph=True,smooth=True,loc='best', \
2193
+ date_range=False,date_freq=False,annotate=False, \
2194
+ mark_end=True,ticker_type='auto',source='auto',facecolor='white'):
2195
+ """
2196
+ 套壳函数,为了与stock_MACD/RSI/KDJ保持相似
2197
+ """
2198
+
2199
+ #=========== 日期转换与检查
2200
+ # 检查日期:截至日期
2201
+ import datetime as dt; today=dt.date.today()
2202
+ if end in ['default','today']:
2203
+ end=today
2204
+ else:
2205
+ validdate,end=check_date2(end)
2206
+ if not validdate:
2207
+ print(" #Warning(stock_Bollinger): invalid date for",end)
2208
+ end=today
2209
+
2210
+ # 检查日期:开始日期
2211
+ if start in ['default']:
2212
+ start=date_adjust(end,adjust=-31)
2213
+ else:
2214
+ validdate,start=check_date2(start)
2215
+ if not validdate:
2216
+ print(" #Warning(stock_Bollinger): invalid date for",start)
2217
+ start=date_adjust(todate,adjust=-31)
2218
+
2219
+ df=security_bollinger(ticker=ticker,fromdate=start,todate=end,boll_days=boll_days, \
2220
+ graph=graph,smooth=smooth,loc=loc, \
2221
+ date_range=date_range,date_freq=date_freq,annotate=annotate, \
2222
+ mark_end=mark_end,ticker_type=ticker_type,source=source,facecolor=facecolor)
2223
+ return df
2224
+
2225
+
2226
+ def security_bollinger(ticker,fromdate,todate,boll_days=20, \
2227
+ graph=True,smooth=True,loc='best', \
2228
+ date_range=False,date_freq=False,annotate=False, \
2229
+ mark_end=True,ticker_type='auto',source='auto',facecolor='white'):
2230
+ """
2231
+ 功能:单个证券,绘制布林带
2232
+ date_range=False:指定开始结束日期绘图
2233
+ date_freq=False:指定横轴日期间隔,例如'D'、'2D'、'W'、'M'等,横轴一般不超过25个标注,否则会重叠
2234
+
2235
+ 解释布林带:
2236
+ 1、上线:压力线;下线:支撑线;均线:中界线
2237
+ 2、上下限之间:置信区间+/-2std
2238
+ 3、价格由下向上穿越下线时,可视为买进信号;价格由下向上穿越中间线时,则有可能加速上行,是加仓买进的信号。
2239
+ 4、价格在中界线与上线之间波动运行时为多头市场,可持多或加码;价格长时间在中界线与上线间运行后,由上往下跌破中间线为卖出信号。
2240
+ 5、价格在中界线与下线之间向下波动运行时为空头市场,可持空或加抛。
2241
+ """
2242
+ # 提前开始日期
2243
+ fromdate1=date_adjust(fromdate,adjust=-365*1)
2244
+
2245
+ try:
2246
+ #pricedf=get_price(ticker,fromdate1,todate)
2247
+ pricedf,found=get_price_1ticker_mixed(ticker=ticker,fromdate=fromdate1, \
2248
+ todate=todate,ticker_type=ticker_type,source=source)
2249
+ except:
2250
+ print(" #Error(security_bollinger): price info not found for",ticker)
2251
+ return None
2252
+ if found not in ['Found']:
2253
+ print(" #Error(security_bollinger): ticker info either inaccessible or not found for",ticker)
2254
+ return None
2255
+
2256
+ # 滚动均值与标准差
2257
+ pricedf['bmiddle']=pricedf["Close"].rolling(window=boll_days).mean()
2258
+ pricedf['bsd']=pricedf["Close"].rolling(window=boll_days).std()
2259
+ pricedf['bupper']=pricedf["bmiddle"] + pricedf['bsd']*2
2260
+ pricedf['blower']=pricedf["bmiddle"] - pricedf['bsd']*2
2261
+
2262
+ df=pricedf[['bupper','bmiddle','blower','Close']]
2263
+ df.rename(columns={'bupper':text_lang('上(压力)线','Upper Line'),'bmiddle':text_lang('中(界)线','Mid Line'),'blower':text_lang('下(支撑)线','Lower Line'),'Close':text_lang('收盘价','Close')},inplace=True)
2264
+
2265
+ # 截取时间段
2266
+ result,start,end=check_period(fromdate,todate)
2267
+ df1=df[(df.index >= start) & (df.index <= end)]
2268
+
2269
+ y_label=text_lang('价格',"Price")
2270
+ import datetime; today = datetime.date.today()
2271
+ x_label=text_lang("数据来源:Sina/EM/Stooq/Yahoo,","Data source: Sina/EM/Stooq/Yahoo, ")+str(today)
2272
+
2273
+ axhline_value=0
2274
+ axhline_label=''
2275
+ title_txt=text_lang("证券技术分析:","Technical Analysis: ")+ticker_name(ticker,ticker_type)+text_lang(",布林带",", Bollinger Band")
2276
+
2277
+ """
2278
+ draw_lines(df1,y_label,x_label,axhline_value,axhline_label,title_txt, \
2279
+ data_label=False,resample_freq='H',smooth=smooth,loc=loc,annotate=annotate)
2280
+ """
2281
+ #plt.rcParams['axes.facecolor']='gray'
2282
+ colorlist=['orange','black','purple','red']
2283
+ lslist=['--',':','-.','-']
2284
+ lwlist=[2.0,2.0,2.0,2.5]
2285
+ draw_lines2(df1,y_label,x_label,axhline_value,axhline_label,title_txt, \
2286
+ data_label=False,resample_freq='6H',smooth=smooth, \
2287
+ date_range=date_range,date_freq=date_freq,date_fmt='%Y-%m-%d', \
2288
+ colorlist=colorlist,lslist=lslist,lwlist=lwlist, \
2289
+ band_area=[text_lang('上(压力)线','Upper Line'),text_lang('下(支撑)线','Lower Line')], \
2290
+ mark_end=mark_end,loc=loc,facecolor=facecolor)
2291
+
2292
+ return df1
2293
+
2294
+ #==============================================================================
2295
+ #==============================================================================
2296
+ if __name__ =="__main__":
2297
+ ticker='600809.SS'
2298
+ start='2013-11-06'
2299
+ end='2024-02-29'
2300
+ boll_years=7
2301
+ graph=True
2302
+ smooth=False
2303
+ loc='best'
2304
+ date_range=False
2305
+ date_freq=False
2306
+ annotate=False
2307
+
2308
+ indicator='PE'
2309
+
2310
+ def security_Bubble(ticker,start='default',end='default',boll_years=7, \
2311
+ indicator='MV', \
2312
+ graph=True,smooth=True,loc='best', \
2313
+ date_range=False,date_freq=False,annotate=False, \
2314
+ mark_end=True,facecolor='whitesmoke'):
2315
+ """
2316
+ 套壳函数,为了与security_MACD/RSI/KDJ保持相似
2317
+ """
2318
+
2319
+ #=========== 日期转换与检查
2320
+ # 检查日期:截至日期
2321
+ import datetime as dt; todaydt=dt.date.today()
2322
+ if end in ['default','today']:
2323
+ end=todaydt
2324
+ else:
2325
+ validdate,end=check_date2(end)
2326
+ if not validdate:
2327
+ print(" #Warning(security_Bubble): invalid date for",end)
2328
+ end=todaydt
2329
+
2330
+ # 检查日期:开始日期
2331
+ if start in ['default']:
2332
+ start=date_adjust(end,adjust=-31)
2333
+ else:
2334
+ validdate,start=check_date2(start)
2335
+ if not validdate:
2336
+ print(" #Warning(security_Bubble): invalid date for",start)
2337
+ start=date_adjust(todate,adjust=-31)
2338
+
2339
+ df=security_bubble(ticker=ticker,fromdate=start,todate=end,boll_years=boll_years, \
2340
+ indicator=indicator, \
2341
+ graph=graph,smooth=smooth,loc=loc, \
2342
+ date_range=date_range,date_freq=date_freq,annotate=annotate, \
2343
+ mark_end=mark_end,facecolor=facecolor)
2344
+ return df
2345
+
2346
+ if __name__ =="__main__":
2347
+ ticker='600519.SS'
2348
+ ticker='AAPL'
2349
+ fromdate='2023-7-3'
2350
+ todate='2024-2-21'
2351
+ boll_years=7
2352
+ indicator='MV'
2353
+
2354
+ def security_bubble(ticker,fromdate,todate,boll_years=7, \
2355
+ indicator='MV', \
2356
+ graph=True,smooth=True,loc='best', \
2357
+ date_range=False,date_freq=False,annotate=False, \
2358
+ mark_end=True,facecolor='whitesmoke'):
2359
+ """
2360
+ 功能:单个证券,绘制估值布林带(MV, PE, PB)
2361
+ date_range=False:指定开始结束日期绘图
2362
+ date_freq=False:指定横轴日期间隔,例如'D'、'2D'、'W'、'M'等,横轴一般不超过25个标注,否则会重叠
2363
+
2364
+ 解释布林带:
2365
+ 1、上线:压力线;下线:支撑线;均线:中界线
2366
+ 2、上下限之间:置信区间+/-2std,2倍标准差的含义是落在此区间内的概率不低于95%!
2367
+ 3、估值由下向上穿越下线时,可视为买进信号;估值由下向上穿越中间线时,则有可能加速上行,是加仓买进的信号。
2368
+ 4、估值在中界线与上线之间波动运行时为多头市场,可持多或加码;估值长时间在中界线与上线间运行后,由上往下跌破中间线为卖出信号。
2369
+ 5、估值在中界线与下线之间向下波动运行时为空头市场,可持空或加抛。
2370
+ """
2371
+ if not isinstance(ticker,str):
2372
+ print(" #Warning(security_bubble): not a valid stock code format for",ticker)
2373
+ return None
2374
+
2375
+ if indicator.lower()=='mv':
2376
+ indicatorname='市值(十亿)'
2377
+ elif indicator.lower()=='pe':
2378
+ indicatorname='市盈率'
2379
+ elif indicator.lower()=='pb':
2380
+ indicatorname='市净率'
2381
+ elif indicator.lower()=='roe':
2382
+ indicatorname='净资产回报率'
2383
+ else:
2384
+ print(" #Error(security_bubble): unsupported valuation indicator for",indicator)
2385
+ return None
2386
+
2387
+ # 提前开始日期
2388
+ fromdate1=date_adjust(fromdate,adjust=-365*boll_years)
2389
+
2390
+ try:
2391
+ val=get_valuation(ticker,indicator,fromdate1,todate)
2392
+ colname=list(val[indicator])[0]
2393
+ pricedf=val[indicator]
2394
+ pricedf.rename(columns={colname:indicator},inplace=True)
2395
+ except:
2396
+ print(" #Error(security_bubble): valuation info not found for",ticker,'on',indicator)
2397
+ return None
2398
+
2399
+ # 滚动均值与标准差
2400
+ #注意:A股估值数据为非日频,港股/美股/波兰股为日频
2401
+ day0=pricedf.index[0].strftime("%Y-%m-%d")
2402
+ day1=pricedf.index[-1].strftime("%Y-%m-%d")
2403
+ day_diff=calculate_days(day0,day1)
2404
+ annualTradeDays=len(pricedf) / (day_diff/365)
2405
+ #boll_days=int(boll_years*annualTradeDays+0.5)
2406
+ boll_days=int(boll_years*annualTradeDays)
2407
+ """
2408
+ _,_,suffix=split_prefix_suffix(ticker)
2409
+ if suffix in ['SS','SZ','BJ']:
2410
+ boll_days=int(boll_years*annualTradeDays)
2411
+ else:
2412
+ boll_days=boll_years*252
2413
+ """
2414
+ if boll_days >= len(pricedf):
2415
+ boll_years=int(len(pricedf)/annualTradeDays)
2416
+ boll_days=int(boll_years*annualTradeDays)
2417
+
2418
+ record_left=len(pricedf)-boll_days
2419
+
2420
+ if record_left < 65:#一个季度
2421
+ boll_years=boll_years-1
2422
+ #boll_days=boll_years*252
2423
+ boll_days=int(boll_years*annualTradeDays)
2424
+ record_left=len(pricedf)-boll_days
2425
+
2426
+ pricedf_left=pricedf.tail(record_left)
2427
+ start_left=pricedf_left.index[0].strftime('%Y-%m-%d')
2428
+
2429
+ pricedf['bmiddle']=pricedf[indicator].rolling(window=boll_days).mean()
2430
+ pricedf['bsd']=pricedf[indicator].rolling(window=boll_days).std()
2431
+ pricedf['bupper']=pricedf["bmiddle"] + pricedf['bsd']*2
2432
+ pricedf['blower']=pricedf["bmiddle"] - pricedf['bsd']*2
2433
+
2434
+ df=pricedf[['bupper','bmiddle','blower',indicator]]
2435
+
2436
+ df.rename(columns={'bupper':'上(压力)线','bmiddle':'中(界)线','blower':'下(支撑)线',indicator:indicatorname},inplace=True)
2437
+
2438
+ # 截取时间段
2439
+ result,start,end=check_period(fromdate,todate)
2440
+ start1=date_adjust(start,adjust=-10)
2441
+ if start_left > start1:
2442
+ start1=start_left
2443
+
2444
+ df1=df[(df.index >= start1) & (df.index <= end)]
2445
+
2446
+ y_label=indicatorname
2447
+ import datetime; today = datetime.date.today()
2448
+ x_label="数据来源:Sina/EM/Stooq/Yahoo,"+str(today)+",估值前置窗口期"+str(boll_years)+"年"
2449
+
2450
+ axhline_value=0
2451
+ axhline_label=''
2452
+ title_txt="证券估值趋势布林带:"+ticker_name(ticker)
2453
+
2454
+ """
2455
+ draw_lines(df1,y_label,x_label,axhline_value,axhline_label,title_txt, \
2456
+ data_label=False,resample_freq='H',smooth=smooth,loc=loc,annotate=annotate)
2457
+ """
2458
+ #plt.rcParams['axes.facecolor']='gray'
2459
+ colorlist=['orange','black','purple','red']
2460
+ lslist=['--',':','-.','-']
2461
+ lwlist=[2.0,2.0,2.0,2.5]
2462
+ draw_lines2(df1,y_label,x_label,axhline_value,axhline_label,title_txt, \
2463
+ data_label=False,resample_freq='6H',smooth=smooth, \
2464
+ date_range=date_range,date_freq=date_freq,date_fmt='%Y-%m-%d', \
2465
+ colorlist=colorlist,lslist=lslist,lwlist=lwlist, \
2466
+ band_area=['上(压力)线','下(支撑)线'],loc=loc, \
2467
+ mark_end=mark_end,facecolor=facecolor)
2468
+
2469
+ return df1
2470
+
2471
+ #==============================================================================
2472
+ if __name__ =="__main__":
2473
+ ticker='600519.SS'
2474
+ ticker='600809.SS'
2475
+ start='MRH'
2476
+ start='L10Y'
2477
+ end='default'
2478
+
2479
+ start='2023-1-1'
2480
+ end='2024-2-20'
2481
+
2482
+ boll_years=7
2483
+ technical='MV'
2484
+ technical='Bollinger'
2485
+ indicator='PE'
2486
+
2487
+ def security_technical(ticker,technical=['MACD'],indicator='Close', \
2488
+ start='default',end='default', \
2489
+ MA_days=[5,20],EMA_days=[5,20], \
2490
+ MACD_fastperiod=12,MACD_slowperiod=26,MACD_signalperiod=9, \
2491
+
2492
+ #RSI参数个数必须为3个,否则出错
2493
+ RSI_days=[6,12,24],RSI_lines=[20,50,80], \
2494
+ KDJ_days=[9,3,3],matypes=[0,0],KDJ_lines=[20,50,80], \
2495
+ boll_days=20,boll_years=7, \
2496
+
2497
+ resample_freq='6H',smooth=True,linewidth=1.5, \
2498
+ loc1='best',loc2='best', \
2499
+ graph=['ALL'],printout=False, \
2500
+ date_range=False,date_freq=False,annotate=False, \
2501
+ ticker_type='auto',source='auto', \
2502
+ price_line_color='red', \
2503
+ facecolor='k'):
2504
+
2505
+ """
2506
+ ===========================================================================
2507
+ 功能:技术分析指标的深度分析,适用于MACD/RSI/KDJ/Bollinger,支持金叉/死叉分析等。
2508
+ 支持的产品:全球股票,债券(限中国内地的上市债券),基金(支持中国和美国的上市基金)。
2509
+ 主要参数:
2510
+ ticker:证券代码
2511
+ technical:技术分析指标,仅支持'MACD'、'RSI'、'KDJ'、'Bollinger',可一次指定多个。
2512
+ start/end:起止日期。支持简洁方式,仅需使用start指定近期的期间长度。简洁方式如下:
2513
+ mrw(近1周),l2w(近2周),l3w(近3周),mrm(近1个月),l2m(近2个月),
2514
+ mrq(近3个月),mrh(近6个月),mry(近1年),l2y(近2年),l3y(近3年),
2515
+ l5y(近5年),l8y(近8年),l10y(近10年),l20y(近20年),l30y(近30年),
2516
+ ytd(今年以来)
2517
+ 技术分析指标的参数,可手动调节,默认如下:
2518
+ MACD_fastperiod=12,MACD_slowperiod=26,MACD_signalperiod=9
2519
+ MA_days=[5,20],EMA_days=[5,20]
2520
+ RSI_days=[6,12,24],RSI_lines=[20,50,80]
2521
+ KDJ_days=[9,3,3],matypes=[0,0],KDJ_lines=[20,50,80]
2522
+ boll_days=20,boll_years=7
2523
+ loc1/loc2:第1和第2图例的位置,默认'best'。如果发生重合或位置不合适,可手动调节。
2524
+ graph:显示的结果图,默认'ALL'。分析MACD时若只希望看到MACD图,可指定'MACD'。
2525
+ annotate:是否希望将图例标示在曲线末端,默认False,防止标示文字重叠。
2526
+ ticker_type:证券种类,默认'auto'。可强制指定股票'stock'、债券'bond'、基金'fund'。
2527
+ source:证券价格数据源,默认'auto'。若指定'yahoo'需要特别连接互联网。
2528
+ price_line_color:价格曲线颜色,默认'red'。
2529
+ facecolor:背景颜色,默认黑色。
2530
+ 注意:白色背景可能遮盖部分曲线,不建议;
2531
+ 黑色背景可能与布林带中线文字标示颜色冲突,可手动指定其他颜色,例如'whitesmoke'。
2532
+
2533
+ 其他说明:套壳函数security_MACD/RSI/KDJ/Bollinger
2534
+ """
2535
+
2536
+ # 检查日期:如有错误自动更正
2537
+ fromdate,todate=start_end_preprocess(start=start,end=end)
2538
+
2539
+ # 检查类别
2540
+ if isinstance(technical,str):
2541
+ technical1=[technical]
2542
+ else:
2543
+ technical1=technical
2544
+
2545
+ technical_list=['MACD','RSI','KDJ','Bollinger']
2546
+ technical1 = [x.upper() for x in technical1]
2547
+ technical1 = [x.replace('BOLLINGER','Bollinger') if 'BOLLINGER' in x else x for x in technical1]
2548
+
2549
+ for t in technical1:
2550
+ if t not in technical_list:
2551
+ print(" Warning(security_technical): unsupported technical pattern",t)
2552
+ print(" Supported patterns:",technical_list)
2553
+ return None
2554
+
2555
+ #检查布林带的指标
2556
+ if isinstance(indicator,str):
2557
+ indicator1=[indicator]
2558
+ else:
2559
+ indicator1=indicator
2560
+
2561
+ indicator_list1=['MV','PE','PB','Close','ROE']
2562
+ indicator1 = [x.upper() for x in indicator1]
2563
+ indicator1 = [x.replace('CLOSE','Close') if 'CLOSE' in x else x for x in indicator1]
2564
+
2565
+ for t in indicator1:
2566
+ if t not in indicator_list1:
2567
+ print(" Warning(security_technical): unsupported Bollinger indicator",t)
2568
+ print(" Supported Bollinger indicator:",indicator_list1)
2569
+ return None
2570
+
2571
+ # 检查绘图种类
2572
+ if isinstance(graph,str):
2573
+ graph1=[graph]
2574
+ else:
2575
+ graph1=graph
2576
+
2577
+ if 'MACD' in technical1:
2578
+ df=security_MACD(ticker=ticker,start=fromdate,end=todate, \
2579
+ MA_days=MA_days,EMA_days=EMA_days, \
2580
+ MACD_fastperiod=MACD_fastperiod,MACD_slowperiod=MACD_slowperiod,MACD_signalperiod=MACD_signalperiod, \
2581
+ resample_freq=resample_freq,smooth=smooth,linewidth=linewidth, \
2582
+ loc1=loc1,loc2=loc2, \
2583
+ graph=graph1,printout=printout,ticker_type=ticker_type,source=source, \
2584
+ price_line_color=price_line_color,facecolor=facecolor)
2585
+
2586
+ if 'RSI' in technical1:
2587
+ df=security_RSI(ticker=ticker,start=fromdate,end=todate, \
2588
+ RSI_days=RSI_days,RSI_lines=RSI_lines, \
2589
+ resample_freq=resample_freq,smooth=smooth,linewidth=linewidth, \
2590
+ loc1=loc1,loc2=loc2, \
2591
+ graph=graph1,printout=printout,ticker_type=ticker_type,source=source, \
2592
+ price_line_color=price_line_color,facecolor=facecolor)
2593
+
2594
+ if 'KDJ' in technical1:
2595
+ df=security_KDJ(ticker=ticker,start=fromdate,end=todate, \
2596
+ KDJ_days=KDJ_days,matypes=matypes,KDJ_lines=KDJ_lines, \
2597
+ resample_freq=resample_freq,smooth=smooth,linewidth=linewidth, \
2598
+ loc1=loc1,loc2=loc2, \
2599
+ graph=graph1,printout=printout,ticker_type=ticker_type,source=source, \
2600
+ price_line_color=price_line_color,facecolor=facecolor)
2601
+
2602
+ if 'Bollinger' in technical1 and 'Close' in indicator1:
2603
+ df=security_Bollinger(ticker=ticker,start=fromdate,end=todate,boll_days=boll_days, \
2604
+ graph=True,smooth=smooth,loc=loc1, \
2605
+ date_range=date_range,date_freq=date_freq,annotate=annotate, \
2606
+ ticker_type=ticker_type,source=source,facecolor=facecolor)
2607
+
2608
+ """
2609
+ if 'Bollinger' in technical1 and 'MV' in indicator1:
2610
+ df=security_Bubble(ticker=ticker,start=fromdate,end=todate,boll_years=boll_years, \
2611
+ indicator='MV', \
2612
+ graph=True,smooth=smooth,loc=loc1, \
2613
+ date_range=date_range,date_freq=date_freq,annotate=annotate)
2614
+
2615
+ if 'Bollinger' in technical1 and 'PE' in indicator1:
2616
+ df=security_Bubble(ticker=ticker,start=fromdate,end=todate,boll_years=boll_years, \
2617
+ indicator='PE', \
2618
+ graph=True,smooth=smooth,loc=loc1, \
2619
+ date_range=date_range,date_freq=date_freq,annotate=annotate)
2620
+
2621
+ if 'Bollinger' in technical1 and 'PB' in indicator1:
2622
+ df=security_Bubble(ticker=ticker,start=fromdate,end=todate,boll_years=boll_years, \
2623
+ indicator='PB', \
2624
+ graph=True,smooth=smooth,loc=loc1, \
2625
+ date_range=date_range,date_freq=date_freq,annotate=annotate)
2626
+
2627
+ if 'Bollinger' in technical1 and 'ROE' in indicator1:
2628
+ df=security_Bubble(ticker=ticker,start=fromdate,end=todate,boll_years=boll_years, \
2629
+ indicator='ROE', \
2630
+ graph=True,smooth=smooth,loc=loc1, \
2631
+ date_range=date_range,date_freq=date_freq,annotate=annotate)
2632
+ """
2633
+
2634
+ if 'Bollinger' in technical1:
2635
+ vallist=['MV','PE','PB','ROE']
2636
+ #if val in vallist and val in indicator1: #只能处理股票估值,无需ticker_type
2637
+ if any(val in indicator1 for val in vallist):
2638
+ val=list(set(indicator1).intersection(set(vallist)))[0] #找出2个列表中第1个共同元素
2639
+ df=security_Bubble(ticker=ticker,start=fromdate,end=todate,boll_years=boll_years, \
2640
+ indicator=val, \
2641
+ graph=True,smooth=smooth,loc=loc1, \
2642
+ date_range=date_range,date_freq=date_freq,annotate=annotate,facecolor=facecolor)
2643
+
2644
+ return df
2645
+
2646
+ #==============================================================================
2647
+ #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2648
+ if __name__ =="__main__":
2649
+ RSI_days=[6,24]
2650
+ OBV_days=[5,10]
2651
+ MA_days=[5,20]; EMA_days=[5,20]
2652
+ MACD_fastperiod=12; MACD_slowperiod=26; MACD_signalperiod=9
2653
+ KDJ_fastk_period=5; KDJ_slowk_period=3; KDJ_slowk_matype=0; KDJ_slowd_period=3; KDJ_slowd_matype=0
2654
+ VOL_fastperiod=5; VOL_slowperiod=10
2655
+ PSY_days=12
2656
+ ARBR_days=26
2657
+ CR_day=16; CR_madays=[5,10,20]
2658
+ EMV_day=14; EMV_madays=9
2659
+ BULL_day=20; BULL_nbdevup=2; BULL_nbdevdn=2; BULL_matype=0
2660
+ TRIX_day=12; TRIX_madays=20
2661
+ DMA_fastperiod=10; DMA_slowperiod=50; DMA_madays=10
2662
+ BIAS_days=[6,12,24]
2663
+ CCI_days=[6,12]
2664
+ WR_days=[10,6]
2665
+ ROC_day=12; ROC_madays=6
2666
+ DMI_DIdays=14; DMI_ADXdays=6
2667
+ MFI_day=14; MFI_madays=[6]
2668
+ MOM_day=12; MOM_madays=6
2669
+ SAR_day=4; SAR_madays=[5,20]
2670
+ BETA_day=5; BETA_madays=[5,20]
2671
+ TSF_day=14; TSF_madays=[5,10]
2672
+ AD_madays=[5]
2673
+
2674
+
2675
+ ticker='002594.SZ';ticker_type='auto'
2676
+ start='2024-5-1'; end='2024-6-13'; ahead_days=30*8
2677
+ technical='BIAS'; indicator='Close'
2678
+
2679
+ attention_values=[0,25,50,75]
2680
+ more_details=True
2681
+ ticker_type='auto'; source='auto'
2682
+ ahead_days=30*8
2683
+ resample_freq='6H'; smooth=True;linewidth=1.5
2684
+ date_range=False; date_freq=False; annotate=False
2685
+ graph=['ALL']; printout=False
2686
+ loc1='best'; loc2='best'
2687
+
2688
+ facecolor=['whitesmoke','papayawhip']
2689
+ price_line_style='dotted'; price_line_color='red'; price_line_width=5; price_line_marker='.'
2690
+ marker_sizes=[30,120,250]
2691
+
2692
+ df=security_technical2(ticker='AAPL',start='2024-5-1',end='2024-6-20', \
2693
+ technical='CR',more_details=True,loc1='upper left',loc2='lower right')
2694
+
2695
+ tlist=['RSI','OBV','MACD','KDJ','VOL','PSY','ARBR','CR','EMV','Bollinger', \
2696
+ 'TRIX','DMA','BIAS','CCI','W%R','ROC','DMI']
2697
+ for t in tlist:
2698
+ df=security_technical2(ticker,start,end,technical=t,loc1='lower left',loc2='lower right')
2699
+
2700
+ def security_technical2(ticker,start='default',end='default',technical='MACD', \
2701
+
2702
+ #不建议使用复权价,因为最高最低价开盘价没有复权价!
2703
+ indicator='Close', \
2704
+
2705
+ #显示指标本身,如果原来未显示的话
2706
+ more_details=False, \
2707
+
2708
+ #显示关注值水平线,每个指标不同,可自定义多个关注值
2709
+ attention_values=[], \
2710
+
2711
+ ticker_type='auto',source='auto', \
2712
+
2713
+ #指标的默认参数
2714
+ RSI_days=[6,14], \
2715
+
2716
+ OBV_days=[5,10], \
2717
+
2718
+ MA_days=[5,20],EMA_days=[5,20], \
2719
+
2720
+ MACD_fastperiod=12,MACD_slowperiod=26,MACD_signalperiod=9, \
2721
+
2722
+ KDJ_fastk_period=9,KDJ_slowk_period=5,KDJ_slowk_matype=1,KDJ_slowd_period=5,KDJ_slowd_matype=1, \
2723
+
2724
+ VOL_fastperiod=5,VOL_slowperiod=10, \
2725
+
2726
+ PSY_days=[6,12], \
2727
+
2728
+ ARBR_days=[26], \
2729
+
2730
+ CR_day=30,CR_madays=[10,20,40,60], \
2731
+
2732
+ EMV_day=14,EMV_madays=[9], \
2733
+
2734
+ BULL_day=20,BULL_nbdevup=2,BULL_nbdevdn=2,BULL_matype=0, \
2735
+
2736
+ DMA_fastperiod=10,DMA_slowperiod=50,DMA_madays=[10], \
2737
+
2738
+ TRIX_day=12,TRIX_madays=[20], \
2739
+
2740
+ BIAS_days=[6,12,24], \
2741
+
2742
+ CCI_days=[6,12], \
2743
+
2744
+ WR_days=[13,34,89], \
2745
+
2746
+ ROC_day=12,ROC_madays=[65,12,18], \
2747
+
2748
+ DMI_DIdays=7,DMI_ADXdays=6, \
2749
+
2750
+ #资金流:
2751
+ MFI_day=14,MFI_madays=[6], \
2752
+
2753
+ MOM_day=12,MOM_madays=6, \
2754
+
2755
+ #需要显示SAR
2756
+ SAR_day=4,SAR_madays=[5,20], \
2757
+
2758
+ #需要显示BETA
2759
+ BETA_day=5,BETA_madays=[5,20], \
2760
+
2761
+ #需要显示TSF
2762
+ TSF_day=14,TSF_madays=[5,10], \
2763
+
2764
+ #需要显示AD
2765
+ AD_madays=[], \
2766
+
2767
+ #数据提前量,用于前置计算指标的移动平均值
2768
+ ahead_days=30*8, \
2769
+
2770
+ #指标线的绘图参数
2771
+ resample_freq='2H',smooth=True,linewidth=1.5, \
2772
+ date_range=False,date_freq=False, \
2773
+
2774
+ #启用,替代loc1图例
2775
+ annotate=True, \
2776
+
2777
+ #除了MACD外,其他指标均应为ALL
2778
+ graph=['ALL'], \
2779
+ printout=False, \
2780
+ loc1='best',loc2='best', \
2781
+
2782
+ #图形上下半区的背景颜色
2783
+ facecolor=['whitesmoke','papayawhip'], \
2784
+
2785
+ #价格线的绘图参数
2786
+ #price_line_style=(0,(1,1)), \
2787
+ price_line_style=(0,(3,1,1,1,1,1)), \
2788
+ price_line_color=['red','green'], \
2789
+ price_line_width=1,price_line_marker='o', \
2790
+ marker_sizes=[30,120,250], \
2791
+ ):
2792
+ """
2793
+ ===========================================================================
2794
+ 功能:技术分析指标的短线德宏图,建议两个月内,适合观察日差价和价量关系变化,图示简洁。
2795
+ 主要参数:
2796
+ ticker:证券代码,除美股外需要交易所后缀,例如港股小米'01810.HK',美股苹果'AAPL'
2797
+ start:开始日期,格式YYYY-MM-DD,默认一个月前
2798
+ end:结束日期,格式与start相同,默认已收盘的最近交易日
2799
+ technical:技术分析指标,默认为MACD,单次仅可指定一个指标。支持的指标如下:
2800
+ Bollinger:布林带
2801
+ MACD:移动异同平均线
2802
+ RSI:相对强弱
2803
+ KDJ:随机指标
2804
+ OBV:能量潮
2805
+ SAR:抛物线/停损转向指标
2806
+ VOL:成交量指标
2807
+ ARBR:人气(AR)/意愿(BR)指标
2808
+ CR:中间意愿指标
2809
+ EMV:简易波动
2810
+ TRIX:三重指数平滑均线
2811
+ DMA:均线差
2812
+ BIAS:乖离率
2813
+ CCI:顺势指标
2814
+ W%R:威廉超买/超卖指标
2815
+ ROC:变动率
2816
+ DMI:动向指标
2817
+ PSY:心理线
2818
+ MFI:资金流向指标
2819
+ MOM:动量指标
2820
+ BETA:移动贝塔系数
2821
+ TSF:时间序列分析
2822
+ AD:集散指标
2823
+ MA:移动平均
2824
+ EMA:指数移动平均
2825
+
2826
+ RSI_days:默认[6,14]
2827
+ OBV_days:默认[5,10]
2828
+ MA_days:默认[5,20]
2829
+ EMA_days:默认[5,20];EMV_day:默认14:EMV_madays:默认[9]
2830
+ MACD_fastperiod:默认12;MACD_slowperiod:默认26;MACD_signalperiod:默认9
2831
+ KDJ_fastk_period:默认9;KDJ_slowk_period:默认5;KDJ_slowk_matype:默认1
2832
+ KDJ_slowd_period:默认5;KDJ_slowd_matype:默认1
2833
+ VOL_fastperiod:默认5;VOL_slowperiod:默认10
2834
+ PSY_days:默认[6,12]
2835
+ ARBR_days:默认[26]
2836
+ CR_day:默认30;CR_madays:默认[10,20,40,60]
2837
+ BULL_day:默认20;BULL_nbdevup:默认2;BULL_nbdevdn:默认2;BULL_matype:默认0
2838
+ DMA_fastperiod:默认10;DMA_slowperiod:默认50;DMA_madays:默认[10]
2839
+ TRIX_day:默认12;TRIX_madays:默认[20]
2840
+ BIAS_days:默认[6,12,24]
2841
+ CCI_days:默认[6,12]
2842
+ WR_days:默认[13,34,89]
2843
+ ROC_day:默认12;ROC_madays:默认[65,12,18]
2844
+ DMI_DIdays:默认7;DMI_ADXdays:默认6
2845
+ MFI_day:默认14;MFI_madays:默认[6]
2846
+ MOM_day:默认12;MOM_madays:默认6
2847
+ SAR_day:默认4;SAR_madays:默认[5,20]
2848
+ BETA_day:默认5;BETA_madays:默认[5,20]
2849
+ TSF_day:默认14;TSF_madays:默认[5,10]
2850
+ AD_madays:默认[]
2851
+
2852
+ more_details:显示指标本身,如果原来未显示的话。默认不显示
2853
+ attention_values:显示关注值水平线,每个技术指标可能不同,可使用列表自定义多个关注值
2854
+ ticker_type:证券类别,默认'auto'。如果识别错误,可强制指定'stock'、'bond'、'fund'
2855
+ source:证券价格来源,默认'auto'。特殊来源可自行指定
2856
+
2857
+ loc1:第1个图例的位置,默认'best'。当annotate=True时被替代
2858
+ loc2:第2个图例的位置,默认'best'。可手动指定9个位置,例如'upper left'左上角等
2859
+ facecolor:图形上下半区的背景颜色,默认['whitesmoke','papayawhip']
2860
+ attention_values:关注的阈值,默认[0,25,50,75,100], 可以自定义
2861
+
2862
+ 下列指标可以使用强化指令security_technical:MACD、RSI、KDJ、Bollinger
2863
+ """
2864
+ # 检查ta-lib是否安装,避免浪费后续的处理
2865
+ try:
2866
+ import talib
2867
+ except:
2868
+ print(" #Error(security_technical2): lack of necessary package - talib")
2869
+ talib_install_method()
2870
+ return None
2871
+
2872
+ #检查证券代码
2873
+ if not isinstance(ticker,str):
2874
+ print(" #Warning(security_technical2): not a security code for",ticker)
2875
+ return None
2876
+
2877
+ #检查indicator
2878
+ if indicator not in ['Open','Close','High','Low','Adj Close']:
2879
+ print(" #Warning(security_technical2): not a valid price type for",indicator)
2880
+ return None
2881
+
2882
+ #检查日期:如有错误自动更正
2883
+ fromdate,todate=start_end_preprocess(start=start,end=end)
2884
+
2885
+ #检查指标类别
2886
+ tech_list={'Bollinger':text_lang('布林带','Bollinger Band'), \
2887
+ 'MACD':text_lang('移动异同平均线','Moving Average Convergence Divergence'), \
2888
+ 'RSI':text_lang('相对强弱','Relative Strength Index'), \
2889
+ 'KDJ':text_lang('随机指标','Stochastics'), \
2890
+ 'OBV':text_lang('能量潮','On-Balance-Volume'), \
2891
+ 'SAR':text_lang('抛物线/停损转向指标','Stop and Reverse Indicator'), \
2892
+ 'VOL':text_lang('成交量指标','Volume Indicator'), \
2893
+ 'ARBR':text_lang('人气(AR)意愿(BR)指标','Emotion AR & Willingness BR'), \
2894
+ 'CR':text_lang('中间意愿指标','Commodity Channel Index Reversal'), \
2895
+ 'EMV':text_lang('简易波动','Ease of Movement Value'), \
2896
+ 'TRIX':text_lang('三重指数平滑均线','Triple Exponentially Smoothed Moving Average'), \
2897
+ 'DMA':text_lang('均线差','Difference in Moving Averages'), \
2898
+ 'BIAS':text_lang("乖离率",'Bias Indicator'), \
2899
+ 'CCI':text_lang('顺势指标','Commodity Channel Index'), \
2900
+ 'W%R':text_lang('威廉超买/超卖指标','William Overbought/Oversold Index'), \
2901
+ 'ROC':text_lang('变动率','Rate of Change'), \
2902
+ 'DMI':text_lang('动向指标','Directional Movement Index'), \
2903
+ 'PSY':text_lang('心理线','Phycholoigical Line'), \
2904
+ 'MFI':text_lang('资金流向指标','Money Flow Index'), \
2905
+ 'MOM':text_lang('动量指标','Momentum'), \
2906
+ 'BETA':text_lang("移动贝塔系数",'Moving Beta Coefficient'), \
2907
+ 'TSF':text_lang("时间序列分析",'Time Series Forecasting'), \
2908
+ 'AD':text_lang('集散指标','Accumulation/Distribution'), \
2909
+ 'MA':text_lang('移动平均','Moving Average'), \
2910
+ 'EMA':text_lang('指数移动平均','Exponential Moving Average')}
2911
+
2912
+ technical1=technical
2913
+ if isinstance(technical,list):
2914
+ technical1=technical[0]
2915
+ technical1=technical1.upper()
2916
+ if technical1 == 'BOLLINGER': technical1=technical1.title()
2917
+
2918
+ if technical1 not in list(tech_list):
2919
+ print(" #Warning(security_technical2): unsupported technical pattern",technical)
2920
+ print(" Supported patterns:",list(tech_list))
2921
+ return None
2922
+
2923
+ #抓取抓取价格数据
2924
+ fromdate1=date_adjust(fromdate,adjust=-ahead_days)
2925
+ if 'Adj' in indicator.title():
2926
+ adjust='Adj_only' #最高最低价开盘收盘价均为复权价
2927
+ else:
2928
+ adjust=''
2929
+
2930
+ price,found=get_price_1ticker_mixed(ticker=ticker,fromdate=fromdate1,adjust=adjust, \
2931
+ todate=todate,ticker_type=ticker_type,fill=False,source=source)
2932
+
2933
+ if found not in ['Found']:
2934
+ print(" #Warning(security_technical2): no prices found for",ticker,'as type',ticker_type)
2935
+ return None
2936
+
2937
+ #当日涨跌
2938
+ price['up_down']=price['Close']-price['Open']
2939
+ price['up_down_abs']=abs(price['up_down'])
2940
+
2941
+ #分位数
2942
+ import numpy as np
2943
+ q70=np.percentile(price['up_down_abs'],70)
2944
+ q30=np.percentile(price['up_down_abs'],30)
2945
+
2946
+ small_size=marker_sizes[0]; mid_size=marker_sizes[1]; big_size=marker_sizes[2]
2947
+ price['marker_size']=price['up_down_abs'].apply(lambda x: big_size if x>=q70 else mid_size if x>=q30 else small_size)
2948
+
2949
+ #计算技术指标
2950
+ df,calculated=calc_technical(price,fromdate,todate,technical=technical, \
2951
+
2952
+ RSI_days=RSI_days, \
2953
+ OBV_days=OBV_days, \
2954
+
2955
+ MA_days=MA_days,EMA_days=EMA_days, \
2956
+
2957
+ MACD_fastperiod=MACD_fastperiod,MACD_slowperiod=MACD_slowperiod,MACD_signalperiod=MACD_signalperiod, \
2958
+
2959
+ KDJ_fastk_period=KDJ_fastk_period,KDJ_slowk_period=KDJ_slowk_period, \
2960
+ KDJ_slowk_matype=KDJ_slowk_matype,KDJ_slowd_period=KDJ_slowd_period,KDJ_slowd_matype=KDJ_slowd_matype, \
2961
+
2962
+ VOL_fastperiod=VOL_fastperiod,VOL_slowperiod=VOL_slowperiod, \
2963
+
2964
+ PSY_days=PSY_days, \
2965
+ ARBR_days=ARBR_days, \
2966
+ CR_day=CR_day,CR_madays=CR_madays, \
2967
+ EMV_day=EMV_day,EMV_madays=EMV_madays, \
2968
+
2969
+ BULL_day=BULL_day,BULL_nbdevup=BULL_nbdevup,BULL_nbdevdn=BULL_nbdevdn,BULL_matype=BULL_matype, \
2970
+
2971
+ DMA_fastperiod=DMA_fastperiod,DMA_slowperiod=DMA_slowperiod,DMA_madays=DMA_madays, \
2972
+
2973
+ TRIX_day=TRIX_day,TRIX_madays=TRIX_madays, \
2974
+ BIAS_days=BIAS_days, \
2975
+ CCI_days=CCI_days, \
2976
+ WR_days=WR_days, \
2977
+ ROC_day=ROC_day,ROC_madays=ROC_madays, \
2978
+ DMI_DIdays=DMI_DIdays,DMI_ADXdays=DMI_ADXdays, \
2979
+
2980
+ MFI_day=MFI_day,MFI_madays=MFI_madays, \
2981
+ MOM_day=MOM_day,MOM_madays=MOM_madays, \
2982
+
2983
+ #需要显示SAR
2984
+ SAR_day=SAR_day,SAR_madays=SAR_madays, \
2985
+
2986
+ #需要显示BETA
2987
+ BETA_day=BETA_day,BETA_madays=BETA_madays, \
2988
+
2989
+ #需要显示TSF
2990
+ TSF_day=TSF_day,TSF_madays=TSF_madays, \
2991
+
2992
+ #需要显示AD
2993
+ AD_madays=AD_madays, \
2994
+
2995
+ indicator=indicator, \
2996
+ more_details=more_details)
2997
+
2998
+ #技术指标的绘图线
2999
+ tech_line_default={'RSI':['rsi'],
3000
+ 'OBV':['obv'],
3001
+ 'MACD':['DIF','DEA'],
3002
+ 'KDJ':['kdj'],
3003
+ 'SAR':['sar'],
3004
+ 'VOL':['vol'],
3005
+ 'PSY':['psy'],
3006
+ 'ARBR':['ar','br'],
3007
+ 'CR':['cr'],
3008
+ 'EMV':['emv'],
3009
+ 'Bollinger':['upper','mid','lower'],
3010
+ 'TRIX':['trix'],
3011
+ 'BIAS':['bias'],
3012
+ 'CCI':['cci'],
3013
+ 'W%R':['wr'],
3014
+ 'ROC':['roc'],
3015
+ 'DMI':['pdi','mdi'],
3016
+ 'DMA':['dma'],
3017
+ 'MFI':['mfi'],
3018
+ 'MOM':['mom'],
3019
+ 'BETA':['beta'],
3020
+ 'TSF':['tsf'],
3021
+ 'AD':['ad'],
3022
+ 'MA':['ma'],'EMA':['ema'],
3023
+ }
3024
+
3025
+ #检查计算结果:有问题?
3026
+ if not calculated:
3027
+ print(" #Warning(security_technical2): unsupported technical parameter",technical)
3028
+ print(" Supported technical parameters:")
3029
+ printlist(sorted(list(tech_line_default.keys())),numperline=11,beforehand=' ',separator=' ')
3030
+ return None
3031
+
3032
+ #绘图数值缩放比例,以便使指标数量级与股价更加协调
3033
+ magnitude_list={'RSI':[1,''],
3034
+ 'OBV':[1/1000000,text_lang('百万','in millions')],
3035
+ 'MACD':[1,''],
3036
+ 'KDJ':[1,''],
3037
+ 'SAR':[1,''],
3038
+ 'VOL':[1/1000000,text_lang('百万','in millions')],
3039
+ 'PSY':[1,''],
3040
+ 'ARBR':[1,''],
3041
+ 'CR':[1,''],
3042
+ 'EMV':[1000000000,text_lang('十亿分之一','in 1 billionth')],
3043
+ 'Bollinger':[1,''],
3044
+ 'TRIX':[100,text_lang('百分之一','%')],
3045
+ 'BIAS':[1,''],
3046
+ 'CCI':[1,''],
3047
+ 'W%R':[1,''],
3048
+ 'ROC':[1,''],
3049
+ 'DMI':[1,''],
3050
+ 'DMA':[1,''],
3051
+ 'MA':[1,''],
3052
+ 'EMA':[1,''],
3053
+ 'MFI':[1,''],
3054
+ 'MOM':[1,''],
3055
+ 'BETA':[1,''],
3056
+ 'TSF':[1,''],
3057
+ 'AD':[1/1000000,text_lang('百万','in millions')],
3058
+ 'Volume':[1/1000000,text_lang('百万','in millions')]}
3059
+
3060
+ mag_times=magnitude_list[technical1][0]
3061
+ mag_label=magnitude_list[technical1][1]
3062
+
3063
+ if 'ALL' in graph or 'all' in graph or 'All' in graph:
3064
+ tech_line_prefix=tech_line_default[technical1]
3065
+ else:
3066
+ if not isinstance(graph,list):
3067
+ tech_line_prefix=[graph]
3068
+ else:
3069
+ tech_line_prefix=graph
3070
+
3071
+ tech_line_collist=[]
3072
+ df_collist=list(df)
3073
+ for p in tech_line_prefix:
3074
+ for c in df_collist:
3075
+ if p in c:
3076
+ tech_line_collist=tech_line_collist+[c]
3077
+ #去掉重复项
3078
+ tech_line_collist=list(set(tech_line_collist))
3079
+ #去掉误选项
3080
+ if technical1 == 'ARBR':
3081
+ remove_cols=[]; remove_item='sar'
3082
+ for c in tech_line_collist:
3083
+ if remove_item in c:
3084
+ tech_line_collist.remove(c)
3085
+
3086
+ #改变测度
3087
+ for c in tech_line_collist:
3088
+ df[c]=df[c] * mag_times
3089
+
3090
+ df['Volume']=df['Volume'] * magnitude_list['Volume'][0]
3091
+
3092
+ #字段排序
3093
+ #tech_line_collist.sort()
3094
+ if 'marker_size' in tech_line_collist:
3095
+ df1=df[tech_line_collist+[indicator,'Volume','up_down']]
3096
+ else:
3097
+ df1=df[tech_line_collist+[indicator,'Volume','up_down','marker_size']]
3098
+
3099
+ #绘图----------------------------------------------------------------------
3100
+ print('') #距离上条信息空一行
3101
+
3102
+ import matplotlib.pyplot as plt
3103
+ import matplotlib.dates as mdates
3104
+ #import matplotlib.gridspec as gridspec
3105
+
3106
+ # 创建两行的布局,上半部分高度为4,下半部分高度为1
3107
+ fig = plt.figure(figsize=(14,9))
3108
+
3109
+ if isinstance(facecolor,str):
3110
+ facecolor1=facecolor2=facecolor
3111
+ elif isinstance(facecolor,list):
3112
+ if len(facecolor) >= 2:
3113
+ facecolor1=facecolor[0]
3114
+ facecolor2=facecolor[1]
3115
+ elif len(facecolor) == 1:
3116
+ facecolor1=facecolor2=facecolor[0]
3117
+ else:
3118
+ facecolor1='whitesmoke'; facecolor2='papayawhip'
3119
+
3120
+ gs = fig.add_gridspec(2, 1, height_ratios=[4, 1], hspace=0.05)
3121
+ ax = fig.add_subplot(gs[0])
3122
+ try:
3123
+ ax.set_facecolor(facecolor1)
3124
+ except:
3125
+ ax.set_facecolor('whitesmoke')
3126
+
3127
+ ax3 = fig.add_subplot(gs[1], sharex=ax)
3128
+ try:
3129
+ ax3.set_facecolor(facecolor2)
3130
+ except:
3131
+ ax3.set_facecolor('papayawhip')
3132
+
3133
+ color_list=['k','g','b','c','m','yellowgreen','tomato','lime','orange','deepskyblue']
3134
+
3135
+ if isinstance(attention_values,int):
3136
+ attention_values=[attention_values]
3137
+ attention_draws=[False] * len(attention_values)
3138
+
3139
+ for l in tech_line_collist:
3140
+ if l == 'marker_size': continue
3141
+
3142
+ labeltxt=l.upper()
3143
+ if labeltxt =='DEA':
3144
+ labeltxt=text_lang('慢线(DEA)','DEA (Slow line)')
3145
+ if labeltxt =='DIF':
3146
+ labeltxt=text_lang('快线(DIF)','DIF (Fast line)')
3147
+
3148
+ axline, = ax.plot(df1.index,df1[l],label=labeltxt)
3149
+ last_line_color = axline.get_color()
3150
+
3151
+ if annotate:
3152
+ df_end=df1.tail(1)
3153
+ # df_end[c]必须为数值类型,否则可能出错
3154
+ y_end = df_end[l].min() # 末端的y坐标
3155
+ x_end = df_end[l].idxmin() # 末端值的x坐标
3156
+ """
3157
+ if annotate_value: #在标记曲线名称的同时标记其末端数值
3158
+ y1=str(int(y_end)) if abs(y_end) >= 100 else str(round(y_end,2)) if abs(y_end) >= 10 else str(round(y_end,3))
3159
+ plt.annotate(text=c+':'+y1,
3160
+ xy=(x_end, y_end),
3161
+ xytext=(x_end, y_end),color=last_line_color)
3162
+ else:
3163
+ plt.annotate(text=c,
3164
+ xy=(x_end, y_end),
3165
+ xytext=(x_end, y_end),color=last_line_color)
3166
+ """
3167
+ ax.annotate(text=''+l.upper(),
3168
+ xy=(x_end, y_end),
3169
+ xytext=(x_end, y_end),color=last_line_color,fontsize=legend_txt_size)
3170
+
3171
+ #判断是否绘制关注线
3172
+ lmax=df1[l].max(); lmin=df1[l].min()
3173
+
3174
+ for al in attention_values:
3175
+ pos=attention_values.index(al)
3176
+
3177
+ line_al=False
3178
+ if (lmax >= al) and (al >= lmin):
3179
+ line_al=True
3180
+
3181
+ #如果需要绘制关注线,且尚未绘制过,则绘制
3182
+ if line_al and not attention_draws[pos]:
3183
+ ax.axhline(y=attention_values[pos],ls='dotted',c=color_list[pos],linewidth=1)
3184
+
3185
+ attention_draws[pos]=True
3186
+
3187
+ ylabeltxt1=technical1+text_lang('指标',' indicators ')
3188
+ if mag_label != '':
3189
+ ylabeltxt1=ylabeltxt1+'('+mag_label+')'
3190
+ ax.set_ylabel(ylabeltxt1,fontsize=ylabel_txt_size)
3191
+
3192
+ #对图例项目排序
3193
+ if not annotate:
3194
+ # 获取图例句柄和标签
3195
+ handles, labels = ax.get_legend_handles_labels()
3196
+
3197
+ # 指定显示顺序
3198
+ labels_sorted = sort_list_by_len(labels,reverse=False)
3199
+ handles_sorted = []
3200
+ for l in labels_sorted:
3201
+ pos=labels.index(l)
3202
+ h=handles[pos]
3203
+ handles_sorted =handles_sorted +[h]
3204
+
3205
+ # 在指定位置添加新的图例,并按照指定顺序显示
3206
+ ax.legend(handles=handles_sorted,labels=labels_sorted,loc=loc1,fontsize=legend_txt_size)
3207
+ #ax.legend(loc=loc1,fontsize=legend_txt_size)
3208
+
3209
+ interval=int(len(df1)/10)+1
3210
+ ax.xaxis.set_major_locator(mdates.DayLocator(interval=interval)) # 隔interval天一个标记
3211
+ ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
3212
+ #ax.autoscale_view()
3213
+
3214
+ #区分涨跌颜色:红涨绿跌
3215
+ df1up=df1[df1['up_down'] >= 0]
3216
+ df1down=df1[df1['up_down'] < 0]
3217
+
3218
+ #绘制收盘价
3219
+ if isinstance(price_line_color,str):
3220
+ price_line_color1=price_line_color2=price_line_color
3221
+ elif isinstance(price_line_color,list):
3222
+ if len(price_line_color) >= 2:
3223
+ price_line_color1=price_line_color[0]
3224
+ price_line_color2=price_line_color[1]
3225
+ elif len(price_line_color) == 1:
3226
+ price_line_color1=price_line_color2=price_line_color[0]
3227
+ else:
3228
+ price_line_color1='red'; price_line_color2='green'
3229
+
3230
+ import numpy as np
3231
+ df1['segment'] = (np.sign(df1['up_down'].shift(1)) != np.sign(df1['up_down'])).cumsum()
3232
+ seg_list=list(set(list(df1['segment'])))
3233
+
3234
+ ax2 = ax.twinx()
3235
+ ylabeltxt2=ectranslate(indicator)
3236
+ ax2.set_ylabel(ylabeltxt2,fontsize=ylabel_txt_size)
3237
+
3238
+ #细灰线先画出轮廓
3239
+ ax2.plot(df1.index,df1[indicator],label=ylabeltxt2, \
3240
+ linestyle=price_line_style,color='k',lw=2)
3241
+
3242
+ #不同颜色绘制涨跌价格线
3243
+ first_time=True; second_time=False
3244
+ for seg in seg_list:
3245
+ df1seg=df1[df1['segment']==seg]
3246
+ if df1seg['up_down'].values[0] >=0:
3247
+ seg_color=price_line_color1
3248
+ #labeltxt=ylabeltxt2+'(当日↑)'
3249
+ #labeltxt=ylabeltxt2+'(当日阳线)'
3250
+ labeltxt=text_lang('当日↑','Bullish day')
3251
+ else:
3252
+ seg_color=price_line_color2
3253
+ #labeltxt=ylabeltxt2+'(当日↓)'
3254
+ #labeltxt=ylabeltxt2+'(当日阴线)'
3255
+ labeltxt=text_lang('当日↓','Bearish day')
3256
+
3257
+ if first_time:
3258
+ first_time=False; second_time=True
3259
+ elif second_time:
3260
+ second_time=False
3261
+ else:
3262
+ labeltxt=''
3263
+
3264
+ ax2.scatter(df1seg.index,df1seg[indicator], \
3265
+ s=df1seg['marker_size'], \
3266
+ label=labeltxt, \
3267
+ linestyle=':',color=seg_color,lw=price_line_width,marker=price_line_marker,alpha=0.5)
3268
+
3269
+ ax2.legend(loc=loc2,fontsize=legend_txt_size)
3270
+
3271
+ #绘制交易量柱状图
3272
+ ax3.bar(df1up.index,df1up['Volume'],color=price_line_color1)
3273
+ ax3.bar(df1down.index,df1down['Volume'],color=price_line_color2)
3274
+
3275
+ ax3.set_ylabel(text_lang("交易量(百万股)","Volume (in millions)"),fontsize=ylabel_txt_size -4)
3276
+
3277
+ footnote1=text_lang("\n注:","\nNote: ")
3278
+ footnote2=text_lang("价格曲线端点大中小对应当日涨跌幅度的高中低情形。","The node sizes in curve refelct the amplitudes of close-open price changes in a day\n")
3279
+ footnote3=text_lang("横轴日期上的空白处为非交易日。\n","The blank areas of bars are non-trading days. ")
3280
+
3281
+ import datetime; todaydt = str(datetime.date.today())
3282
+ footnote4=text_lang("数据来源:新浪/Stooq/Yahoo等,","Data source: Sina/Stooq/Yahoo, ")+todaydt+text_lang("统计",'')
3283
+
3284
+ footnote=footnote1+footnote2+footnote3+footnote4
3285
+ ax3.set_xlabel(footnote,fontsize=ylabel_txt_size -2)
3286
+
3287
+ #fig.text(0.5, 0.04, 'x', ha='center')
3288
+ plt.subplots_adjust(hspace=0.2)
3289
+
3290
+ titletxt=ticker_name(ticker)+': '+text_lang(tech_list[technical1]+technical1,technical1+' ('+tech_list[technical1]+')')
3291
+ plt.title(titletxt,fontweight='bold',fontsize=title_txt_size)
3292
+
3293
+ plt.gcf().autofmt_xdate()
3294
+ #fig.autofmt_xdate()
3295
+
3296
+ plt.show(); plt.close()
3297
+
3298
+ return df
3299
+
3300
+
3301
+ #==============================================================================
3302
+ #==============================================================================
3303
+ #==============================================================================
3304
+
3305
+