def common_fetch(exchange: str, to_date: str = date_utils.get_current_dt()): """ 按交易所获取交易日历 :param exchange: SSE上交所,SZSE深交所 :param to_date: 获取截止日期 :return: """ from_date = fetch_from_date(exchange) while from_date <= to_date: next_date = date_utils.format_delta(from_date, 1000) if next_date > to_date: next_date = to_date for cnt in range(2): log.info('To fetch trade calendar %s %s~%s' % (exchange, from_date, next_date)) try: df: DataFrame = ts_client.fetch_trade_cal(exchange=exchange, start_date=from_date, end_date=next_date) if not df.empty: db_client.store_dataframe(df, TsTradeCal.__tablename__) log.info('Successfully save trade cal: %s~%s' % (from_date, to_date)) break except Exception as e: log.exception( 'Calling TuShare too fast. Will sleep 1 minutes...', exc_info=e) time.sleep(60) ts_client.init_token() from_date = date_utils.format_delta(next_date, 1)
def common_fetch_data(ts_code: str, api_name: str, table: Table, date_field: str, code_field: str, to_date: str = date_utils.get_current_dt(), to_do: bool = True, page_size: int = 1000): """ :param ts_code: 股票编码 :param api_name: 调用tsclient的方法名 :param table: sqlalchemy的表定义 :param date_field: 对应发布日期的字段名 用于获取该类型数据在DB中最新日期 :param code_field: 对应股票编码的字段名 :param to_date: 数据要获取到哪天 :param to_do: 是否要进行此次获取 :param page_size: 每次获取多少条数据 :return: """ if not to_do: return False, None # 财报的 end_date 是第二天,不过目前是没有那么快有数据的 to_date = trade_date_service.get_next_trade_date(to_date) from_date = fetch_from_date(date_field, code_field, ts_code) from_date_to_ret = None while from_date < to_date: stock_data = None next_date = date_utils.format_delta(from_date, page_size) if next_date >= to_date: next_date = to_date for cnt in range(2): log.info('To fetch %s of stock %s %s~%s' % (table.__tablename__, ts_code, from_date, next_date)) try: param = { 'ts_code': ts_code, 'start_date': from_date, 'end_date': next_date } stock_data = ts_client.fetch_data_frame(api_name, **param) break except Exception as e: log.exception( 'Calling TuShare too fast. Will sleep 1 minutes...', exc_info=e) time.sleep(60) ts_client.init_token() if stock_data is not None and not stock_data.empty: db_client.store_dataframe(stock_data, table.__tablename__) if from_date_to_ret is None: from_date_to_ret = from_date log.info('Successfully save %s of stock %s %s~%s' % (table.__tablename__, ts_code, from_date, next_date)) from_date = date_utils.format_delta(next_date, 1) return True, from_date_to_ret
def get_period_open_close(ts_code_list, from_date, to_date): sql = """ select ori.* from ( select * from mq_daily_price where is_trade = 1 and trade_date between '%s' and '%s' and ts_code in (%s) ) ori left join ( select ts_code, max(trade_date) as max_dt, min(trade_date) as min_dt from mq_daily_price where is_trade = 1 and trade_date between '%s' and '%s' and ts_code in (%s) group by ts_code ) target on ori.ts_code = target.ts_code and (ori.trade_date = target.max_dt or ori.trade_date = target.min_dt) where target.ts_code is not null """ % ( from_date, to_date, "'" + "','".join(ts_code_list) + "'", from_date, to_date, "'" + "','".join(ts_code_list) + "'") price_list = db_client.query_with_sql(sql, MqDailyPrice) result = {} for p in price_list: # type: MqDailyPrice if p.ts_code not in result: result[p.ts_code] = { 'min_dt': date_utils.format_delta(to_date, 1), 'max_dt': date_utils.format_delta(from_date, -1), 'open': None, 'close': None, 'ret': None } if p.trade_date < result[p.ts_code]['min_dt']: result[p.ts_code]['min_dt'] = p.trade_date result[p.ts_code]['open'] = p.open * p.adj if p.trade_date > result[p.ts_code]['max_dt']: result[p.ts_code]['max_dt'] = p.trade_date result[p.ts_code]['close'] = p.close * p.adj for code in result: if result[code]['open'] is None or result[code]['close'] is None: continue result[code]['ret'] = result[code]['close'] / result[code]['open'] - 1 return result
def update_dividend_to(to_date: str): session: Session = db_client.get_session() td: list = session.query(TsDividend).order_by( TsDividend.imp_ann_date.desc()).limit(1).all() session.close() now: str = fetch_data_start_date if len( td) == 0 else date_utils.format_delta(td[0].imp_ann_date, 1) dt_code_map = {} while now <= to_date: current = common_fetch_dividend(ts_code=None, dt=now) fetch.merge_from_map(dt_code_map, current) now = date_utils.format_delta(now, 1) return dt_code_map
def get_price_between(self, ts_code_arr: list, now_date: str, from_date: str, to_date: str, adj_type: str) -> list: """ 获取股票的每日价格信息 :param ts_code_arr: 股票编码列表,为空时全取 :param now_date: 查看数据的日期,会显示当时能看到的前复权价格,需为交易日,不能为空 :param from_date: 获取价格的时间段 开始时间,不能为空 必须小于等于 now_date :param to_date: 获取价格的时间段 结束时间,不能为空 必须小于等于 now_date :param adj_type: qfq - 前复权, hfq - 后复权, 其他 - 不复权 :return: list(MqDailyPrice) """ if now_date is None or not date_utils.is_valid_dt(now_date): raise Exception('now_date is empty or invalid') if from_date is None or not date_utils.is_valid_dt(from_date): raise Exception('from_date is empty or invalid') if to_date is None or not date_utils.is_valid_dt(to_date): raise Exception('to_date is empty or invalid') if from_date > now_date or to_date > now_date: raise Exception('Cant get future price %s~%s in %s' % (from_date, to_date, now_date)) all_date_arr = [now_date] i = from_date while i < to_date: all_date_arr.append(i) i = date_utils.format_delta(i, 1) mq_list: list = self.get_normal_price_in(ts_code_arr, all_date_arr) return self.__convert_to_sim_price(mq_list, adj_type, now_date, now_date > to_date)
def analyse(self): d = self.__sd max_mv = self.__cash last_mv = self.__cash max_retrieve = 0 ra = [] da = [] while d <= self.__ed: if d in self.__dr: record: SimDailyRecord = self.__dr[d] mv = record.get_total() ret = (mv - last_mv) / last_mv if mv > max_mv: max_mv = mv last_mv = mv retrieve = (max_mv - last_mv) / max_mv if retrieve > max_retrieve: max_retrieve = retrieve ra.append(float(ret)) da.append(date_utils.parse_str(d)) d = date_utils.format_delta(d, 1) returns = pd.Series(ra, da) pf.plot_returns(returns) pyplot.show()
def __init__(self, ds: SimDataService, sd: str, ed: str, cash: Decimal = 500000, charge: Decimal = 0.00025, tax: Decimal = 0.001, pass_tax: Decimal = 0.00002): """ :param ds: 获取数据服务 :param sd: 回测开始日期 :param ed: 回测结束日期 :param cash: 起始现金 :param charge: 交易费率 - 券商佣金 :param tax: 印花税率 :param pass_tax: 过户费率 """ self.__sd = sd # 回测开始日期 self.__ed = ed # 回测结束日期 self.__init_cash = Decimal(cash) # 初始现金 self.__cash = Decimal(cash) # 现金 self.__charge = Decimal(charge) # 交易费率 - 券商佣金 self.__tax = Decimal(tax) # 印花税率 self.__pass = Decimal(pass_tax) # 过户费率 self.__data = ds self.__sz, self.__sh = ds.get_trade_calendar(sd, ed) # 深沪市交易日历 self.__shares = {} # 拥有的股票 self.__shares_just_buy = {} # 当日买的股票 self.__dividend = {} # 登记了分红送股的 self.__records = {} # self.__orders = {} # 订单 self.__price = {} # 每日价格信息,只保存当天的 self.__cd = format_delta(sd, -1) # 当前日期 self.next_day() # 进入下一个交易日 self.info('Ready for simulation') self.__register_code = set() self.__inited = False
def extract_from_cash_flow(result_list: list, store: MqQuarterStore, period_dict: dict, cf: TsCashFlow): update_date = date_utils.format_delta(cf.mq_ann_date, -1) ts_code = cf.ts_code period = cf.end_date call_find = partial(store.find_period_exact, ts_code=ts_code, period=period, update_date=update_date) call_add_nx = partial(add_nx, ts_code=ts_code, period=period, update_date=update_date, report_type=mq_report_type.report if cf.report_type == '1' else mq_report_type.report_adjust, store=store, result_list=result_list, period_dict=period_dict) for i in mq_quarter_metric_enum.extract_from_cf_list: if i.is_cf_h and date_utils.get_quarter_num(cf.end_date) == 3: q2: MqManualMetric = store.find_period_latest( ts_code=cf.ts_code, name=i.name, period=date_utils.period_delta(cf.end_date, -1), update_date=update_date) if q2 is None: log.error('Cant find q2 in cash flow %s %s %s' % (cf.ts_code, cf.end_date, cf.mq_ann_date)) call_add_nx(name=i.name, value=getattr(cf, i.from_name)) else: call_add_nx(name=i.name, value=q2.value) else: call_add_nx(name=i.name, value=getattr(cf, i.from_name))
def init_cache(self, ts_code_set: set, from_date: str, to_date: str): """ 预先缓存指定日期的数据 :param ts_code_set: 编码列表 :param from_date: 开始日期 :param to_date: 结束日期 :return: """ if from_date is None or not date_utils.is_valid_dt(from_date): raise Exception('from_date is empty or invalid') if to_date is None or not date_utils.is_valid_dt(to_date): raise Exception('to_date is empty or invalid') if len(ts_code_set) == 0: log.warn('No ts code to cache, skip') return all_date_arr = [] i = from_date while i < to_date: all_date_arr.append(i) i = date_utils.format_delta(i, 1) mq_list: list = self.get_normal_price_in(ts_code_set, all_date_arr) for p in mq_list: # type: MqDailyPrice d = p.trade_date if d not in self.__cache: self.__cache[d] = [] self.__cache[d].append(p) log.info("Cache init: mq_price")
def default_previous_trade_date(self, dt: str): session: Session = db_client.get_session() cal_arr = session.query(TsTradeCal).filter(TsTradeCal.is_open == '1', TsTradeCal.cal_date < dt) \ .order_by(TsTradeCal.cal_date.desc()).limit(1).all() session.close() return cal_arr[0].cal_date if len( cal_arr) > 0 else date_utils.format_delta(dt, -1)
def extract_from_dividend(result_list: list, store: MqQuarterStore, period_dict: dict, d: TsDividend): # 有可能有非固定分红的派钱 lp: str = date_utils.latest_period_date(d.end_date) update_date: str = date_utils.format_delta(d.imp_ann_date, -1) call_add_nx = partial(add_nx, ts_code=d.ts_code, period=lp, update_date=update_date, report_type=mq_report_type.report, store=store, result_list=result_list, period_dict=period_dict) # 叠加到最近一个季度的分红中 dividend: Decimal = decimal_utils.mul(d.cash_div_tax, d.base_share) if lp != d.end_date: ori_d: MqQuarterMetric = store.find_period_latest( ts_code=d.ts_code, period=lp, update_date=update_date, name=mq_quarter_metric_enum.dividend.name) if ori_d is not None: dividend = dividend + ori_d.value call_add_nx(name='dividend', value=dividend)
def fetch_from_date(exchange: str): session: Session = db_client.get_session() result = session.query(func.max( TsTradeCal.cal_date)).filter(TsTradeCal.exchange == exchange).all() session.close() from_date = fetch_data_start_date if len(result) > 0 and not result[0][0] is None: from_date = date_utils.format_delta(result[0][0], day_num=1) return from_date
def fetch_from_date(ts_code: str = None): s: Session = db_client.get_session() result = s.query(func.max(TsCashFlow.mq_ann_date)) \ .filter(TsCashFlow.ts_code == ts_code if ts_code is not None else True) \ .all() s.close() from_date = fetch_data_start_date if len(result) > 0 and not result[0][0] is None: from_date = date_utils.format_delta(result[0][0], day_num=-7) return from_date
def generate_report_message_by_code( ts_code: str, share_name: str, to_date: str = date_utils.get_current_dt()): result_list = [] from_date = mq_calculate_start_date session = db_client.get_session() last_one: MqMessage = session.query(MqMessage) \ .filter(MqMessage.ts_code == ts_code, MqMessage.msg_type <= 3) \ .order_by(MqMessage.pub_date.desc()).limit(1).all() if len(last_one) > 0: from_date = date_utils.format_delta(last_one[0].pub_date, 1) quarter_store = mq_quarter_store.init_quarter_store_by_date( ts_code, from_date) session.close() while from_date <= to_date: # type: MqQuarterBasic latest = quarter_store.find_latest(ts_code, mq_quarter_metric_enum.dprofit.name, from_date) if latest is None: from_date = date_utils.format_delta(from_date, 1) continue for i in range(5): period = date_utils.period_delta(latest.period, -i) dprofit = quarter_store.find_period_exact( ts_code, mq_quarter_metric_enum.dprofit.name, period, from_date) if dprofit is None: continue # 只保留官方财报 if not ((dprofit.report_type & (1 << mq_report_type.report)) > 0 or \ (dprofit.report_type & (1 << mq_report_type.forecast)) > 0 or \ (dprofit.report_type & (1 << mq_report_type.express)) > 0): continue result_list.append( MqMessage(ts_code=ts_code, msg_type=mq_message_type.report, message=get_report_message_content( dprofit, share_name), pub_date=date_utils.format_delta(from_date, 1))) from_date = date_utils.format_delta(from_date, 1) return result_list
def run(to_date: str = date_utils.get_current_dt()): dt_code_map = {} # 股票基础信息 init_ts_basic.init() # 交易日历 fetch_trade_cal.fetch(to_date) trade_date_service.refresh_cache(fetch_data_start_date, date_utils.format_delta(to_date, 30)) to_fetch_list = [ # 同花顺指数 fetch_ths_index.run, # 券商金股 fetch_broker_recommend.fetch, # 分红记录 fetch_dividend.update_dividend_to, # 每日指标 fetch_daily_basic.fetch_by_date, # 每日交易 fetch_daily_bar.fetch_by_date, # 复权因子 fetch_adj_factor.fetch_by_date, # 涨跌停价格 fetch_stk_limit.fetch_by_date, # 利润表 fetch_income.fetch_by_period, # 资产负债表 fetch_balance_sheet.fetch_by_period, # 现金流量表 fetch_cash_flow.fetch_by_period, # 预报 fetch_forecast.fetch_by_period, # 快报 fetch_express.fetch_by_period, # 财务指标 fetch_fina_indicator.fetch_by_period ] if env_utils.parallel(): fl = [] for func in to_fetch_list: fl.append(threadpool.submit(func, to_date=to_date)) for f in fl: m = f.result() if m is not None: fetch.merge_from_map(dt_code_map, m) else: for func in to_fetch_list: m = func(to_date=to_date) if m is not None: fetch.merge_from_map(dt_code_map, m) return dt_code_map
def extract_from_manual(result_list: list, store: MqQuarterStore, period_dict: dict, manual: MqManualMetric): add_nx(ts_code=manual.ts_code, period=manual.period, update_date=date_utils.format_delta(manual.update_date, -1), report_type=manual.report_type, store=store, result_list=result_list, period_dict=period_dict, name=manual.name, value=manual.value)
def fetch_from_date(date_column: dict(type=str, help='对应发布日期的字段名 用于获取该类型数据在DB中最新日期'), code_column: dict(type=str, help='对应股票编码的字段名'), ts_code: dict(type=str, help='股票编码')): session: Session = db_client.get_session() result = session.query( func.max(date_column)).filter(code_column == ts_code).all() from_date = fetch_data_start_date if len(result) > 0 and not result[0][0] is None: from_date = date_utils.format_delta(result[0][0], day_num=1) session.close() return from_date
def extract_from_income_adjust(result_list: list, store: MqQuarterStore, period_dict: dict, income: TsIncome): update_date = date_utils.format_delta(income.mq_ann_date, -1) nprofit_new = store.find_period_exact(income.ts_code, mq_quarter_metric_enum.nprofit.name, income.end_date, update_date) nprofit_old = store.find_period_latest( income.ts_code, mq_quarter_metric_enum.nprofit.name, income.end_date, date_utils.format_delta(update_date, -1)) dprofit_old = store.find_period_latest( income.ts_code, mq_quarter_metric_enum.dprofit.name, income.end_date, date_utils.format_delta(update_date, -2)) to_add = None if nprofit_new is None: log.error('Cant find nprofit in adjust income. %s %s' % (income.ts_code, income.end_date)) elif nprofit_old is None or dprofit_old is None: to_add = MqQuarterMetric( ts_code=income.ts_code, report_type=(1 << mq_report_type.report_adjust), period=income.end_date, update_date=update_date, name=mq_quarter_metric_enum.dprofit.name, value=nprofit_new.value) else: to_add = MqQuarterMetric( ts_code=income.ts_code, report_type=(1 << mq_report_type.report_adjust), period=income.end_date, update_date=update_date, name=mq_quarter_metric_enum.dprofit.name, value=decimal_utils.sub( nprofit_new.value, decimal_utils.sub(nprofit_old.value, dprofit_old.value))) if to_add is not None: common_add(result_list, store, to_add)
def get_from_date(index_code): """ 获取指标已有的最大日期,计算从哪开始补数 :param index_code: 指标代码 :return: """ s: Session = db_client.get_session() result = s.query(func.max(MqIndexTradeAmount.trade_date)).filter( MqIndexTradeAmount.index_code == index_code).all() s.close() from_date = mq_calculate_start_date if len(result) > 0 and result[0][0] is not None: from_date = date_utils.format_delta(result[0][0], day_num=1) return from_date
def extract_from_fina_indicator(result_list: list, store: MqQuarterStore, period_dict: dict, fina: TsFinaIndicator): call_add_nx = partial(add_nx, ts_code=fina.ts_code, period=fina.end_date, update_date=date_utils.format_delta( fina.ann_date, -1), report_type=mq_report_type.report, store=store, result_list=result_list, period_dict=period_dict) for i in mq_quarter_metric_enum.extract_from_fi_list: call_add_nx(name=i.name, value=getattr(fina, i.from_name))
def extract_from_express(result_list: list, store: MqQuarterStore, period_dict: dict, express: TsExpress): call_add_nx = partial(add_nx, ts_code=express.ts_code, period=express.end_date, update_date=date_utils.format_delta( express.ann_date, -1), report_type=mq_report_type.express, store=store, result_list=result_list, period_dict=period_dict) for i in mq_quarter_metric_enum.extract_from_express_list: call_add_nx(name=i.name, value=getattr(express, i.from_name))
def extract_from_balance(result_list: list, store: MqQuarterStore, period_dict: dict, bs: TsBalanceSheet): call_add_nx = partial(add_nx, ts_code=bs.ts_code, period=bs.end_date, update_date=date_utils.format_delta( bs.mq_ann_date, -1), report_type=mq_report_type.report if bs.report_type == '1' else mq_report_type.report_adjust, store=store, result_list=result_list, period_dict=period_dict) for i in mq_quarter_metric_enum.extract_from_bs_list: call_add_nx(name=i.name, value=getattr(bs, i.from_name))
def cal_by_ths_index_code(index_code, index_name, to_date: str = date_utils.get_current_dt()): from_date = get_from_date(index_code) history_avl = get_history_percent(index_code, from_date) s: Session = db_client.get_session() member_list = s.query(ThsMember).filter( ThsMember.ts_code == index_code).all() s.close() share_code_list = [m.code for m in member_list] result_list = [] while from_date <= to_date: log.info('Calculating %s %s %s' % (MqIndexTradeAmount.__tablename__, index_code, from_date)) s: Session = db_client.get_session() trade_list = s.query(TsDailyTradeInfo) \ .filter(TsDailyTradeInfo.trade_date == from_date) \ .all() s.close() if len(trade_list) > 0: total_amount = Decimal(0) target_amount = Decimal(0) for i in trade_list: # type: TsDailyTradeInfo total_amount += i.amount if i.ts_code in share_code_list: target_amount += i.amount val = float(decimal_utils.div(target_amount, total_amount)) history_percent = get_history_high_percent(history_avl, val) history_avl.add(val) result_list.append( MqIndexTradeAmount(index_code=index_code, index_name=index_name, trade_date=from_date, amount=target_amount, percent=val, history_high_ratio=history_percent)) from_date = date_utils.format_delta(from_date, 1) if len(result_list) >= 1000: db_client.batch_insert(result_list) result_list = [] db_client.batch_insert(result_list)
def extract_from_income(result_list: list, store: MqQuarterStore, period_dict: dict, income: TsIncome): call_add_nx = partial( add_nx, ts_code=income.ts_code, period=income.end_date, update_date=date_utils.format_delta(income.mq_ann_date, -1), report_type=mq_report_type.report if income.report_type == '1' else mq_report_type.report_adjust, store=store, result_list=result_list, period_dict=period_dict) for i in mq_quarter_metric_enum.extract_from_income_list: call_add_nx(name=i.name, value=getattr(income, i.from_name)) # 处理调整 - 净利根据往年调整 if income.report_type == '4': extract_from_income_adjust(result_list, store, period_dict, income)
def after_trade(self, context: SimContext, data: SimDataService): # 清除已补的缺口,或30天前的缺口 dt: str = context.get_dt() old_dt: str = date_utils.format_delta(dt, -30) for ts_code in self.target: p: SimDailyPrice = context.get_today_price(ts_code) if p is None or p.is_trade == 0: # 未上市 或 停牌就不用看了 continue self.last_trade_price[ts_code] = p ts_gap: list = self.gap[ts_code] if len(ts_gap) == 0: continue to_remove: list = [] for gap_price in ts_gap: # type: SimDailyPrice if gap_price.trade_date < old_dt or p.high >= gap_price.low: to_remove.append(gap_price) if len(to_remove) > 0: for r in to_remove: # type: SimDailyPrice ts_gap.remove(r) log.info('Gap clear: %s %s %.2f. Gap num: %d' % (ts_code, dt, gap_price.low, len(ts_gap))) self.yesterday_buy = set() just_buy: dict = context.get_holding_just_buy() for ts_code in just_buy: # type: str self.yesterday_buy.add(ts_code) self.before_yesterday_buy = set() old_holdings: dict = context.get_holding() for ts_code in old_holdings: # type: str self.before_yesterday_buy.add(ts_code) # 新的缺口数目标应该是 max(剩余缺口数+1,标准缺口数) for ts_code in self.target: # type: str new_gap_num: int = len(self.gap[ts_code]) + 1 if new_gap_num < self.standard_gap_num: new_gap_num = self.standard_gap_num self.gap_num_target[ts_code] = new_gap_num
def cal_top_amount_share(to_date: str = date_utils.get_current_dt()): from_date = get_from_date(mq_index.top_amount_code) history_avl = get_history_percent(mq_index.top_amount_code, from_date) result_list = [] while from_date <= to_date: log.info('Calculating %s %s %s' % (MqIndexTradeAmount.__tablename__, mq_index.top_amount_code, from_date)) s: Session = db_client.get_session() trade_list = s.query(TsDailyTradeInfo) \ .filter(TsDailyTradeInfo.trade_date == from_date) \ .order_by(TsDailyTradeInfo.amount.desc()) \ .all() s.close() if len(trade_list) > 0: top_to_count = math.floor(len(trade_list) * 0.05) total_amount = Decimal(0) top_amount = Decimal(0) for i in range(len(trade_list)): total_amount += trade_list[i].amount if i <= top_to_count: top_amount += trade_list[i].amount val = float(decimal_utils.div(top_amount, total_amount)) history_percent = get_history_high_percent(history_avl, val) history_avl.add(val) result_list.append( MqIndexTradeAmount(index_code=mq_index.top_amount_code, index_name=mq_index.top_amount_name, trade_date=from_date, amount=top_amount, percent=val, history_high_ratio=history_percent)) from_date = date_utils.format_delta(from_date, 1) if len(result_list) >= 1000: db_client.batch_insert(result_list) result_list = [] db_client.batch_insert(result_list)
def extract_from_forecast(result_list: list, store: MqQuarterStore, period_dict: dict, forecast: TsForecast): update_date = date_utils.format_delta(forecast.ann_date, -1) call_add_nx = partial(add_nx, ts_code=forecast.ts_code, period=forecast.end_date, update_date=update_date, report_type=mq_report_type.forecast, store=store, result_list=result_list, period_dict=period_dict) forecast_nprofit = None if forecast.net_profit_min is not None: forecast_nprofit = forecast.net_profit_min elif forecast.net_profit_max is not None: forecast_nprofit = forecast.net_profit_max else: percent = None # choose minimum percent. if forecast.p_change_min is not None: percent = forecast.p_change_min if forecast.p_change_max is not None: if percent is None or forecast.p_change_max < percent: percent = forecast.p_change_max if percent is not None: percent = (percent / 100) + 1 if forecast.last_parent_net is not None: forecast_nprofit = percent * forecast.last_parent_net else: nprofit_ly = store.find_period_latest( forecast.ts_code, mq_quarter_metric_enum.nprofit.name, forecast.end_date, update_date) if nprofit_ly is not None and nprofit_ly.value is not None: forecast_nprofit = percent * nprofit_ly.value call_add_nx(name=mq_quarter_metric_enum.nprofit.name, value=forecast_nprofit)
def run(dt: str, min_mv=5e10, gap_num=3): """ 2022年开始市值 > 500亿 一个月内出现三个以上向下缺口时买入,只能在新缺口当天买入 一字跌停缺口不算 少于1%的缺口不算 买入日回补缺口则次日开盘价卖出 买入日没回补缺口则次日挂补缺价卖出 最后收盘价无脑卖出 :param dt: 对应日期 :param min_mv: 最低市值要求 :param gap_num: 需要达到多少个缺口 :return: """ delete_old(dt) ts = ts_client.get_ts() pro = ts_client.get_pro() trade_df = pro.trade_cal(exchange='SSE', start_date=dt, end_date=dt, is_open=1) if len(trade_df) == 0: log.info('[Down Gap] Not trade date') return trade_df = pro.trade_cal(exchange='SSE', start_date='20220101', end_date=dt, is_open=1) first_trade_date = trade_df.iloc[0]['cal_date'] basic = pro.daily_basic(trade_date=first_trade_date, fields=['ts_code', 'total_mv']) start = date_utils.format_delta(dt, -30) info = pro.stock_basic(fields=["ts_code", "name"]).set_index('ts_code') candidate_num = 0 for ts_code in basic.loc[basic['total_mv'] * 10000 > min_mv]['ts_code']: log.info('[Down Gap] %s' % ts_code) strategy_record = [] gap_record = {} from_dt = start last_price = None price_df = ts.pro_bar(ts_code=ts_code, start_date=from_dt, adj='qfq').set_index('trade_date') current_trade = None while from_dt <= dt: can_buy_today = current_trade is None # 如果今天持股,即使有卖出也不该再买入了 if from_dt not in price_df.index: # 非交易日 from_dt = date_utils.format_delta(from_dt, 1) continue current_price = price_df.loc[from_dt] if last_price is not None: # 判断是否应该无脑卖出 按开盘价卖出 if current_trade is not None and current_trade['should_sell']: current_trade['sell_date'] = from_dt current_trade['sell_price'] = current_price['open'] strategy_record.append(current_trade) current_trade = None # 先判断开盘有没有下缺口 if current_price['open'] < last_price['low'] and last_price[ 'low'] / current_price['open'] > 1.01: down_limit = current_price['open'] == current_price['close'] and current_price['open'] == \ current_price['high'] and current_price['open'] == current_price['low'] new_gap = { 'trade_date': from_dt, 'low': last_price['low'], 'high': current_price['open'], 'down_limit': down_limit } gap_record[from_dt] = new_gap # 达到3个下缺口时按开盘价买入 if can_buy_today and can_buy(gap_record, from_dt, gap_num): current_trade = { 'ts_code': ts_code, 'buy_date': from_dt, 'buy_price': current_price['open'], 'target_price': last_price['low'], 'should_sell': False } # 判断最高价回补了多少个缺口 high = current_price['high'] for d in list(gap_record.keys()): if high >= gap_record[d]['low']: # 补缺了 del (gap_record[d]) elif from_dt >= date_utils.format_delta(d, 30): # 超过30天了 del (gap_record[d]) elif current_price['high'] > gap_record[d]['high']: gap_record[d]['high'] = current_price['high'] # 判断今天有没有卖出 if current_trade is not None: if high >= current_trade['target_price']: # 补缺了 # 当天买的,卖不了,等第二天 if current_trade['buy_date'] == from_dt: current_trade['should_sell'] = True else: current_trade['sell_date'] = from_dt current_trade['sell_price'] = current_trade[ 'target_price'] strategy_record.append(current_trade) current_trade = None else: # 没补缺 看是不是收盘卖 if current_trade['buy_date'] != from_dt: current_trade['sell_date'] = from_dt current_trade['sell_price'] = current_price[ 'close'] strategy_record.append(current_trade) current_trade = None last_price = current_price from_dt = date_utils.format_delta(from_dt, 1) if len(strategy_record) > 0: last_trade = strategy_record[-1] if last_trade['buy_date'] == dt: # 今日买入 strategy_buy = MqStrategyTrade( strategy=__strategy, strategy_version=__version, strategy_intro=__wiki, ts_code=ts_code, share_name=info.loc[ts_code]['name'], buy_date=last_trade['buy_price'], buy_price=last_trade['buy_date']) db_client.batch_insert([strategy_buy]) elif last_trade['sell_date'] == dt: # 今日卖出 s: Session = db_client.get_session() s.query(MqStrategyTrade).filter( MqStrategyTrade.strategy == __strategy, MqStrategyTrade.strategy_version == __version, MqStrategyTrade.ts_code == ts_code, MqStrategyTrade.buy_date == last_trade['buy_date']).delete() s.close() strategy_sell = MqStrategyTrade( strategy=__strategy, strategy_version=__version, strategy_intro=__wiki, ts_code=ts_code, share_name=info.loc[ts_code]['name'], buy_date=last_trade['buy_date'], buy_price=last_trade['buy_price'], sell_date=last_trade['sell_date'], sell_price=last_trade['sell_price']) db_client.batch_insert([strategy_sell]) if valid_gap_num(gap_record, dt) == gap_num - 1: to_insert = MqSharePool( dt=dt, strategy=__strategy, ts_code=ts_code, share_name=info.loc[ts_code]['name'], suggest_price=decimal_utils.cal_price_with_gain_percent( current_price['low'], -1), suggest_comment='次日下缺口超1%买入') db_client.batch_insert([to_insert]) candidate_num += 1 log.info('[Down Gap] Done. Dt: %s. Total: %d' % (dt, candidate_num))
from_date=from_date, to_date=to_date) def fetch_from_period(): s: Session = db_client.get_session() result = s.query(func.max(TsCashFlow.end_date)) \ .all() s.close() from_date = fetch_data_start_period if len(result) > 0 and not result[0][0] is None: from_date = date_utils.period_delta(result[0][0], -4) return from_date def fetch_by_period(to_date: str = date_utils.get_current_dt()): to_period = date_utils.next_period(to_date) from_period = fetch_from_period() return fetch.common_fetch_report_by_period(TsCashFlow, 'fetch_cash_flow', TsCashFlow.end_date, from_period=from_period, to_period=to_period) if __name__ == '__main__': trade_date_service.refresh_cache( fetch_data_start_date, date_utils.format_delta(date_utils.get_current_dt(), 30)) print(fetch_by_date())
to_date = trade_date_service.get_next_trade_date(to_date) from_date = fetch_from_date() trade_date_service.refresh_cache(trade_date_service.get_previous_trade_date(from_date), to_date) return fetch.common_fetch_report(TsExpress, 'fetch_express', TsExpress.ts_code, TsExpress.mq_ann_date, from_date=from_date, to_date=to_date) def fetch_from_period(): s: Session = db_client.get_session() result = s.query(func.max(TsExpress.end_date)) \ .all() s.close() from_date = fetch_data_start_period if len(result) > 0 and not result[0][0] is None: from_date = date_utils.period_delta(result[0][0], -4) return from_date def fetch_by_period(to_date: str = date_utils.get_current_dt()): to_period = date_utils.next_period(to_date) from_period = fetch_from_period() return fetch.common_fetch_report_by_period( TsExpress, 'fetch_express', TsExpress.end_date, from_period=from_period, to_period=to_period) if __name__ == '__main__': trade_date_service.refresh_cache(fetch_data_start_date, date_utils.format_delta(date_utils.get_current_dt(), 30)) print(fetch_by_date())