daxiaowai_and_guojiuxiao.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. # 克隆自聚宽文章:https://www.joinquant.com/post/50551
  2. # 标题:大小外择时小市值2.0
  3. # 作者:DanD
  4. from jqdata import *
  5. from jqfactor import *
  6. import numpy as np
  7. import pandas as pd
  8. import pickle
  9. import talib
  10. import warnings
  11. from jqlib.technical_analysis import *
  12. warnings.filterwarnings("ignore")
  13. # 初始化函数
  14. def initialize(context):
  15. # 设定基准
  16. set_benchmark('000300.XSHG')
  17. # 用真实价格交易
  18. set_option('use_real_price', True)
  19. # 打开防未来函数
  20. set_option("avoid_future_data", True)
  21. # 将滑点设置为0.246%
  22. set_slippage(PriceRelatedSlippage(0.00246),type='stock')
  23. # 设置交易成本万分之三,不同滑点影响可在归因分析中查看
  24. set_order_cost(OrderCost(open_tax=0, close_tax=0.001, open_commission=0.0003, close_commission=0.0003,
  25. close_today_commission=0, min_commission=5), type='stock')
  26. # 过滤order中低于error级别的日志
  27. log.set_level('order', 'error')
  28. # 初始化全局变量
  29. g.no_trading_today_signal = False
  30. g.market_temperature = "warm"
  31. g.stock_num = 3
  32. g.highest = 50
  33. g.buy_stock_count = 5
  34. g.hold_list = [] # 当前持仓的全部股票
  35. g.yesterday_HL_list = [] # 记录持仓中昨日涨停的股票
  36. g.bought_stocks = {} #记录补跌的股票和金额
  37. g.foreign_ETF = [
  38. '518880.XSHG', # 黄金ETF
  39. '513030.XSHG', # 德国ETF
  40. '513100.XSHG', # 纳指ETF
  41. '164824.XSHE', # 印度LOF
  42. '159866.XSHE', # 日经ETF
  43. ]
  44. # 设置交易运行时间
  45. run_daily(prepare_stock_list, '9:05')
  46. run_monthly(singal, 1, '9:00')
  47. run_weekly(clear, 1, '9:30')
  48. run_weekly(monthly_adjustment, 1, '9:30')
  49. run_daily(stop_loss, '14:00')
  50. def clear(context):#卖出补跌的仓位
  51. log.info(f"g.bought_stocks: {g.bought_stocks}")
  52. if g.bought_stocks!={}:
  53. for stock, amount in g.bought_stocks.items():
  54. if stock in context.portfolio.positions:
  55. order_value(stock, -amount) # 卖出股票至目标价值为0
  56. log.info("Sell cover stocks: %s, value: %s" % (stock, amount))
  57. # 清空记录
  58. g.bought_stocks.clear()
  59. def prepare_stock_list(context):
  60. """
  61. 准备每日股票列表,包括持仓股票列表和昨日涨停股票列表。
  62. 1. 遍历当前持仓中的所有股票,将其加入到 g.hold_list 中。
  63. 2. 如果持仓列表不为空:
  64. - 获取持仓股票在前一个交易日的收盘价和涨停价。
  65. - 过滤出收盘价等于涨停价的股票,存入昨日涨停列表 g.yesterday_HL_list。
  66. 3. 如果持仓列表为空,昨日涨停列表 g.yesterday_HL_list 也置为空。
  67. 返回:
  68. - g.hold_list: 持仓股票清单
  69. - g.yesterday_HL_list: 持仓的昨日涨停清单
  70. """
  71. log.info('Daily run is enabled')
  72. # 获取已持有列表
  73. g.hold_list = []
  74. for position in list(context.portfolio.positions.values()):
  75. stock = position.security
  76. g.hold_list.append(stock)
  77. log.info(f"Day position {len(g.hold_list)}:{g.hold_list}")
  78. # 获取昨日涨停列表
  79. if g.hold_list != []:
  80. df = get_price(g.hold_list, end_date=context.previous_date, frequency='daily', fields=['close', 'high_limit'],
  81. count=1, panel=False, fill_paused=False)
  82. df = df[df['close'] == df['high_limit']]
  83. g.yesterday_HL_list = list(df.code)
  84. else:
  85. g.yesterday_HL_list = []
  86. def stop_loss(context):
  87. """
  88. 执行止损策略,根据条件卖出股票或进行补仓。
  89. 1. 初始化计数器 num 用于记录卖出股票的数量和获取当前时间 now_time。
  90. 2. 如果昨日涨停列表 g.yesterday_HL_list 不为空:
  91. - 对于每只昨日涨停的股票,获取当前价格和涨停价。
  92. - 如果当前价格低于涨停价,打印日志信息并卖出该股票,计数器 num 加1。
  93. - 如果当前价格等于涨停价,打印日志信息并继续持有该股票。
  94. 3. 初始化两个列表 SS 和 S 用于记录持仓股票的跌幅。
  95. 4. 遍历持仓列表 g.hold_list:
  96. - 如果股票在当前持仓中,并且当前价格低于平均成本价的92%,执行止损卖出操作,计数器 num 加1。
  97. - 否则,将股票加入列表 S,并计算当前价格相对于平均成本价的涨跌幅,存入 SS。
  98. 5. 如果有股票被止损卖出(num >= 1),且 SS 列表不为空:
  99. - 重置 num 为3。
  100. - 找出 SS 中跌幅最大的 num 支股票。
  101. - 计算每只股票的补仓金额为当前现金除以 num。
  102. - 对于跌幅最大的股票,执行补仓操作,并记录日志信息。如果股票不在已买入列表 g.bought_stocks 中,更新其买入金额。
  103. """
  104. num = 0
  105. now_time = context.current_dt
  106. if g.yesterday_HL_list != []:
  107. # 对昨日涨停股票观察到尾盘如不涨停则提前卖出,如果涨停即使不在应买入列表仍暂时持有
  108. for stock in g.yesterday_HL_list:
  109. current_data = get_price(stock, end_date=now_time, frequency='1m', fields=['close', 'high_limit'],
  110. skip_paused=False, fq='pre', count=1, panel=False, fill_paused=True)
  111. if current_data.iloc[0, 0] < current_data.iloc[0, 1]:
  112. log.info("[%s]Limit ends, sell" % (stock))
  113. position = context.portfolio.positions[stock]
  114. close_position(position)
  115. num = num+1
  116. else:
  117. log.info("[%s]Limit continues, continue to hold" % (stock))
  118. SS=[]
  119. S=[]
  120. for stock in g.hold_list:
  121. if stock in list(context.portfolio.positions.keys()):
  122. if context.portfolio.positions[stock].price < context.portfolio.positions[stock].avg_cost * 0.92:
  123. order_target_value(stock, 0)
  124. log.info("Loss control: Selling out %s" % (stock))
  125. num = num+1
  126. else:
  127. S.append(stock)
  128. NOW = (context.portfolio.positions[stock].price - context.portfolio.positions[stock].avg_cost)/context.portfolio.positions[stock].avg_cost
  129. SS.append(np.array(NOW))
  130. else:
  131. log.info(f"stop_loss else after for loop, num: {num}, SS: {len(SS)}")
  132. if num >=1:
  133. if len(SS) > 0:
  134. # 清空记录
  135. num=3
  136. min_values = sorted(SS)[:num]
  137. min_indices = [SS.index(value) for value in min_values]
  138. min_strings = [S[index] for index in min_indices]
  139. log.info(f"Need to cover positions are min_strings: {min_strings}, min_indices: {min_indices}")
  140. cash = context.portfolio.cash/num
  141. for ss in min_strings:
  142. cover_order = order_value(ss, cash)
  143. if cover_order:
  144. log.info("Cover the n that fell the most with Order %s and %.2f" % (ss, cash))
  145. log.debug(f"cover_order: {cover_order}")
  146. amount = cover_order.amount
  147. log.debug(f"amount: {type(amount)}, {amount}")
  148. if ss not in g.bought_stocks:
  149. g.bought_stocks[ss] = amount
  150. else:
  151. g.bought_stocks[ss] = amount + g.bought_stocks[ss]
  152. log.info(f"g.bought_stocks: {g.bought_stocks}")
  153. def filter_roic(context,stock_list):
  154. """
  155. 根据最近一期的ROIC(投入资本回报率)过滤股票列表。
  156. 1. 获取前一个交易日的日期。
  157. 2. 遍历输入的股票列表 stock_list,对于每只股票:
  158. - 获取该股票最近一期的 ROIC 值。
  159. - 如果 ROIC 大于 0.08,将该股票添加到 list 中。
  160. """
  161. yesterday = context.previous_date
  162. list=[]
  163. for stock in stock_list:
  164. roic=get_factor_values(stock, 'roic_ttm', end_date=yesterday,count=1)['roic_ttm'].iloc[0,0]
  165. if roic>0.08:
  166. list.append(stock)
  167. return list
  168. def filter_highprice_stock(context,stock_list):
  169. # 过滤出最近一分钟的收盘价低于10元的股票,或当前已持有的股票。
  170. last_prices = history(1, unit='1m', field='close', security_list=stock_list)
  171. return [stock for stock in stock_list if stock in context.portfolio.positions.keys()
  172. or last_prices[stock][-1] < 10]
  173. def filter_highprice_stock2(context,stock_list):
  174. # 过滤出最近一分钟的收盘价低于300元的股票,或当前已持有的股票。
  175. last_prices = history(1, unit='1m', field='close', security_list=stock_list)
  176. return [stock for stock in stock_list if stock in context.portfolio.positions.keys()
  177. or last_prices[stock][-1] < 300]
  178. def get_recent_limit_up_stock(context, stock_list, recent_days):
  179. # 获取最近指定天数内出现过涨停的股票。
  180. stat_date = context.previous_date
  181. new_list = []
  182. for stock in stock_list:
  183. df = get_price(stock, end_date=stat_date, frequency='daily', fields=['close','high_limit'], count=recent_days, panel=False, fill_paused=False)
  184. df = df[df['close'] == df['high_limit']]
  185. if len(df) > 0:
  186. new_list.append(stock)
  187. return new_list
  188. def get_recent_down_up_stock(context, stock_list, recent_days):
  189. """获取最近指定天数内出现过跌停的股票。"""
  190. stat_date = context.previous_date
  191. new_list = []
  192. for stock in stock_list:
  193. df = get_price(stock, end_date=stat_date, frequency='daily', fields=['close','low_limit'], count=recent_days, panel=False, fill_paused=False)
  194. df = df[df['close'] == df['low_limit']]
  195. if len(df) > 0:
  196. new_list.append(stock)
  197. return new_list
  198. #1-2 选股模块
  199. def get_stock_list(context):
  200. """
  201. 小市值选股逻辑:
  202. 针对399101里的股票,过滤次新、科创北交、st,先获取实质5-30的最小市值100支股票
  203. 再过滤掉停牌、当前涨停和跌停的股票
  204. 最后获得市值最小的50支股票
  205. """
  206. final_list = []
  207. MKT_index = '399101.XSHE'
  208. initial_list = get_index_stocks(MKT_index)
  209. initial_list = filter_new_stock(context, initial_list)
  210. initial_list = filter_kcbj_stock(initial_list)
  211. initial_list = filter_st_stock(initial_list)
  212. q = query(valuation.code,valuation.market_cap).filter(valuation.code.in_(initial_list),valuation.market_cap.between(5,30)).order_by(valuation.market_cap.asc())
  213. df_fun = get_fundamentals(q)
  214. df_fun = df_fun[:100]
  215. initial_list = list(df_fun.code)
  216. initial_list = filter_paused_stock(initial_list)
  217. initial_list = filter_limitup_stock(context, initial_list)
  218. initial_list = filter_limitdown_stock(context, initial_list)
  219. #log.info('initial_list has {} stocks'.format(len(initial_list)))
  220. q = query(valuation.code,valuation.market_cap).filter(valuation.code.in_(initial_list)).order_by(valuation.market_cap.asc())
  221. df_fun = get_fundamentals(q)
  222. df_fun = df_fun[:50]
  223. final_list = list(df_fun.code)
  224. return final_list
  225. #1-2 选股模块
  226. def get_stock_list_2(context):
  227. """
  228. 1. 获取综小中指的成分股 initial_list。
  229. 2. 对 initial_list 进行一系列过滤操作:
  230. - 过滤掉新股。
  231. - 过滤掉科创北交股票。
  232. - 过滤掉ST股票。
  233. 5. 构建查询条件 q,筛选出符合以下条件的股票:
  234. - 股票代码在 initial_list 中。
  235. - 总市值在5亿到30亿之间。
  236. - 归属于母公司所有者的净利润为正。
  237. - 净利润为正。
  238. - 营业收入大于1亿。
  239. 6. 按总市值升序排序,限制返回最多50只股票。
  240. 7. 获取基本面数据 df,并从中提取股票代码列表 final_list。
  241. 8. 获取 final_list 中股票最近一天的收盘价。
  242. 9. 返回符合以下条件的股票列表:
  243. - 当前持有的股票。
  244. - 最近一天收盘价不高于全局变量 g.highest 的股票。
  245. """
  246. final_list = []
  247. MKT_index = '399101.XSHE'
  248. initial_list = get_index_stocks(MKT_index)
  249. initial_list = filter_new_stock(context, initial_list)
  250. initial_list = filter_kcbj_stock(initial_list)
  251. initial_list = filter_st_stock(initial_list)
  252. # 国九更新:过滤近一年净利润为负且营业收入小于1亿的
  253. # 国九更新:过滤近一年期末净资产为负的 (经查询没有为负数的,所以直接pass这条)
  254. # 国九更新:过滤近一年审计建议无法出具或者为负面建议的 (经过净利润等筛选,审计意见几乎不会存在异常)
  255. q = query(
  256. valuation.code,
  257. valuation.market_cap, # 总市值 circulating_market_cap/market_cap
  258. income.np_parent_company_owners, # 归属于母公司所有者的净利润
  259. income.net_profit, # 净利润
  260. income.operating_revenue # 营业收入
  261. #security_indicator.net_assets
  262. ).filter(
  263. valuation.code.in_(initial_list),
  264. valuation.market_cap.between(5,30),
  265. income.np_parent_company_owners > 0,
  266. income.net_profit > 0,
  267. income.operating_revenue > 1e8
  268. ).order_by(valuation.market_cap.asc()).limit(50)
  269. df = get_fundamentals(q)
  270. final_list = list(df.code)
  271. last_prices = history(1, unit='1d', field='close', security_list=final_list)
  272. return [stock for stock in final_list if stock in g.hold_list or last_prices[stock][-1] <= g.highest]
  273. def SMALL(context, choice):
  274. """
  275. 获取经过多重筛选后的股票列表,并根据财务指标进行进一步筛选。
  276. 1. 获取两组初步筛选的股票列表 target_list_1 和 target_list_2。
  277. 2. 合并去重这两组股票列表,得到 target_list。
  278. 3. 限制 target_list 的长度为全局变量 g.stock_num 的三倍。
  279. 4. 构建查询条件,筛选出符合以下条件的股票:
  280. - 股票代码在 target_list 中。
  281. - 按总市值升序排序。
  282. 5. 获取基本面数据,并提取股票代码列表 final_list。
  283. 6. 返回最终筛选的股票列表 final_list。
  284. """
  285. target_list_1 = get_stock_list(context)
  286. target_list_2 = get_stock_list_2(context)
  287. target_list= list(dict.fromkeys(target_list_1 + target_list_2))
  288. target_list=target_list[:g.stock_num*3]
  289. #target_list = get_stock_list_2(context)[:g.stock_num*3]
  290. final_list = get_fundamentals(query(
  291. valuation.code,
  292. indicator.roe,
  293. indicator.roa,
  294. ).filter(
  295. valuation.code.in_(target_list),
  296. #valuation.pb_ratio<1
  297. ).order_by(
  298. valuation.market_cap.asc()
  299. )).set_index('code').index.tolist()
  300. return final_list
  301. def singal(context):
  302. """
  303. 1. 首先获得B_stocks,沪深300指数的成分股和S_stocks,中小综指的成分股,去掉科创北上、st、次新股
  304. 2. 从B_stocks当中获得市值最大的20支股,从S_stocks当中获得市值最小的20支股
  305. 3. 针对B_stocks和S_stocks,使用最新的价格和过去的价格比值*100,各自获得一个平均值B_mean,S_mean
  306. 4. g.signal的4种情况
  307. - B_mean>S_mean且B_mean>5:small
  308. - B_mean>S_mean且5>=B_mean>0:big
  309. - B_mean<S_mean且S_mean>0:small
  310. - 所有其他:etf (黄金或外盘)
  311. 返回:
  312. - g.singal: small, big, etf三种投资标的的选择
  313. """
  314. today = context.current_dt
  315. dt_last = context.previous_date
  316. N=10
  317. B_stocks = get_index_stocks('000300.XSHG', dt_last)
  318. B_stocks = filter_kcbj_stock(B_stocks)
  319. B_stocks = filter_st_stock(B_stocks)
  320. B_stocks = filter_new_stock(context, B_stocks)
  321. S_stocks = get_index_stocks('399101.XSHE', dt_last)
  322. S_stocks = filter_kcbj_stock(S_stocks)
  323. S_stocks = filter_st_stock(S_stocks)
  324. S_stocks = filter_new_stock(context, S_stocks)
  325. q = query(
  326. valuation.code, valuation.circulating_market_cap
  327. ).filter(
  328. valuation.code.in_(B_stocks)
  329. ).order_by(
  330. valuation.circulating_market_cap.desc()
  331. )
  332. df = get_fundamentals(q, date=dt_last)
  333. Blst = list(df.code)[:20]
  334. q = query(
  335. valuation.code, valuation.circulating_market_cap
  336. ).filter(
  337. valuation.code.in_(S_stocks)
  338. ).order_by(
  339. valuation.circulating_market_cap.asc()
  340. )
  341. df = get_fundamentals(q, date=dt_last)
  342. Slst = list(df.code)[:20]
  343. #
  344. B_ratio = get_price(Blst, end_date=dt_last, frequency='1d', fields=['close'], count=N, panel=False
  345. ).pivot(index='time', columns='code', values='close')
  346. change_BIG = (B_ratio.iloc[-1] / B_ratio.iloc[0] - 1) * 100
  347. A1 = np.array(change_BIG)
  348. A1 = np.nan_to_num(A1)
  349. B_mean = np.mean(A1)
  350. # log.info(f"B_ratio: {B_ratio}")
  351. # log.info(f"change_BIG: {change_BIG}")
  352. # log.info(f"A1: {A1}")
  353. # log.info(f"B_mean: {B_mean}")
  354. S_ratio = get_price(Slst, end_date=dt_last, frequency='1d', fields=['close'], count=N, panel=False
  355. ).pivot(index='time', columns='code', values='close')
  356. change_SMALL = (S_ratio.iloc[-1] / S_ratio.iloc[0] - 1) * 100
  357. A1 = np.array(change_SMALL)
  358. A1 = np.nan_to_num(A1)
  359. S_mean = np.mean(A1)
  360. if B_mean>S_mean and B_mean>0:
  361. if B_mean>5:
  362. g.signal='small'
  363. log.info('Big has ended, change to Small')
  364. else:
  365. g.signal='big'
  366. log.info('Big')
  367. elif B_mean < S_mean and S_mean > 0:
  368. g.signal='small'
  369. log.info('Small')
  370. else:
  371. log.info('Foreign')
  372. g.signal='etf'
  373. # 1-3 整体调整持仓
  374. def monthly_adjustment(context):
  375. """
  376. 每月(目前是每周)进行整体持仓调整,根据市场信号调整股票组合,进行买入和卖出操作。
  377. 1. 根据全局信号 g.signal 确定调整策略:
  378. - 如果信号为 'big',调用 White_Horse 方法获取大市值股票的目标列表。
  379. - 如果信号为 'small',从中小综指获取成分股,过滤掉科创板、ST和次新股后,调用 SMALL 方法获取小市值股票的目标列表。
  380. - 如果信号为 'etf',使用全局变量 g.foreign_ETF 作为目标列表。
  381. - 如果信号不在预期范围内,打印提示信息。
  382. 5. 打印目标股票列表 target_list。
  383. 6. 对目标列表进行进一步的过滤:
  384. - 使用 filter_limitup_stock 过滤掉涨停的股票。
  385. - 使用 filter_limitdown_stock 过滤掉跌停的股票。
  386. - 使用 filter_paused_stock 过滤掉停牌的股票。
  387. 7. 遍历当前持仓的股票 g.hold_list:
  388. - 对于不在目标列表和昨日持仓列表 g.yesterday_HL_list 中的股票,平掉其仓位。
  389. 8. 获取当前持仓数量 position_count 和目标股票数量 target_num。
  390. 9. 如果目标股票数量大于当前持仓数量:
  391. - 计算每个新股票的投资金额 value 为当前现金除以新增目标股票数量。
  392. - 遍历目标股票列表 target_list,对于未持有的股票,尝试开仓。
  393. - 如果开仓成功且持仓数量达到目标数量,停止操作。
  394. """
  395. log.info("Make weekly overall position adjustments")
  396. today = context.current_dt
  397. dt_last = context.previous_date
  398. target_list=[]
  399. log.info(f"g.signal: {g.signal}")
  400. if g.signal=='big':
  401. target_list = White_Horse(context)
  402. elif g.signal=='small':
  403. S_stocks = get_index_stocks('399101.XSHE', dt_last)
  404. S_stocks = filter_kcbj_stock(S_stocks)
  405. S_stocks = filter_st_stock(S_stocks)
  406. S_stocks = filter_new_stock(context, S_stocks)
  407. choice = S_stocks
  408. target_list = SMALL(context,choice)
  409. elif g.signal=='etf':
  410. target_list = g.foreign_ETF
  411. else:
  412. log.info("g.signal is not the one expected")
  413. log.info(f"target_list before filter: {target_list}")
  414. target_list = filter_limitup_stock(context,target_list)
  415. target_list = filter_limitdown_stock(context,target_list)
  416. target_list = filter_paused_stock(target_list)
  417. log.info(f"target_list after filter: {target_list}")
  418. # target_list = target_list[:5] # 可以控制持股数量到5
  419. for stock in g.hold_list:
  420. if (stock not in target_list) and (stock not in g.yesterday_HL_list):
  421. position = context.portfolio.positions[stock]
  422. close_position(position)
  423. position_count = len(context.portfolio.positions)
  424. target_num = len(target_list)
  425. if target_num > position_count:
  426. value = context.portfolio.cash / (target_num - position_count)
  427. for stock in target_list:
  428. if stock not in list(context.portfolio.positions.keys()):
  429. if open_position(stock, value):
  430. if len(context.portfolio.positions) == target_num:
  431. break
  432. def boll_filter(stocks,date):
  433. """
  434. 使用布林带策略过滤股票列表,返回符合条件的股票。
  435. 1. 获取指定日期 date 的股票数据,包括高价、高价和收盘价。
  436. - 使用 get_bars 函数获取 stocks 列表中每只股票的最近一天数据。
  437. - 将数据的索引设置为股票代码。
  438. 2. 计算布林带指标:
  439. - 使用 Bollinger_Bands 函数计算上轨(upperband)、中轨(middleband)和下轨(lowerband)。
  440. - 参数设置为:时间周期为20,标准差倍数为2,单位为每日。
  441. 3. 将布林带指标结果添加到数据框 x 中:
  442. - 将 upperband、middleband 和 lowerband 的结果分别存储到 x 数据框中的 'up'、'mid' 和 'lowe' 列。
  443. 4. 过滤符合条件的股票:
  444. - 筛选出收盘价低于上轨且最低价高于下轨的股票。
  445. - 这些股票符合布林带策略的买入信号。
  446. 5. 返回符合条件的股票代码列表。
  447. 参数:
  448. - stocks: 股票代码列表。
  449. - date: 筛选的日期。
  450. 返回:
  451. - 符合布林带策略条件的股票代码列表。
  452. """
  453. x=get_bars(stocks, 1, unit='1d', fields=['high','low','close'],end_dt=date,df=True)
  454. x.index=stocks
  455. upperband, middleband, lowerband=Bollinger_Bands(stocks, date, timeperiod=20,
  456. nbdevup=2, nbdevdn=2, unit = '1d', include_now = True, fq_ref_date = None)
  457. log.info(f"result for bollinger_bands, upperband: {type(upperband)}, {upperband}; middleband: {type(middleband)}, {middleband}; lowerband: {type(lowerband)}, {lowerband}")
  458. x['up']= pd.DataFrame(upperband, index=[0]).T.values
  459. x['mid']=pd.DataFrame(middleband, index=[0]).T.values
  460. x['lowe']=pd.DataFrame(lowerband, index=[0]).T.values
  461. x=x[(x['close']<x['up'])&(x['lowe']<x['low'])]
  462. return(list(x.index))
  463. # 3-1 交易模块-自定义下单
  464. def order_target_value_(security, value):
  465. if value == 0:
  466. log.info("Selling out %s" % (security))
  467. else:
  468. log.info("Order %s to value %f" % (security, value))
  469. return order_target_value(security, value)
  470. # 3-2 交易模块-开仓
  471. def open_position(security, value):
  472. order = order_target_value_(security, value)
  473. if order != None and order.filled > 0:
  474. return True
  475. return False
  476. # 3-3 交易模块-平仓
  477. def close_position(position):
  478. security = position.security
  479. order = order_target_value_(security, 0) # 可能会因停牌失败
  480. if order != None:
  481. if order.status == OrderStatus.held and order.filled == order.amount:
  482. return True
  483. return False
  484. def filter_paused_stock(stock_list):
  485. current_data = get_current_data()
  486. return [stock for stock in stock_list if not current_data[stock].paused]
  487. # 2-2 过滤ST及其他具有退市标签的股票
  488. def filter_st_stock(stock_list):
  489. current_data = get_current_data()
  490. return [stock for stock in stock_list
  491. if not current_data[stock].is_st
  492. and 'ST' not in current_data[stock].name
  493. and '*' not in current_data[stock].name
  494. and '退' not in current_data[stock].name]
  495. # 2-3 过滤科创北交股票
  496. def filter_kcbj_stock(stock_list):
  497. for stock in stock_list[:]:
  498. if stock[0] == '4' or stock[0] == '8' or stock[:2] == '68' or stock[0] == '3':
  499. stock_list.remove(stock)
  500. return stock_list
  501. # 2-4 过滤涨停的股票
  502. def filter_limitup_stock(context, stock_list):
  503. last_prices = history(1, unit='1m', field='close', security_list=stock_list)
  504. current_data = get_current_data()
  505. return [stock for stock in stock_list if stock in context.portfolio.positions.keys()
  506. or last_prices[stock][-1] < current_data[stock].high_limit]
  507. # 2-5 过滤跌停的股票
  508. def filter_limitdown_stock(context, stock_list):
  509. last_prices = history(1, unit='1m', field='close', security_list=stock_list)
  510. current_data = get_current_data()
  511. return [stock for stock in stock_list if stock in context.portfolio.positions.keys()
  512. or last_prices[stock][-1] > current_data[stock].low_limit]
  513. # 2-6 过滤次新股
  514. def filter_new_stock(context, stock_list):
  515. yesterday = context.previous_date
  516. return [stock for stock in stock_list if
  517. not yesterday - get_security_info(stock).start_date < datetime.timedelta(days=375)]
  518. ## 开盘前运行函数
  519. def White_Horse(context):
  520. """
  521. 该方法根据市场温度筛选出符合特定财务指标的股票列表。
  522. 1. 首先调用 Market_temperature 函数获取市场温度。
  523. 2. 初始化一个空列表 check_out_lists 用于存储筛选出的股票。
  524. 3. 获取当前的股票数据 current_data,以及200天前的所有股票列表 all_stocks。
  525. 4. 将 all_stocks 更新为沪深300指数的成分股。
  526. 5. 根据以下条件过滤股票:
  527. - 排除涨停或跌停开盘的股票。
  528. - 排除停牌的股票。
  529. - 排除 ST、*ST、退市风险股。
  530. - 排除创业板、科创板、北交所的股票。
  531. 6. 根据市场温度(冷、暖、热)设置不同的筛选条件和排序规则:
  532. - 冷:筛选市净率(PB)在0到1之间,经营活动现金流入大于0,调整后利润大于0,现金流入与调整后利润比大于2.0,收益增长率大于1.5,净利润同比增长率大于-15的股票,并按 ROA/PB 降序排列。
  533. - 暖:筛选市净率(PB)在0到1之间,经营活动现金流入大于0,调整后利润大于0,现金流入与调整后利润比大于1.0,收益增长率大于2.0,净利润同比增长率大于0的股票,并按 ROA/PB 降序排列。
  534. - 热:筛选市净率(PB)大于3,经营活动现金流入大于0,调整后利润大于0,现金流入与调整后利润比大于0.5,收益增长率大于3.0,净利润同比增长率大于20的股票,并按 ROA 降序排列。
  535. 7. 将符合条件的股票代码存入 check_out_lists。
  536. 8. 返回筛选出的股票列表 check_out_lists,并记录日志信息。
  537. """
  538. Market_temperature(context)
  539. log.info(f"This month's temperature is:{g.market_temperature}")
  540. check_out_lists = []
  541. current_data = get_current_data()
  542. check_date = context.previous_date - datetime.timedelta(days=200)
  543. # all_stocks = list(get_all_securities(date=check_date).index)
  544. # log.info(f"first all_stocks: {len(all_stocks)}, {all_stocks}")
  545. all_stocks = get_index_stocks("000300.XSHG")
  546. # log.info(f"second all_stocks: {len(all_stocks)}, {all_stocks}")
  547. # 过滤创业板、ST、停牌、当日涨停
  548. all_stocks = [stock for stock in all_stocks if not (
  549. (current_data[stock].day_open == current_data[stock].high_limit) or # 涨停开盘
  550. (current_data[stock].day_open == current_data[stock].low_limit) or # 跌停开盘
  551. current_data[stock].paused or # 停牌
  552. current_data[stock].is_st or # ST
  553. ('ST' in current_data[stock].name) or
  554. ('*' in current_data[stock].name) or
  555. ('退' in current_data[stock].name) or
  556. (stock.startswith('30')) or # 创业
  557. (stock.startswith('68')) or # 科创
  558. (stock.startswith('8')) or # 北交
  559. (stock.startswith('4')) # 北交
  560. )]
  561. if g.market_temperature == "cold":
  562. q = query(
  563. valuation.code,
  564. ).filter(
  565. valuation.pb_ratio > 0,
  566. valuation.pb_ratio < 1,
  567. cash_flow.subtotal_operate_cash_inflow > 0,
  568. indicator.adjusted_profit > 0,
  569. cash_flow.subtotal_operate_cash_inflow/indicator.adjusted_profit>2.0,
  570. indicator.inc_return > 1.5,
  571. indicator.inc_net_profit_year_on_year > -15,
  572. valuation.code.in_(all_stocks)
  573. ).order_by(
  574. (indicator.roa/valuation.pb_ratio).desc()
  575. ).limit(
  576. g.buy_stock_count + 1
  577. )
  578. elif g.market_temperature == "warm":
  579. q = query(
  580. valuation.code,
  581. ).filter(
  582. valuation.pb_ratio > 0,
  583. valuation.pb_ratio < 1,
  584. cash_flow.subtotal_operate_cash_inflow > 0,
  585. indicator.adjusted_profit > 0,
  586. cash_flow.subtotal_operate_cash_inflow/indicator.adjusted_profit>1.0,
  587. indicator.inc_return > 2.0,
  588. indicator.inc_net_profit_year_on_year > 0,
  589. valuation.code.in_(all_stocks)
  590. ).order_by(
  591. (indicator.roa/valuation.pb_ratio).desc()
  592. ).limit(
  593. g.buy_stock_count + 1
  594. )
  595. elif g.market_temperature == "hot":
  596. q = query(
  597. valuation.code,
  598. ).filter(
  599. valuation.pb_ratio > 3,
  600. cash_flow.subtotal_operate_cash_inflow > 0,
  601. indicator.adjusted_profit > 0,
  602. cash_flow.subtotal_operate_cash_inflow/indicator.adjusted_profit>0.5,
  603. indicator.inc_return > 3.0,
  604. indicator.inc_net_profit_year_on_year > 20,
  605. valuation.code.in_(all_stocks)
  606. ).order_by(
  607. indicator.roa.desc()
  608. ).limit(
  609. g.buy_stock_count + 1
  610. )
  611. check_out_lists = list(get_fundamentals(q).code)
  612. # 取需要的只数
  613. #check_out_lists = check_out_lists[:g.buy_stock_count]
  614. log.info("Today's stock pool:%s" % check_out_lists)
  615. return check_out_lists
  616. # tttttttttttt
  617. def Market_temperature(context):
  618. """
  619. 根据沪深300过去220天的收盘价,计算一个权重market_height:
  620. (最新5个的平均值-最小值)/(最大值-最小值)
  621. - 冷:market_height小于0.2,temp为200
  622. - 热:market_height大于0.9,temp为400
  623. - 温:最新60个的平均值/最小值>1.2,temp为300
  624. """
  625. index300 = attribute_history('000300.XSHG', 220, '1d', ('close'), df=False)['close']
  626. market_height = (mean(index300[-5:]) - min(index300)) / (max(index300) - min(index300))
  627. if market_height < 0.20:
  628. g.market_temperature = "cold"
  629. elif market_height > 0.90:
  630. g.market_temperature = "hot"
  631. elif max(index300[-60:]) / min(index300) > 1.20:
  632. g.market_temperature = "warm"
  633. if g.market_temperature == "cold":
  634. temp = 200
  635. elif g.market_temperature == "warm":
  636. temp = 300
  637. else:
  638. temp = 400
  639. if context.run_params.type != 'sim_trade': # 不是模拟交易
  640. record(temp=temp)