# quandomo **Repository Path**: domodo/quandomo ## Basic Information - **Project Name**: quandomo - **Description**: 基于事件驱动的量化交易平台 - **Primary Language**: Unknown - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-11-03 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README quandomo 框架概述 一、数据存贮与使用 1、数据存贮 从最基础的远程数据库中取数据,以相同的结构保存到本地(sqlite)数据库中。 2、数据使用 回测时,将数据从本地数据库中读取后,逐个时间戳形成市场事件推送入队列。 然后从队列中取数据,针对不同的事件类型,已注册的不同监控函数对应响应。 二、整体框架 1、后台事务在策略的父类中进行,具体的策略本身继承之后,重写 handle_bar、handle_order、handle_trade 等方法,实现具体的策略。 2、回测时,将数据从本地数据库中读出,逐个时间戳新建市场事件并推送入队列,之后从队列中取事件,不同的事件类型有已注册的不同监控函数响应,从而驱动策略运行。 三、例程 # ExampleStrategy.py # -*- coding: utf-8 -*- Import … from … import ... class TrialStrategyStock(StrategyBaseBacktestStock): def __init__(self, universe_limit): super(TrialStrategyStock, self).__init__(universe_limit) def init_strategy(self, log_full_path: list, color_log=True, start_str='20180101', end_str='20200222'): # 确定参数值 self.gateway = 'ctp' self.run_mode = RunMode.BACKTESTING self.start = date_str_to_int(start_str) self.end = date_str_to_int(end_str) self.account = [{'name': 'acc0', 'equity': 200000}] self.benchmark = "000300.SH" # self.rights_adjustment = RightsAdjustment.NONE # 只在画 K 线时有效,对策略运行无影响 # 动态股票池(沪深300指数成分股) self.is_universe_dynamic = '000300.SH' # 固定股票池 # self.is_universe_dynamic = False # self.universe = ['000562.SZ', '600710.SH', '600757.SH', '600761.SH'] # 设置回测滑点 self.set_slippage(symbol_type=Product.STOCK.value, slippage_type=Slippage.FIX, value=0.01) # 设置沪市交易成本 self.set_commission(symbol_type=Product.STOCK_SH, tax=0.001, open_commission=0.0003, close_commission=0.0005, close_today_commission=0, min_commission=5) # 设置深市交易成本 self.set_commission(symbol_type=Product.STOCK_SZ, tax=0.001, open_commission=0.0003, close_commission=0.0003, close_today_commission=0, min_commission=5) if not color_log: self.context.logger = Logger(log_full_path[0]) else: self.context.logger = ColorLogger(log_full_path[0] + log_full_path[1]) # color log def calc_position_size(self, account_available, symbol_price): psize = account_available / len(self.universe) * 0.9 / symbol_price psize = int(psize / 100) * 100 return psize def handle_bar(self, event_bar): self.context.logger.info("handle_bar() @ {0}".format(event_bar.dt)) self.activate_trade_signal = False available_position_dict = self.context.bar_position_data_dict account_data = self.context.current_account_data current_date_int = date_str_to_int(event_bar.dt) cur_start_date = to_datetime(event_bar.dt) - Timedelta(days=60) cur_start_date_int = date_str_to_int(str(cur_start_date)[:10]) # 循环处理旧持仓 for pos_symbol in available_position_dict.keys(): close_price = self.get_data.get_market_data(self.context.daily_data, all_symbol_code=[pos_symbol], field=["close"], start=cur_start_date_int, end=current_date_int) if len(close_price) > 0: # 指标计算 ma5 = talib.MA(array(close_price), timeperiod=5) ma20 = talib.MA(array(close_price), timeperiod=20) if current_date_int in close_price.keys() and (not isnull(ma5[-1])) and (not isnull(ma20[-1])): if ma5[-1] < ma20[-1]: if available_position_dict[pos_symbol].volume > 0 and available_position_dict[pos_symbol].direction == Direction.LONG: self.activate_trade_signal = True pos_abs = abs(available_position_dict[pos_symbol].volume) comments = "限价卖出" self.sell(event_bar.dt, self.account[0]['name'], pos_symbol, close_price.iloc[-1], pos_abs, False, comments) self.context.logger.info( "-- 资金账号 {3} 发出委托卖出 {0} {1} 股,委托价为 {2}".format(pos_symbol, pos_abs, close_price.iloc[-1], self.account[0]['name'])) # 循环遍历股票池,看是否有新信号 for symbol in self.universe: # 取当前股票的数据 close_price = self.get_data.get_market_data(self.context.daily_data, all_symbol_code=[symbol], field=["close"], start=cur_start_date_int, end=current_date_int) # close_array = array(close_price) if len(close_price) > 0: # 指标计算 ma5 = talib.MA(array(close_price), timeperiod=5) ma20 = talib.MA(array(close_price), timeperiod=20) # 数据有效时才判断 if current_date_int in close_price.keys() and (not isnull(ma5[-1])) and (not isnull(ma20[-1])): # 如果5日均线突破20日均线,并且没有持仓,则买入这只股票100股,委托价为当前bar的收盘价 if ma5[-1] > ma20[-1]: # live模式下,此处需要更新一次持仓状态,这样判别持仓才准确 if symbol not in available_position_dict.keys(): self.activate_trade_signal = True comments = "限价买入" # psize = self.calc_position_size(account_data.available, close_price.iloc[-1]) psize = 300 if psize > 0: self.buy(event_bar.dt, self.account[0]['name'], symbol, close_price.iloc[-1], psize, False, comments) self.context.logger.info( "-- 资金账号 {0} 发出委托买入 {1} {2} 股,委托价为 {3}".format(self.account[0]['name'], symbol, psize, close_price.iloc[-1])) def handle_order(self, event_order): """针对委托单状态变动的响应""" self.context.logger.info('-- {0} 的委托单 {1} 当前状态是 {2}'.format(event_order.data.symbol, event_order.data.order_id, event_order.data.status)) def handle_trade(self, event_trade): """成交后立即添加止损止盈""" self.context.logger.info('-- {0} 的委托单 {1} 已成交,如有必要请立即添加止损止盈'.format(event_trade.data.symbol, event_trade.data.order_id)) if __name__ == "__main__": output_path = 'D:/python projects/quandomo/doc/' log_name = '{0}.log'.format(strftime('%Y-%m-%d_%H%M%S')) is_color_log = True start_date = '20160104' # hs300 成分股数据开始于 20050408 end_date = '20190902' universe_limit = 5 # 测试运行完整个策略所需时间 time_test = Timer(True) with time_test: trial_strategy = TrialStrategyStock(universe_limit) trial_strategy.init_strategy([output_path, log_name], is_color_log, start_date, end_date) trial_strategy.run_strategy() trial_strategy.strategy_analysis(output_path) # 绩效分析 trial_strategy.show_results(output_path) # 运行结果展示 四、结果展示 1、log 文件(节选) [2020-11-02 12:33:17,289] [INFO]- handle_bar() @ 20170519 [2020-11-02 12:33:17,301] [INFO]- handle_bar() @ 20170522 [2020-11-02 12:33:17,312] [INFO]- handle_bar() @ 20170523 [2020-11-02 12:33:17,325] [INFO]- handle_bar() @ 20170524 [2020-11-02 12:33:17,338] [INFO]- -- 600000.SH 今日 20170525 除权除息,所持仓位量价相应变动 [2020-11-02 12:33:17,339] [INFO]- handle_bar() @ 20170525 [2020-11-02 12:33:17,343] [INFO]- -- 资金账号 acc0 发出委托卖出 600000.SH 389.99999999999994 股,委托价为 12.93 [2020-11-02 12:33:17,348] [INFO]- -- 资金账号 acc0 发出委托买入 000001.SZ 300 股,委托价为 9.1 [2020-11-02 12:33:17,352] [INFO]- -- 资金账号 acc0 发出委托买入 000002.SZ 300 股,委托价为 20.5 [2020-11-02 12:33:17,356] [INFO]- -- 600000.SH 的委托单 order_45210397 当前状态是 Status.NOT_TRADED [2020-11-02 12:33:17,356] [INFO]- -- 000001.SZ 的委托单 order_23461750 当前状态是 Status.NOT_TRADED [2020-11-02 12:33:17,357] [INFO]- -- 000002.SZ 的委托单 order_07982456 当前状态是 Status.NOT_TRADED [2020-11-02 12:33:17,359] [INFO]- -- 600000.SH 的委托单 order_45210397 无法成交,撤回重发 [2020-11-02 12:33:17,360] [INFO]- -- 600000.SH 的委托单 order_45210397 当前状态是 Status.WITHDRAW [2020-11-02 12:33:17,360] [INFO]- -- 资金账号 acc0 发出委托卖出 600000.SH 389.99999999999994 股,委托价为 12.81 [2020-11-02 12:33:17,361] [INFO]- -- 600000.SH 的委托单 order_78069125 当前状态是 Status.NOT_TRADED [2020-11-02 12:33:17,361] [INFO]- -- 600000.SH 的委托单 order_78069125 当前状态是 Status.ALL_TRADED [2020-11-02 12:33:17,362] [INFO]- -- 600000.SH 的委托单 order_78069125 于 20170526 成交,成交价为 12.92 [2020-11-02 12:33:17,363] [INFO]- -- 600000.SH 的委托单 order_78069125 已成交,如有必要请立即添加止损止盈 [2020-11-02 12:33:17,364] [INFO]- -- 000001.SZ 的委托单 order_23461750 当前状态是 Status.ALL_TRADED [2020-11-02 12:33:17,366] [INFO]- -- 000001.SZ 的委托单 order_23461750 于 20170526 成交,成交价为 9.09 [2020-11-02 12:33:17,366] [INFO]- -- 000001.SZ 的委托单 order_23461750 已成交,如有必要请立即添加止损止盈 [2020-11-02 12:33:17,367] [INFO]- -- 000002.SZ 的委托单 order_07982456 当前状态是 Status.ALL_TRADED [2020-11-02 12:33:17,368] [INFO]- -- 000002.SZ 的委托单 order_07982456 于 20170526 成交,成交价为 20.31 [2020-11-02 12:33:17,369] [INFO]- -- 000002.SZ 的委托单 order_07982456 已成交,如有必要请立即添加止损止盈 [2020-11-02 12:33:17,370] [INFO]- handle_bar() @ 20170526 [2020-11-02 12:33:17,388] [INFO]- handle_bar() @ 20170531 [2020-11-02 12:33:17,405] [INFO]- handle_bar() @ 20170601 .... 2、策略简要绩效指标 +-----------------------------------------------+ | Start Date | 20160104 | | End Date | 20170902 | | Initial Equity | 200000 | | Final Equity | 210881.55 | | total commission | 1400.5727 | | Benchmark Annual Return % (arith) | 6.2657 | | Strategy Annual Return % (arith) | 3.2716 | | Strategy Volatility | 0.0255 | | Strategy Max Drawdown % | -2.0 | | Sharp Ratio | 2.1368 | | Downside Risk | 0.0013 | | Sortino Ratio | 40.7727 | | Tracking Error | 0.1625 | | Information Ratio | -0.1842 | +-----------------------------------------------+ 五、其他 感谢 AmazingQuant 和 vnpy。