siat 2.13.43__py3-none-any.whl → 2.14.2__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.
siat/common.py CHANGED
@@ -284,6 +284,12 @@ def calculate_days(start_date, end_date):
284
284
 
285
285
 
286
286
  #==============================================================================
287
+ if __name__ =="__main__":
288
+ basedate='2020-3-17'
289
+ adjust=-365
290
+ newdate = date_adjust(basedate, adjust)
291
+ print(newdate)
292
+
287
293
  def date_adjust(basedate, adjust=0):
288
294
  """
289
295
  功能:将给定日期向前或向后调整特定的天数
@@ -309,11 +315,105 @@ def date_adjust(basedate, adjust=0):
309
315
  return str(newdate)
310
316
 
311
317
  if __name__ =="__main__":
312
- basedate='2020-3-17'
313
- adjust=-365
314
- newdate = date_adjust(basedate, adjust)
315
- print(newdate)
318
+ basedate='2024-3-17'
319
+
320
+ adjust_year=-1; adjust_month=-1; adjust_day=-1
321
+ adjust_year=-1; adjust_month=-1; adjust_day=-17
322
+ adjust_year=-1; adjust_month=0; adjust_day=-17
323
+ adjust_year=0; adjust_month=-3; adjust_day=0
324
+ adjust_year=0; adjust_month=-13; adjust_day=0
325
+ adjust_year=0; adjust_month=-15; adjust_day=0
326
+ adjust_year=0; adjust_month=-27; adjust_day=0
327
+ adjust_year=0; adjust_month=-27; adjust_day=0
328
+
329
+ adjust_year=0; adjust_month=0; adjust_day=15
330
+ adjust_year=0; adjust_month=10; adjust_day=0
331
+ adjust_year=0; adjust_month=22; adjust_day=0
332
+
333
+ adjust_year=0; adjust_month=-1; adjust_day=-1
334
+ adjust_year=0; adjust_month=-2; adjust_day=-1
335
+ adjust_year=0; adjust_month=-3; adjust_day=-1
336
+ adjust_year=0; adjust_month=-6; adjust_day=-1
337
+ adjust_year=-1; adjust_month=0; adjust_day=0
338
+ adjust_year=-2; adjust_month=0; adjust_day=0
339
+ adjust_year=-3; adjust_month=0; adjust_day=0
340
+ adjust_year=-5; adjust_month=0; adjust_day=0
341
+
342
+ to_prev_month_end=False
343
+ to_prev_month_end=True
344
+
345
+ to_prev_year_end=False
346
+ to_prev_year_end=True
347
+
348
+ date_adjust2(basedate,adjust_year,adjust_month,adjust_day,to_prev_month_end,to_prev_year_end)
349
+
350
+ def date_adjust2(basedate,adjust_year=0,adjust_month=0,adjust_day=0, \
351
+ to_prev_month_end=False,to_prev_year_end=False):
352
+ """
353
+ 功能:将给定日期向前或向后调整特定的天数,按照年月日精确调整
354
+ 输入:基础日期,需要调整的天数。
355
+ basedate: 基础日期。
356
+ adjust_year, adjust_month, adjust_day:分别调整的年月日数量,负数向前调整,正数向后调整。
357
+ 输出:调整后的日期字符串。
358
+ """
359
+ import pandas as pd
360
+ from datetime import datetime, timedelta
361
+
362
+ #检查基础日期的合理性
363
+ try:
364
+ bd=pd.to_datetime(basedate)
365
+ except:
366
+ print(" #Error(date_adjust2): invalid date",basedate)
367
+ return None
316
368
 
369
+ #将基础日期分解为年月日
370
+ bd_year=bd.year
371
+ bd_month=bd.month
372
+ bd_day=bd.day
373
+
374
+ #预处理调整的月数
375
+ if adjust_month <= -12:
376
+ adjust_year=adjust_year + int(adjust_month / 12)
377
+ adjust_month=adjust_month % -12
378
+ if adjust_month >= 12:
379
+ adjust_year=adjust_year + int(adjust_month / 12)
380
+ adjust_month=adjust_month % 12
381
+
382
+ #调整年
383
+ new_year=bd_year + adjust_year
384
+
385
+ #调整月份
386
+ new_month=bd_month + adjust_month
387
+ if new_month <= 0:
388
+ new_month=new_month + 12
389
+ new_year=new_year - 1
390
+ if new_month > 12:
391
+ new_month=new_month % 12
392
+ new_year=new_year + 1
393
+
394
+ #合成中间日期:新年份,新月份,原日期
395
+ ndym=datetime(new_year,new_month,bd_day)
396
+
397
+ #调整日期
398
+ nd=ndym + timedelta(days=adjust_day)
399
+
400
+ #如果还需要调整到上年年末,但不可同时使用调整到上月月末
401
+ if to_prev_year_end:
402
+ to_prev_month_end=False
403
+
404
+ nd_year=nd.year - 1
405
+ nd=datetime(nd_year,12,31)
406
+
407
+ #如果还需要调整到上月月末
408
+ if to_prev_month_end:
409
+ nd_day=nd.day
410
+ nd=nd + timedelta(days=-nd_day)
411
+
412
+ #提取日期字符串
413
+ newdate=nd.date()
414
+
415
+ return str(newdate)
416
+
317
417
  #==============================================================================
318
418
  if __name__ =="__main__":
319
419
  portfolio={'Market':('US','^GSPC'),'EDU':0.4,'TAL':0.3,'TEDU':0.2}
@@ -357,13 +457,35 @@ def portfolio_name(portfolio):
357
457
  try:
358
458
  name=portfolio[keylist[0]][2]
359
459
  except:
360
- name="投资组合"
460
+ name="PF1"
361
461
 
362
462
  return name
363
463
 
364
464
  if __name__=='__main__':
365
465
  portfolio={'Market':('US','^GSPC','我的组合001'),'EDU':0.4,'TAL':0.3,'TEDU':0.2}
366
466
  portfolio_name(portfolio)
467
+
468
+ if __name__=='__main__':
469
+ portfolio={'Market':('US','^GSPC','China Edtraining'),'EDU':0.4,'TAL':0.3,'TEDU':0.2}
470
+ portfolio='600519.SS'
471
+
472
+ isinstance_portfolio(portfolio)
473
+
474
+ def isinstance_portfolio(portfolio):
475
+ """
476
+ 功能:判断是否投资组合
477
+ 输入:投资组合
478
+ 输出:True, False
479
+ 注意:为了维持兼容性,特此定义此函数
480
+ """
481
+ result=True
482
+
483
+ try:
484
+ scope,mktidx,tickerlist,sharelist=decompose_portfolio(portfolio)
485
+ except:
486
+ result=False
487
+
488
+ return result
367
489
 
368
490
  #==============================================================================
369
491
  def calc_monthly_date_range(start,end):
@@ -598,19 +720,20 @@ def save_to_excel(df,filedir,excelfile,sheetname="Sheet1"):
598
720
  注意:如果df中含有以文本表示的数字,写入到Excel会被自动转换为数字类型保存。
599
721
  从Excel中读出后为数字类型,因此将会与df的类型不一致
600
722
  """
723
+ DEBUG=True
601
724
 
602
725
  #检查目录是否存在
603
726
  import os
604
727
  try:
605
728
  os.chdir(filedir)
606
729
  except:
607
- print("Error #1(save_to_excel): folder does not exist")
608
- print("Information:",filedir)
730
+ print(" #Error(save_to_excel): folder does not exist",filedir)
609
731
  return
610
732
 
611
733
  #取得df字段列表
612
734
  dflist=df.columns
613
735
  #合成完整的带目录的文件名
736
+ #filename=filedir+'\\'+excelfile
614
737
  filename=filedir+'/'+excelfile
615
738
 
616
739
  import pandas as pd
@@ -618,9 +741,9 @@ def save_to_excel(df,filedir,excelfile,sheetname="Sheet1"):
618
741
  file1=pd.ExcelFile(excelfile)
619
742
  except:
620
743
  #不存在excelfile文件,直接写入
621
- df.to_excel(filename,sheet_name=sheetname, \
622
- header=True,encoding='utf-8')
623
- print("***Results saved in",filename,"@ sheet",sheetname)
744
+ #df.to_excel(filename,sheet_name=sheetname,header=True,encoding='utf-8')
745
+ df.to_excel(filename,sheet_name=sheetname,header=True)
746
+ print(" Successfully saved in",filename,"@ sheet",sheetname)
624
747
  return
625
748
  else:
626
749
  #已存在excelfile文件,先将所有sheet的内容读出到dict中
@@ -648,18 +771,25 @@ def save_to_excel(df,filedir,excelfile,sheetname="Sheet1"):
648
771
  result=pd.ExcelWriter(filename)
649
772
  for s in sheetlist:
650
773
  df1=dict[s][dflist]
651
- df1.to_excel(result,s,header=True,index=True,encoding='utf-8')
774
+ #df1.to_excel(result,s,header=True,index=True,encoding='utf-8')
775
+ df1.to_excel(result,s,header=True,index=True)
652
776
  #写入新内容
653
777
  if not dup: #sheetname未重复
654
- df.to_excel(result,sheetname,header=True,index=True,encoding='utf-8')
655
- try:
656
- result.save()
657
- result.close()
658
- except:
659
- print("Error #2(save_to_excel): writing file permission denied")
660
- print("Information:",filename)
661
- return
662
- print("***Results saved in",filename,"@ sheet",sheetname)
778
+ #df.to_excel(result,sheetname,header=True,index=True,encoding='utf-8')
779
+ df.to_excel(result,sheetname,header=True,index=True)
780
+ if DEBUG:
781
+ #result.save()
782
+ result.close()
783
+ else:
784
+ try:
785
+ #result.save()
786
+ result.close()
787
+ except:
788
+ print(" #Error(save_to_excel): writing file failed for",filename)
789
+ print(" Solution: change file name and try again")
790
+ return
791
+
792
+ print(" Successfully saved in",filename,"@ sheet",sheetname)
663
793
  return
664
794
  #==============================================================================
665
795
  def set_df_period(df,df_min,df_max):
@@ -1744,7 +1874,7 @@ if __name__=='__main__':
1744
1874
  footnote="This is the footnote"
1745
1875
 
1746
1876
  def descriptive_statistics(df,titletxt,footnote,decimals=4,sortby='tpw_mean', \
1747
- recommend_only=False,trailing=20,trend_threshhold=0.01):
1877
+ recommend_only=False,trailing=7,trend_threshhold=0.01):
1748
1878
  """
1749
1879
  功能:进行描述性统计,并打印结果
1750
1880
  df的要求:
@@ -1764,7 +1894,8 @@ def descriptive_statistics(df,titletxt,footnote,decimals=4,sortby='tpw_mean', \
1764
1894
  return
1765
1895
 
1766
1896
  # 计算短期趋势
1767
- df20=df[-trailing:]
1897
+ #df20=df[-trailing:]
1898
+ df20=df.tail(trailing)
1768
1899
  """
1769
1900
  df20ds=df20.describe()
1770
1901
  df20mean=df20ds[df20ds.index=='mean'].T
@@ -1993,6 +2124,259 @@ def descriptive_statistics(df,titletxt,footnote,decimals=4,sortby='tpw_mean', \
1993
2124
 
1994
2125
  return dst5
1995
2126
 
2127
+ #==============================================================================
2128
+
2129
+ if __name__=='__main__':
2130
+ tickers=['NIO','LI','XPEV','TSLA']
2131
+ df=compare_mrar(tickers,
2132
+ rar_name='sharpe',
2133
+ start='2022-1-1',end='2023-1-31',
2134
+ market='US',market_index='^GSPC',
2135
+ window=240,axhline_label='零线')
2136
+
2137
+ titletxt="This is the title text"
2138
+ footnote="This is the footnote"
2139
+ decimals=4
2140
+ sortby='tpw_mean'
2141
+ recommend_only=False
2142
+ trailing=7
2143
+ trend_threshhold=0.01
2144
+
2145
+ def descriptive_statistics2(df,titletxt,footnote,decimals=4,sortby='tpw_mean', \
2146
+ recommend_only=False,trailing=7,trend_threshhold=0.01, \
2147
+ printout=True,style_print=False):
2148
+ """
2149
+ 功能:进行描述性统计,并打印结果
2150
+ df的要求:
2151
+ 索引列为datetime格式,不带时区
2152
+ 各个列为比较对象,均为数值型,降序排列
2153
+
2154
+ sortby='tpw_mean':按照近期时间优先加权(time priority weighted)平均数排序
2155
+ recommend_only=False:是否仅打印推荐的证券
2156
+ """
2157
+
2158
+ # 检查df
2159
+ if df is None:
2160
+ print(" #Error(descriptive_statistics2): none info found")
2161
+ return
2162
+ if len(df) == 0:
2163
+ print(" #Error(descriptive_statistics2): zero data found")
2164
+ return
2165
+
2166
+ # 计算短期趋势
2167
+ df20=df.tail(trailing)
2168
+
2169
+ ds=df.describe(include='all',percentiles=[.5])
2170
+ dst=ds.T
2171
+ cols=['min','max','50%','mean','std']
2172
+
2173
+ dst['item']=dst.index
2174
+ cols2=['item','min','max','50%','mean','std']
2175
+
2176
+ dst2=dst[cols2]
2177
+ for c in cols:
2178
+ dst2[c]=dst2[c].apply(lambda x: round(x,decimals))
2179
+
2180
+ if sortby != 'tpw_mean':
2181
+ if sortby=='median':
2182
+ sortby='50%'
2183
+ dst2.sort_values(by=sortby,ascending=False,inplace=True)
2184
+
2185
+ cols2cn=['比较对象','最小值','最大值','中位数','平均值','标准差']
2186
+ dst2.columns=cols2cn
2187
+
2188
+ # 近期优先加权平均
2189
+ dst2['近期优先加权平均']=dst2['比较对象'].apply(lambda x:time_priority_weighted_average(df,x,4))
2190
+ if sortby == "tpw_mean":
2191
+ dst2.sort_values(by='近期优先加权平均',ascending=False,inplace=True)
2192
+
2193
+ dst3=dst2
2194
+ dst3=dst3[(dst3['比较对象'] != 'time_weight') & (dst3['比较对象'] != 'relative_weight')]
2195
+
2196
+ dst3.reset_index(drop=True,inplace=True)
2197
+ dst3.index=dst3.index+1
2198
+
2199
+ # 趋势标记
2200
+ dst3['期间趋势']=dst3['比较对象'].apply(lambda x:curve_trend_direct(df,x,trend_threshhold))
2201
+ dst3['近期趋势']=dst3['比较对象'].apply(lambda x:curve_trend_direct(df20,x,trend_threshhold))
2202
+
2203
+ # 推荐标记:最大五颗星
2204
+ dst3['推荐标记']=''
2205
+ if sortby in ['tpw_mean','trailing']: #重视近期趋势
2206
+ #注意:务必先加后减!
2207
+ #若近期优先加权平均>0,给3星
2208
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+++') if (x['近期优先加权平均']>0) else x['推荐标记'],axis=1)
2209
+ #若近期趋势➹,加2星
2210
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'++') if (x['近期趋势']=='➹') else x['推荐标记'],axis=1)
2211
+ #若期间趋势➹,加1星
2212
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['期间趋势']=='➹') else x['推荐标记'],axis=1)
2213
+ #若平均值或中位数>0,加1星
2214
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['平均值']>0) | (x['中位数']>0) else x['推荐标记'],axis=1)
2215
+ #若最小值>0,加1星
2216
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['最小值']>0) else x['推荐标记'],axis=1)
2217
+
2218
+ #若近期趋势➷,减2星
2219
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['近期趋势']=='➷') else x['推荐标记'],axis=1)
2220
+ #若期间趋势➷,减1星
2221
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['期间趋势']=='➷') else x['推荐标记'],axis=1)
2222
+ #若平均值且中位数<0,减1星
2223
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['平均值']<0) & (x['中位数']<0) else x['推荐标记'],axis=1)
2224
+ #若最小值<0,减1星
2225
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['最小值']<0) else x['推荐标记'],axis=1)
2226
+
2227
+ #若近期优先加权平均<0,星星清零
2228
+ dst3['推荐标记']=dst3.apply(lambda x: '' if (x['近期优先加权平均']<0) else x['推荐标记'],axis=1)
2229
+
2230
+ elif sortby == 'min': #保守推荐
2231
+ #若最小值>0,加5星
2232
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+++++') if (x['最小值']>0) else x['推荐标记'],axis=1)
2233
+ #若近期优先加权平均>0,加1星
2234
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['近期优先加权平均']>0) else x['推荐标记'],axis=1)
2235
+ #若近期趋势➹,加1星
2236
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['近期趋势']=='➹') else x['推荐标记'],axis=1)
2237
+ #若期间趋势➹,加1星
2238
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['期间趋势']=='➹') else x['推荐标记'],axis=1)
2239
+ #若平均值或中位数>0,加1星
2240
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['平均值']>0) | (x['中位数']>0) else x['推荐标记'],axis=1)
2241
+
2242
+ #若近期优先加权平均<0,减1星
2243
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['近期优先加权平均']<0) else x['推荐标记'],axis=1)
2244
+ #若平均值且中位数<0,减1星
2245
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['平均值']<0) & (x['中位数']<0) else x['推荐标记'],axis=1)
2246
+ #若近期趋势➷,减1星
2247
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['近期趋势']=='➷') else x['推荐标记'],axis=1)
2248
+ #若期间趋势➷,减1星
2249
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['期间趋势']=='➷') else x['推荐标记'],axis=1)
2250
+
2251
+ elif sortby == 'mean': #进取推荐,均值,重视整体趋势,不在乎近期趋势
2252
+ #若平均值>0,给3星
2253
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+++') if (x['平均值']>0) else x['推荐标记'],axis=1)
2254
+ #若期间趋势➹,加2星
2255
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'++') if (x['期间趋势']=='➹') else x['推荐标记'],axis=1)
2256
+ #若中位数>0,加1星
2257
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['中位数']>0) else x['推荐标记'],axis=1)
2258
+ #若近期趋势➹,加1星
2259
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['近期趋势']=='➹') else x['推荐标记'],axis=1)
2260
+ #若近期优先加权平均>0,加1星
2261
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['近期优先加权平均']>0) else x['推荐标记'],axis=1)
2262
+ #若最小值>0,加1星
2263
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['最小值']>0) else x['推荐标记'],axis=1)
2264
+
2265
+ #若期间趋势➷,减2星
2266
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['期间趋势']=='➷') else x['推荐标记'],axis=1)
2267
+ #若近期趋势➷,减1星
2268
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['近期趋势']=='➷') else x['推荐标记'],axis=1)
2269
+ #若近期优先加权平均<0,减1星
2270
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['近期优先加权平均']<0) else x['推荐标记'],axis=1)
2271
+ #若中位数<0,减1星
2272
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['中位数']<0) else x['推荐标记'],axis=1)
2273
+ #若最小值<0,减1星
2274
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['最小值']<0) else x['推荐标记'],axis=1)
2275
+
2276
+ #若平均值<0,星星清零
2277
+ dst3['推荐标记']=dst3.apply(lambda x: '' if (x['平均值']<0) else x['推荐标记'],axis=1)
2278
+
2279
+ elif sortby == 'median': #进取推荐,中位数,看重整体趋势,不在乎近期短期变化
2280
+ #若中位数>0,给3星
2281
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+++') if (x['中位数']>0) else x['推荐标记'],axis=1)
2282
+ #若期间趋势➹,加2星
2283
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'++') if (x['期间趋势']=='➹') else x['推荐标记'],axis=1)
2284
+ #若平均值>0,加1星
2285
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['平均值']>0) else x['推荐标记'],axis=1)
2286
+
2287
+ #若近期趋势➹,加1星
2288
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['近期趋势']=='➹') else x['推荐标记'],axis=1)
2289
+ #若近期优先加权平均>0,加1星
2290
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['近期优先加权平均']>0) else x['推荐标记'],axis=1)
2291
+ #若最小值>0,加1星
2292
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'+') if (x['最小值']>0) else x['推荐标记'],axis=1)
2293
+
2294
+ #若期间趋势➷,减2星
2295
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['期间趋势']=='➷') else x['推荐标记'],axis=1)
2296
+ #若近期趋势➷,减1星
2297
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['近期趋势']=='➷') else x['推荐标记'],axis=1)
2298
+ #若近期优先加权平均<0,减1星
2299
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['近期优先加权平均']<0) else x['推荐标记'],axis=1)
2300
+ #若平均值<0,减1星
2301
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['平均值']<0) else x['推荐标记'],axis=1)
2302
+ #若最小值<0,减1星
2303
+ dst3['推荐标记']=dst3.apply(lambda x: change_stars2(x['推荐标记'],'-') if (x['最小值']<0) else x['推荐标记'],axis=1)
2304
+
2305
+ #若中位数<0,星星清零
2306
+ dst3['推荐标记']=dst3.apply(lambda x: '' if (x['中位数']<0) else x['推荐标记'],axis=1)
2307
+ else:
2308
+ pass
2309
+
2310
+ #最多5颗星星
2311
+ dst3['推荐标记']=dst3.apply(lambda x: x['推荐标记'][:5] if (hzlen(x['推荐标记'])>5) else x['推荐标记'],axis=1)
2312
+ #为了打印对齐,强制向左移动,不管用!
2313
+ #dst3['推荐标记']=dst3.apply(lambda x: x['推荐标记']+' ' if (hzlen(x['推荐标记'])>4) else x['推荐标记'],axis=1)
2314
+
2315
+ dst4=dst3
2316
+
2317
+ # 重排序:按照星星个数+数值,降序
2318
+ dst5=dst4
2319
+ if sortby == "tpw_mean":
2320
+ dst5.sort_values(by=['推荐标记','近期优先加权平均'],ascending=[False,False],inplace=True)
2321
+ #dst5.sort_values(by=['推荐标记','近期优先加权平均'],ascending=False,inplace=True)
2322
+ elif sortby == "min":
2323
+ dst5.sort_values(by=['推荐标记','最小值'],ascending=[False,False],inplace=True)
2324
+ elif sortby == "mean":
2325
+ dst5.sort_values(by=['推荐标记','平均值'],ascending=[False,False],inplace=True)
2326
+ elif sortby == "median":
2327
+ dst5.sort_values(by=['推荐标记','中位数'],ascending=[False,False],inplace=True)
2328
+ elif sortby == "trailing":
2329
+ dst5.sort_values(by=['推荐标记','最新均值差'],ascending=[False,False],inplace=True)
2330
+ else:
2331
+ pass
2332
+
2333
+ #是否过滤无推荐标志的证券,防止过多无推荐标志的记录使得打印列表过长
2334
+ if recommend_only:
2335
+ dst6=dst5[dst5['推荐标记'] != '']
2336
+ dst_num=len(dst6)
2337
+ #若无推荐标志也要显示头十个
2338
+ if dst_num < 10:
2339
+ dst6=dst5.head(10)
2340
+ else:
2341
+ dst6=dst5.head(dst_num+3)
2342
+ else:
2343
+ dst6=dst5
2344
+
2345
+ dst6.reset_index(drop=True,inplace=True)
2346
+ dst6.index=dst6.index+1
2347
+
2348
+ if printout:
2349
+ #控制显示的小数点位数
2350
+ for c in dst6.columns:
2351
+ try:
2352
+ dst6[c]=dst6[c].apply(lambda x: round(x,4))
2353
+ except:
2354
+ pass
2355
+ #确保display显示时不再自动在数值尾部添加零至6位小数
2356
+ dst6[c]=dst6[c].apply(lambda x: str(x))
2357
+
2358
+ if not style_print: #markdown打印
2359
+ print("\n"+titletxt+"\n")
2360
+ #如果index=True则显示index,这样alignlist的长度就需要dst6列数+1
2361
+ alignlist=['right','left']+['center']*(len(list(dst6))-3)+['center','left']
2362
+ try:
2363
+ print(dst6.to_markdown(index=True,tablefmt='plain',colalign=alignlist))
2364
+ except:
2365
+ #解决汉字编码gbk出错问题
2366
+ dst7=dst6.to_markdown(index=True,tablefmt='plain',colalign=alignlist)
2367
+ dst8=dst7.encode("utf-8",errors="strict")
2368
+ print(dst8)
2369
+ print("\n"+footnote)
2370
+
2371
+ else: #style打印
2372
+ print("\n"+titletxt)
2373
+ dst6sd= dst6.style.set_properties(**{'text-align': 'center'})
2374
+ from IPython.display import display
2375
+ display(dst6sd)
2376
+ print(footnote+"\n")
2377
+
2378
+ return dst5
2379
+
1996
2380
 
1997
2381
  #==============================================================================
1998
2382
  if __name__=='__main__':
@@ -2010,6 +2394,31 @@ def print_list(alist,leading_blanks=1):
2010
2394
  print('\n')
2011
2395
 
2012
2396
  return
2397
+
2398
+ if __name__=='__main__':
2399
+ alist=['NIO','LI','XPEV','TSLA']
2400
+ list2str(alist)
2401
+
2402
+ def list2str(alist):
2403
+ """
2404
+ 功能:将列表转换为字符串,不带引号,节省空间
2405
+ """
2406
+ if len(alist) > 1:
2407
+ result='['
2408
+ for i in alist:
2409
+ result=result+str(i)
2410
+ if i != alist[-1]:
2411
+ result=result+', '
2412
+ result=result+']'
2413
+
2414
+ elif len(alist) == 1:
2415
+ result=str(alist[0])
2416
+
2417
+ else:
2418
+ result=''
2419
+
2420
+ return result
2421
+
2013
2422
  #==============================================================================
2014
2423
  # FUNCTION TO REMOVE TIMEZONE
2015
2424
  def remove_timezone(dt):
@@ -2144,18 +2553,18 @@ def curve_trend_regress(df,col,threshhold=0.0001):
2144
2553
 
2145
2554
  return result
2146
2555
 
2147
- def curve_trend_direct(df,col,threshhold=0.0001):
2556
+ def curve_trend_direct(df,col,threshhold=0.01):
2148
2557
  """
2149
2558
  功能:直接对比首尾值大小。目的为判断曲线走势
2150
2559
 
2151
2560
  输入项:
2152
2561
  df: 数据框,假设其索引为日期项,且已升序排列
2153
2562
  col: 考察变量,检查该变量的走势,向上,向下,or 不明显(无显著性星星)
2154
-
2155
- 返回值:尾值-首值
2156
- '➠':差接近零(其绝对值小于threshhold)
2157
- '➷':差为负数且其绝对值不小于threshhold
2158
- '➹':差为正数且其绝对值不小于threshhold
2563
+ threshhold:相对值
2564
+ 返回值:(尾值-首值)/首值
2565
+ '➠':变化率接近零(其绝对值小于threshhold)
2566
+ '➷':变化率为负数且其绝对值不小于threshhold
2567
+ '➹':变化率为正数且其绝对值不小于threshhold
2159
2568
  """
2160
2569
  # 检查df是否为空
2161
2570
  if df is None:
@@ -2168,15 +2577,32 @@ def curve_trend_direct(df,col,threshhold=0.0001):
2168
2577
  df1.sort_index(ascending=True,inplace=True)
2169
2578
  first_value=df1.head(1)[col].values[0]
2170
2579
  last_value=df1.tail(1)[col].values[0]
2171
- diff=last_value - first_value
2580
+
2581
+ #采用相对值,避免数量级差异,同时避免负负得正
2582
+ if first_value != 0.0:
2583
+ diff=(last_value - first_value)/abs(first_value)
2584
+ elif last_value != 0.0:
2585
+ #不得已
2586
+ diff=(last_value - first_value)/abs(last_value)
2587
+ else:
2588
+ #实在不得已
2589
+ diff=last_value - first_value
2590
+
2172
2591
  diff_abs=abs(diff)
2173
2592
 
2174
2593
  # 判断斜率方向
2175
2594
  result='➠'
2595
+ """
2176
2596
  if diff_abs >= threshhold and diff > 0:
2177
2597
  result='➹'
2178
2598
  if diff_abs >= threshhold and diff < 0:
2179
2599
  result='➷'
2600
+ """
2601
+ #留出区间-threshhold至threshhold视为平行趋势
2602
+ if diff > threshhold:
2603
+ result='➹'
2604
+ if diff < -threshhold:
2605
+ result='➷'
2180
2606
 
2181
2607
  return result
2182
2608
  #==============================================================================
@@ -2244,8 +2670,40 @@ def change_recommend_stars(stars_current,change='+'):
2244
2670
  stars_new=stars2
2245
2671
 
2246
2672
  return stars_new
2673
+
2674
+ #==============================================================================
2675
+ if __name__=='__main__':
2676
+ stars_current=''
2677
+ stars_current='✮✮'
2678
+
2679
+ change='+'
2680
+ change='++'
2681
+ change='-'
2682
+
2683
+ change_stars2(stars_current,change)
2684
+
2685
+ def change_stars2(stars_current,change='+'):
2686
+ """
2687
+ 功能:增减推荐的星星个数
2688
+ 注意:change中不能同时出现+-符号,只能出现一种,但可以多个
2689
+ """
2690
+ stars1='✮'
2247
2691
 
2692
+ num_plus=change.count('+')
2693
+ if num_plus > 0:
2694
+ stars_new=stars_current+stars1 * num_plus
2248
2695
 
2696
+ num_minus=change.count('-')
2697
+ if num_minus >= 1:
2698
+ stars_new=stars_current
2699
+ for n in range(1,num_minus+1):
2700
+ stars_new=stars_new[:-1]
2701
+ if hzlen(stars_new)==0: break
2702
+ """
2703
+ if hzlen(stars_new)>5:
2704
+ stars_new=stars_new[:5]
2705
+ """
2706
+ return stars_new
2249
2707
 
2250
2708
  #==============================================================================
2251
2709
  if __name__=='__main__':
@@ -2320,6 +2778,12 @@ def fix_package(file='stooq.py',package='pandas_datareader'):
2320
2778
  """
2321
2779
  功能:修复stooq.py,使用siat包中的stooq.py覆盖pandas_datareader中的同名文件
2322
2780
  注意:执行本程序需要系统管理员权限,可以系统管理员权限启动Jupyter或Spyder
2781
+
2782
+ 改进:建立一个Excel文件,记录需要修复的文件和包,例如:
2783
+ file package
2784
+ stooq.py pandas_datareader
2785
+ bond_zh_sina.py akshare
2786
+
2323
2787
  """
2324
2788
  #判断操作系统
2325
2789
  import sys; czxt=sys.platform
@@ -2420,7 +2884,7 @@ def df_preprocess(dfs,measure,axhline_label,x_label,y_label,lang='Chinese', \
2420
2884
  #相对起点值变化的百分数(起点值为0)
2421
2885
  scalinglist=['mean','min','start','percentage','change%']
2422
2886
  if not (scaling_option in scalinglist):
2423
- print(" #Error(compare_msecurity): invalid scaling option",scaling_option)
2887
+ print(" #Error(df_preprocess): invalid scaling option",scaling_option)
2424
2888
  print(" Valid scaling option:",scalinglist)
2425
2889
  return None
2426
2890
  if scaling_option == 'mean':
@@ -2536,7 +3000,8 @@ def df_preprocess(dfs,measure,axhline_label,x_label,y_label,lang='Chinese', \
2536
3000
  measure_suffix='(相对百分数%)'
2537
3001
  elif scaling_option == 'change%':
2538
3002
  std_notes="注释:为突出变化趋势,图中数值为相对期间起点的增减百分比"
2539
- measure_suffix='(增/减%)'
3003
+ #measure_suffix='(增/减%)'
3004
+ measure_suffix='(涨跌幅度%)'
2540
3005
  axhline_label='零线' #可以在security_trend中使用critical_value选项指定水平线位置,默认0
2541
3006
  #axhline_value=0
2542
3007
 
@@ -2550,4 +3015,266 @@ def df_preprocess(dfs,measure,axhline_label,x_label,y_label,lang='Chinese', \
2550
3015
  #返回内容
2551
3016
  return dfs2,axhline_label,x_label,y_label,plus_sign
2552
3017
  #==============================================================================
3018
+ if __name__=='__main__':
3019
+ is_running_in_jupyter()
3020
+
3021
+
3022
+ def is_running_in_jupyter():
3023
+ """
3024
+ 功能:检测当前环境是否在Jupyter中,误判Spyder为Jupyter,不行!
3025
+ """
3026
+ try:
3027
+ # 尝试导入IPython的一些模块
3028
+ from IPython import get_ipython
3029
+
3030
+ ipython = get_ipython()
3031
+ if 'IPKernelApp' not in ipython.config:
3032
+ return False
3033
+ return True
3034
+
3035
+ except ImportError:
3036
+ return False
3037
+
3038
+ #==============================================================================
3039
+ if __name__=='__main__':
3040
+ date1=pd.to_datetime('2024-1-2')
3041
+ date2=pd.to_datetime('2024-1-9')
3042
+ days_between_dates(date1, date2)
3043
+
3044
+ def days_between_dates(date1, date2):
3045
+ """
3046
+ 注意:date1和date2为datetime类型
3047
+ """
3048
+ from datetime import datetime
3049
+ delta = date2 - date1
3050
+ return delta.days
3051
+
3052
+ #==============================================================================
3053
+ if __name__=='__main__':
3054
+ stars_num=0
3055
+ stars_num=3
3056
+ stars_num=4.2
3057
+ stars_num=4.6
3058
+
3059
+ generate_stars(stars_num)
3060
+
3061
+ def generate_stars(stars_num):
3062
+ """
3063
+ 功能:基于星星个数stars_num生成推荐星星符号,支持半颗星
3064
+ """
3065
+ stars1='✮'
3066
+ starsh='☆'
3067
+
3068
+ stars_int=int(stars_num)
3069
+ result=stars_int * stars1
3070
+
3071
+ if (stars_num - stars_int) >= 0.5:
3072
+ result=result + starsh
3073
+
3074
+ return result
3075
+
3076
+
3077
+ #==============================================================================
3078
+ if __name__=='__main__':
3079
+ df=get_price('000001.SS','2000-1-1','2024-3-22')
3080
+ column='Close'
3081
+ minimum=30
3082
+ method='max'
3083
+
3084
+ df1=df[column].resample('4H').max()
3085
+ df2=df1.interpolate(method='cubic')
3086
+
3087
+ period='24H'
3088
+ df2=df_resampling(df,column,period,method='mean',minimum=minimum)
3089
+ df2[column].plot(title=period)
3090
+
3091
+
3092
+ period='auto'; method='mean'; minimum=50
3093
+ df2=df_resampling(df,column,period,method='mean',minimum=minimum)
3094
+ df2[column].plot()
3095
+
3096
+
3097
+ def df_resampling(df,column,period='auto',method='max',minimum=30):
3098
+ """
3099
+ 注意:未完成状态。遇到的问题:平滑后数据的后面时间段丢失严重!
3100
+ 目的:将大量时间序列数据绘制重新采样,减少极端值,使得数据趋势简洁明了,可能丢失部分细节
3101
+ 功能:将df按照period对column字段数值重新采样,采样方法为method,采样前个数不少于minimum
3102
+ 注意:df需为时间序列;column为单个字段,需为数值型
3103
+ period:采样间隔,支持小时'H'或'nH'(n=2,3,...)、周'W'、月'M'、季'Q'或年'Y',默认由系统决定
3104
+ method:暂仅支持平均值方法mean(典型)和求和方法sum
3105
+ minimum:采样后需保留的最少数据个数,默认30个
3106
+ """
3107
+ DEBUG=True
3108
+
3109
+ import pandas as pd
3110
+ import numpy as np
3111
+
3112
+ #检查df长度:是否需要重新采样
3113
+ if len(df) <= minimum:
3114
+ if DEBUG: print(" #Notice(df_resampling): no need to resample",len(df))
3115
+ return df #无需重新采样,返回原值
3116
+
3117
+ #仅对采样字段进行处理
3118
+ if column not in df.columns:
3119
+ if DEBUG: print(" #Warning(df_resampling): non-exist column",column)
3120
+ return df #返回原值
3121
+
3122
+ #取出采样字段
3123
+ df1=df[[column]].copy() #避免影响到原值
3124
+
3125
+ #寻求最佳采样间隔
3126
+ period_list=['2H','4H','6H','8H','12H','24H']
3127
+ std_list=[]
3128
+ dft_list=[]
3129
+ for p in period_list:
3130
+ dft1=df1[column].resample(p).mean()
3131
+ dft2=dft1.interpolate(method='linear')
3132
+ stdt=dft2.std()
3133
+ std_list=std_list+[stdt]
3134
+ dft_list=dft_list+[dft2]
3135
+
3136
+ std_min=min(std_list)
3137
+ pos=std_list.index(std_min)
3138
+ period_min=period_list[pos]
3139
+ df2=dft_list[pos]
3140
+
3141
+
3142
+ #再次检查采样后的长度
3143
+ if len(df2) < minimum:
3144
+ if DEBUG: print(" #Warning(df_resampling): resampled number",len(df2),"< minimum",minimum)
3145
+ return df #返回原值
3146
+
3147
+ return df2
3148
+
3149
+ #==============================================================================
3150
+ if __name__=='__main__':
3151
+ df=get_price('000001.SS','2000-1-1','2024-3-22')
3152
+
3153
+
3154
+ def linewidth_adjust(df):
3155
+ """
3156
+ 功能:根据df中元素个数多少调节绘制曲线时线段的宽度linewidth
3157
+ """
3158
+
3159
+ dflen=len(df)
3160
+ if dflen > 100: lwadjust=1.2
3161
+ elif dflen > 200: lwadjust=1.0
3162
+ elif dflen > 300: lwadjust=0.8
3163
+ elif dflen > 500: lwadjust=0.4
3164
+ elif dflen > 1000: lwadjust=0.2
3165
+ elif dflen > 2000: lwadjust=0.1
3166
+ elif dflen > 3000: lwadjust=0.05
3167
+ elif dflen > 5000: lwadjust=0.01
3168
+ else: lwadjust=1.5
3169
+
3170
+ return lwadjust
3171
+
3172
+ #==============================================================================
3173
+ if __name__=='__main__':
3174
+ start='MRM'
3175
+ start='L3M'
3176
+ start='MRY'
3177
+ start='L30Y'
3178
+
3179
+ start='default'; end='default'
3180
+
3181
+ start='2024-1-1'; end='2023-1-1'
3182
+
3183
+ start_end_preprocess(start,end='today')
3184
+
3185
+ def start_end_preprocess(start,end='today'):
3186
+ """
3187
+ 功能:处理简约日期为具体日期,并检查日期的合理性
3188
+ """
3189
+
3190
+ # 检查日期:截至日期
3191
+ import datetime as dt;
3192
+ todaydt=dt.date.today();todaystr=todaydt.strftime('%Y-%m-%d')
3193
+
3194
+ end=end.lower()
3195
+ if end in ['default','today']:
3196
+ todate=todaystr
3197
+ else:
3198
+ validdate,todate=check_date2(end)
3199
+ if not validdate:
3200
+ print(" #Warning(start_end_preprocess): invalid date for",end)
3201
+ todate=todaystr
3202
+
3203
+ # 检查日期:开始日期
3204
+ start=start.lower()
3205
+
3206
+ """
3207
+ if start in ['default','mrm','l1m']: # 默认近一个月
3208
+ fromdate=date_adjust(todate,adjust=-31-7) #多几天有利于绘图坐标标示
3209
+ elif start in ['l2m']: # 近2个月
3210
+ fromdate=date_adjust(todate,adjust=-31*2-14)
3211
+ elif start in ['mrq','l3m']: # 近三个月
3212
+ fromdate=date_adjust(todate,adjust=-31*3-16)
3213
+ elif start in ['l6m','mrh']: # 近6个月
3214
+ fromdate=date_adjust(todate,adjust=-31*6-16)
3215
+ elif start in ['mry','l12m']: # 近一年
3216
+ fromdate=date_adjust(todate,adjust=-366-16)
3217
+ elif start in ['l2y']: # 近两年以来
3218
+ fromdate=date_adjust(todate,adjust=-366*2-15)
3219
+ elif start in ['l3y']: # 近三年以来
3220
+ fromdate=date_adjust(todate,adjust=-366*3-14)
3221
+ elif start in ['l5y']: # 近五年以来
3222
+ fromdate=date_adjust(todate,adjust=-366*5-13)
3223
+ elif start in ['l8y']: # 近八年以来
3224
+ fromdate=date_adjust(todate,adjust=-366*8-11)
3225
+ elif start in ['l10y']: # 近十年以来
3226
+ fromdate=date_adjust(todate,adjust=-366*10-9)
3227
+ elif start in ['l20y']: # 近20年以来
3228
+ fromdate=date_adjust(todate,adjust=-366*20-1)
3229
+ elif start in ['l30y']: # 近30年以来
3230
+ fromdate=date_adjust(todate,adjust=-366*30)
3231
+ elif start in ['ytd']: # 今年以来
3232
+ fromdate=str(todaydt.year-1)+'-12-1'
3233
+ else:
3234
+ validdate,fromdate=check_date2(start)
3235
+ if not validdate:
3236
+ print(" #Warning(security_trend): invalid date for",start,"/b, reset to MRM")
3237
+ fromdate=date_adjust(todate,adjust=-31-16)
3238
+ """
3239
+ if start in ['default','mrm','l1m']: # 默认近一个月
3240
+ fromdate=date_adjust2(todate,adjust_month=-1,adjust_day=-1) #有利于绘图横坐标日期标示
3241
+ elif start in ['l2m']: # 近2个月
3242
+ fromdate=date_adjust2(todate,adjust_month=-2,adjust_day=-1)
3243
+ elif start in ['mrq','l3m']: # 近三个月
3244
+ fromdate=date_adjust2(todate,adjust_month=-3,adjust_day=-1)
3245
+ elif start in ['l6m','mrh']: # 近6个月
3246
+ fromdate=date_adjust2(todate,adjust_month=-6,adjust_day=-1)
3247
+ elif start in ['mry','l12m']: # 近一年
3248
+ fromdate=date_adjust2(todate,adjust_year=-1,to_prev_month_end=True)
3249
+ elif start in ['l2y']: # 近两年以来
3250
+ fromdate=date_adjust2(todate,adjust_year=-2,to_prev_month_end=True)
3251
+ elif start in ['l3y']: # 近三年以来
3252
+ fromdate=date_adjust2(todate,adjust_year=-3,to_prev_month_end=True)
3253
+ elif start in ['l5y']: # 近五年以来
3254
+ fromdate=date_adjust2(todate,adjust_year=-5,to_prev_year_end=True)
3255
+ elif start in ['l8y']: # 近八年以来
3256
+ fromdate=date_adjust2(todate,adjust_year=-8,to_prev_year_end=True)
3257
+ elif start in ['l10y']: # 近十年以来
3258
+ fromdate=date_adjust2(todate,adjust_year=-10,to_prev_year_end=True)
3259
+ elif start in ['l20y']: # 近20年以来
3260
+ fromdate=date_adjust2(todate,adjust_year=-20,to_prev_year_end=True)
3261
+ elif start in ['l30y']: # 近30年以来
3262
+ fromdate=date_adjust2(todate,adjust_year=-30,to_prev_year_end=True)
3263
+ elif start in ['ytd']: # 今年以来
3264
+ fromdate=str(todaydt.year-1)+'-12-31'
3265
+ else:
3266
+ validdate,fromdate=check_date2(start)
3267
+ if not validdate:
3268
+ print(" #Warning(security_trend): invalid date",start)
3269
+ fromdate=date_adjust2(todate,adjust_month=-1,adjust_day=-1)
3270
+
3271
+ result,_,_=check_period(fromdate,todate)
3272
+ if not result:
3273
+ todate=todaystr
3274
+ print(" #Warning(start_end_preprocess): invalid date period between",fromdate,todate)
3275
+
3276
+ return fromdate,todate
3277
+
3278
+ #==============================================================================
3279
+ #==============================================================================
2553
3280