def query(self, qname, tscode_str='', **kwargs): exchangeid = self.parseCode(tscode_str, seg='exch') cryptoSymbol = self.parseCode(tscode_str, seg='pair') if qname == 'ohlcv': return self.dct_exchanges[exchangeid].fetch_ohlcv( symbol=cryptoSymbol, limit=5000, timeframe='1d', **kwargs) elif qname == 'tickers': return self.dct_exchanges[exchangeid].fetch_tickers( symbols=cryptoSymbol) elif qname == 'load_alld': # 读取本地已存储的csv格式历史信息 fname = '{}{}.csv'.format(cfg.PATH_CPTFILE, tscode_str) if os.path.isfile(fname): df_result = pd.read_csv(fname, encoding=cfg.FILE_ENCODE).astype( dtype={'trade_date': 'int64'}) df_result = df_result if kwargs[ 'tpl_dateindex'] is None else df_result[ df_result['trade_date'].isin(kwargs['tpl_dateindex'])] return df_result else: libs.log_csv( str_cat='WARNING', str_op='query', str_desc='WARNING! no csv file found for {}'.format( tscode_str)) return False else: return False
def __init__(self,ts_code,heartbeat=None,tags=[]): self.ts_code = ts_code self.tags = tags # can label a sec with a chain of tags at run time so that they can be filtered self.cursorBar = None self.histBars = None self.allBars = None self.mchart = None self.tradeunit = 0 # 0代表最低可买一股,-2代表100(1手)-3代表1000 self.commission = Decimal('0') self.basicCols = ['ts_code','timeAxis','unit_intdate','trade_date','open','high','low','close','vol', 'sec_type','sec_market','candle_size','box_size','upper_stick','lower_stick','MA_S','MA_M','MA_L', 'MA_Y','VolMA_S','VolMA_M','VolMA_L'] self._dataagent = DataAgent_Factory.singleton() # expecting dataagent object to be a singleton self._heartbeat = heartbeat self._cursorTick = None # should be a str or a row in calendar table self.anchorObj = None # to hold underlying anchor security object targeted = self._dataagent.registry.loc[self._dataagent.registry['ts_code']==ts_code] if self.ts_code=='cash': self.exists = False self.sec_name = 'cash' self.sec_type = 'cash' self.ts_market = '' elif len(targeted)==0: self.exists = False libs.log_csv(str_cat='WARNING', str_op='Security initiation', str_desc='{} does not exist in registry'.format(self.ts_code)) raise Exception('{} does not exist in registry'.format(self.ts_code)) else: self.exists = True self.sec_name = targeted['name'].values[0] self.sec_type = targeted['sec_type'].values[0] self.ts_market = targeted['sec_market'].values[0] # used to, e.g. capture the underlying index of an ETF fund self.anchor_tscode = None if targeted['anchor'].isnull().values.any() else targeted['anchor'].values[0]
def get_livebar(self, assign_timestamp, assign_unitdate=None): df_liveQuote = self._dataagent.query('quote_now', [ self.ts_code, ]) if len(df_liveQuote) > 0: df_liveQuote.rename(columns={ 'open': 'open', 'high': 'high', 'low': 'low', 'price': 'close', 'volume': 'vol', 'amount': 'amount' }, inplace=True) df_liveQuote = df_liveQuote.astype( dtype={ 'open': 'float', 'high': 'float', 'low': 'float', 'close': 'float', 'vol': 'float', 'amount': 'float' }) # all self.basicCols need to be normalised here, extendCols to be normalised in child class df_liveQuote['timeAxis'] = assign_timestamp # unit_intdate 和timestamp有可能不是同一日,如周六时timeAxis是周六的某一时间而unit_intdate应是下一交易日即下周一日期,因此此处应对liveQuote设置之后再append allBar df_liveQuote['unit_intdate'] = int( datetime.strftime( assign_timestamp, '%Y%m%d')) if assign_unitdate is None else assign_unitdate df_liveQuote['trade_date'] = df_liveQuote['date'].str.replace( '-', '', regex=False).astype(int) df_liveQuote['ts_code'] = self.ts_code df_liveQuote['vol'] = df_liveQuote[ 'vol'] / 100 if self.sec_type == 'stk' or self.ts_code[:3] == '399' else df_liveQuote[ 'vol'] df_liveQuote['amount'] = df_liveQuote['amount'] / 1000 df_liveQuote['sec_type'] = self.sec_type df_liveQuote['sec_market'] = self.ts_market # 凡是需要前一bar数据配合计算的此处不做处理 df_liveQuote['candle_size'] = np.nan df_liveQuote['box_size'] = np.nan df_liveQuote['upper_stick'] = np.nan df_liveQuote['lower_stick'] = np.nan df_liveQuote['MA_S'] = np.nan df_liveQuote['MA_M'] = np.nan df_liveQuote['MA_L'] = np.nan df_liveQuote['MA_Y'] = np.nan df_liveQuote['VolMA_S'] = np.nan df_liveQuote['VolMA_M'] = np.nan df_liveQuote['VolMA_L'] = np.nan return df_liveQuote else: libs.log_csv(str_cat='WARNING', str_op='get_livebar method', str_desc='{} query returned none result'.format( self.ts_code)) return None
def __init__(self,ts_code,heartbeat=None,tags=[]): super().__init__(ts_code,heartbeat=heartbeat,tags=tags) self.kchart = None self.exists = False if self.sec_type != 'idx' else self.exists self.tradeunit = 0 self.commission = Decimal('0') if self.exists == False: libs.log_csv(str_cat='WARNING', str_op='Security initiation', str_desc='{} does not exist in registry'.format(self.ts_code)) raise Exception('{} no such index in registry'.format(self.ts_code)) else: self.extendCols = [*self.basicCols,'pre_close','amount'] self.ts_market = self.ts_market self.allBars = self._get_all_bars()
def sync_repo(self): libs.log_csv(str_cat='INFO', str_op='sync', str_desc='Tsaisendo crypto data sync job started...') ''' 遍历给定code列表并同步本地存储数据 ''' for str_tscrypto in self.symbol_list: str_paircode = self.parseCode(str_tscrypto, seg='pair') fname = '{}{}.csv'.format(cfg.PATH_CPTFILE, str_tscrypto) writeMode = 'a' if os.path.isfile(fname): int_lastRecDate = int( pd.read_csv( fname, encoding=cfg.FILE_ENCODE)[-1:].iloc[0]['exch_timeid']) result = self.query(qname='ohlcv', tscode_str=str_tscrypto, since=int_lastRecDate + 1) else: result = self.query(qname='ohlcv', tscode_str=str_tscrypto, since=0) writeMode = 'w' if len(result) > 0: df_result = pd.DataFrame(result, columns=[ 'exch_timeid', 'open', 'high', 'low', 'close', 'volume' ]) df_result['ts_code'] = str_tscrypto # df_result['trade_date'] = (df_result['exch_timeid']/1000).astype('int').astype('datetime64[s]').dt.strftime("%Y-%m-%d %H:%M:%S") df_result['trade_date'] = ( df_result['exch_timeid'] / 1000).astype('int').astype( 'datetime64[s]').dt.strftime("%Y%m%d%H%M%S") libs.df_csv(fname, (df_result, ), writeMode) libs.log_csv(str_cat='INFO', str_op='sync', str_desc='{} crypto file syncronized...'.format( str_tscrypto)) else: libs.log_csv( str_cat='INFO', str_op='sync', str_desc='{} crypto file skipped...'.format(str_tscrypto)) libs.log_csv(str_cat='INFO', str_op='sync', str_desc='Tsaisendo crypto data sync job ended...')
def pollPulse(self): libs.log_csv(str_cat='INFO', str_op='poll', str_desc='Tsaisendo polling job started...') # 定义历史数据留多久 str_oldestViewDateTime = str( self.da.int_findOffsetDate(self.int_pollPulseDate, -21)) + '000000' df_indexpoll = pd.DataFrame() df_stockpoll = pd.DataFrame() ''' 遍历所有时间点并请求最新数据''' for beat in self.pollPulseRange: while True: timecheck = datetime.now().astimezone( timezone(cfg.STR_TIMEZONE)).replace(tzinfo=None) if timecheck.replace(second=0, microsecond=0) > beat: libs.log_csv( str_cat='INFO', str_op='poll', str_desc= 'WARNING! {}/{} - {}:{} heartbeat dropped at {} due to happening too late...' .format( beat.month, beat.day, beat.hour, beat.minute, datetime.now().astimezone( timezone(cfg.STR_TIMEZONE)))) break elif timecheck.replace(second=0, microsecond=0) == beat: libs.log_csv(str_cat='INFO', str_op='poll', str_desc='poll time...{}'.format(timecheck)) df_index = self.da.query('allindex_now') df_stock = self.da.query('allstock_now') # data_store = libs.tryOpenH5(cfg.H5_FILE_POLL) if len(df_index) > 0: df_index['time_index'] = int( datetime.strftime(beat, '%Y%m%d%H%M%S')) df_index['candle_size'] = round( (df_index['high'] - df_index['low']) / df_index['pre_close'], 3) df_index['box_size'] = round( (df_index['close'] - df_index['open']) / df_index['pre_close'], 3) df_index['upper_stick'] = round( (df_index['high'] - df_index[['open', 'close']].max(axis=1)) / df_index['pre_close'], 3) df_index['lower_stick'] = round( (df_index[['open', 'close']].min(axis=1) - df_index['low']) / df_index['pre_close'], 3) binRange0 = [0, 0.005, 0.010, 0.016, float('inf')] # 指数类的尺寸划分标准 df_index['klineSML'] = '' df_index.loc[df_index['sec_type'] == 0, ['klineSML']] = DataAgent.classifyKline( df_index, DataAgent.binRange0, 0) df_index.to_sql('index_poll', DataAgent.dbsession.bind, if_exists='append', index=False) # data_store.append('indexPoll', df_index,format='t') if len(df_stock) > 0: df_stock['time_index'] = int( datetime.strftime(beat, '%Y%m%d%H%M%S')) df_stock['candle_size'] = round( (df_stock['high'] - df_stock['low']) / df_stock['pre_close'], 3) df_stock['box_size'] = round( (df_stock['close'] - df_stock['open']) / df_stock['pre_close'], 3) df_stock['upper_stick'] = round( (df_stock['high'] - df_stock[['open', 'close']].max(axis=1)) / df_stock['pre_close'], 3) df_stock['lower_stick'] = round( (df_stock[['open', 'close']].min(axis=1) - df_stock['low']) / df_stock['pre_close'], 3) binRange1 = [0, 0.01, 0.033, 0.066, float('inf')] # 股票类的尺寸划分标准 df_stock['klineSML'] = '' df_stock.loc[df_stock['sec_type'] == 1, ['klineSML']] = DataAgent.classifyKline( df_stock, DataAgent.binRange1, 1) '''df_stock.to_sql('stock_poll', DataAgent.dbsession.bind, if_exists='append',index=False)''' # data_store.append('stockPoll', df_stock,format='t') # data_store.close() libs.log_csv( str_cat='INFO', str_op='poll', str_desc='[{}] index and [{}] stock data acquired'. format(len(df_index), len(df_stock))) break else: libs.log_csv( str_cat='INFO', str_op='poll', str_desc= 'waiting {} seconds until next poll heartbeat - {}'. format(libs.datediff_insec(timecheck, beat), beat)) time.sleep(libs.datediff_insec(timecheck, beat) + 1) ''' 删除掉过旧的数据''' # 创建储存对象,将 DataFrame 放进对象中,并设置 key '''DataAgent.dbsession.query(Stock_poll).filter(Stock_poll.time_index<int(str_oldestViewDateTime)).delete() DataAgent.dbsession.query(Index_poll).filter(Index_poll.time_index<int(str_oldestViewDateTime)).delete()''' ''' data_store = libs.tryOpenH5(cfg.H5_FILE_POLL,mode='a') try: df_toRemove = data_store['indexPoll'][data_store['indexPoll']['time_index']<=int(str_oldestViewDateTime)] if len(df_toRemove)>0: df_toKeep = data_store['indexPoll'][data_store['indexPoll']['time_index']>int(str_oldestViewDateTime)] data_store.put('indexPoll', df_toKeep,format='t') libs.log_csv(str_cat='INFO',str_op='poll',str_desc='{} rows of old index data removed from offline data'.format(len(df_toRemove))) except Exception as e: data_store.put('indexPoll', df_indexpoll,format='t') libs.log_csv(str_cat='INFO',str_op='poll',str_desc='{} ---- indexPoll dataset doesnot exist in datastore, new one created'.format(e.__class__.__name__)) try: df_toRemove = data_store['stockPoll'][data_store['stockPoll']['time_index']<=int(str_oldestViewDateTime)] if len(df_toRemove)>0: df_toKeep = data_store['stockPoll'][data_store['stockPoll']['time_index']>int(str_oldestViewDateTime)] data_store.put('stockPoll', df_toKeep,format='t') libs.log_csv(str_cat='INFO',str_op='poll',str_desc='{} rows of old stock ata removed from offline data'.format(len(df_toRemove))) except Exception as e: data_store.put('stockPoll', df_indexpoll,format='t') libs.log_csv(str_cat='INFO',str_op='poll',str_desc='{} ---- stockPoll dataset doesnot exist in datastore, new one created'.format(e.__class__.__name__)) print(data_store.info()) data_store.close() ''' libs.log_csv(str_cat='INFO', str_op='poll', str_desc='Tsaisendo polling job ended...')
def candlechart(self,str_tscode=None,subset='masterCandle',dat_cursor=None,MA=[5,10,21], renderfile=False) -> Grid: str_tscode = DataAgent.formatCode(str_tscode) int_chartsize = 500 if str_tscode == False or str_tscode is None: print('invalid sec code provided...') return False dat_cursor = datetime.now().astimezone(timezone(cfg.STR_TIMEZONE)) if dat_cursor is None else dat_cursor closestTradeDate = self.da.int_findClosestTradeDate(datetime.strftime(dat_cursor,'%Y%m%d') ) tpl_dateindex = tuple(str(i) for i in self.da.df_calTbl.loc[self.da.df_calTbl['cal_date'] <= closestTradeDate]['cal_date'][-int_chartsize:] ) # 连接储存对象 if subset=='masterCandle': data_store = libs.tryOpenH5(cfg.H5_FILE_PRE,mode='r') else: data_store = libs.tryOpenH5('{}{}.dat'.format(cfg.H5_FILE_PATH,subset),mode='r') # 初始化交易日表格,作为标准交易日序列join个股交易数据 df_toPlot = data_store['masterCandle'].loc[data_store['masterCandle']['ts_code']==str_tscode] df_dateindex = pd.DataFrame({'trade_caldate': tpl_dateindex}).astype(int) data_store.close() try: str_secname = self.da.df_lookupTbl.loc[self.da.df_lookupTbl['ts_code']==str_tscode]['name'].values[0] str_sectype = self.da.df_lookupTbl.loc[self.da.df_lookupTbl['ts_code']==str_tscode]['sec_type'].values[0] except Exception as e: return libs.log_csv(str_cat='WARNING',str_op='candlechart',str_desc='security code {} does not exist in basic info table...'.format(str_tscode)) str_plotname = '{} - {}'.format(str_tscode,str_secname) # 交易日时间轴标准化,补全停牌日,当日live数据等k线 '''------ 获取实时交易数据部分--------------------''' int_liveDate = self.da.int_findClosestTradeDate(datetime.strftime(datetime.now().astimezone(timezone(cfg.STR_TIMEZONE)),'%Y%m%d') ) if int_liveDate<=closestTradeDate and int_liveDate>df_toPlot['trade_date'].max(): df_liveQuote = self.da.query('quote_now',[self.da.formatCode(str_tscode,False),]) if len(df_liveQuote)>0: df_liveQuote.rename(columns={'open':'adj_open', 'high':'adj_high', 'low':'adj_low', 'price':'adj_close', 'volume':'vol', 'amount':'amount'}, inplace=True) df_liveQuote=df_liveQuote.astype(dtype= {'adj_open':'float','adj_high':'float','adj_low':'float','adj_close':'float','vol':'float','amount':'float'}) df_liveQuote['trade_date'] = int_liveDate df_liveQuote['ts_code'] = str_tscode df_liveQuote['vol'] = df_liveQuote['vol']/100 if str_sectype==1 or str_tscode[:3]=='399' else df_liveQuote['vol'] df_liveQuote['amount'] = df_liveQuote['amount']/1000 df_toPlot = df_toPlot.append(df_liveQuote[['ts_code','trade_date','adj_open','adj_high','adj_low','adj_close','vol','amount']],sort=False) '''------ 结束 获取实时交易数据部分--------------------''' df_toPlot = pd.merge(df_dateindex, df_toPlot, left_on='trade_caldate', right_on='trade_date', how='left') df_toPlot['close'].fillna(method='ffill',inplace=True) df_toPlot['ts_code'].fillna(method='ffill',inplace=True) df_toPlot['klineSML'].fillna('NNNN',inplace=True) df_toPlot['adj_close'].fillna(method='ffill',inplace=True) df_toPlot['vol'].fillna(value=0,inplace=True) df_toPlot['amount'].fillna(value=0,inplace=True) df_toPlot['open'].fillna(df_toPlot['close'],inplace=True) df_toPlot['high'].fillna(df_toPlot['close'],inplace=True) df_toPlot['low'].fillna(df_toPlot['close'],inplace=True) df_toPlot['adj_open'].fillna(df_toPlot['adj_close'],inplace=True) df_toPlot['adj_high'].fillna(df_toPlot['adj_close'],inplace=True) df_toPlot['adj_low'].fillna(df_toPlot['adj_close'],inplace=True) self.kchartdf = df_toPlot #输出至可访问对象属性中 '''-----------------画图部分---------------------''' lst_ohlcv=df_toPlot.loc[:,['adj_open','adj_close','adj_low','adj_high']].values.tolist() lst_vol=list(df_toPlot['vol'].values) lst_amount=list(df_toPlot['amount'].values) lst_peaks=list(abs(df_toPlot['peaks'].values)) # lst_pivotdown=[float('nan') if i<0 else i for i in list(df_toPlot['pivots'].values)] lst_pivotdown=[float('nan') if i<0 else i for i in list(df_toPlot['valid_pivots'].values)] # lst_pivotup=[float('nan') if i>0 else abs(i) for i in list(df_toPlot['pivots'].values)] lst_pivotup=[float('nan') if i>0 else abs(i) for i in list(df_toPlot['valid_pivots'].values)] lst_xaxis=list(df_toPlot['trade_caldate'].astype(str)) def calculate_ma(day_count: int, d): result: List[Union[float, str]] = [] for i in range(len(d)): if i < day_count: result.append("-") continue sum_total = 0.0 for j in range(day_count): sum_total += float(d[i - j][1]) result.append(abs(float("%.3f" % (sum_total / day_count)))) return result kline = (Kline() .add_xaxis(lst_xaxis) .add_yaxis(series_name=str_secname, y_axis=lst_ohlcv, markpoint_opts=opts.MarkPointOpts( data=[opts.MarkPointItem(type_="min",value_dim="close"), opts.MarkPointItem(type_="max",value_dim="close")], symbol_size = [20, 20], #表示标记宽为 20,高为 10 ), itemstyle_opts=opts.ItemStyleOpts( color="#ec0000", color0="#00da3c", border_color="#ec0000", border_color0="#00da3c", ),) .set_global_opts( title_opts=opts.TitleOpts( title=str_plotname, subtitle='MA='+str(MA), ), xaxis_opts=opts.AxisOpts(type_="category"), yaxis_opts=opts.AxisOpts( is_scale=True, splitarea_opts=opts.SplitAreaOpts( is_show=True, areastyle_opts=opts.AreaStyleOpts(opacity=1) ), ), legend_opts=opts.LegendOpts(is_show=True, pos_top='10%', pos_left="center"), datazoom_opts=[ opts.DataZoomOpts( is_show=False, type_="inside", xaxis_index=[0,1,2], range_start=75, range_end=100, ), opts.DataZoomOpts( is_show=True, xaxis_index=[0,1,2], type_="slider", pos_top="90%", range_start=75, range_end=100, ), ], tooltip_opts=opts.TooltipOpts( trigger="axis", axis_pointer_type="cross", background_color="rgba(245, 245, 245, 0.8)", border_width=1, border_color="#ccc", textstyle_opts=opts.TextStyleOpts(color="#000",font_size=10), ), # 阴量绿阳量红 visualmap_opts=opts.VisualMapOpts( is_show=False, dimension=2, series_index=list(map(lambda x: x+len(MA),[4,5])), #动态算出vol和amt柱状图的series index is_piecewise=True, pieces=[ {"value": 1, "color": "#ec0000"}, {"value": -1, "color": "#00da3c"}, ], ), axispointer_opts=opts.AxisPointerOpts( is_show=True, link=[{"xAxisIndex": "all"}], label=opts.LabelOpts(background_color="#777"), ), brush_opts=opts.BrushOpts( x_axis_index="all", brush_link="all", out_of_brush={"colorAlpha": 0.1}, brush_type="lineX", ), ) ) trendline = ( Line() .add_xaxis(lst_xaxis) .add_yaxis('高低点', lst_peaks, itemstyle_opts=opts.ItemStyleOpts(color="green")) ) for i in MA: if i is not None: trendline.add_yaxis( series_name='MA'+str(i), y_axis=calculate_ma(day_count=i, d=lst_ohlcv), is_smooth=True, is_hover_animation=False, linestyle_opts=opts.LineStyleOpts(width=1, opacity=0.5), label_opts=opts.LabelOpts(is_show=False), ) trendline.set_series_opts(label_opts=opts.LabelOpts(is_show=False)) trendline.set_global_opts(legend_opts=opts.LegendOpts(is_show=False)) keyPoints = ( EffectScatter() .add_xaxis(lst_xaxis) .add_yaxis("末跌高", lst_pivotdown,symbol=SymbolType.ARROW,symbol_rotate=180,symbol_size=5,itemstyle_opts=opts.ItemStyleOpts(color="purple")) .add_yaxis("末升低", lst_pivotup,symbol=SymbolType.ARROW,symbol_size=5,itemstyle_opts=opts.ItemStyleOpts(color="blue")) .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) ) vol_bar = ( Bar() .add_xaxis(lst_xaxis) .add_yaxis( series_name="交易量", yaxis_data=[ [i, lst_vol[i], 1 if lst_ohlcv[i][0] < lst_ohlcv[i][1] else -1] for i in range(len(lst_vol)) ], xaxis_index=1, yaxis_index=1, label_opts=opts.LabelOpts(is_show=False), ) .set_global_opts( xaxis_opts=opts.AxisOpts( type_="category", is_scale=True, grid_index=1, boundary_gap=False, axisline_opts=opts.AxisLineOpts(is_on_zero=False), axistick_opts=opts.AxisTickOpts(is_show=False), splitline_opts=opts.SplitLineOpts(is_show=False), axislabel_opts=opts.LabelOpts(is_show=False), split_number=20, min_="最低", max_="最高", ), yaxis_opts=opts.AxisOpts( grid_index=1, is_scale=True, split_number=2, axislabel_opts=opts.LabelOpts(is_show=False), axisline_opts=opts.AxisLineOpts(is_show=False), axistick_opts=opts.AxisTickOpts(is_show=False), splitline_opts=opts.SplitLineOpts(is_show=False), ), legend_opts=opts.LegendOpts(is_show=False), ) # .add_yaxis("交易量", lst_vol,itemstyle_opts=opts.ItemStyleOpts(color="#456A76")) ) amnt_bar = ( Bar() .add_xaxis(lst_xaxis) .add_yaxis( series_name="交易额", yaxis_data=[ [i, lst_amount[i], 1 if lst_ohlcv[i][0] < lst_ohlcv[i][1] else -1] for i in range(len(lst_amount)) ], xaxis_index=2, yaxis_index=2, label_opts=opts.LabelOpts(is_show=False), ) .set_global_opts( xaxis_opts=opts.AxisOpts( type_="category", is_scale=True, grid_index=2, boundary_gap=False, axisline_opts=opts.AxisLineOpts(is_on_zero=False), axistick_opts=opts.AxisTickOpts(is_show=False), splitline_opts=opts.SplitLineOpts(is_show=False), axislabel_opts=opts.LabelOpts(is_show=False), split_number=20, min_="最低", max_="最高", ), yaxis_opts=opts.AxisOpts( grid_index=2, is_scale=True, split_number=2, axislabel_opts=opts.LabelOpts(is_show=False), axisline_opts=opts.AxisLineOpts(is_show=False), axistick_opts=opts.AxisTickOpts(is_show=False), splitline_opts=opts.SplitLineOpts(is_show=False), ), legend_opts=opts.LegendOpts(is_show=False), ) # .add_yaxis("交易额", lst_amount,itemstyle_opts=opts.ItemStyleOpts(color="#456A76")) ) priceChart = kline.overlap(trendline).overlap(keyPoints) gridChart = Grid() gridChart.add( priceChart, grid_opts=opts.GridOpts(pos_left="10%", pos_right="8%", pos_bottom='40%'), ) gridChart.add( vol_bar, grid_opts=opts.GridOpts(pos_left="10%", pos_right="8%", pos_top="60%", height='15%'), ) gridChart.add( amnt_bar, grid_opts=opts.GridOpts(pos_left="10%", pos_right="8%", pos_top="75%"), ) fname = '{}{}.html'.format(cfg.PATH_ANAFILE,'kline') gridChart.render(fname) if renderfile else None self.kchart = gridChart # 将结果输出到analytics对象属性中用于function以外的调用 return self