def is_stop(self, code, date): """ 判断是否需要止盈,盈利超过指定值,则止盈 :param code: 股票代码 :param date: 日期 :return: True - 止盈,False - 不止盈 """ holding_stock = self.account.get_holding(code) print('止盈判断:%s' % code, flush=True) print(holding_stock, flush=True) dm = DataModule() if holding_stock is not None: df_daily = dm.get_k_data(code, autype=None, begin_date=date, end_date=date) if df_daily.index.size > 0: df_daily.set_index(['date'], 1, inplace=True) profit = (holding_stock['volume'] * df_daily.loc[date]['close'] - holding_stock['cost']) \ * 100 / holding_stock['cost'] return profit >= self.max_profit return False
class TrackingStopLoss(BaseStopLoss): def __init__(self, account, max_loss): BaseStopLoss.__init__(self, account) self.max_loss = max_loss self.dm = DataModule() def update_holding(self, code, date): """ 更新持仓股的最高价 :param code: :param date: :return: """ df_daily = self.dm.get_k_data(code, autype='hfq', begin_date=date, end_date=date) if df_daily.index.size > 0: df_daily.set_index(['date'], 1, inplace=True) close = df_daily.loc[date]['close'] holding = self.account.get_holding(code) if 'highest' not in holding or holding['highest'] < close: holding['highest'] = close self.account.update_holding(code, holding) def is_stop(self, code, date): """ 判断股票在当前日期是否需要止损 :param code: 股票代码 :param date: 日期 :return: True - 止损, False - 不止损 """ holding_stock = self.account.get_holding(code) print('止损判断:%s' % code, flush=True) print(holding_stock, flush=True) if holding_stock is not None: df_daily = self.dm.get_k_data(code, autype='hfq', begin_date=date, end_date=date) if df_daily.index.size > 0: df_daily.set_index(['date'], 1, inplace=True) close = df_daily.loc[date]['close'] # 计算回落的百分比 if 'highest' in holding_stock and close < holding_stock[ 'highest']: profit = (close - holding_stock['highest']) \ * 100 / holding_stock['highest'] print('止损判断:收盘价:%6.2f, 历史最高:%6.2f, 回撤:%5.2f' % (close, holding_stock['highest'], profit), flush=True) return (profit < 0) & (abs(profit) >= abs(self.max_loss)) return False
def compute(self, begin_date, end_date): """ 计算指定时间段内所有股票的该因子的值,并保存到数据库中 :param begin_date: 开始时间 :param end_date: 结束时间 """ dm = DataModule() frc = FinanceReportCrawler() code_report_dict = frc.get_code_reports() codes = set(code_report_dict.keys()) for code in codes: dailies = dm.get_k_data(code, autype=None, begin_date=begin_date, end_date=end_date) # 如果没有合适的数据 if dailies.index.size == 0: continue # 业绩报告列表 reports = code_report_dict[code] dailies.set_index(['date'], inplace=True) update_requests = [] for current_date in dailies.index: # 用来保存最后一个公告日期小于等于当前日期的财报 last_report = None for report in reports: announced_date = report['announced_date'] # 如果公告日期大于当前调整日,则结束循环 if announced_date > current_date: break last_report = report # 如果找到了正确时间范围的年报, 则计算PE if last_report is not None: pe = dailies.loc[current_date]['close'] / last_report['eps'] pe = round(pe, 3) print('%s, %s, %s, eps: %5.2f, pe: %6.2f' % (code, current_date, last_report['announced_date'], last_report['eps'], pe), flush=True) update_requests.append( UpdateOne( {'code': code, 'date': current_date}, {'$set': {'code': code, 'date': current_date, 'pe': pe}}, upsert=True)) if len(update_requests) > 0: save_result = self.collection.bulk_write(update_requests, ordered=False) print('股票代码: %s, 因子: %s, 插入:%4d, 更新: %4d' % (code, self.name, save_result.upserted_count, save_result.modified_count), flush=True)
def __init__(self, strategy_option, begin_date=None, end_date=None): self.strategy_option = strategy_option if begin_date is None: self.begin_date = self.strategy_option.begin_date() else: self.begin_date = begin_date if end_date is None: self.end_date = self.strategy_option.end_date() else: self.end_date = end_date self.dm = DataModule() self.code_daily_cache = dict()
def __init__(self, strategy_option, begin_date=None, end_date=None): self.strategy_option = strategy_option if begin_date is None: self.begin_date = self.strategy_option.begin_date() else: self.begin_date = begin_date if end_date is None: self.end_date = self.strategy_option.end_date() else: self.end_date = end_date self.dm = DataModule() self.code_daily_cache = dict() self.logger = QuantLogger('backtest') self.candidatesLogger = QuantLogger('candidates')
def train_model(user_image: Image, style_image: Image): """Trains a Deep Learning model to extract the stylings from `style_image` and applies them onto `user_image` then returns `user_image`. Args: user_image (Image): Image you want to apply styles onto. style_image (Image): Image you want to extract styles from. Returns: user_image (Image): `user_image` with styling applied. """ image_processor = ImageProcessor(maximum_image_size=(512, 512)) print(f"user_image.size: {user_image.size} | style_image.size: {style_image.size}") image_size = image_processor.get_common_image_size(user_image, style_image) # device = torch.device("cuda" if torch.cuda.is_available() else "cpu") device = "cpu" user_image = image_processor.prepare_images(user_image, image_size) style_image = image_processor.prepare_images(style_image, image_size) normalization_mean = torch.tensor([0.485, 0.456, 0.406]).to(image_processor.device) normalization_std = torch.tensor([0.229, 0.224, 0.225]).to(image_processor.device) datamodule = DataModule(user_image, style_image) model = StyleTransferModel( user_image=user_image, style_image=style_image, normalization_mean=normalization_mean, normalization_std=normalization_std, ) cnn = models.vgg19(pretrained=True).features.to(device).eval() model._build_model_and_loss_functions(base_cnn_model=cnn) print("Training...") tb_logger = pl_loggers.TensorBoardLogger("logs/") trainer_params = {"max_epochs": 6000} if device != "cpu": print("Using a gpu!") trainer_params["gpus"] = 1 trainer = pl.Trainer(**trainer_params) trainer.fit(model, datamodule) sample = user_image.to(device) model.to(device) image = model(sample) image = image_processor.save_image( image, "output.png", "Sample Output", display_image=True ) return image
def compute(self, begin_date, end_date): codes = get_all_codes() dm = DataModule() for code in codes: df_daily = dm.get_k_data(code, begin_date=begin_date, end_date=end_date) if df_daily.index.size == 0: continue # 当日放量下跌 df_daily['change'] = df_daily['close'] - df_daily['pre_close'] df_daily = df_daily[df_daily['change'] < 0] df_daily['last_volume'] = df_daily['volume'].shift(1) df_daily.dropna(inplace=True) df_daily['volume_change'] = round(df_daily['volume']/df_daily['last_volume'], 2) df_daily = df_daily[df_daily['volume_change'] > 1.5] # 收的阴线实体不大 df_daily['entity'] = round(abs((df_daily['open'] -df_daily['close'])) * 100/df_daily['close'], 2) df_daily = df_daily[df_daily['entity'] < 3] # 大部分时间在昨日之上运行 df_daily.set_index(['date'], 1, inplace=True) update_requests = [] for date in df_daily.index: # 大部分时间在昨日之上运行 pre_close = df_daily.loc[date]['pre_close'] df_minute = dm.get_k_data(code, period='M1', begin_date=date, end_date=date) df_minute = df_minute[df_minute['close'] > pre_close] if df_minute.index.size > 150: update_requests.append(UpdateOne({ 'code': code, 'date': date}, {'$set': {'code': code, 'date': date}}, upsert=True)) if len(update_requests) > 0: save_result = self.collection.bulk_write(update_requests, ordered=False) print('股票代码: %s, 因子: %s, 插入:%4d, 更新: %4d' % (code, self.name, save_result.upserted_count, save_result.modified_count), flush=True)
def is_stop(self, code, date): """ 判断股票在当前日期是否需要止损 :param code: 股票代码 :param date: 日期 :return: True - 止损, False - 不止损 """ holding_stock = self.account.get_holding(code) print('止损判断:%s' % code, flush=True) print(holding_stock, flush=True) dm = DataModule() if holding_stock is not None: df_daily = dm.get_k_data(code, autype=None, begin_date=date, end_date=date) if df_daily.index.size > 0: df_daily.set_index(['date'], 1, inplace=True) profit = (holding_stock['volume'] * df_daily.loc[date]['close'] - holding_stock['cost']) \ * 100 / holding_stock['cost'] return (profit < 0) & (abs(profit) >= abs(self.max_loss)) return False
class Backtest: def __init__(self, strategy_option, begin_date=None, end_date=None): self.strategy_option = strategy_option if begin_date is None: self.begin_date = self.strategy_option.begin_date() else: self.begin_date = begin_date if end_date is None: self.end_date = self.strategy_option.end_date() else: self.end_date = end_date self.dm = DataModule() self.code_daily_cache = dict() self.logger = QuantLogger('backtest') self.candidatesLogger = QuantLogger('candidates') def start(self): """ 策略回测。结束后打印出收益曲线(沪深300基准)、年化收益、最大回撤 """ # 初始总资金 initial_capital = self.strategy_option.capital() # 初始现金 cash = initial_capital # 单只股票仓位上限 single_position = self.strategy_option.single_position() # 从获取策略配置中获取股票池 stock_pool = self.strategy_option.stock_pool() # 保存持仓股的日期 account = Account() # 获取卖出信号 sell_signal = self.strategy_option.sell_signal(account) # 获取买入信号 buy_signal = self.strategy_option.buy_signal(account) # 时间为key的净值、收益和同期沪深基准 df_profit = DataFrame(columns=['net_value', 'profit', 'hs300']) # 获取交易日历, all_dates = get_trading_dates(begin_date=self.begin_date, end_date=self.end_date) # 获取沪深300在统计周期内的第一天的值 hs300_k = self.dm.get_k_data('000300', index=True, begin_date=all_dates[0], end_date=all_dates[0]) hs300_begin_value = hs300_k.loc[hs300_k.index[0]]['close'] # 获取股票池数据 rebalance_dates, date_codes_dict = stock_pool.get_option_stocks() # 获取止损策略 stop_loss_policy = self.strategy_option.get_stop_loss(account) # 获取止盈策略 stop_profit_policy = self.strategy_option.get_stop_profit(account) # 获取加仓策略 add_position_policy = self.strategy_option.get_add_position(account) # 获取回测周期内股票池内所有股票的收盘价和前收价 all_option_code_set = set() for rebalance_date in rebalance_dates: for code in date_codes_dict[rebalance_date]: all_option_code_set.add(code) # 缓存股票的日线数据 for code in all_option_code_set: dailies_df = self.dm.get_k_data(code, autype=None, begin_date=self.begin_date, end_date=self.end_date) dailies_df.set_index(['date'], inplace=True) self.code_daily_cache[code] = dailies_df last_phase_codes = None this_phase_codes = None to_be_sold_codes = set() to_be_bought_codes = set() last_date = None # 加仓 to_be_added_signals = dict() to_be_added_codes = set() # 按照日期一步步回测 for _date in all_dates: self.logger.info('开始回测,日期:' + _date) # 处理复权 account.adjust_holding_volume_at_open(last_date, _date) # 卖出 if len(to_be_sold_codes) > 0: sold_codes_tmp = set(to_be_sold_codes) for code in sold_codes_tmp: try: if code in account.holding_codes: holding_stock = account.get_holding(code) holding_volume = holding_stock['volume'] sell_price = self.code_daily_cache[code].loc[ _date]['open'] low_limit = self.code_daily_cache[code].loc[_date][ 'low_limit'] if sell_price > low_limit: sell_amount = holding_volume * sell_price cash += sell_amount cost = holding_stock['cost'] single_profit = (sell_amount - cost) * 100 / cost print('卖出 %s, %6d, %6.2f, %8.2f, %4.2f' % (code, holding_volume, sell_price, sell_amount, single_profit)) else: print( '当日跌停,无法卖出,股票代码:%s, 日期: %s,价格:%7.2f,跌停价:%7.2f' % (code, _date, sell_price, low_limit), flush=True) # 从持仓股中卖出 account.sell_out(code) # 从代码列表中删除 to_be_sold_codes.remove(code) except: print('卖出时,发生异常:%s, %s' % (code, _date), flush=True) print('卖出后,现金: %10.2f' % cash) # 加仓逻辑 add_codes_tmp = set(to_be_added_codes) for code in add_codes_tmp: add_signal = to_be_added_signals[code] try: if cash > add_signal['position']: daily = self.code_daily_cache[code].loc[_date] buy_price = daily['open'] high_limit = daily['high_limit'] if buy_price < high_limit: volume = int( int(add_signal['position'] / buy_price) / 100) * 100 buy_amount = buy_price * volume cash -= buy_amount print('加仓 %s, %6d, %6.2f, %8.2f' % (code, volume, buy_price, buy_amount), flush=True) # 更新加仓后的持仓股 holding = account.get_holding(code) holding['cost'] += buy_amount holding['last_value'] += buy_amount holding['volume'] += volume holding['add_times'] += 1 holding['last_buy_hfq_price'] = buy_price * daily[ 'au_factor'] account.update_holding(code, holding) # 从待加仓列表中删除 to_be_added_codes.remove(code) del to_be_added_signals[code] else: print( '当日涨停,无法加仓,股票代码:%s, 日期: %s,价格:%7.2f,涨停价:%7.2f' % (code, _date, buy_price, high_limit), flush=True) except: print('加仓时,发生错误:%s, %s' % (code, _date), flush=True) # 买入 if len(to_be_bought_codes) > 0: sorted_to_be_bought_list = list(to_be_bought_codes) sorted_to_be_bought_list.sort() for code in sorted_to_be_bought_list: try: if cash > single_position: daily = self.code_daily_cache[code].loc[_date] buy_price = daily['open'] high_limit = daily['high_limit'] if buy_price < high_limit: volume = int( int(single_position / buy_price) / 100) * 100 buy_amount = buy_price * volume cash -= buy_amount print('买入 %s, %6d, %6.2f, %8.2f' % (code, volume, buy_price, buy_amount), flush=True) # 维护账户的持仓股 account.buy_in(code, volume=volume, cost=buy_amount) # 如果加仓策略不为空,则更新持仓股 if add_position_policy is not None: holding = account.get_holding(code) holding[ 'last_buy_hfq_price'] = buy_price * daily[ 'au_factor'] add_position_policy.update_holding( code, _date, holding) else: print( '当日涨停,无法买入,股票代码:%s, 日期: %s,价格:%7.2f,涨停价:%7.2f' % (code, _date, buy_price, high_limit), flush=True) except: print('买入时,发生错误:%s, %s' % (code, _date), flush=True) print('买入后,现金: %10.2f' % cash) # 持仓股代码列表 holding_codes = account.holding_codes # 如果调整日,则获取新一期的股票列表 if _date in rebalance_dates: # 暂存为上期的日期 if this_phase_codes is not None: last_phase_codes = this_phase_codes this_phase_codes = date_codes_dict[_date] # 找到所有调出股票代码,在第二日开盘时卖出 if last_phase_codes is not None: out_codes = self.find_out_stocks(last_phase_codes, this_phase_codes) for out_code in out_codes: if out_code in holding_codes: to_be_sold_codes.add(out_code) # 检查是否有需要第二天卖出的股票 for holding_code in holding_codes: if sell_signal.is_match(holding_code, _date): to_be_sold_codes.add(holding_code) # 检测止损信号 if stop_loss_policy is not None: for holding_code in holding_codes: if stop_loss_policy.is_stop(holding_code, _date): to_be_sold_codes.add(holding_code) print('止损,股票:%s' % holding_code, flush=True) else: stop_loss_policy.update_holding(holding_code, _date) # 检测止盈信号 if stop_profit_policy is not None: for holding_code in holding_codes: if stop_profit_policy.is_stop(holding_code, _date): to_be_sold_codes.add(holding_code) print('止盈,股票:%s' % holding_code, flush=True) else: stop_profit_policy.update_holding(holding_code, _date) print('待卖股票,日期:%s,代码列表:' % _date, to_be_sold_codes, flush=True) # 检测是否有需要建仓的股票 if add_position_policy is not None: for holding_code in holding_codes: add_signal = add_position_policy.get_add_signal( holding_code, _date) if add_signal is not None: to_be_added_signals[holding_code] = add_signal to_be_added_codes.add(holding_code) # 检查是否有需要第二天买入的股票 to_be_bought_codes.clear() if this_phase_codes is not None: for _code in this_phase_codes: if _code not in holding_codes and \ buy_signal.is_match(_code, _date): to_be_bought_codes.add(_code) self.logger.info('待买股票,日期:%s,代码列表:%s', _date, to_be_bought_codes) self.candidatesLogger.info('待买股票,日期:%s,代码列表:%s', _date, to_be_bought_codes) # 计算总市值 total_value = account.get_total_value(_date) # 计算总资产 total_capital = total_value + cash print('收盘后,现金: %10.2f, 总资产: %10.2f' % (cash, total_capital)) # 计算沪深300的增长 hs300_k_current = self.dm.get_k_data('000300', index=True, begin_date=_date, end_date=_date) hs300_current_value = hs300_k_current.loc[ hs300_k_current.index[0]]['close'] last_date = _date df_profit.loc[_date] = { 'net_value': round(total_capital / initial_capital, 2), 'profit': round( 100 * (total_capital - initial_capital) / initial_capital, 2), 'hs300': round( 100 * (hs300_current_value - hs300_begin_value) / hs300_begin_value, 2) } # 打印回测收益曲线数值 print('Profit history start') for index_date in df_profit.index: print('%s, %6.2f, %6.2f' % (index_date, df_profit.loc[index_date]['profit'], df_profit.loc[index_date]['hs300']), flush=True) print('Profit history end') drawdown = self.compute_drawdown(df_profit['net_value']) annual_profit, sharpe_ratio = self.compute_sharpe_ratio( df_profit['net_value']) print('回测结果 %s - %s,年化收益: %7.3f, 最大回撤:%7.3f, 夏普比率:%4.2f' % (self.begin_date, self.end_date, annual_profit, drawdown, sharpe_ratio)) df_profit.plot(title='Backtest Result', y=['profit', 'hs300'], kind='line') plt.show() def compute_sharpe_ratio(self, net_values): """ 计算夏普比率 :param net_values: 净值列表 """ # 总交易日数 trading_days = len(net_values) # 所有收益的DataFrame profit_df = DataFrame(columns={'profit'}) # 收益之后,初始化为第一天的收益 profit_df.loc[0] = {'profit': round((net_values[0] - 1) * 100, 2)} # 计算每天的收益 for index in range(1, trading_days): # 计算每日的收益变化 profit = (net_values[index] - net_values[index - 1]) / net_values[index - 1] profit = round(profit * 100, 2) profit_df.loc[index] = {'profit': profit} # 计算标准差 profit_std = pow(profit_df.var()['profit'], 1 / 2) # 年化收益 annual_profit = self.compute_annual_profit(trading_days, net_values[-1]) # 夏普比率 sharpe_ratio = (annual_profit - 4.75) / profit_std return annual_profit, sharpe_ratio def compute_drawdown(self, net_values): """ 计算最大回撤 :param net_values: 净值列表 """ # 最大回撤初始值设为0 max_drawdown = 0 index = 0 # 双层循环找出最大回撤 for net_value in net_values: for sub_net_value in net_values[index:]: drawdown = 1 - sub_net_value / net_value if drawdown > max_drawdown: max_drawdown = drawdown index += 1 return max_drawdown def compute_annual_profit(self, trading_days, net_value): """ 计算年化收益 """ annual_profit = 0 if trading_days > 0: # 计算年数 years = trading_days / 245 # 计算年化收益 annual_profit = pow(net_value, 1 / years) - 1 annual_profit = round(annual_profit * 100, 2) return annual_profit def find_out_stocks(self, last_phase_codes, this_phase_codes): """ 找到上期入选本期被调出的股票,这些股票将必须卖出 :param last_phase_codes: 上期的股票列表 :param this_phase_codes: 本期的股票列表 :return: 被调出的股票列表 """ out_stocks = [] for code in last_phase_codes: if code not in this_phase_codes: out_stocks.append(code) return out_stocks
def compute(self, begin_date=None, end_date=None): """ 计算指定日期内的信号 :param begin_date: 开始日期 :param end_date: 结束日期 """ codes = get_all_codes() dm = DataModule() for code in codes: try: df_dailies = dm.get_k_data(code, autype='hfq', begin_date=begin_date, end_date=end_date) if df_dailies.index.size == 0: continue # 计算MA10 df_dailies['ma'] = df_dailies['close'].rolling(10).mean() # 计算当日收盘和MA10的差值 df_dailies['delta'] = df_dailies['close'] - df_dailies['ma'] # 删除不再使用的ma和close列 df_dailies.drop(['ma', 'close'], 1, inplace=True) # 判断突破类型 index_size = df_dailies.index.size breaks = [0] for index in range(1, index_size): # 如果当前日期为停牌状态,则后面连续11日不参与计算 if df_dailies.loc[ df_dailies.index[index]]['is_trading'] is False: count = 10 while count > 0: index += 1 count -= 1 breaks.append(0) index += 1 last = df_dailies.loc[df_dailies.index[index - 1]]['delta'] current = df_dailies.loc[df_dailies.index[index]]['delta'] # 向上突破设为1,向下突破设为-1,不是突破设为0 break_direction = 1 if last <= 0 < current else -1 if last >= 0 > current else 0 breaks.append(break_direction) # 设置突破信号 df_dailies['break'] = breaks # 将日期作为索引 df_dailies.set_index(['date'], 1, inplace=True) # 删除不再使用的trade_status和delta数据列 df_dailies.drop(['is_trading', 'delta'], 1, inplace=True) # 只保留突破的日期 df_dailies = df_dailies[df_dailies['break'] != 0] # 将信号保存到数据库 update_requests = [] for index in df_dailies.index: doc = { 'code': code, 'date': index, # 方向,向上突破 up,向下突破 down 'direction': 'up' if df_dailies.loc[index]['break'] == 1 else 'down' } update_requests.append( UpdateOne(doc, {'$set': doc}, upsert=True)) if len(update_requests) > 0: update_result = self.collection.bulk_write(update_requests, ordered=False) print('%s, upserted: %4d, modified: %4d' % (code, update_result.upserted_count, update_result.modified_count), flush=True) except: traceback.print_exc()
def compute(self, begin_date, end_date): print(self.name, flush=True) dm = DataModule() df_daily = dm.get_k_data() print(df_daily)
def get_option_stocks(self): """ 实现股票池选股逻辑,找到指定日期范围的候选股票 条件:0 < PE < 30, 按从小到大排序,剔除停牌后,取前100个;再平衡周期:7个交易日 :return: tuple,再平衡的日期列表,以及一个dict(key: 再平衡日, value: 当期的股票列表) """ factor_module = FactorModule() dm = DataModule() # 因为上证指数没有停牌不会缺数,所以用它作为交易日历, szzz_hq_df = dm.get_k_data('000001', index=True, begin_date=self.begin_date, end_date=self.end_date) all_dates = list(szzz_hq_df['date']) # 缓存股票和其对应有交易的日期 code_dates_cache = dict() # 调整日和其对应的股票 rebalance_date_codes_dict = dict() rebalance_dates = [] # 保存上一期的股票池 last_phase_codes = [] # 所有的交易日数 dates_count = len(all_dates) # 用再平衡周期作为步长循环 for index in range(0, dates_count, self.interval): # 当前的调整日 rebalance_date = all_dates[index] # 获取本期符合条件的备选股票 df_pe = factor_module.get_single_date_factors('pe', rebalance_date) df_pe.sort_values('pe', ascending=True, inplace=True) # 只保留小于30的数据 df_pe = df_pe[(0 < df_pe['pe']) & (df_pe['pe'] < 30)] df_pe.set_index(['code'], inplace=True) this_phase_option_codes = list(df_pe.index)[0:100] print(this_phase_option_codes, flush=True) # 本期入选的股票代码列表 this_phase_codes = [] # 找到在上一期的股票池,但是当前停牌的股票,保留在当期股票池中 if len(last_phase_codes) > 0: for code in last_phase_codes: if code not in list(code_dates_cache.keys()): daily_ks = dm.get_k_data(code, autype=None, begin_date=self.begin_date, end_date=self.end_date) daily_ks.set_index(['date'], inplace=True) code_dates_cache[code] = list(daily_ks.index) if rebalance_date not in code_dates_cache[code]: this_phase_codes.append(code) print('上期停牌的股票:', flush=True) print(this_phase_codes, flush=True) # 剩余的位置用当前备选股票的 option_size = len(this_phase_option_codes) if option_size > (100 - len(this_phase_codes)): this_phase_codes += this_phase_option_codes[ 0:100 - len(this_phase_codes)] else: this_phase_codes += this_phase_option_codes # 当期股票池作为下次循环的上期股票池 last_phase_codes = this_phase_codes # 保存到返回结果中 rebalance_date_codes_dict[rebalance_date] = this_phase_codes rebalance_dates.append(rebalance_date) print('当前最终的备选票:%s' % rebalance_date, flush=True) print(this_phase_codes, flush=True) return rebalance_dates, rebalance_date_codes_dict
def compute(self, begin_date, end_date): """ 计算指定时间段内所有股票的该因子的值,并保存到数据库中 :param begin_date: 开始时间 :param end_date: 结束时间 """ dm = DataModule() # 获取所有股票 codes = get_all_codes() for code in codes: print('计算市盈率, %s' % code) df_daily = dm.get_k_data(code, autype=None, begin_date=begin_date, end_date=end_date) if df_daily.index.size > 0: df_daily.set_index(['date'], 1, inplace=True) update_requests = [] for date in df_daily.index: finance_report = DB_CONN['finance_report'].find_one( { 'code': code, 'report_date': { '$regex': '\d{4}-12-31' }, 'announced_date': { '$lte': date } }, sort=[('announced_date', DESCENDING)]) if finance_report is None: continue # 计算滚动市盈率并保存到daily_k中 eps = 0 if finance_report['eps'] != '-': eps = finance_report['eps'] # 计算PE if eps != 0: pe = round(df_daily.loc[date]['close'] / eps, 3) print('%s, %s, %s, eps: %5.2f, pe: %6.2f' % (code, date, finance_report['announced_date'], finance_report['eps'], pe), flush=True) update_requests.append( UpdateOne({ 'code': code, 'date': date }, { '$set': { 'code': code, 'date': date, 'pe': pe } }, upsert=True)) if len(update_requests) > 0: save_result = self.collection.bulk_write(update_requests, ordered=False) print('股票代码: %s, 因子: %s, 插入:%4d, 更新: %4d' % (code, self.name, save_result.upserted_count, save_result.modified_count), flush=True)
def analyze(self): # 初始化对数据管理子系统接口的调用 dm = DataModule() # 初始化对因子管理子系统接口的调用 fm = FactorModule() # 获取分析周期内的 all_dates = get_trading_dates(self.begin_date, self.end_date) # 首档和末档,股票代码和后复权价格的Dictionary top_dailies = dict() bottom_dailies = dict() # 暂存上一个调整 last_adjust_date = None # 设置沪深300的首日值 hs300_k = dm.get_k_data('000300', index=True, begin_date=all_dates[0], end_date=all_dates[0]) self.hs300_first_value = hs300_k.loc[0]['close'] # 计算每日收益 for index in range(0, len(all_dates), self.interval): adjust_date = all_dates[index] # 获取因子值,按照指定的顺序排序 df_factor = fm.get_single_date_factors(self.factor, adjust_date) if df_factor.index.size == 0: continue df_factor.sort_values(self.factor, ascending=self.ascending, inplace=True) # 将股票代码设为index df_factor.set_index(['code'], inplace=True) # 获取当日所有股票的行情 df_dailies = dm.get_one_day_k_data(autype='hfq', date=adjust_date) # 将code设为index df_dailies.set_index(['code'], inplace=True) # 计算收益 self.compute_profit(last_adjust_date, df_dailies, top_dailies, bottom_dailies, adjust_date) # 删除停牌股票 df_dailies = df_dailies[df_dailies['is_trading']] # 计算每当包含的股票数 total_size = df_dailies.index.size single_position_count = int(total_size / self.position) # 调整首档组合 self.adjust_top_position(top_dailies, df_factor, df_dailies, single_position_count) # 调整末档组合 self.adjust_bottom_position(bottom_dailies, df_factor, df_dailies, single_position_count) # 保存上一个调整日 last_adjust_date = adjust_date # 生成零投资组合的组合收益 self.profit_df[ 'portfolio'] = self.profit_df['top'] - self.profit_df['bottom'] self.draw()
class TrackingStopProfit(BaseStopProfit): def __init__(self, account, max_profit, profit_drawdown): BaseStopProfit.__init__(self, account) self.max_profit = max_profit self.profit_drawdown = profit_drawdown self.dm = DataModule() def update_holding(self, code, date): """ 更新持仓股的最高市值 :param code: :param date: :return: """ df_daily = self.dm.get_k_data(code, autype=None, begin_date=date, end_date=date) if df_daily.index.size > 0: df_daily.set_index(['date'], 1, inplace=True) close = df_daily.loc[date]['close'] holding = self.account.get_holding(code) current_value = holding['volume'] * close if 'highest_value' in holding.keys(): if current_value > holding['highest_value']: holding['highest_value'] = current_value self.account.update_holding(code, holding) else: # 判断是否已经达到了最高的收益线 profit = (current_value - holding['cost']) * 100 / holding['cost'] if profit > self.max_profit: holding['highest_value'] = current_value self.account.update_holding(code, holding) def is_stop(self, code, date): """ 判断股票在当前日期是否需要止盈,如果当前收益相对于最高收益的回撤已经达到了指定幅度,则止盈 :param code: 股票代码 :param date: 日期 :return: True - 止盈, False - 不止盈 """ holding_stock = self.account.get_holding(code) print('止盈判断:%s' % code, flush=True) print(holding_stock, flush=True) if holding_stock is not None: df_daily = self.dm.get_k_data(code, autype=None, begin_date=date, end_date=date) if df_daily.index.size > 0: df_daily.set_index(['date'], 1, inplace=True) current_value = df_daily.loc[date]['close'] * holding_stock[ 'volume'] # 计算回落的百分比 if 'highest_value' in holding_stock and current_value < holding_stock[ 'highest_value']: profit = (current_value - holding_stock['highest_value']) \ * 100 / holding_stock['highest_value'] print('止盈判断, profit: %5.2f, drawdown: %5.2f' % (profit, self.profit_drawdown), flush=True) return (profit < 0) & (abs(profit) >= abs( self.profit_drawdown)) return False
def compute(self, begin_date=None, end_date=None): """ 计算指定时间段内所有股票的该因子的值,并保存到数据库中 :param begin_date: 开始时间 :param end_date: 结束时间 """ dm = DataModule() # 如果没有指定日期范围,则默认为计算当前交易日的数据 if begin_date is None: begin_date = datetime.now().strftime('%Y-%m-%d') if end_date is None: end_date = datetime.now().strftime('%Y-%m-%d') dates = get_trading_dates(begin_date, end_date) for date in dates: # 查询出股票在某一交易日的总股本 df_basics = dm.get_stock_basic_at(date) if df_basics.index.size == 0: continue # 将索引改为code df_basics.set_index(['code'], 1, inplace=True) # 查询出股票在某一个交易日的收盘价 df_dailies = dm.get_one_day_k_data(autype=None, date=date) if df_dailies.index.size == 0: continue # 将索引设为code df_dailies.set_index(['code'], 1, inplace=True) update_requests = [] for code in df_dailies.index: try: # 股价 close = df_dailies.loc[code]['close'] # 总股本 total_shares = df_basics.loc[code]['totals'] # 总市值 = 股价 * 总股本 total_capital = round(close * total_shares, 2) print('%s, %s, mkt_cap: %15.2f' % (code, date, total_capital), flush=True) update_requests.append( UpdateOne( {'code': code, 'date': date}, {'$set': {'code': code, 'date': date, self.name: total_capital}}, upsert=True)) except: print('计算规模因子时发生异常,股票代码:%s,日期:%s' % (code, date), flush=True) if len(update_requests) > 0: save_result = self.collection.bulk_write(update_requests, ordered=False) print('股票代码: %s, 因子: %s, 插入:%4d, 更新: %4d' % (code, self.name, save_result.upserted_count, save_result.modified_count), flush=True)
def __init__(self): self.holding = dict() self.holding_codes = set() self.dm = DataModule()
def __init__(self, account, strategy_option): BasePosition.__init__(self, account, strategy_option) self.dm = DataModule()
class ATRPosition(BasePosition): def __init__(self, account, strategy_option): BasePosition.__init__(self, account, strategy_option) self.dm = DataModule() def update_holding(self, code, date, updated_holding): """ 更新持仓股,为新的持仓股增加ATR、加仓次数 :param code: 股票代码 :param date: 日期 :param updated_holding: 更新后的持仓股 """ try: existing_holding = self.account.get_holding(code) print(existing_holding, flush=True) if existing_holding is not None and 'atr' not in existing_holding: atr = self.compute_atr(code, date) print('Computed atr: %s' % str(atr), flush=True) if atr is not None: updated_holding['atr'] = atr updated_holding['add_times'] = 0 self.account.update_holding(code, updated_holding) except: print('加仓策略,更新持仓股信息时,发生错误,股票代码:%s,日期:%s,' % (code, date), flush=True) traceback.print_exc() def get_add_signal(self, code, date): """ 如果符合加仓条件,则计算加仓信号 :param code: 股票代码 :param date: 日期 :return: 加仓信号,如果没有则返回None,正常信号包括:code - 股票代码,position - 仓位 """ add_signal = None try: df_daily = self.dm.get_k_data(code, autype=None, begin_date=date, end_date=date) if df_daily.index.size > 0: holding_stock = self.account.get_holding(code) a = 5 / 0 df_daily.set_index(['date'], 1, inplace=True) daily = df_daily.loc[date] if 'atr' in holding_stock and daily['is_trading']: au_factor = daily['au_factor'] # ATR atr = holding_stock['atr'] # 已加仓次数 add_times = holding_stock['add_times'] # 末次加仓价格,比较时用的实际价格 last_buy_price = holding_stock['last_buy_hfq_price'] hfq_close = daily['close'] * au_factor # 最多加仓4次 价格超过上一个加仓点+atr if add_times < 4 and hfq_close - last_buy_price > atr: position = self.compute_position(code, date) add_signal = {'code': code, 'position': position} except: self.logger.error('计算加仓信号时,发生错误,股票代码:%s, 日期:%s' % (code, date)) return add_signal def compute_atr(self, code, date): """ 计算ATR :param code: 股票代码 :param date: 日期 :return: ATR的值 """ minute_cursor = DB_CONN['minute'].find( { 'code': code, 'time': { '$lte': date + ' 15:00' } }, sort=[('time', DESCENDING)], projection={ 'date': True, 'time': True, 'close': True, 'high': True, 'low': True, '_id': False }, limit=61) minutes = [minute for minute in minute_cursor] minutes.reverse() if len(minutes) >= 60: before_date = (datetime.strptime(date, '%Y-%m-%d') - timedelta(days=20)).strftime('%Y-%m-%d') df_daily = self.dm.get_k_data(code, autype=None, begin_date=before_date, end_date=date) if df_daily.index.size == 0: return None df_daily.set_index(['date'], 1, inplace=True) atr_df = DataFrame(columns=['tr']) for index in range(1, len(minutes)): minute = minutes[index] minute_date = minute['date'] try: au_factor = df_daily.loc[minute_date]['au_factor'] high = minute['high'] * au_factor low = minute['low'] * au_factor pre_close = minutes[index - 1]['close'] * au_factor atr_df.loc[index] = { 'tr': max([ high - low, abs(high - pre_close), abs(pre_close - low) ]) } except Exception as e: self.logger.error( '计算ATR发生异常,股票代码:%s, 分钟线日期:%s' % (code, minute_date), e) print(atr_df) atrs = atr_df['tr'].rolling(60).mean() self.logger.debug(atrs) return atrs[60] else: print('加仓策略(ATR),分钟线数据不足,股票代码:%s,日期:%s,分钟线数:%2d' % (code, date, len(minutes)), flush=True) return None def compute_position(self, code, date): """ 计算应该分配的仓位, 这里采用固定仓位,总资金的0.015 :param code: 股票代码 :param date: 日期 :return: """ amount = self.strategy_option.capital() * 0.015 return amount
def statistic_profit(self): """ 统计股票池的收益 """ # 设定评测周期 rebalance_dates, codes_dict = self.get_option_stocks() dm = DataModule() # 用DataFrame保存收益 df_profit = DataFrame(columns=['profit', 'hs300']) df_profit.loc[rebalance_dates[0]] = {'profit': 0, 'hs300': 0} # 获取沪深300在统计周期内的第一天的值 hs300_k = dm.get_k_data('000300', index=True, begin_date=rebalance_dates[0], end_date=rebalance_dates[0]) hs300_begin_value = hs300_k.loc[hs300_k.index[0]]['close'] # 通过净值计算累计收益 net_value = 1 for _index in range(1, len(rebalance_dates) - 1): last_rebalance_date = rebalance_dates[_index - 1] current_rebalance_date = rebalance_dates[_index] # 获取上一期的股票池 codes = codes_dict[last_rebalance_date] # 统计当前的收益 profit_sum = 0 # 参与统计收益的股票个数 profit_code_count = 0 for code in codes: daily_ks = dm.get_k_data(code, autype='hfq', begin_date=last_rebalance_date, end_date=current_rebalance_date) index_size = daily_ks.index.size # 如果没有数据,则跳过,长期停牌 if index_size == 0: continue # 买入价 in_price = daily_ks.loc[daily_ks.index[0]]['close'] # 卖出价 out_price = daily_ks.loc[daily_ks.index[index_size - 1]]['close'] # 股票池内所有股票的收益 profit_sum += (out_price - in_price) / in_price profit_code_count += 1 profit = round(profit_sum / profit_code_count, 4) hs300_k_current = dm.get_k_data('000300', index=True, begin_date=current_rebalance_date, end_date=current_rebalance_date) hs300_close = hs300_k_current.loc[ hs300_k_current.index[0]]['close'] # 计算净值和累积收益 net_value = net_value * (1 + profit) df_profit.loc[current_rebalance_date] = { 'profit': round((net_value - 1) * 100, 4), 'hs300': round((hs300_close - hs300_begin_value) * 100 / hs300_begin_value, 4) } print(df_profit) # 绘制曲线 df_profit.plot(title='Stock Pool Profit Statistic', kind='line') # 显示图像 plt.show()
class DailyKBreakMA10Signal: def __init__(self): self.dm = DataModule() def is_k_up_break_ma10(self, code, begin_date, end_date): """ 判断某只股票在某日是否满足K线上穿10日均线 :param code: 股票代码 :param begin_date: 开始日期 :param end_date: 结束日期 :return: True/False """ # 如果没有指定开始日期和结束日期,则直接返回False if begin_date is None or end_date is None: return False daily_ks = self.dm.get_k_data(code, autype='hfq', begin_date=begin_date, end_date=end_date) # 需要判断两日的K线和10日均线的相对位置,所以如果K线数不满足11个, # 也就是无法计算两个MA10,则直接返回False index_size = daily_ks.index.size if index_size < 11: return False # 比较收盘价和MA10的关系 daily_ks['ma'] = daily_ks['close'].rolling(10).mean() daily_ks['delta'] = daily_ks['close'] - daily_ks['ma'] is_break_up = (daily_ks.loc[daily_ks.index[9]]['delta'] <= 0 < daily_ks.loc[daily_ks.index[10]]['delta']) return is_break_up def is_k_down_break_ma10(self, code, begin_date, end_date): """ 判断某只股票在某日是否满足K线下穿10日均线 :param code: 股票代码 :param begin_date: 开始日期 :param end_date: 结束日期 :return: True/False """ # 如果没有指定开始日期和结束日期,则直接返回False if begin_date is None or end_date is None: return False daily_ks = self.dm.get_k_data(code, autype='hfq', begin_date=begin_date, end_date=end_date) # 需要判断两日的K线和10日均线的相对位置,所以如果K线数不满足11个, # 也就是无法计算两个MA10,则直接返回False if daily_ks.index.size < 11: return False # 比较收盘价和MA10的关系 daily_ks['ma'] = daily_ks['close'].rolling(10).mean() daily_ks['delta'] = daily_ks['close'] - daily_ks['ma'] return daily_ks.loc[daily_ks.index[9]]['delta'] >= 0 > daily_ks.loc[ daily_ks.index[10]]['delta']
def __init__(self): self.dm = DataModule()
def compute_profit(self, last_adjust_date, df_dailies, top_dailies, bottom_dailies, adjust_date): """ 计算收益 :param last_adjust_date: 上一个调整日 :param df_dailies: :param top_dailies: :param bottom_dailies: :param adjust_date: 当前调整日 :return: """ # 只有存在上一个调整日,才计算上期的收益 if last_adjust_date is not None: # 计算首档收益 top_profit = self.compute_average_profit(df_dailies, top_dailies) # 计算末档收益 bottom_profit = self.compute_average_profit( df_dailies, bottom_dailies) # 计算组合收益 portfolio_profit = top_profit[0] - bottom_profit[0] # 添加结果的DataFrame中 self.profit_df.loc[last_adjust_date] = { 'top': top_profit[0], 'bottom': bottom_profit[0], 'portfolio': portfolio_profit } # 计算累积收益(复利方式) # 首档 top_cumulative_profit = round( (self.last_top_net_value * (1 + top_profit[0] / 100) - 1) * 100, 2) self.last_top_net_value *= (1 + top_profit[0] / 100) # 末档 bottom_cumulative_profit = round( (self.last_bottom_net_value * (1 + bottom_profit[0] / 100) - 1) * 100, 2) self.last_bottom_net_value *= (1 + bottom_profit[0] / 100) # 组合 portfolio_cumulative_profit = round( (self.last_portfolio_net_value * (1 + portfolio_profit / 100) - 1) * 100, 2) self.last_portfolio_net_value *= (1 + portfolio_profit / 100) # 计算沪深300的累计收益 dm = DataModule() hs300_k = dm.get_k_data('000300', index=True, begin_date=adjust_date, end_date=adjust_date) hs300_k.set_index(['date'], 1, inplace=True) hs300_profit = (hs300_k.loc[adjust_date]['close'] - self.hs300_first_value) / self.hs300_first_value self.cumulative_profit.loc[last_adjust_date] = { 'top': top_cumulative_profit, 'bottom': bottom_cumulative_profit, 'portfolio': portfolio_cumulative_profit, 'hs300': hs300_profit } self.count_df.loc[last_adjust_date] = { 'top': top_profit[1], 'bottom': bottom_profit[1] }
class Account: def __init__(self): self.holding = dict() self.holding_codes = set() self.dm = DataModule() def buy_in(self, code, volume, cost): self.holding[code] = { 'volume': volume, 'cost': cost, 'last_value': cost } self.holding_codes.add(code) def sell_out(self, code): del self.holding[code] self.holding_codes.remove(code) def get_holding(self, code): """ 通过股票代码获取该股票的持仓情况 :param code: 股票代码 :return: 持仓对象,如果没有该股票的持仓,则返回None """ if code in self.holding_codes: return self.holding[code] else: return None def update_holding(self, code, updated_holding): """ 更新该持仓股的字段 :param code: 股票代码 :param updated_holding: 新的持仓股信息 """ if code in self.holding_codes: self.holding[code] = updated_holding def adjust_holding_volume_at_open(self, last_date=None, current_date=None): """ 开盘时,处理持仓股的复权 :param last_date: 上一个交易日 :param current_date: 当前交易日 """ if last_date is not None and len(self.holding_codes) > 0: for code in self.holding_codes: try: dailies = self.dm.get_k_data(code=code, begin_date=last_date, end_date=current_date) if dailies.index.size == 2: dailies.set_index(['date'], 1, inplace=True) current_au_factor = dailies.loc[current_date][ 'au_factor'] before_volume = self.holding[code]['volume'] last_au_factor = dailies.loc[last_date]['au_factor'] after_volume = int( before_volume * (current_au_factor / last_au_factor)) self.holding[code]['volume'] = after_volume print('持仓量调整:%s, %6d, %10.6f, %6d, %10.6f' % (code, before_volume, last_au_factor, after_volume, current_au_factor), flush=True) except: print('持仓量调整时,发生错误:%s, %s' % (code, current_date), flush=True) traceback.print_exc() def get_total_value(self, date): """ 计算当期那持仓股在某一天的总市值,并更新持仓股的上一个市值 :param date: 日期 """ total_value = 0 dailies = self.dm.get_stocks_one_day_k_data(list(self.holding_codes), date=date) if dailies.index.size > 0: dailies.set_index(['code'], 1, inplace=True) for code in self.holding_codes: try: holding_stock = self.holding[code] value = dailies.loc[code]['close'] * holding_stock['volume'] total_value += value # 计算总收益 profit = (value - holding_stock['cost'] ) * 100 / holding_stock['cost'] # 计算单日收益 last_value = holding_stock['last_value'] one_day_profit = (value - last_value) * 100 / last_value # 暂存当日市值 self.holding[code]['last_value'] = value print('持仓: %s, %10.2f, %10.2f, %4.2f, %4.2f' % (code, value, last_value, profit, one_day_profit)) except: print('计算收益时发生错误:%s, %s' % (code, date), flush=True) return total_value
def __init__(self, account, max_loss): BaseStopLoss.__init__(self, account) self.max_loss = max_loss self.dm = DataModule()
class Backtest: def __init__(self, begin_date, end_date): self.begin_date = begin_date self.end_date = end_date self.dm = DataModule() self.code_daily_cache = dict() def start(self): """ 策略回测。结束后打印出收益曲线(沪深300基准)、年化收益、最大回撤、 :param begin_date: 回测开始日期 :param end_date: 回测结束日期 """ total_capital = 1E7 cash = 1E7 single_position = 2E5 # 初始化信号对象 daily_k_break_ma10 = DailyKBreakMA10Signal() low_pe_stock_pool = LowPeStockPool(self.begin_date, self.end_date, 7) # 保存持仓股的日期 code_date_volume_dict = dict() # 时间为key的净值、收益和同期沪深基准 df_profit = DataFrame(columns=['net_value', 'profit', 'hs300']) # 因为上证指数没有停牌不会缺数,所以用它作为交易日历, szzz_hq_df = self.dm.get_k_data('000001', index=True, begin_date=self.begin_date, end_date=self.end_date) all_dates = list(szzz_hq_df['date']) # 获取沪深300在统计周期内的第一天的值 hs300_k = self.dm.get_k_data('000300', index=True, begin_date=all_dates[0], end_date=all_dates[0]) hs300_begin_value = hs300_k.loc[hs300_k.index[0]]['close'] # 获取股票池数据 rebalance_dates, date_codes_dict = low_pe_stock_pool.get_option_stocks( ) # 获取回测周期内股票池内所有股票的收盘价和前收价 all_option_code_set = set() for rebalance_date in rebalance_dates: for code in date_codes_dict[rebalance_date]: all_option_code_set.add(code) # 缓存股票的日线数据 for code in all_option_code_set: dailies_df = self.dm.get_k_data(code, autype=None, begin_date=self.begin_date, end_date=self.end_date) dailies_hfq_df = self.dm.get_k_data(code, autype='hfq', begin_date=self.begin_date, end_date=self.end_date) # 计算复权因子 dailies_df[ 'au_factor'] = dailies_hfq_df['close'] / dailies_df['close'] dailies_df.set_index(['date'], inplace=True) self.code_daily_cache[code] = dailies_df last_phase_codes = None this_phase_codes = None to_be_sold_codes = set() to_be_bought_codes = set() holding_code_dict = dict() last_date = None # 按照日期一步步回测 for _date in all_dates: print('Backtest at %s.' % _date) # 当期持仓股票列表 before_sell_holding_codes = list(holding_code_dict.keys()) # 处理复权 if last_date is not None and len(before_sell_holding_codes) > 0: for code in before_sell_holding_codes: try: dailies = self.code_daily_cache[code] current_au_factor = dailies.loc[_date]['au_factor'] before_volume = holding_code_dict[code]['volume'] last_au_factor = dailies.loc[last_date]['au_factor'] after_volume = int( before_volume * (current_au_factor / last_au_factor)) holding_code_dict[code]['volume'] = after_volume print('持仓量调整:%s, %6d, %10.6f, %6d, %10.6f' % (code, before_volume, last_au_factor, after_volume, current_au_factor), flush=True) except: print('持仓量调整时,发生错误:%s, %s' % (code, _date), flush=True) # 卖出 if len(to_be_sold_codes) > 0: code_set_tmp = set(to_be_sold_codes) for code in code_set_tmp: try: if code in before_sell_holding_codes: holding_stock = holding_code_dict[code] holding_volume = holding_stock['volume'] sell_price = self.code_daily_cache[code].loc[ _date]['open'] sell_amount = holding_volume * sell_price cash += sell_amount cost = holding_stock['cost'] single_profit = (sell_amount - cost) * 100 / cost print('卖出 %s, %6d, %6.2f, %8.2f, %4.2f' % (code, holding_volume, sell_price, sell_amount, single_profit)) del holding_code_dict[code] to_be_sold_codes.remove(code) except: print('卖出时,发生异常:%s, %s' % (code, _date), flush=True) print('卖出后,现金: %10.2f' % cash) # 买入 if len(to_be_bought_codes) > 0: sorted_to_be_bought_list = list(to_be_bought_codes) sorted_to_be_bought_list.sort() for code in sorted_to_be_bought_list: try: if cash > single_position: buy_price = self.code_daily_cache[code].loc[_date][ 'open'] volume = int( int(single_position / buy_price) / 100) * 100 buy_amount = buy_price * volume cash -= buy_amount holding_code_dict[code] = { 'volume': volume, 'cost': buy_amount, 'last_value': buy_amount } print('买入 %s, %6d, %6.2f, %8.2f' % (code, volume, buy_price, buy_amount), flush=True) except: print('买入时,发生错误:%s, %s' % (code, _date), flush=True) print('买入后,现金: %10.2f' % cash) # 持仓股代码列表 holding_codes = list(holding_code_dict.keys()) # 如果调整日,则获取新一期的股票列表 if _date in rebalance_dates: # 暂存为上期的日期 if this_phase_codes is not None: last_phase_codes = this_phase_codes this_phase_codes = date_codes_dict[_date] # 找到所有调出股票代码,在第二日开盘时卖出 if last_phase_codes is not None: out_codes = self.find_out_stocks(last_phase_codes, this_phase_codes) for out_code in out_codes: if out_code in holding_code_dict: to_be_sold_codes.add(out_code) # 获取检测信号的开始日期和结束日期 current_date_index = all_dates.index(_date) signal_begin_date = None if current_date_index >= 10: signal_begin_date = all_dates[current_date_index - 10] # 检查是否有需要第二天卖出的股票 for holding_code in holding_codes: if daily_k_break_ma10.is_k_down_break_ma10( holding_code, begin_date=signal_begin_date, end_date=_date): to_be_sold_codes.add(holding_code) # 检查是否有需要第二天买入的股票 to_be_bought_codes.clear() if this_phase_codes is not None: for _code in this_phase_codes: if _code not in holding_codes and \ daily_k_break_ma10.is_k_up_break_ma10(_code, begin_date=signal_begin_date, end_date=_date): to_be_bought_codes.add(_code) # 计算总资产 total_value = 0 for code in holding_codes: try: holding_stock = holding_code_dict[code] value = self.code_daily_cache[code].loc[_date][ 'close'] * holding_stock['volume'] total_value += value # 计算总收益 profit = (value - holding_stock['cost'] ) * 100 / holding_stock['cost'] # 计算单日收益 one_day_profit = (value - holding_stock['last_value'] ) * 100 / holding_stock['last_value'] # 暂存当日市值 holding_stock['last_value'] = value print('持仓: %s, %10.2f, %4.2f, %4.2f' % (code, value, profit, one_day_profit)) # 保存每一日股票的持仓数 code_date_volume_dict[code + '_' + _date] = holding_stock['volume'] except: print('计算收益时发生错误:%s, %s' % (code, _date), flush=True) total_capital = total_value + cash hs300_k_current = self.dm.get_k_data('000300', index=True, begin_date=_date, end_date=_date) hs300_current_value = hs300_k_current.loc[ hs300_k_current.index[0]]['close'] print('收盘后,现金: %10.2f, 总资产: %10.2f' % (cash, total_capital)) last_date = _date df_profit.loc[_date] = { 'net_value': round(total_capital / 1e7, 2), 'profit': round(100 * (total_capital - 1e7) / 1e7, 2), 'hs300': round( 100 * (hs300_current_value - hs300_begin_value) / hs300_begin_value, 2) } # 打印回测收益曲线数值 print('Profit history start') for index_date in df_profit.index: print('%s, %6.2f, %6.2f' % (index_date, df_profit.loc[index_date]['profit'], df_profit.loc[index_date]['hs300']), flush=True) print('Profit history end') drawdown = self.compute_drawdown(df_profit['net_value']) annual_profit, sharpe_ratio = self.compute_sharpe_ratio( df_profit['net_value']) print('回测结果 %s - %s,年化收益: %7.3f, 最大回撤:%7.3f, 夏普比率:%4.2f' % (self.begin_date, self.end_date, annual_profit, drawdown, sharpe_ratio)) df_profit.plot(title='Backtest Result', y=['profit', 'hs300'], kind='line') plt.show() def compute_sharpe_ratio(self, net_values): """ 计算夏普比率 :param net_values: 净值列表 """ # 总交易日数 trading_days = len(net_values) # 所有收益的DataFrame profit_df = DataFrame(columns={'profit'}) # 收益之后,初始化为第一天的收益 profit_df.loc[0] = {'profit': round((net_values[0] - 1) * 100, 2)} # 计算每天的收益 for index in range(1, trading_days): # 计算每日的收益变化 profit = (net_values[index] - net_values[index - 1]) / net_values[index - 1] profit = round(profit * 100, 2) profit_df.loc[index] = {'profit': profit} # 计算标准差 profit_std = pow(profit_df.var()['profit'], 1 / 2) # 年化收益 annual_profit = self.compute_annual_profit(trading_days, net_values[-1]) # 夏普比率 sharpe_ratio = (annual_profit - 4.75) / profit_std return annual_profit, sharpe_ratio def compute_drawdown(self, net_values): """ 计算最大回撤 :param net_values: 净值列表 """ # 最大回撤初始值设为0 max_drawdown = 0 index = 0 # 双层循环找出最大回撤 for net_value in net_values: for sub_net_value in net_values[index:]: drawdown = 1 - sub_net_value / net_value if drawdown > max_drawdown: max_drawdown = drawdown index += 1 return max_drawdown def compute_annual_profit(self, trading_days, net_value): """ 计算年化收益 """ annual_profit = 0 if trading_days > 0: # 计算年数 years = trading_days / 245 # 计算年化收益 annual_profit = pow(net_value, 1 / years) - 1 annual_profit = round(annual_profit * 100, 2) return annual_profit def find_out_stocks(self, last_phase_codes, this_phase_codes): """ 找到上期入选本期被调出的股票,这些股票将必须卖出 :param last_phase_codes: 上期的股票列表 :param this_phase_codes: 本期的股票列表 :return: 被调出的股票列表 """ out_stocks = [] for code in last_phase_codes: if code not in this_phase_codes: out_stocks.append(code) return out_stocks
def __init__(self, begin_date, end_date): self.begin_date = begin_date self.end_date = end_date self.dm = DataModule() self.code_daily_cache = dict()
def __init__(self, account, max_profit, profit_drawdown): BaseStopProfit.__init__(self, account) self.max_profit = max_profit self.profit_drawdown = profit_drawdown self.dm = DataModule()
def compute(self, begin_date, end_date): """ 计算指定日期内的信号 :param begin_date: 开始日期 :param end_date: 结束日期 """ all_codes = get_all_codes() dm = DataModule() N = 20 k = 2 for code in all_codes: try: df_daily = dm.get_k_data(code, autype='hfq', begin_date=begin_date, end_date=end_date) # 计算MB,盘后计算,这里用当日的Close df_daily['MID'] = df_daily['close'].rolling(N).mean() # 计算STD20 df_daily['std'] = df_daily['close'].rolling(N).std() # 计算UP df_daily['UP'] = df_daily['MID'] + k * df_daily['std'] # 计算down df_daily['DOWN'] = df_daily['MID'] - k * df_daily['std'] # 将日期作为索引 df_daily.set_index(['date'], inplace=True) # 上轨和中轨右移一位 shifted_up = df_daily['UP'].shift(1) shifted_middle = df_daily['MID'].shift(1) # 收盘价突破或者跌破中轨的幅度占上轨和中轨宽度的比例 ref_line = (df_daily['close'] - shifted_middle) / (shifted_up - shifted_middle) ref_prev = ref_line.shift(1) # 找到时间窗口内的最小值 min_val = ref_line.rolling(10).min() # 找到时间窗口内最低点前的最大值 max_leading_value = ref_line.rolling(10).apply( lambda vec: vec[:np.argmin(vec) + 1].max().astype(float), raw=True) # 中轨支撑的作用的范围 delta = 0.15 # 判断是否存在中轨支撑反弹的信号,要求: # 时间窗口的最低点之前的最大值大于delta,最小值的绝对值小于delta,就有一个穿越阈值分界线的动作; # 当前日期在也在阈值之上,表示又从最低点穿越到阈值分界线之上; # 而判断前一日在阈值分界线之下,表示穿越是在当前交易日完成 m_rebound_mask = (abs(min_val) <= delta) & (ref_line > delta) & (ref_prev <= delta) & \ (max_leading_value > delta) # 将信号保存到数据库 update_requests = [] df_daily['m_rebound_mask'] = m_rebound_mask df_daily = df_daily[df_daily['m_rebound_mask']] for date in df_daily.index: doc = {'code': code, 'date': date, 'signal': 'mid_rebound'} update_requests.append( UpdateOne(doc, {'$set': doc}, upsert=True)) if len(update_requests) > 0: update_result = self.collection.bulk_write(update_requests, ordered=False) print('%s, upserted: %4d, modified: %4d' % (code, update_result.upserted_count, update_result.modified_count), flush=True) except: traceback.print_exc()