qff.frame.context 源代码

# coding:utf-8

# The MIT License (MIT)
#
# Copyright (c) 2021-2029 XuHaiJiang/QFF
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from datetime import datetime

from qff.frame.const import RUN_TYPE, RUN_STATUS
from qff.tools.date import get_pre_trade_day, get_trade_gap


class TradeCost:
    """
    股票交易费用对象
    """
    def __init__(self, open_tax=0,
                 close_tax=0.001,
                 open_commission=0.0002,
                 close_commission=0.0002,
                 min_commission=5):
        self.open_tax = open_tax  # 买入时印花税 (只股票类标的收取,基金与期货不收)
        self.close_tax = close_tax  # 卖出时印花税 (只股票类标的收取,基金与期货不收)
        self.open_commission = open_commission  # 买入时佣金,申购场外基金的手续费
        self.close_commission = close_commission  # 卖出时佣金,赎回场外基金的手续费
        self.min_commission = min_commission  # 最低佣金,不包含印花税


class Strategy:
    """
     策略对象,由run_daily()注册
    """
    def __init__(self):
        self.initialize = None              # 策略初始化函数
        self.handle_data = None             # 运行策略函数,该函数每个单位时间会调用一次, 如果按天回测,则每天调用一次,如果按分钟,则每分钟调用一次。
        self.before_trading_start = None    # 开盘前运行的策略函数
        self.after_trading_end = None       # 收盘后运行的策略函数
        self.on_strategy_end = None         # 策略运行结束时调用(可选)
        self.process_initialize = None      # 程序恢复启动时运行函数(可选),如果模拟盘每天重启, 所以这个函数会每天都执行.
        self.run_daily = {}             # 每天固定时间点运行的策略函数,由run_daily注册,key为‘HH:MM',value为策略函数


[文档]class Context: """ 策略上下文 系统全局变量context保存策略运行信息,包含账户、策略、时间、运行参数等。用户可以直接读取context相关属性,但 **注意不能直接修改** ================== ===================== ======================================================================= 属性 类型 说明 ================== ===================== ======================================================================= strategy_name str 策略名称 run_type :class:`.RUN_TYPE` 当前框架运行模式,回测还是模拟 status :class:`.RUN_STATUS` 策略当前运行状态 run_freq str 策略运行频率 包括”day" ,"tick"和 "min" start_date str 回测开始日期 end_date str 回测结束日期 current_dt str 策略执行的当前时间 "yyyy-mm-dd HH:MM:SS" previous_date str 策略执行的当前时间的前一天"yyyy-mm-dd” benchmark str 基准指数代码 portfolio :class:`.Portfolio` 交易账户对象 order_list Dict 当日的所有订单列表,key为order_id, value为 :class:`.Order` order_hists List 历史订单列表,以List保存 :class:`.Order` 对象的message属性 positions_hists List 历史仓位列表,以List保存 :class:`.Position` 对象的message属性 asset_hists List 历史账户资产列表,以List保存 :class:`.Portfolio` 对象的message属性 strategy_file str 策略文件名称及路径 log_file str 日志文件名称及路径 run_start str 回测开始时间,格式"yyyy-mm-dd HH:MM:SS" run_end str 回测运行结束时间(用于计算回测耗时) output_dir str 策略运行结果文件输出路径 ================== ===================== ======================================================================= """ status_tb = ['停止', '运行中', '已完成', '失败', '', '取消', '暂停'] def __init__(self): self.strategy_name = None # 策略名称 self.run_type = RUN_TYPE.BACK_TEST # 当前框架运行的是回测还是模拟 self.status = RUN_STATUS.NONE # 回测框架运行状态 self.run_freq = None # 策略运行频率 包括”day" ,"tick"和 "min" self.start_date = None # 回测开始日期 self.end_date = None # 回测结束日期 self._current_dt = None # 回测时对应的策略执行的当前时间 "yyyy-mm-dd HH:MM:SS" self.benchmark = "000300" # 指数基准 self.bm_data = None self.bm_start = None # 基准指数回测前一天的收盘点数 self.slippage = 0.00246 # 固定滑点 self.universe = [] # 股票池,通过set_universe(stock_list)设定 self.trade_cost = TradeCost() # 股票交易费用对象 self.portfolio = None # 股票账户信息对象 self.order_list = {} # 当日的所有订单列表,key为order_ID self.order_hists = [] # 历史订单列表,以List保存order对象的message属性 self.positions_hists = [] # 历史仓位列表,以List保存Position对象的message属性 self.asset_hists = [] # 历史账户资产,以List保存Portfolio对象的message属性 self.pass_today = False # 分钟运行频率时,设置该值则跳过当天分钟循环 self.strategy_file = None # 策略文件名称及路径 self.log_file = None # 日志文件名称及路径 self.run_start = None # 回测运行开始时间(用于计算回测耗时) self.run_end = None # 回测运行结束时间(用于计算回测耗时) self.output_dir = None # 策略运行结果文件输出路径 @property def current_dt(self): if context.run_type == RUN_TYPE.BACK_TEST: return self._current_dt else: return datetime.now().strftime('%Y-%m-%d %H:%M:%S') @current_dt.setter def current_dt(self, str_datetime): if context.run_type == RUN_TYPE.BACK_TEST: self._current_dt = str_datetime return @property def previous_date(self): return get_pre_trade_day(self.current_dt)[:10] @property def get_run_type(self): return '回测' if self.run_type == RUN_TYPE.BACK_TEST else '实盘' @property def get_run_status(self): return self.status_tb[self.status.value] def read_log_file(self): ret = [] for str_line in open(self.log_file): if str_line.startswith('qff>>'): str_time = str_line[7:26] str_level = str_line[29:39].split(' - ')[0] str_content = str_line[32+len(str_level):] ret.append([str_time, str_level, str_content]) return ret @property def run_progress(self): if self.run_type == RUN_TYPE.BACK_TEST: total = get_trade_gap(self.start_date, self.end_date) crt = get_trade_gap(self.start_date, self.current_dt[:10]) return round(crt / total, 4) else: return 1 @property def spend_time(self): if self.run_type == RUN_TYPE.BACK_TEST and self.status == RUN_STATUS.DONE: end = self.run_end else: end = datetime.now() delta = end - self.run_start hour = int(delta.seconds / 3600) minute = int((delta.seconds % 3600) / 60) second = delta.seconds % 60 return "{}{}{}{}秒".format(delta.days, hour, minute, second) @property def message(self): return { "策略名称": self.strategy_name, "框架类型": self.get_run_type, "当前状态": self.get_run_status, "运行频率": self.run_freq, "开始日期": self.start_date, "结束日期": self.end_date, "回测周期": get_trade_gap(self.start_date, self.end_date) if self.run_type == RUN_TYPE.BACK_TEST else None, "当前日期": self.current_dt, "运行天数": get_trade_gap(self.start_date, self.current_dt[:10]), "基准指数": self.benchmark, "初始资金": self.portfolio.starting_cash, }
[文档]class GlobalVar: """ 全局对象 g,用来存储用户的各类可被pickle.dumps函数序列化的全局数据 在模拟盘中,如果中途进程中断,我们会使用[pickle.dumps]序列化所有的g下面的变量内容, 保存到磁盘中,再启动的时候模拟盘就不会 有任何数据影响。如果没有用g声明,会出现模拟盘重启后,变量数据丢失的问题。 **如果不想 g 中的某个变量被序列化, 可以让变量以 '__' 开头, 这样, 这个变量在序列化时就会被忽略** 示例: :: def initialize(context): g.security = "000001" g.count = 1 g.flag = 0 def process_initialize(context): # 保存不能被序列化的对象, 进程每次重启都初始化, 更多信息, 请看 [process_initialize] g.__q = ["000001", "000002"] def handle_data(context, data): log.info(g.security) log.info(g.count) log.info(g.flag) """ def __init__(self): self.type = None
strategy = Strategy() context = Context() g = GlobalVar() def run_strategy_funcs(strategy_funcs): # try: if isinstance(strategy_funcs, list): for func in strategy_funcs: if callable(func): func() elif callable(strategy_funcs): strategy_funcs() # except Exception as e: # print(e)