Example #1
0
    def __init__(self,
                 name: Text,
                 import_name,
                 engine_method: str = "thread",
                 instance_path=None):
        """ 初始化 """
        self.name = name
        self.import_name = import_name
        if engine_method == "thread":
            self.event_engine = EventEngine()
            self.recorder = Recorder(self, self.event_engine)
        elif engine_method == "async":
            self.event_engine = AsyncEngine()
            self.recorder = AsyncRecorder(self, self.event_engine)
        else:
            raise TypeError("引擎参数错误,只支持thread和async,请检查代码")
        if instance_path is None:
            instance_path = self.auto_find_instance_path()
        elif not os.path.isabs(instance_path):
            raise ValueError(
                'If an instance path is provided it must be absolute.'
                ' A relative path was given instead.')
        self.risk_gateway = RiskLevel(self)

        self.instance_path = instance_path
        self.config = self.make_config()
        self.interface = Interface()
        _app_context_ctx.push(self.name, self)
Example #2
0
    def __init__(self,
                 name: Text,
                 import_name,
                 engine_method: str = "thread",
                 work_mode="limit_time",
                 refresh: bool = False,
                 instance_path=None):
        """ 初始化 """
        self.name = name
        self.import_name = import_name
        self.engine_method = engine_method
        self.refresh = refresh
        if engine_method == "thread":
            self.event_engine = EventEngine()
            self.recorder = Recorder(self, self.event_engine)
        elif engine_method == "async":
            self.event_engine = AsyncEngine()
            self.recorder = AsyncRecorder(self, self.event_engine)
        else:
            raise TypeError("引擎参数错误,只支持 thread 和 async,请检查代码")
        if instance_path is None:
            instance_path = self.auto_find_instance_path()
        elif not os.path.isabs(instance_path):
            raise ValueError(
                'If an instance path is provided it must be absolute.'
                ' A relative path was given instead.')
        self.risk_gateway_class = None
        self.instance_path = instance_path
        self.config = self.make_config()
        self.init_finished = False
        self.p = None
        self.p_flag = True

        self.r = None
        self.r_flag = True
        self.work_mode = work_mode

        _app_context_ctx.push(self.name, self)
Example #3
0
    def __init__(self, name: Text, import_name, action_class: Action = None, engine_method: str = "thread",
                 logger_class=None, logger_config_path=None,
                 refresh: bool = False, risk=None,
                 instance_path=None):
        """ 初始化 """
        self.name = name if name else 'ctpbee'
        self.import_name = import_name
        self.engine_method = engine_method
        self.refresh = refresh
        self.active = False
        # 是否加载以使用默认的logger类/ choose if use the default logging class
        if logger_class is None:
            self.logger = VLogger(CP, app_name=self.name)
            self.logger.set_default(name=self.logger.app_name, owner='App')
        else:
            if logger_config_path:
                self.logger = logger_class(logger_config_path, app_name=self.name)
            else:
                self.logger = logger_class(CP, app_name=self.name)
            self.logger.set_default(name=self.logger.app_name, owner='App')
        if engine_method == "thread":
            self.event_engine = EventEngine()
            self.recorder = Recorder(self, self.event_engine)
        elif engine_method == "async":
            self.event_engine = AsyncEngine()
            self.recorder = AsyncRecorder(self, self.event_engine)
        else:
            raise TypeError("引擎参数错误,只支持 thread 和 async,请检查代码")

        """
              If no risk is specified by default, set the risk_decorator to None
              如果默认不指定action参数, 那么使用设置风控装饰器为空
              """
        if risk is None:
            self.risk_decorator = None
        else:
            self.risk_decorator = risk
        """
        If no action is specified by default, use the default Action class
        如果默认不指定action参数, 那么使用默认的Action类 
        """
        if action_class is None:
            self.action = Action(self)
        else:
            self.action = action_class(self)
        """
        根据action里面的函数更新到CtpBee上面来
        bind the function of action to CtpBee
        """

        """ update """
        if self.risk_decorator is not None:
            self.risk_decorator.update_app(self)

        for x in dir(self.action):
            func = getattr(self.action, x)
            if x.startswith("__"):
                continue
            if ismethod(func):
                setattr(self, func.__name__, func)
        """
        If engine_method is specified by default, use the default EventEngine and Recorder or use the engine
            and recorder basis on your choice
        如果不指定engine_method参数,那么使用默认的事件引擎 或者根据你的参数使用不同的引擎和记录器
        """

        if instance_path is None:
            instance_path = self.auto_find_instance_path()
        elif not os.path.isabs(instance_path):
            raise ValueError(
                'If an instance path is provided it must be absolute.'
                ' A relative path was given instead.'
            )
        self.instance_path = instance_path
        self.config = self.make_config()
        self.init_finished = False

        # default monitor and flag
        self.p = None
        self.p_flag = True

        self.r = None
        self.r_flag = True

        _app_context_ctx.push(self.name, self)
Example #4
0
class CtpBee(object):
    """
    ctpbee 源于我对于做项目的痛点需求, 旨在开发出一套具有完整api的交易微框架
    I hope it will help you !

    """
    # 默认回测配置参数
    default_params = {
        'cash': 10000.0,
        'check_submit': True,
        'eos_bar': False,
        'filler': None,
        "commision": 0.01,
        # slippage options
        'slip_percent': 0.0,
        'slip_fixed': 0.0,
        'slip_open': False,
        'slip_match': True,
        'slip_limit': True,
        'slip_out': False,
        'coc': False,
        'coo': False,
        'int2pnl': True,
        'short_cash': True,
        'fund_start_val': 100.0,
        'fund_mode': False
    }
    default_config = ImmutableDict(
        dict(LOG_OUTPUT=True,  # 是否开启输出模式
             TD_FUNC=False,  # 是否开启交易功能
             INTERFACE="ctp",  # 接口参数,默认指定国内期货ctp
             MD_FUNC=True,  # 是否开启行情功能
             XMIN=[],  # k线序列周期, 支持一小时以内的k线任意生成
             ALL_SUBSCRIBE=False,
             SHARE_MD=False,  # 是否多账户之间共享行情,---> 等待完成
             SLIPPAGE_COVER=0,  # 平多头滑点设置
             SLIPPAGE_SELL=0,  # 平空头滑点设置
             SLIPPAGE_SHORT=0,  # 卖空滑点设置
             SLIPPAGE_BUY=0,  # 买多滑点设置
             LOOPER_PARAMS=default_params,  # 回测需要设置的参数
             SHARED_FUNC=False,  # 分时图数据 --> 等待优化
             REFRESH_INTERVAL=1.5,  # 定时刷新秒数, 需要在CtpBee实例化的时候将refresh设置为True才会生效
             INSTRUMENT_INDEPEND=False,  # 是否开启独立行情,策略对应相应的行情
             CLOSE_PATTERN="today",  # 面对支持平今的交易所,优先平今或者平昨 ---> today: 平今, yesterday: 平昨, 其他:处罚异常
             TODAY_EXCHANGE=[Exchange.SHFE.value, Exchange.INE.value],  # 需要支持平今的交易所代码列表
             AFTER_TIMEOUT=3,  # 设置after线程执行超时
             ))

    config_class = Config
    import_name = None

    # 交易api与行情api / trade api and market api
    market = None
    trader = None

    # 插件Api系统 /Extension system
    extensions = {}

    # 工具, 用于提供一些比较优秀的工具/ Toolbox, using by providing some good tools
    tools = {}

    def __init__(self, name: Text, import_name, action_class: Action = None, engine_method: str = "thread",
                 logger_class=None, logger_config_path=None,
                 refresh: bool = False, risk=None,
                 instance_path=None):
        """ 初始化 """
        self.name = name if name else 'ctpbee'
        self.import_name = import_name
        self.engine_method = engine_method
        self.refresh = refresh
        self.active = False
        # 是否加载以使用默认的logger类/ choose if use the default logging class
        if logger_class is None:
            self.logger = VLogger(CP, app_name=self.name)
            self.logger.set_default(name=self.logger.app_name, owner='App')
        else:
            if logger_config_path:
                self.logger = logger_class(logger_config_path, app_name=self.name)
            else:
                self.logger = logger_class(CP, app_name=self.name)
            self.logger.set_default(name=self.logger.app_name, owner='App')
        if engine_method == "thread":
            self.event_engine = EventEngine()
            self.recorder = Recorder(self, self.event_engine)
        elif engine_method == "async":
            self.event_engine = AsyncEngine()
            self.recorder = AsyncRecorder(self, self.event_engine)
        else:
            raise TypeError("引擎参数错误,只支持 thread 和 async,请检查代码")

        """
              If no risk is specified by default, set the risk_decorator to None
              如果默认不指定action参数, 那么使用设置风控装饰器为空
              """
        if risk is None:
            self.risk_decorator = None
        else:
            self.risk_decorator = risk
        """
        If no action is specified by default, use the default Action class
        如果默认不指定action参数, 那么使用默认的Action类 
        """
        if action_class is None:
            self.action = Action(self)
        else:
            self.action = action_class(self)
        """
        根据action里面的函数更新到CtpBee上面来
        bind the function of action to CtpBee
        """

        """ update """
        if self.risk_decorator is not None:
            self.risk_decorator.update_app(self)

        for x in dir(self.action):
            func = getattr(self.action, x)
            if x.startswith("__"):
                continue
            if ismethod(func):
                setattr(self, func.__name__, func)
        """
        If engine_method is specified by default, use the default EventEngine and Recorder or use the engine
            and recorder basis on your choice
        如果不指定engine_method参数,那么使用默认的事件引擎 或者根据你的参数使用不同的引擎和记录器
        """

        if instance_path is None:
            instance_path = self.auto_find_instance_path()
        elif not os.path.isabs(instance_path):
            raise ValueError(
                'If an instance path is provided it must be absolute.'
                ' A relative path was given instead.'
            )
        self.instance_path = instance_path
        self.config = self.make_config()
        self.init_finished = False

        # default monitor and flag
        self.p = None
        self.p_flag = True

        self.r = None
        self.r_flag = True

        _app_context_ctx.push(self.name, self)

    def update_action_class(self, action_class):
        if isinstance(action_class, Action):
            raise TypeError(f"更新action_class出现错误, 你传入的action_class类型为{type(action_class)}")
        self.action = action_class()

    def update_risk_gateway(self, gateway_class):
        self.risk_decorator = gateway_class
        self.risk_decorator.update_app(self)

    def make_config(self):
        """ 生成class类"""
        defaults = dict(self.default_config)
        return self.config_class(self.instance_path, defaults)

    def auto_find_instance_path(self):
        prefix, package_path = find_package(self.import_name)
        if prefix is None:
            return os.path.join(package_path)
        return os.path.join(prefix, 'var', self.name + '-instance')

    @property
    def td_login_status(self):
        """ 交易 API 都应该实现td_status"""
        return self.trader.td_status

    @property
    def md_login_status(self):
        """ 行情 API 都应该实现md_status"""
        return self.market.md_status

    def _load_ext(self):
        """根据当前配置文件下的信息载入行情api和交易api,记住这个api的选项是可选的"""
        self.active = True
        if "CONNECT_INFO" in self.config.keys():
            info = self.config.get("CONNECT_INFO")
        else:
            raise ConfigError(message="没有相应的登录信息", args=("没有发现登录信息",))
        MdApi, TdApi = Interface.get_interface(self)
        if self.config.get("MD_FUNC"):
            self.market = MdApi(self.event_engine)
            self.market.connect(info)

        if self.config.get("TD_FUNC"):
            if self.config['INTERFACE'] == "looper":
                self.trader = TdApi(self.event_engine, self)
            else:
                self.trader = TdApi(self.event_engine)
            self.trader.connect(info)

        show_me = graphic_pattern(__version__, self.engine_method)
        print(show_me)
        if self.refresh:
            if self.r is not None:
                self.r_flag = False
                sleep(self.config['REFRESH_INTERVAL'] + 1.5)
                self.r = Thread(target=refresh_query, args=(self,),daemon=True)
                self.r.start()
            else:
                self.r = Thread(target=refresh_query, args=(self,), daemon=True)
                self.r.start()
            self.r_flag = True

    @locked_cached_property
    def name(self):
        if self.import_name == '__main__':
            fn = getattr(sys.modules['__main__'], '__file__', None)
            if fn is None:
                return '__main__'
            return os.path.splitext(os.path.basename(fn))[0]
        return self.import_name

    def start(self, log_output=True, debug=False):
        """
        开启处理
        :param log_output: 是否输出log信息
        :param debug: 是否开启调试模式 ----> 等待完成
        :return:
        """
        if not self.event_engine.status:
            self.event_engine.start()
        self.config["LOG_OUTPUT"] = log_output
        self._load_ext()

    def stop(self):
        """ 停止运行 """
        if self.event_engine.status:
            self.event_engine.stop()

    def remove_extension(self, extension_name: Text) -> None:
        """移除插件"""
        if extension_name in self.extensions:
            del self.extensions[extension_name]

    def add_extension(self, extension: CtpbeeApi):
        """添加插件"""
        self.extensions.pop(extension.extension_name, None)
        extension.init_app(self)
        self.extensions[extension.extension_name] = extension

    def suspend_extension(self, extension_name):
        extension = self.extensions.get(extension_name, None)
        if not extension:
            return False
        extension.frozen = True
        return True

    def enable_extension(self, extension_name):
        extension = self.extensions.get(extension_name, None)
        if not extension:
            return False
        extension.frozen = False
        return True

    def del_extension(self, extension_name):
        self.extensions.pop(extension_name, None)

    def reload(self):
        """ 重新载入接口 """
        if self.market is not None:
            self.market.close()
        if self.trader is not None:
            self.trader.close()
        # 清空处理队列
        self.event_engine._queue.empty()
        sleep(3)
        self.market, self.trader = None, None
        self._load_ext()

    def release(self):
        """ 释放账户,安全退出 """
        try:
            if self.market is not None:
                self.market.close()
            if self.trader is not None:
                self.trader.close()
            self.market, self.trader = None, None
            self.event_engine.stop()
            del self.event_engine
            if self.r is not None:
                """ 强行终结掉线程 """
                end_thread(self.r)
        except AttributeError:
            pass
Example #5
0
class CtpBee(object):
    """
    ctpbee 源于我对于做项目的痛点需求, 旨在开发出一套具有完整api的交易微框架 ,
    在这里你可以看到很多借鉴自flask的设计 , 毕竟本人实在热爱flask ....
    ctpbee提供完整的支持 ,一个CtpBee对象可以登录一个账户 ,
    当你登录多个账户时, 可以通过current_app, 以及switch_app还有 get_app提供完整的支持,
    每个账户对象都拥有单独的发单接口 ,在你实现策略的地方 可以通过上述api实现发单支持,
    当然ctpbee提供了CtpbeeApi 抽象插件 ,继承此插件即可快速载入支持.
    总而言之,希望能够极大的简化目前的开发流程 !
    """

    # 默认配置
    default_config = ImmutableDict(
        dict(LOG_OUTPUT=True,
             TD_FUNC=False,
             INTERFACE="ctp",
             MD_FUNC=True,
             XMIN=[],
             ALL_SUBSCRIBE=False,
             SHARE_MD=False,
             ENGINE_METHOD="thread"))
    config_class = Config
    import_name = None
    # 数据记录载体
    __active = False

    # 交易api与行情api
    market = None
    trader = None

    # 插件系统
    # todo :等共享内存块出来了 是否可以尝试在外部进行
    extensions = {}

    def __init__(self,
                 name: Text,
                 import_name,
                 engine_method: str = "thread",
                 instance_path=None):
        """ 初始化 """
        self.name = name
        self.import_name = import_name
        if engine_method == "thread":
            self.event_engine = EventEngine()
            self.recorder = Recorder(self, self.event_engine)
        elif engine_method == "async":
            self.event_engine = AsyncEngine()
            self.recorder = AsyncRecorder(self, self.event_engine)
        else:
            raise TypeError("引擎参数错误,只支持thread和async,请检查代码")
        if instance_path is None:
            instance_path = self.auto_find_instance_path()
        elif not os.path.isabs(instance_path):
            raise ValueError(
                'If an instance path is provided it must be absolute.'
                ' A relative path was given instead.')
        self.risk_gateway = RiskLevel(self)

        self.instance_path = instance_path
        self.config = self.make_config()
        self.interface = Interface()
        _app_context_ctx.push(self.name, self)

    def make_config(self):
        """ 生成class类"""
        defaults = dict(self.default_config)
        return self.config_class(self.instance_path, defaults)

    def auto_find_instance_path(self):
        prefix, package_path = find_package(self.import_name)
        if prefix is None:
            return os.path.join(package_path)
        return os.path.join(prefix, 'var', self.name + '-instance')

    @property
    def td_login_status(self):
        """ 交易 API 都应该实现td_status"""
        return self.trader.td_status

    @property
    def md_login_status(self):
        """ 行情 API 都应该实现md_status"""
        return self.market.md_status

    def _load_ext(self):
        """根据当前配置文件下的信息载入行情api和交易api,记住这个api的选项是可选的"""
        self.__active = True
        if "CONNECT_INFO" in self.config.keys():
            info = self.config.get("CONNECT_INFO")
        else:
            raise ConfigError(message="没有相应的登录信息", args=("没有发现登录信息", ))
        MdApi, TdApi = self.interface.get_interface(self)
        if self.config.get("MD_FUNC"):
            self.market = MdApi(self.event_engine)
            self.market.connect(info)

        if self.config.get("TD_FUNC"):
            self.trader = TdApi(self.event_engine)
            self.trader.connect(info)
            sleep(0.5)

    @locked_cached_property
    def name(self):
        if self.import_name == '__main__':
            fn = getattr(sys.modules['__main__'], '__file__', None)
            if fn is None:
                return '__main__'
            return os.path.splitext(os.path.basename(fn))[0]
        return self.import_name

    def start(self, log_output=True):
        """开始"""
        if not self.event_engine.status:
            self.event_engine.start()
        self.config["LOG_OUTPUT"] = log_output
        self._load_ext()

    def stop(self):
        """ 停止运行 """
        if self.event_engine.status:
            self.event_engine.stop()

    @check(type="trader")
    def send_order(self, order_req: OrderRequest) -> AnyStr:
        """发单"""
        result = self.risk_gateway.send(self)
        if False in result:
            event = Event(type=EVENT_LOG, data="风控阻止下单")
            self.event_engine.put(event)
            return
        send_monitor.send(order_req)
        return self.trader.send_order(order_req)

    @check(type="trader")
    def cancel_order(self, cancle_req: CancelRequest):
        """撤单"""
        cancle_monitor.send(cancle_req)
        self.trader.cancel_order(cancle_req)

    @check(type="market")
    def subscribe(self, symbol: AnyStr):
        """订阅行情"""
        if "." in symbol:
            symbol = symbol.split(".")[1]
        return self.market.subscribe(symbol)

    @check(type="trader")
    def query_position(self):
        """查询持仓"""
        return self.trader.query_position()

    @check(type="trader")
    def transfer(self, req, type):
        """
        req currency attribute
        ["USD", "HKD", "CNY"]
        :param req:
        :param type:
        :return:
        """
        self.trader.transfer(req, type=type)

    @check(type="trader")
    def query_account_register(self, req):
        self.trader.query_account_register(req)

    @check(type="trader")
    def query_bank_account_money(self, req):
        self.trader.query_bank_account_money(req)

    @check(type="trader")
    def query_transfer_serial(self, req):
        self.trader.query_transfer_serial(req)

    @check(type="trader")
    def query_bank(self):
        pass

    @check(type="trader")
    def query_account(self):
        """查询账户"""
        return self.trader.query_account()

    def remove_extension(self, extension_name: Text) -> None:
        """移除插件"""
        if extension_name in self.extensions:
            del self.extensions[extension_name]

    def add_extension(self, extension: CtpbeeApi):
        """添加插件"""
        if extension.extension_name in self.extensions:
            return
        extension.init_app(self)
        self.extensions[extension.extension_name] = extension

    def suspend_extension(self, extension_name):
        extension = self.extensions.get(extension_name, None)
        if not extension:
            return False
        extension.frozen = True
        return True

    def enable_extension(self, extension_name):
        extension = self.extensions.get(extension_name, None)
        if not extension:
            return False
        extension.frozen = False
        return True

    def reload(self):
        """ 重新载入接口 """
        if self.market is not None:
            self.market.close()
        if self.trader is not None:
            self.trader.close()
        self._load_ext()

    def __del__(self):
        """释放账户 安全退出"""
        print("注销")
        if self.market is not None:
            self.market.close()
        if self.trader is not None:
            self.trader.close()
        self.market, self.trader = None, None

        del self.event_engine
Example #6
0
class CtpBee(object):
    """
    ctpbee 源于我对于做项目的痛点需求, 旨在开发出一套具有完整api的交易微框架
    I hope it will help you !

    """
    # 默认回测配置参数
    default_params = {
        'cash': 10000.0,
        'check_submit': True,
        'eos_bar': False,
        'filler': None,
        "commision": 0.01,
        # slippage options
        'slip_percent': 0.0,
        'slip_fixed': 0.0,
        'slip_open': False,
        'slip_match': True,
        'slip_limit': True,
        'slip_out': False,
        'coc': False,
        'coo': False,
        'int2pnl': True,
        'short_cash': True,
        'fund_start_val': 100.0,
        'fund_mode': False
    }
    default_config = ImmutableDict(
        dict(LOG_OUTPUT=True,
             TD_FUNC=False,
             INTERFACE="ctp",
             MD_FUNC=True,
             XMIN=[],
             ALL_SUBSCRIBE=False,
             SHARE_MD=False,
             ENGINE_METHOD="thread",
             LOOPER_SETTING=default_params,
             SHARED_FUNC=False,
             REFRESH_INTERVAL=1.5))
    config_class = Config
    import_name = None

    __active = False

    # 交易api与行情api
    market = None
    trader = None

    # 插件Api系统
    extensions = {}

    # 工具, 用于提供一些比较优秀的工具
    tools = {}

    def __init__(self,
                 name: Text,
                 import_name,
                 engine_method: str = "thread",
                 work_mode="limit_time",
                 refresh: bool = False,
                 instance_path=None):
        """ 初始化 """
        self.name = name
        self.import_name = import_name
        self.engine_method = engine_method
        self.refresh = refresh
        if engine_method == "thread":
            self.event_engine = EventEngine()
            self.recorder = Recorder(self, self.event_engine)
        elif engine_method == "async":
            self.event_engine = AsyncEngine()
            self.recorder = AsyncRecorder(self, self.event_engine)
        else:
            raise TypeError("引擎参数错误,只支持 thread 和 async,请检查代码")
        if instance_path is None:
            instance_path = self.auto_find_instance_path()
        elif not os.path.isabs(instance_path):
            raise ValueError(
                'If an instance path is provided it must be absolute.'
                ' A relative path was given instead.')
        self.risk_gateway_class = None
        self.instance_path = instance_path
        self.config = self.make_config()
        self.init_finished = False
        self.p = None
        self.p_flag = True

        self.r = None
        self.r_flag = True
        self.work_mode = work_mode

        _app_context_ctx.push(self.name, self)

    def add_risk_gateway(self, gateway_class, risk=True):
        self.risk_gateway_class = gateway_class
        self.risk_gateway_class.update_app(self)
        if risk:
            self.send_order = self.risk_gateway_class(self.send_order)
            self.cancel_order = self.risk_gateway_class(self.cancel_order)

    def make_config(self):
        """ 生成class类"""
        defaults = dict(self.default_config)
        return self.config_class(self.instance_path, defaults)

    def auto_find_instance_path(self):
        prefix, package_path = find_package(self.import_name)
        if prefix is None:
            return os.path.join(package_path)
        return os.path.join(prefix, 'var', self.name + '-instance')

    @property
    def td_login_status(self):
        """ 交易 API 都应该实现td_status"""
        return self.trader.td_status

    @property
    def md_login_status(self):
        """ 行情 API 都应该实现md_status"""
        return self.market.md_status

    def _load_ext(self):
        """根据当前配置文件下的信息载入行情api和交易api,记住这个api的选项是可选的"""
        self.__active = True
        if "CONNECT_INFO" in self.config.keys():
            info = self.config.get("CONNECT_INFO")
        else:
            raise ConfigError(message="没有相应的登录信息", args=("没有发现登录信息", ))
        MdApi, TdApi = Interface.get_interface(self)
        if self.config.get("MD_FUNC"):
            self.market = MdApi(self.event_engine)
            self.market.connect(info)

        if self.config.get("TD_FUNC"):
            if self.config['INTERFACE'] == "looper":
                self.trader = TdApi(self.event_engine, self)
            else:
                self.trader = TdApi(self.event_engine)
            self.trader.connect(info)

        show_me = \
            f"""
{"*" * 60}                                                               
*                                                          *
*          -------------------------------------           *
*          |                                   |           *
*          |      ctpbee:    {__version__.ljust(16, " ")}  |           *
*          |      work_mode: {self.work_mode.ljust(16, " ")}  |           *
*          |      engine:    {self.engine_method.ljust(16, " ")}  |           *
*          |                                   |           *
*          -------------------------------------           *
*                                                          *
{"*" * 60}                       
         """
        print(show_me)

        # 检查work_mode
        if self.work_mode == "forever":
            """ 7×24小时 """
            # 启动监视器

            if self.p is not None:
                self.p_flag = False
                sleep(1.5)
                self.p = Thread(target=run_forever, args=(self, ))
                self.p.start()

            else:
                self.p = Thread(target=run_forever, args=(self, ))
                self.p.start()
            self.p_flag = True
        else:
            pass

        if self.refresh:
            if self.r is not None:
                self.r_flag = False
                sleep(self.config['REFRESH_INTERVAL'] + 1.5)
                self.r = Thread(target=refresh_query, args=(self, ))
                self.r.start()
            else:
                self.r = Thread(target=refresh_query, args=(self, ))
                self.r.start()
            self.r_flag = True
        self.p_flag = True

    @locked_cached_property
    def name(self):
        if self.import_name == '__main__':
            fn = getattr(sys.modules['__main__'], '__file__', None)
            if fn is None:
                return '__main__'
            return os.path.splitext(os.path.basename(fn))[0]
        return self.import_name

    def start(self, log_output=True):
        """开始"""
        if not self.event_engine.status:
            self.event_engine.start()
        self.config["LOG_OUTPUT"] = log_output
        self._load_ext()

    def stop(self):
        """ 停止运行 """
        if self.event_engine.status:
            self.event_engine.stop()

    @check(type="trader")
    def send_order(self, order_req: OrderRequest) -> AnyStr:
        """发单"""
        send_monitor.send(order_req)
        return self.trader.send_order(order_req)

    @check(type="trader")
    def cancel_order(self, cancle_req: CancelRequest):
        """撤单"""
        cancel_monitor.send(cancle_req)
        return self.trader.cancel_order(cancle_req)

    @check(type="market")
    def subscribe(self, symbol: AnyStr):
        """订阅行情"""
        if "." in symbol:
            symbol = symbol.split(".")[1]
        return self.market.subscribe(symbol)

    @check(type="trader")
    def query_position(self):
        """查询持仓"""
        return self.trader.query_position()

    @check(type="trader")
    def transfer(self, req, type):
        """
        req currency attribute
        ["USD", "HKD", "CNY"]
        :param req:
        :param type:
        :return:
        """
        self.trader.transfer(req, type=type)

    @check(type="trader")
    def query_account_register(self, req):
        self.trader.query_account_register(req)

    @check(type="trader")
    def query_bank_account_money(self, req):
        self.trader.query_bank_account_money(req)

    @check(type="trader")
    def query_transfer_serial(self, req):
        self.trader.query_transfer_serial(req)

    @check(type="trader")
    def query_bank(self):
        pass

    @check(type="trader")
    def query_account(self):
        """查询账户"""
        return self.trader.query_account()

    def remove_extension(self, extension_name: Text) -> None:
        """移除插件"""
        if extension_name in self.extensions:
            del self.extensions[extension_name]

    def add_extension(self, extension: CtpbeeApi):
        """添加插件"""
        if extension.extension_name in self.extensions:
            return
        extension.init_app(self)
        self.extensions[extension.extension_name] = extension

    def suspend_extension(self, extension_name):
        extension = self.extensions.get(extension_name, None)
        if not extension:
            return False
        extension.frozen = True
        return True

    def enable_extension(self, extension_name):
        extension = self.extensions.get(extension_name, None)
        if not extension:
            return False
        extension.frozen = False
        return True

    def reload(self):
        """ 重新载入接口 """
        if self.market is not None:
            self.market.close()
        if self.trader is not None:
            self.trader.close()
        self._load_ext()

    def __del__(self):
        """释放账户 安全退出"""
        print("注销")
        if self.market is not None:
            self.market.close()
        if self.trader is not None:
            self.trader.close()
        self.market, self.trader = None, None

        del self.event_engine