def capm(cls, goods, kline): """ 计算Alpha, Beta这两个指标,由于这两个指标的计算比较复杂, 所以在回测过程中不主动计算他们,当用户需要计算这两个指标 时,把verify_XXX函数返回的goods传递给capm就可以计算得出 Alpha, Beta. :param kline: :param goods: :return: """ try: kline = 'index_' + re.search('(min\d+)|(day)', kline, re.I).group() index_data = cls.index_profit(sd=goods.open_date.min(), ed=goods.close_date.max(), kline=kline) index_profit = list() for i, r in goods.iterrows(): idp = index_data[(index_data.date > r['open_date']) & (index_data.date <= r['close_date'])] index_profit.append(idp.profit.sum()) goods['index_profit'] = index_profit menu = goods.copy(deep=True) menu['MC'] = menu.profit * menu.Rr # 边际贡献 if re.search('min', kline, re.I) is not None: menu['close_date'] = menu.close_date.dt.strftime('%Y-%m-%d') menu['close_date'] = pd.to_datetime(menu.close_date, format='%Y-%m-%d') menu = menu.groupby(['close_date'], as_index=False).agg({'MC': 'sum', 'index_profit': 'mean'}) menu = menu.rename(columns={'MC': 'profit', 'close_date': 'date'}) Alpha, Beta = fi.CAPM(menu.profit.tolist(), menu.index_profit.tolist()) return Alpha, Beta, menu except Exception as e: ExceptionInfo(e) return 'N/A', 'N/A', None
def accept(self, callback): try: BroadCast.skt.bind(('', self.PORT)) print(fontcolor.F_GREEN + '-' * 80) print('Listening for broadcast at ', BroadCast.skt.getsockname()) print('Datetime:%s' % dt.datetime.now()) print('-' * 80 + fontcolor.END) except Exception as e: print(fontcolor.F_RED + '-' * 80) print('Broadcast system failed to start. error ' 'code : ' + str(e)) print('-' * 80 + fontcolor.END) sys.exit() # print('Listening for broadcast at ', skt.getsockname()) while True: if BroadCast.status is not True: break print('Waiting for a new broadcast...') data, address = BroadCast.skt.recvfrom(65535) print(fontcolor.F_GREEN + '-' * 80) print('Received broadcast from:{0}, datetime: {1}'.format( address, dt.datetime.now())) print('-' * 80 + fontcolor.END) rec = data.decode('utf-8') try: callback(rec) except Exception as e: ExceptionInfo(e) BroadCast.status = True
def param_optimization(cls, param_table, on='day', order_close=None): """ 暴力参数求解 :param param_table:包含max_pst_vol,max_pst_days,stop_get,stop_loss交易参数组合 的参数表,这是一个df :param on:何种级别的回测 :return: """ if on not in ('day', 'min'): raise ValueError('this "on" must in (day, min)') rls = list() for i, r in param_table.iterrows(): try: # p = dict(max_pst_vol=r.max_pst_vol, max_pst_days=r.max_pst_days, stop_get=r.stop_get, stop_loss=r.stop_loss) cls.modelparammodify(max_pst_vol=r.max_pst_vol, max_pst_days=r.max_pst_days, stop_get=r.stop_get, stop_loss=r.stop_loss) dit, mus, tdr = cls.verify_day( order_close) if on == 'day' else cls.verify_min( order_close) rp = dict(dit, **r) except Exception as e: ExceptionInfo(e) rp = dict(r) finally: rls.append(rp) rls = pd.DataFrame(rls) return rls
def usa_order_close(cls, signal): """ 适用于美股融资融券当日冲销账户(要成为这一类用户,账户余额必须大于$25,000), 执行T+0交易规则,不受交割制度困扰(T+3),但可能会受到资金规模困扰,这与券商相关, 外汇也适用于这种类型的平仓 :param signal: :return: """ try: code = signal['code'] od = signal['open_date'] open_price = signal['open_price'] mark = signal['type'] s = od for d in range(0, 12, 1): if cls.ntf: # 持有时间按交易日计算 # 可能存在不合理的情况,因为目前我们不确定美股市场休市的安排 e = td.trade_period(od, days=d + cls.model_param['max_pst_days'], holidays=[]) else: e = od + datetime.timedelta(days=d + cls.model_param['max_pst_days']) # 持有时间按自然日计算 e = e + datetime.timedelta(minutes=cls.model_param['max_pst_mins']) try: data = cls.func(code, s, e, cls.kline) except Exception: raise TypeError('User-defined function raise a Exception') if len(data): data = data[(data.date >= s) & (data.date <= e)] if len(data): return cls.close(data, mark, open_price) return e, 1, 404, 0 except Exception as ep: ExceptionInfo(ep)
def finally_datetime(cls, date, max_pst_days=1, bsi=False): """ 根据开仓时间和最长持有时间计算买出时间,适用于中国A股股票 :param bsi: False表示按自然日计算持有的时间, True表示按交易日计算 :param date: :param max_pst_days: :return: """ try: time = dt.timedelta(hours=14, minutes=55) start = dt.datetime(date.year, date.month, date.day) if bsi: end = trading.trade_period(start, max_pst_days) return end + time else: end = start + dt.timedelta(days=max_pst_days) if trading.is_trade_day(end): return end + time else: # end 不是交易日 if trading.trade_days(start, end) > 0: # 向前推算 for i in range(1, max_pst_days, 1): end = end - dt.timedelta(days=1) if trading.is_trade_day(end): return end + time else: # 向后推算 for i in range(1, 11, 1): end = start + dt.timedelta(days=i) if trading.is_trade_day(end): return end + time return date except Exception as e: ExceptionInfo(e) return date
def unreplenishment(cls, data, method='local'): """ 将XDXR_day复权后的价格更新至现价 :param method:获取现价的方法:net表示从网络接口获取最新价作为现价 local表示从本地数据库获取。 目前反复权操作指针对中国A股XDXR_day 表,将其close-->现价 :param data: :return: """ try: if method == 'net': real_data = rd.get_stocks_data(list(data.stock_code)) data.drop('close', axis=1, inplace=True) data = pd.merge(data, real_data.loc[:, ['stock_code', 'price']], on='stock_code') data.rename(columns={'price': 'close'}, inplace=True) if method == 'local': data['close'] = data.apply(lambda r: kd.read_one( r['stock_code'], r['date'], 'kline_day')['close'], axis=1) return data except Exception as e: ExceptionInfo(e) return pd.DataFrame()
def real_profit(cls, data, market='ZH'): """ 计算实时收益 :param market: :param data:信号片集,来自于signals,orders...表 :return: """ try: from Calf.data import RealData as rd if market == 'ZH': real_data = rd.get_stocks_data(list(data.stock_code.unique())) elif market == 'US': real_data = rd.usa_stock_data(list(data.stock_code.unique())) elif market == 'HK': real_data = rd.hk_stock_data(list(data.stock_code.unique())) else: raise ValueError('Calf current only support (ZH, US, HK)') if real_data is None: return pd.DataFrame() data = pd.merge(data, real_data, on=['stock_code']) data['price'] = data.price.astype('float') # 实时价格 data['profit'] = (data.price - data.open_price) / data.open_price data.fillna(0, inplace=True) data = data[data.price > 0] # 避免数据没读到,而返回0 return data except Exception as e: ExceptionInfo(e) return pd.DataFrame()
def simple_close(cls, signal): """ 平仓的信息直接由回测的历史信号给出,这样我们在执行历史信号平仓函数的时候可以直接在 历史信号中找到那个关于平仓的信息。当你想要执行这种类型的回测时,所给出的signal则 必须包含额外的close_date,close_price这两个字段 :param signal: :return: """ try: # code = signal['code'] close_date = signal['close_date'] close_price = signal['close_price'] open_price = signal['open_price'] pf = close_price / open_price # d = cls.kd.read_one(code, close_date, cls.kline) # if close_price < d['close']: # # 以卖出指导价卖出 # pf = close_price / open_price # return close_date, pf, 0 # else: # pf = d['close'] / open_price # return close_date, pf, 0 return close_date, pf, 0, close_date pass except Exception as e: ExceptionInfo(e)
def action_end(args): print(fontcolor.F_GREEN + '-' * 80) print('Calf-Note:end task running on ', dt.datetime.now(tz=tz)) print('-' * 80 + fontcolor.END) try: action.end(args=args) except Exception as ep: ExceptionInfo(ep)
def open_kline_update_log(cls): try: with open(project_dir + '/Calf/kline_update_log.json', encoding='utf-8') as file: content = json.load(file) return content except Exception as e: ExceptionInfo(e) return {}
def broading(self, message): try: network = '<broadcast>' BroadCast.skt.sendto(message.encode('utf-8'), (network, self.PORT)) print(fontcolor.F_GREEN + '-' * 80) print('Broadcast success, datetime: ', dt.datetime.now()) print('-' * 80 + fontcolor.END) except Exception as e: ExceptionInfo(e)
def similar_amount(cls, goods, kline, gap=5): """ 计算交易信号发生后gap分钟内发生的交易额,适用于A股 :param goods: :return: """ try: goods['sa'] = np.nan goods['sl'] = np.nan goods['sh'] = np.nan for i, g in goods.iterrows(): sd = datetime.datetime(g.open_date.year, g.open_date.month, g.open_date.day) if g.open_date.hour == 0 and g.open_date.minute == 0: # 日线,特殊处理 tme = {'$in': [1455, 1500]} else: # 日内 if g.open_date.hour == 15: # 日内收盘时的信号 tme = 1500 elif g.open_date.hour == 11 and g.open_date.minute == 30: # 日内上午收盘时的信号 tme = 1305 else: tme = (g.open_date.minute + gap) // 5 dt = sd + datetime.timedelta(hours=g.open_date.hour, minutes=tme * 5) tme = dt.hour * 100 + dt.minute * 5 d = cls.kd.read_data(code=g.code, start_date=sd, end_date=sd, kline=kline, time=tme) if len(d): sa = d.amount.sum() sl = d.low.min() sh = d.high.max() else: sa, sl, sh = np.nan, np.nan, np.nan goods.at[i, ['sa', 'sl', 'sh']] = sa, sl, sh goods['pv'] = pd.eval('(goods.sh - goods.sl) / goods.open_price') menu = goods.copy(deep=True) menu.dropna(axis=0, inplace=True) if len(menu): menu['date'] = menu.close_date.dt.strftime('%Y-%m-%d') menu['date'] = pd.to_datetime(menu.date, format='%Y-%m-%d') menu = menu.groupby(['date'], as_index=False).agg({ 'sa': 'sum', 'pv': 'mean' }) return goods, menu pass except Exception as e: ExceptionInfo(e)
def sound_notice(sound_name): """ 以多线程的方式播放一段音频文件 :param sound_name: :return: """ try: t = threading.Thread(target=play_music, args=(sound_name,)) return t except Exception as e: ExceptionInfo(e)
def timing(cls, func, times): """ 定时任务. 在交易日执行定时任务 :param times: :param func:形如[[10, 0], [10, 30], [11, 0], [11, 30], [13, 30], [14, 0], [14, 30], [15, 0]] 这会使得func在每个交易日的10点、10点30分···执行 :return: """ try: warnings.warn( "The function will be deprecated\n" "Please use \033[91m DScheduler\033[0m instead.", DeprecationWarning, stacklevel=2, ) def merge(h, m): return pd.Timedelta(hours=h, minutes=m) times['time'] = times.apply( lambda r: merge(r['hour'], r['minute']), axis=1) times['log'] = pd.datetime(2018, 1, 1) def tim(tms): while 1: try: crt = dt.datetime.now() if not trading.is_trade_day(crt): print('{0} today is not in business'.format(crt)) time.sleep(60 * 60 * 2) # sleep two hours continue tms['date'] = pd.datetime(crt.year, crt.month, crt.day) + tms.time tm = tms[tms.hour == crt.hour] if len(tm): for i, r in tm.iterrows(): if crt >= r.date != r.log: print('timing task run', r.date, r.log) func() tms.at[i, 'log'] = r.date pass time.sleep(60) else: time.sleep(60 * 60) pass except Exception as ep: ExceptionInfo(ep) t = threading.Thread(target=tim, args=(times, )) t.start() except Exception as e: ExceptionInfo(e)
def trade_days(cls, start, end): """ 给定两个时间,计算这个时间段内有多少个交易日 :param start: :param end: :return: """ try: cal = Calendar(workdays=cls.workdays, holidays=cls.holidays) days = cal.busdaycount(start, end) return days except Exception as e: ExceptionInfo(e) return 0
def is_trade_day(cls, date, holidays=None): """ 判断给定的这个时间是否是交易日(以日记) :param date: 需要判断的时间 :return: """ try: holidays = cls.holidays if holidays is None else holidays cal = Calendar(workdays=cls.workdays, holidays=holidays) flag = cal.isbusday(dt.datetime(date.year, date.month, date.day)) return flag except Exception as e: ExceptionInfo(e) return False
def rrads(cls, date, n): """ 财务报表公布前或后n个交易日提请入市注意 :param date: :return: """ try: aim = trading.trade_period(date, n) lt = {'$gte': aim, '$lte': date} if n < 0 else {'$gte': date, '$lte': aim} rds = BaseData.read_RRADS(last_time=lt) return rds.loc[:, ['stock_code', 'stock_name', 'last_time']] except Exception as e: ExceptionInfo(e) return pd.DataFrame()
def action_end(args): print(fontcolor.F_GREEN + '-' * 80) print('Calf-Note:end task running on ', dt.datetime.now(tz=tz)) print('-' * 80 + fontcolor.END) try: def end(**args): action.end(**args) t = threading.Thread(target=end, args=(args, )) t.start() except Exception as ep: ExceptionInfo(ep)
def trade_period(cls, start, days, holidays=None): """ 计算某个时间x个交易日后的时间,或之前(days为一个负数) :param start: :param days: :return: """ try: holidays = cls.holidays if holidays is None else holidays cal = Calendar(workdays=cls.workdays, holidays=holidays) end = cal.addbusdays(start, days) return end except Exception as e: ExceptionInfo(e) return start
def update_data(self, table_name, condition, **kw): """ 按condition条件更新table_name表数据 :param table_name: :param condition: 形如{‘date':datetime.datetime(2018,1,1)}的一个字典 :param kw:形如close=0这样的参数组 :return: """ try: r = BaseModel(table_name, self.location, self.dbname).update_batch(condition, kw) return r except Exception as e: ExceptionInfo(e) raise MongoIOError('Failed with update by MongoDB')
def action_execute(args): print(fontcolor.F_GREEN + '-' * 80) print('Calf-Note:execute task running on ', dt.datetime.now(tz=tz)) print('-' * 80 + fontcolor.END) try: def exe(**args): action.execute(**args) t = threading.Thread(target=exe, args=(args, )) t.start() t.join(execute_interval - 1) # action.execute(args=args) except Exception as ep: ExceptionInfo(ep)
def read_one(self, table_name, field=None, **kw): """ 有时候只需要读一条数据,没必要使用read_data, :param table_name: :param field: :param kw: :return: a dict or None """ try: cursor = BaseModel(table_name, self.location, self.dbname).query_one(kw, field) except Exception as e: ExceptionInfo(e) cursor = None finally: return cursor
def modelparammodify(cls, **kw): """ 修改基本参数的值 :param kw: :return: """ try: keys = cls.model_param.keys() for k, v in zip(kw.keys(), kw.values()): if k in keys: cls.model_param[k] = v else: message = 'no find this key=%s in models param' % k raise warning(message) except Exception as e: ExceptionInfo(e)
def real_profit(cls, data): """ 计算实时收益 :param data:信号片集,来自于signals表 :return: """ try: real_data = rd.get_stocks_data(list(data.stock_code)) data = pd.merge(data, real_data, on='stock_code') data['price'] = data.price.astype('float') # 实时价格 data['profit'] = (data.price - data.open_price) / data.open_price data.fillna(0, inplace=True) return data except Exception as e: ExceptionInfo(e) return pd.DataFrame()
def hk_stock_data(cls, stock_code): try: if isinstance(stock_code, str): _code = 'hk' + stock_code stock_code = [stock_code] elif isinstance(stock_code, list): _code = ['hk' + c for c in stock_code] _code = ','.join(_code) else: raise TypeError('This stock code type must in (str, list)') html = urlopen('http://hq.sinajs.cn/list={}'.format(_code)).read() data_l = html.decode('gbk').split('\n') i = 0 res = dict() for data in data_l: if len(data): d = data.split('="') key = stock_code[i] res[i] = [stock_code[i]] + d[1][:-2].split(',') i += 1 data = pd.DataFrame(res).T data[18] = data[18] + ' ' + data[19] data = data.loc[:, [0, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 18]] columns = { 0: 'stock_code', 2: 'stock_name', 3: 'open', 4: 'last_close', 5: 'price', 6: 'low', 7: 'high', 9: 'gains', 10: 'B1', 11: 'S1', 12: 'amount', 13: 'volume', 18: 'datetime' } data = data.rename(columns=columns) data['datetime'] = pd.to_datetime(data.datetime) # data['stock_code'] = data.index # print(data.T) return data except Exception as e: ExceptionInfo(e) return None
def finally_datetime(cls, date, max_pst_days=1, max_pst_hour=14, max_pst_min=55, bsi=False, market=None): """ 根据开仓时间和最长持有时间计算买出时间,默认适用于中国A股股票 :param max_pst_min: :param max_pst_hour: :param bsi: False表示按自然日计算持有的时间, True表示按交易日计算 :param date: :param max_pst_days: :param market: 作用的市场 :return: """ try: tra = trading(market) time = dt.timedelta(hours=max_pst_hour, minutes=max_pst_min) start = dt.datetime(date.year, date.month, date.day) if bsi: end = tra.trade_period(start, max_pst_days) return end + time else: end = start + dt.timedelta(days=max_pst_days) if tra.is_trade_day(end): return end + time else: # end 不是交易日 if tra.trade_days(start, end) > 0: # 向前推算 # for i in range(1, max_pst_days, 1): # end = end - dt.timedelta(days=1) # if tra.is_trade_day(end): # return end + time end = tra.trade_period(end, -1) else: # 向后推算 # for i in range(1, 11, 1): # end = start + dt.timedelta(days=i) # if tra.is_trade_day(end): # return end + time end = tra.trade_period(end, 1) return end + time return date except Exception as e: ExceptionInfo(e) return date
def set_kline_update_log(cls, self, **kw): try: keys = self.keys() for k, v in zip(kw.keys(), kw.values()): if k in keys: self[k] = v else: print(WarningMessage('not find this key in original file')) self['datetime'] = dt.datetime.strftime(dt.datetime.today(), '%Y-%m-%d %H:%M:%S') with open(project_dir + '/Calf/kline_update_log.json', 'w') as file: file.write(json.dumps(self)) return self except Exception as e: ExceptionInfo(e) return self
def read_account_info(cls, **kw): """ 读取账户-订阅 :return: """ try: cursor = MODEL_TABLE(cls.location, cls.dbname, 'accounts').query(kw) if cursor.count(): data = pd.DataFrame(list(cursor)) return data else: return pd.DataFrame() except Exception as e: ExceptionInfo(e) return pd.DataFrame() pass
def read_one(cls, code, date, kline, **kw): """ 读取某只代码的某个时间点的K线数据,这个功能在read_data中也可以完成, 但是这里单独实现是为了更好的性能。 :param code: :param date: :param kline: :return:返回的是一个字典{} """ try: sql = dict(stock_code=code, date=date) sql = dict(sql, **kw) cursor = KLINE_MODEL_TABLE(cls.location, cls.dbname, kline).query_one(sql) return cursor except Exception as e: ExceptionInfo(e) return None
def aggregate(self, table_name, pipeline): """ :param table_name: :param pipeline: a list, 每一个元素相当于一个管道操作,常见的操作包括 匹配('$match')、属性域选择('$project') :return: """ try: cursor = BaseModel(table_name, self.location, self.dbname).aggregate(pipeline) # data = pd.DataFrame() # if cursor.count(): data = pd.DataFrame(list(cursor)) cursor.close() return data except Exception as e: ExceptionInfo(e) return pd.DataFrame()