def buy(code, to_be_bought_codes, cash): ''' 买入股票 ''' print('to buy stocks: ', to_be_bought_codes, flush=True) if len(to_be_bought_codes) > 0: buy_daily_cursor = daily_collection.find( {'code': {'$in': to_be_bought_codes}, 'date': _date, 'index': False, 'is_trading': False}, projection={'code': True, '_id': False, 'open': True} ).hint([('code', 1), ('date', -1)]) for buy_daily in buy_daily_cursor: if cash > single_position: buy_price = buy_daily['open'] buy_volume = int(single_positon / buy_price) buy_amount = buy_price * buy_volume cash -= buy_amount holding_code_dict[code] = { 'volume': buy_volume, 'last_value': buy_price, 'cost': buy_amount } print('buy %s, %6d, %6.2f, %8.2f' % (code, buy_volume, buy_amount)) print('cash after buy: %10.2f' % cash) return holding_code_dict, cash
def is_k_down_break_m10(code, date): ''' 查看当日收盘价是否上穿10日均线 parameter: code: 股票代码 date: 日期 return: True/False ''' current_daily = daily_collection.find_one({ 'code': code, 'date': date }, projection={ 'code': True, '_id': False, 'close': True }) if current_daily is None: print('code: %s have no data in date: %s' % (code, date)) return False daily_cursor = daily_collection.find( { 'code': code, 'date': { '$lte': date }, 'index': False }, sort=[('date', DESCENDING)], limit=11, projection={ 'code': True, 'close': True, 'date': True, '_id': False }).hint([('code', 1), ('date', -1)]) closes = [daily['close'] for daily in daily_cursor] closes.reverse() if closes is None: print('data before 10 days of %s is None, code: %s' % (date, code)) return False if len(closes) < 11: print('K line is not enough: code %s, date: %s' % (code, date)) return False last_close_2_last_ma10 = compare_close_2_ma10(closes[0:10], current_daily) current_close_2_current_ma10 = compare_close_2_ma10( closes[1:], current_daily) if last_close_2_last_ma10 == 1 and current_close_2_current_ma10 == -1: return True else: return False
def pingan_ma5(begin_date=None, end_date=None, code=None): if code is None: code = '000001' CASH = 100000 daily_cursor = daily_collection.find( { 'code': code, 'date': { '$gte': begin_date, '$lte': end_date }, 'index': False }, projection={ 'date': True, 'close': True, '_id': False }, sort=[('date', ASCENDING)], ).hint([('code', 1), ('date', -1)]) df_daily = DataFrame([daily for daily in daily_cursor]) df_daily.set_index('date', inplace=True) # print(df_daily) df_daily['ma5'] = round(df_daily['close'].rolling(5).mean()) df_daily['last_close'] = df_daily['close'].shift(1) df_daily['buy_signal'] = df_daily['close'] > 1.01 * df_daily['ma5'] df_daily['sell_signal'] = df_daily['close'] < df_daily['ma5'] df_daily = df_daily[df_daily['buy_signal'] | df_daily['sell_signal']] record_stock_dict = dict() df_capital = dict() rest_cash = CASH hold_stock_dict = dict() hold_date = set() capital = CASH for date in df_daily.index: price = df_daily.loc[date]['close'] if df_daily.loc[date]['buy_signal']: # price = df_daily.loc[date]['close'] amount = rest_cash / price // 100 * 100 if amount > 0: rest_cash = rest_cash - price * amount hold_stock_dict[date] = {'price': price, 'amount': amount} capital = amount * price + rest_cash hold_date.add(date) print( 'buy, date: %s, price: %5.2f, amount: %8.2f, rest_cash: %10.2f, capital: %10.2f' % (date, price, amount, rest_cash, capital)) if df_daily.loc[date]['sell_signal']: try: if len(hold_date) > 0: amount = 0 for _date in hold_date: amount += hold_stock_dict[_date]['amount'] # amount = CASH / price // 100 * 100 rest_cash = rest_cash + price * amount capital = rest_cash print( 'sell, date: %s, price: %5.2f, amount: %8.2f, rest_cash: %10.2f, capital: %10.2f' % (date, price, amount, rest_cash, capital)) del hold_stock_dict[_date] hold_date.remove(_date) except: traceback.print_exc() dt_date = datetime.strptime(date, '%Y-%m-%d') holding_amount = 0 if len(hold_date) > 0: for _date in hold_date: holding_amount += hold_stock_dict[_date]['amount'] # holding_amount = hold_stock_dict[date]['amount'] df_capital[dt_date] = rest_cash + price * holding_amount else: df_capital[dt_date] = rest_cash df_capital = DataFrame.from_dict(df_capital, orient='index', columns=['capital']) df_capital['profit'] = round((df_capital['capital'] - CASH) / CASH, 2) print(df_capital) df_capital.plot(title='Backtest Result', y=['profit'], kind='line') plt.grid() plt.show()
def evaluate_stock_pool(begin_date, end_date): ''' 评价股票池的的alpha值,基准选为沪深300 ''' # 得到每一期的股票池信息 adjust_dates, codes_dict = stock_pool(begin_date, end_date) # 得到第一组和最后一组的日期 first_phase_date = adjust_dates[0] last_phase_date = adjust_dates[-1] # 计算沪深300的收益率 hs300_begin_value = daily_collection.find_one({ 'code': '000300', 'index': True, 'date': first_phase_date })['close'] df_profit = pd.DataFrame(columns=['profit', 'hs300']) df_profit.loc[datetime.strptime(adjust_dates[0], '%Y-%m-%d')] = { 'profit': 1, 'hs300': 1 } # 计算每一期相对于上一期的收益率 net_value = 1 for _index in range(1, len(adjust_dates) - 1): last_adjust_date = adjust_dates[_index - 1] current_adjust_date = adjust_dates[_index] # 上一期的股票代码 last_phase_codes = codes_dict[last_adjust_date] buy_cursor = daily_collection.find( { 'code': { '$in': last_phase_codes }, 'date': last_adjust_date, 'index': False }, projection={ 'code': True, 'close': True, '_id': False }).hint([('code', 1), ('date', -1)]) code_buy_close_dict = dict() for buy_daily in buy_cursor: code = buy_daily['code'] code_buy_close_dict[code] = buy_daily['close'] sell_cursor = daily_collection.find( { 'code': { '$in': last_phase_codes }, 'date': current_adjust_date, 'index': False }, projection={ 'code': True, 'close': True, '_id': False }).hint([('code', 1), ('date', -1)]) profit_sum = 0 count = 0 for sell_daily in sell_cursor: _code = sell_daily['code'] if _code in code_buy_close_dict: buy_close = code_buy_close_dict[_code] sell_close = sell_daily['close'] profit_sum += (sell_close - buy_close) / buy_close count += 1 if count > 0: profit = round(profit_sum / count, 4) hs300_current_value = daily_collection.find_one({ 'code': '000300', 'index': True, 'date': current_adjust_date })['close'] # 计算净值和累计收益 net_value = net_value * (1 + profit) dt_current_adjust_date = datetime.strptime(current_adjust_date, '%Y-%m-%d') df_profit.loc[dt_current_adjust_date] = { 'profit': round((net_value), 4), 'hs300': round((hs300_current_value - hs300_begin_value) / hs300_begin_value + 1, 4) } # 绘制图片 df_profit.plot(title='Stock Pool Evaluation Result', grid=True, kind='line') plt.show()
def stock_pool(begin_date, end_date): adjust_date_codes_dict = dict() # 调整周期 adjust_interval = 7 all_adjust_dates = [] last_phase_codes = [] # 从数据库中挑选出0<pe<30的股票 all_dates = get_trading_dates(begin_date=begin_date, end_date=end_date) for _index in range(0, len(all_dates), adjust_interval): adjust_date = all_dates[_index] all_adjust_dates.append(adjust_date) print('adjust date: %s' % adjust_date) daily_cursor = daily_collection.find( { 'date': adjust_date, 'pe': { '$gt': 0, '$lt': 30 }, 'is_trading': True, 'index': False }, projection={ 'code': True, '_id': False }, sort=[('pe', ASCENDING)], limit=100) codes = [x['code'] for x in daily_cursor] if codes == []: continue this_phase_codes = [] # 判断是否在调整日停牌 supension_codes = [] if len(last_phase_codes) > 0: supension_cursor = daily_collection.find( { 'code': { '$in': last_phase_codes }, 'date': adjust_date, 'is_trading': False }, projection={ 'code': True }).hint([('code', 1), ('date', -1)]) supension_codes = [x['code'] for x in supension_cursor] this_phase_codes = supension_codes # 判断是否在上一期股票池中 print('last phase code supended in this adjust day:') print(supension_codes) # 得到这一期的股票池 this_phase_codes += codes[0:100 - len(this_phase_codes)] last_phase_codes = this_phase_codes # 建立该调整日和股票列表的对应关系 adjust_date_codes_dict[adjust_date] = this_phase_codes print('this phase codes:') print(this_phase_codes) return all_adjust_dates, adjust_date_codes_dict
def backtest(begin_date, end_date, fun_sell, fun_buy): ''' 回测系统 parameter: fun_sell: 卖出信号 fun_buy: 买入信号函数 ''' # 设置初始值 cash = 1E7 single_positon = 2E5 df_profit = pd.DataFrame(columns=['net_value', 'profit', 'hs300']) # 得到回测日期 all_dates = get_trading_dates(begin_date, end_date) adjust_dates, date_codes_dict = stock_pool(begin_date, end_date) hs300_begin_value = daily_collection.find_one( { 'code': '000300', 'index': True, 'date': adjust_dates[0] }, projection={ 'close': True, '_id': False })['close'] holding_code_dict = dict() last_date = None this_phase_codes = None last_phase_codes = None to_be_bought_codes = set() to_be_sold_codes = set() for _date in all_dates: print('Back test begin 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: # produce_au(before_sell_holding_codes) last_daily_cursor = daily_collection.find( { 'code': { '$in': before_sell_holding_codes }, 'date': last_date }, projection={ 'code': True, '_id': False, 'au_factor': True }).hint([('code', 1), ('date', 1)]) code_last_aufactor_dict = dict() for last_daily in last_daily_cursor: code_last_aufactor_dict[ last_daily['code']] = last_daily['au_factor'] current_daily_cursor = daily_collection.find( { 'code': { '$in': before_sell_holding_codes }, 'date': _date }, projection={ 'code': True, '_id': False, 'au_factor': True }).hint([('code', 1), ('date', 1)]) for current_daily in current_daily_cursor: current_aufactor = current_daily['au_factor'] code = current_daily['code'] before_volume = holding_code_dict[code]['volume'] if code in code_last_aufactor_dict: last_aufactor = code_last_aufactor_dict[code] after_volume = int(before_volume * (current_aufactor / last_aufactor)) holding_code_dict[code]['volume'] = after_volume print( 'hold volume adjust: code: %s, %6d, %10.6f, %6d, %10.6f' % (code, before_volume, last_aufactor, after_volume, current_aufactor)) # 卖出上一期持仓股 # print('to sell stocks: %s' % to_be_sold_codes, flush=True) if len(to_be_sold_codes) > 0: sell_daily_cursor = daily_collection.find( { 'code': { '$in': list(to_be_sold_codes) }, 'date': _date, 'index': True, 'is_trading': True }, projection={ 'open': True, 'code': True, '_id': False }).hint([('code', 1), ('date', -1)]) for sell_daily in sell_daily_cursor: sell_code = sell_daily['code'] if sell_code in before_sell_holding_codes: holding_stock = before_sell_holding_codes[code] sell_price = sell_daily['open'] holding_volume = holding_stock['volume'] sell_amount = holding_volume * sell_price cash += sell_amount cost = holding_stock['cost'] single_profit = (sell_amount - cost) * 100 / cost print('sell: %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) print('cash after sell: %10.2f' % cash) # 买入这一期股票 # print('to buy stocks: ', to_be_bought_codes, flush=True) if len(to_be_bought_codes) > 0: buy_daily_cursor = daily_collection.find( { 'code': { '$in': list(to_be_bought_codes) }, 'date': _date, 'index': False, 'is_trading': True }, projection={ 'code': True, '_id': False, 'open': True }).hint([('code', 1), ('date', -1)]) for buy_daily in buy_daily_cursor: if cash > single_positon: code = buy_daily['code'] buy_price = buy_daily['open'] buy_volume = int( int(single_positon / buy_price) / 100) * 100 buy_amount = buy_price * buy_volume cash -= buy_amount holding_code_dict[code] = { 'volume': buy_volume, 'last_value': buy_amount, 'cost': buy_amount } print('buy %s, %6d, %6.2f, %8.2f' % (code, buy_volume, buy_price, buy_amount)) print('cash after buy: %10.2f' % cash) # 计算收益率 holding_codes = list(holding_code_dict.keys()) if _date in adjust_dates: print('stock pool adjust date: %s' % _date) if this_phase_codes is not None: last_phase_codes = this_phase_codes this_phase_codes = date_codes_dict[_date] # print(this_phase_codes, flush=True) if last_phase_codes is not None: out_codes = 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) for holding_code in holding_codes: if fun_sell(holding_code, _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 fun_buy(_code, _date): to_be_bought_codes.add(_code) # 计算持仓股票市值 total_value = 0 holding_daily_cursor = daily_collection.find( { 'code': { '$in': holding_codes }, 'date': _date, 'index': False }, projection={ 'code': True, '_id': False, 'close': True }).hint([('code', 1), ('date', -1)]) for holding_daily in holding_daily_cursor: code = holding_daily['code'] holding_stock = holding_code_dict[code] value = holding_daily['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('holding stocks: %s, %10.2f, %4.2f, %4.2f' % # (code, value, profit, one_day_profit)) # 计算总资产 total_capital = total_value + cash # 计算基准收益 hs300_current_value = daily_collection.find_one( { 'code': '000300', 'date': _date, 'index': True }, projection={ 'code': True, 'close': True, '_id': False })['close'] print('after close, cash: %10.2f, total_capital: %10.2f' % (cash, total_capital)) dt_date = datetime.strptime(_date, '%Y-%m-%d') df_profit.loc[dt_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) } drawdown = compute_drawdown(df_profit['net_value']) annual_profit, sharpe_ratio = compute_sharpe_ratio(df_profit['net_value']) print( 'Backtest result: %s - %s, annual_profit: %7.3f, maxdrawdown:%7.3f, sharpe ratio: %4.2f' % (begin_date, end_date, annual_profit, drawdown, sharpe_ratio)) df_profit.plot(title='Backtest Result', y=['profit', 'hs300'], kind='line') plt.show()
def pe_computing(codes=None): ''' 利用eps和close计算股票的pe,并保存到mongodb中 ''' # 从finance_report中取出eps if codes is None: codes = get_all_codes() if isinstance(codes, list) is False: codes = [codes] for code in codes: # 从daily中找出close价格 daily_cursor = daily_collection.find({'code': code}, projection={ 'close': True, 'date': True, '_id': False }) update_requests = [] for daily in daily_cursor: date = daily['date'] finance_eps_cursor = finance_report_collection.find_one( { 'code': code, 'report_date': { '$regex': '\d{4}-12-31' }, 'announced_date': { '$lt': date } }, projection={ 'code': True, 'eps': True, "_id": False }, sort=[('announced_date', DESCENDING)]) if finance_eps_cursor is None: continue if date < '2008-01-01': print('have no date in finance_reprot, code: %s' % code) finance_xiaoxiang().crawl_finance_report(code) break # 计算市盈率 eps = 0 if finance_eps_cursor['eps'] != '-': eps = finance_eps_cursor['eps'] if eps != 0: update_requests.append( UpdateOne({ 'code': code, 'date': date }, {'$set': { 'pe': round(daily['close'] / eps, 4) }})) # 将市盈率更新到mongodb中 if len(update_requests) > 0: update_result = daily_collection.bulk_write(update_requests, ordered=False) print('update pe, code: %s, insert: %s, update: %s' % (code, update_result.upserted_count, update_result.modified_count))