def _stat_avg_bar(self, df): """in out之间平均bar数""" bar_sum = 0 # in out 总次数 trade_sum = 0 win = 0 # TODO:放在metrics stack = [] df['trading_datetime'] = pd.to_datetime(df['trading_datetime']) trade_list = df.sort_values('trading_datetime').to_dict( orient='records') frequency = Environment.get_instance().config.base.frequency * 60 for t in trade_list: if not stack: stack.append(t) else: old_trade = stack[-1] old_side = old_trade['side'] new_side = t['side'] amount_cond = old_trade['amount'] == t['amount'] if amount_cond and old_side == const.SIDE.BUY.name and new_side == const.SIDE.SELL.name: old_trade = stack.pop() diff_time = t['trading_datetime'] - old_trade[ 'trading_datetime'] diff_bar = diff_time.seconds / frequency trade_sum += 1 bar_sum += diff_bar win += 1 if old_trade['price'] < t['price'] else 0 # 还有其他判定条件 else: stack.append(t) if trade_sum == 0: return 0, len(stack), 0 return round(bar_sum / trade_sum), len(stack), win / trade_sum
def annualized_returns(self): """ 年化收益率 """ current_date = Environment.get_instance().trading_dt.date() return self.unit_net_value**(DAYS_A_YEAR / float( (current_date - self.start_date.date()).days + 1)) - 1
def formatTime(self, record, datefmt=None): from quant.environment import Environment try: dt = Environment.get_instance().calendar_dt.strftime(datefmt) except Exception: dt = datetime.now().strftime(datefmt) return dt
def _on_bar(self, event): if len(self.positions) == 0: instrument = Environment.get_instance().get_instrument( self.benchmark) price = event.bar_dict[self.benchmark].close position = self.positions[self.benchmark] amount = self.total_cash / price position.amount = amount * (1 - instrument.fee) position.buy_price = price self.total_cash -= price * amount
def register_event(self): event_bus = Environment.get_instance().event_bus # 仓位控制 event_bus.prepend_listener(EVENT.TRADE, self._on_trade) event_bus.add_listener(EVENT.ORDER_PENDING_NEW, self._on_order_pending_new) event_bus.add_listener(EVENT.ORDER_CANCELLATION_PASS, self._on_order_cancel) event_bus.add_listener(EVENT.ORDER_UNSOLICITED_UPDATE, self._on_order_cancel) event_bus.add_listener(EVENT.ORDER_CREATION_REJECT, self._on_order_cancel) # 结算 event_bus.add_listener(EVENT.SETTLEMENT, self._on_settlement)
def __init__(self): self.order_id = None self.create_dt = None self.trade_dt = None self.symbol = None self.price = None self.amount = None self.fill_amount = None self.fee = None self.side = None self.type = None self.status = None self.message = None self.env = Environment.get_instance()
def create_trade(cls, order_id, symbol, side, price=0., frozen_price=0., amount=0., fee=0.): env = Environment.get_instance() trade = cls() trade.trade_id = next(env.id) trade.order_id = order_id trade.create_dt = env.calendar_dt trade.trade_dt = env.trading_dt trade.symbol = symbol trade.side = side trade.fee = fee trade.price = price trade.frozen_price = frozen_price trade.amount = amount return trade
def create_order(cls, symbol, price=0., amount=0., side=None, style=None): env = Environment.get_instance() symbol = symbol or env.config.base.symbol instrument = env.get_instrument(symbol) order = cls() order.order_id = next(env.id) order.create_dt = env.calendar_dt order.trade_dt = env.trading_dt order.symbol = symbol order.price = price order.amount = amount if order.amount < instrument.min_amount: raise ValueError('order amount should > min_amount') order.fill_amount = 0. order.fee = instrument.fee order.side = side order.type = style order.status = ORDER_STATUS.PENDING_NEW order.message = "" return order
def run(config, kwargs): user_funcs = { 'init': kwargs.init, 'handle_bar': kwargs.handle_bar, 'before_trading': kwargs.before_trading, 'after_trading': kwargs.after_trading, } env = Environment(config) mod_handler = ModHandler(env) mod_handler.start() _adjust_env(env) try: context = Context() env.event_bus.publish_event(Event(EVENT.POST_SYSTEM_INIT)) strategy = Strategy(env.event_bus, user_funcs, context) strategy.init() Executor(env).run() result = mod_handler.stop(const.EXIT_CODE.EXIT_SUCCESS) return result except Exception as e: sys_log.error(util.error_msg())
def portfolio(self): """ 投资组合 ========================= ========================= ============================================================================== 属性 类型 注释 ========================= ========================= ============================================================================== accounts dict 账户字典 start_date datetime.datetime 策略投资组合的回测/实时模拟交易的开始日期 units float 份额 unit_net_value float 净值 daily_pnl float 当日盈亏,当日盈亏的加总 daily_returns float 投资组合每日收益率 total_returns float 投资组合总收益率 annualized_returns float 投资组合的年化收益率 total_value float 投资组合总权益 positions dict 一个包含所有仓位的字典,以symbol作为键,position对象作为值 cash float 总的可用资金 market_value float 投资组合当前的市场价值,为子组合市场价值的加总 ========================= ========================= ============================================================================== :property getter: :class:`~Portfolio` """ return Environment.get_instance().portfolio
def last_price(self): """最新价""" return (self._last_price if self._last_price == self._last_price else Environment.get_instance().get_last_price(self.symbol))
def generate_echart(result_dict=None, show_windows=True, savefile=None): summary = result_dict["summary"] portfolio = result_dict["portfolio"] benchmark_portfolio = result_dict.get("benchmark_portfolio") x_axis = portfolio.index # maxdrawdown portfolio_value = portfolio.unit_net_value * portfolio.start_cash xs = portfolio_value.values rt = portfolio.unit_net_value.values max_dd_end = np.argmax(np.maximum.accumulate(xs) / xs) if max_dd_end == 0: max_dd_end = len(xs) - 1 max_dd_start = np.argmax(xs[:max_dd_end]) if max_dd_end > 0 else 0 # maxdrawdown duration al_cum = np.maximum.accumulate(xs) a = np.unique(al_cum, return_counts=True) start_idx = np.argmax(a[1]) m = a[0][start_idx] al_cum_array = np.where(al_cum == m) max_ddd_start_day = al_cum_array[0][0] max_ddd_end_day = al_cum_array[0][-1] max_dd_info = "MaxDD {}~{}, {} days".format( x_axis[max_dd_start], x_axis[max_dd_end], (x_axis[max_dd_end] - x_axis[max_dd_start]).days) max_dd_info += "\nMaxDDD {}~{}, {} days".format( x_axis[max_ddd_start_day], x_axis[max_ddd_end_day], (x_axis[max_ddd_end_day] - x_axis[max_ddd_start_day]).days) red = "#aa4643" blue = "#4572a7" black = "#000000" font_size = 13 value_font_size = 12 label_height, value_height = 10, 40 label_height2, value_height2 = 60, 90 title = [] fig_data = [ ('15%', label_height, value_height, u"收益率", "{0:.3%}".format(summary["total_returns"]), red, black), ('15%', label_height2, value_height2, u"基准收益率", "{0:.3%}".format(summary.get("benchmark_total_returns", 0)), blue, black), ('22%', label_height, value_height, u"年化收益率", "{0:.3%}".format(summary["annualized_returns"]), red, black), ('22%', label_height2, value_height2, u"基准年化收益率", "{0:.3%}".format(summary.get("benchmark_annualized_returns", 0)), blue, black), ('32%', label_height, value_height, u"Alpha", "{0:.4f}".format(summary["alpha"]), black, black), ('32%', label_height2, value_height2, u"Volatility", "{0:.4f}".format(summary["volatility"]), black, black), ('40%', label_height, value_height, u"Beta", "{0:.4f}".format(summary["beta"]), black, black), ('40%', label_height2, value_height2, u"MaxDrawdown", "{0:.3%}".format(summary["max_drawdown"]), black, black), ('50%', label_height, value_height, u"Sharpe", "{0:.4f}".format(summary["sharpe"]), black, black), ('50%', label_height2, value_height2, u"Tracking Error", "{0:.4f}".format(summary["tracking_error"]), black, black), ('60%', label_height, value_height, u"Sortino", "{0:.4f}".format(summary["sortino"]), black, black), ('60%', label_height2, value_height2, u"Downside Risk", "{0:.4f}".format(summary["downside_risk"]), black, black), ('70%', label_height, value_height, u"Information Ratio", "{0:.4f}".format(summary["information_ratio"]), black, black), ] for x, y1, y2, label, value, label_color, value_color in fig_data: title.append(Text(x, y1, label, color=label_color, fontsize=font_size)) title.append( Text(x, y2, value, color=value_color, fontsize=value_font_size)) for x, y1, y2, label, value, label_color, value_color in [ ('70%', label_height2, value_height2, "MaxDD/MaxDDD", max_dd_info, black, black) ]: title.append(Text(x, y1, label, color=label_color, fontsize=font_size)) title.append(Text(x, y2, value, color=value_color, fontsize=8)) page = Page() # title title_chart = Chart(title=title, width=1000, height=180, show_xaxis=False, show_yaxis=False) page.add(title_chart) # line line = Chart(title=Text(text='收益曲线'), animation=False, dataZoom=[ { 'type': 'slider', 'xAxisIndex': [0], }, { 'type': 'inside', 'xAxisIndex': [0], }, ]) line.set_tooltip(**{ 'trigger': 'axis', 'axisPointer': { 'type': 'cross' }, }) line.set_xaxis(Axis(data=x_axis, type='category')) # plot strategy and benchmark line.line(label='strategy', data=portfolio["unit_net_value"] - 1.0, color=red) if benchmark_portfolio is not None: line.line(label='benchmark', data=benchmark_portfolio["unit_net_value"] - 1.0, color=blue) # plot MaxDD/MaxDDD line.line(label='MaxDrawdown', data=[ [x_axis[max_dd_end], rt[max_dd_end] - 1.0], [x_axis[max_dd_start], rt[max_dd_start] - 1.0], ], color='green', smooth=False, fill=True, opacity=0.1) line.line(label='MaxDDD', data=[ [x_axis[max_ddd_start_day], rt[max_ddd_start_day] - 1.0], [x_axis[max_ddd_end_day], rt[max_ddd_end_day] - 1.0], ], color='blue', smooth=False, fill=True, opacity=0.1) page.add(line) # kline upcolor, downcolor = '#52b986', '#ec4d5c' buycolor, sellcolor = '#018ffe', '#cc46ed' env = Environment.get_instance() k_bar = env.get_plot_bar() trade_mark, buy_trade, sell_trade, avg_bar, rest_trade, win_rate = \ KLineMakePoint(mark_size=8).mark_point(result_dict['trades']) k_count = len(k_bar) buy_signal, sell_signal = env.buy_signal, env.sell_signal label_height, value_height = 10, 35 label_height2, value_height2 = 70, 90 profit = portfolio["unit_net_value"] - 1.0 profit_loss_sum = np.sum(profit[profit < 0]) title = [] fig_data = [ ('5%', label_height, value_height, u"buy", "{}|{} {:.2%}".format( buy_trade, buy_signal, buy_trade / buy_signal if buy_signal != 0 else 0), buycolor, black), ('5%', label_height2, value_height2, u"sell", "{}|{} {:.2%}".format( sell_trade, sell_signal, sell_trade / sell_signal if sell_signal != 0 else 0), sellcolor, black), ('17%', label_height, value_height, u"成交比例", "{}|{} {:.2%}".format( buy_trade + sell_trade, buy_signal + sell_signal, (buy_trade + sell_trade) / (buy_signal + sell_signal) if (buy_signal + sell_signal) != 0 else 0), black, black), ('17%', label_height2, value_height2, u"交易bar", "{}".format(k_count), black, black), ('30%', label_height, value_height, u"平均bar", "{}".format(avg_bar), black, black), ('30%', label_height2, value_height2, u"剩余交易", "{}".format(rest_trade), black, black), ('40%', label_height, value_height, u"资金利用率", "{:.2%}".format(1 - np.mean(portfolio['cash'] / portfolio['total_value'])), black, black), ('40%', label_height2, value_height2, u"盈亏比", "{:.2%}".format(-(np.sum(profit[profit > 0]) / profit_loss_sum) if profit_loss_sum != 0 else 0), black, black), ('50%', label_height, value_height, u"平均win", "{:.4%}".format(np.mean(profit[profit > 0])), black, black), ('50%', label_height2, value_height2, u"平均loss", "{:.4%}".format(np.mean(profit[profit < 0])), black, black), ('60%', label_height, value_height, u"胜率", "{:.4%}".format(win_rate), black, black), ] for x, y1, y2, label, value, label_color, value_color in fig_data: title.append(Text(x, y1, label, color=label_color, fontsize=font_size)) title.append( Text(x, y2, value, color=value_color, fontsize=value_font_size)) title_chart = Chart(title=title, height=150, show_xaxis=False, show_yaxis=False) page.add(title_chart) kline_chart = Chart(title=Text(text='K线交易'), height=500, animation=False, show_legend=False, dataZoom=[ { 'type': 'slider', 'xAxisIndex': [0, 1], 'start': 1, 'end': 35, 'top': '85%', }, { 'type': 'inside', 'xAxisIndex': [0, 1], }, ], grid=[{ 'left': '10%', 'right': '8%', 'height': '50%' }, { 'left': '10%', 'right': '8%', 'bottom': '20%', 'height': '15%' }], axisPointer={'link': { 'xAxisIndex': 'all' }}, visualMap={ 'show': False, 'seriesIndex': 1, 'pieces': [{ 'value': 1, 'color': downcolor, }, { 'value': -1, 'color': upcolor, }] }) kline_chart.set_tooltip(**{ 'trigger': 'axis', 'axisPointer': { 'type': 'cross' }, }) kline_chart.set_xaxis([ Axis(data=x_axis, type='category'), Axis(data=x_axis, type='category', axisTick={'show': False}, axisLabel={'show': False}, gridIndex=1) ]) kline_chart.set_yaxis([ Axis(scale=True), Axis(scale=True, axisLabel={'show': False}, axisLine={'show': False}, axisTick={'show': False}, splitLine={'show': False}, gridIndex=1) ]) """k线""" kline_data = k_bar[['open', 'close', 'low', 'high']].copy().tolist() kline_chart.kline(label='k线', data=kline_data, upcolor=upcolor, downcolor=downcolor, markPoint=trade_mark) def color_volume(i, data): """交易量上色""" return [i, data['volume'], 1 if data['open'] > data['close'] else -1] ocv = k_bar[['open', 'close', 'volume']].copy() volume_data = [color_volume(i, data) for i, data in enumerate(ocv)] kline_chart.bar('volume', data=volume_data, xAxisIndex=1, yAxisIndex=1, itemStyle={}) page.add(kline_chart) page.plot()
def __init__(self, cash, positions): super(BenchmarkAccount, self).__init__(cash, positions, True) self.benchmark = Environment.get_instance().config.base.benchmark
def register_event(self): event_bus = Environment.get_instance().event_bus event_bus.prepend_listener(EVENT.SETTLEMENT, self._on_settlement) event_bus.prepend_listener(EVENT.PRE_BAR, self._on_bar)
def __init__(self): config = Environment.get_instance().config self.slippage = getattr(config, 'slippage', 0) self.deciders = { ACCOUNT_TYPE.CRYPTO.name: CryptoSlippage(self.slippage), }
def now(self): """ 使用以上的方式就可以在handle_bar中拿到当前的bar的时间,比如day bar的话就是那天的时间,minute bar的话就是这一分钟的时间点。 """ return Environment.get_instance().calendar_dt
def init(self): if not self._init: return self._init(self._user_context) Environment.get_instance().event_bus.publish_event( Event(EVENT.POST_USER_INIT))
def symbol(self): return Environment.get_instance().config.base.symbol
def config(self): return Environment.get_instance().config
def register_event(self): event_bus = Environment.get_instance().event_bus event_bus.prepend_listener(EVENT.PRE_BEFORE_TRADING, self._pre_before_trading)
def run_info(self): """ :property getter: :class:`~RunInfo` """ config = Environment.get_instance().config return RunInfo(config)