示例#1
0
文件: IC.py 项目: rlcjj/QuantStudio
 def __QS_initArgs__(self):
     DefaultNumFactorList, DefaultStrFactorList = getFactorList(
         dict(self._FactorTable.getFactorMetaData(key="DataType")))
     self.add_trait(
         "TestFactors",
         ListStr(arg_type="MultiOption",
                 label="测试因子",
                 order=0,
                 option_range=tuple(DefaultNumFactorList)))
     self.TestFactors.append(DefaultNumFactorList[0])
     self.add_trait(
         "PriceFactor",
         Enum(*DefaultNumFactorList,
              arg_type="SingleOption",
              label="价格因子",
              order=2))
     self.PriceFactor = searchNameInStrList(DefaultNumFactorList,
                                            ['价', 'Price', 'price'])
     self.add_trait(
         "IndustryFactor",
         Enum(*(["无"] + DefaultStrFactorList),
              arg_type="SingleOption",
              label="行业因子",
              order=3))
     self.add_trait(
         "WeightFactor",
         Enum(*(["等权"] + DefaultNumFactorList),
              arg_type="SingleOption",
              label="权重因子",
              order=4))
示例#2
0
 def __QS_initArgs__(self):
     DefaultNumFactorList, DefaultStrFactorList = getFactorList(
         dict(self._FactorTable.getFactorMetaData(key="DataType")))
     self.add_trait(
         "TestFactors",
         ListStr(arg_type="MultiOption",
                 label="测试因子",
                 order=0,
                 option_range=tuple(DefaultNumFactorList)))
     self.TestFactors.append(DefaultNumFactorList[0])
示例#3
0
 def _on_RiskDS_changed(self, obj, name, old, new):
     if new is None:
         self.add_trait(
             "CorrMethod",
             ListStr(arg_type="MultiOption",
                     label="相关性算法",
                     order=3,
                     option_range=("spearman", "pearson", "kendall")))
     else:
         self.add_trait(
             "CorrMethod",
             ListStr(arg_type="MultiOption",
                     label="相关性算法",
                     order=3,
                     option_range=("spearman", "pearson", "kendall",
                                   "factor-score correlation",
                                   "factor-portfolio correlation")))
     self.CorrMethod = list(
         set(self.CorrMethod).intersection(set(
             self.CorrMethod.option_range)))
示例#4
0
class MdAPIDB(FactorDB):
    """MdAPIDB"""
    UserID = Str("118073", arg_type="String", label="UserID", order=0)
    Pwd = Password("shuntai11", arg_type="String", label="Password", order=1)
    BrokerID = Str("9999", arg_type="String", label="BrokerID", order=2)
    FrontAddr = Str("tcp://180.168.146.187:10010",
                    arg_type="String",
                    label="前置机地址",
                    order=3)
    ConDir = Directory(label="流文件目录", arg_type="Directory", order=4)
    IDs = ListStr(label="订阅ID", arg_type="MultiOption", order=5)

    def __init__(self, sys_args={}, config_file=None, **kwargs):
        super().__init__(sys_args=sys_args,
                         config_file=(__QS_ConfigPath__ + os.sep +
                                      "MdAPIDBConfig.json"
                                      if config_file is None else config_file),
                         **kwargs)
        self._MdAPI = None
        self._CacheData = {}
        # 继承来的属性
        self.Name = "MdAPIDB"
        return

    def connect(self):
        self._MdAPI = _MdApi(fdb=self)  # 创建 API 对象
        self._MdAPI.createFtdcMdApi(
            self.ConDir)  # 在 C++ 环境中创建对象, 传入参数是希望用来保存 .con 文件的地址
        self._MdAPI.registerFront(self.FrontAddr)  # 注册前置机地址
        self._MdAPI.init()  # 初始化, 连接前置机
        return 0

    def disconnect(self):
        self._MdAPI.disconnect()
        self._MdAPI = None
        return 0

    def isAvailable(self):
        return (self._MdAPI is not None)

    # -------------------------------表的操作---------------------------------
    @property
    def TableNames(self):
        return ["MarketData"]

    def getTable(self, table_name, args={}):
        return _TickTable(name=table_name, fdb=self, sys_args=args)
示例#5
0
 def __QS_initArgs__(self):
     DefaultNumFactorList, DefaultStrFactorList = getFactorList(
         dict(self._FactorTable.getFactorMetaData(key="DataType")))
     self.add_trait(
         "Portfolio",
         Enum(*DefaultNumFactorList,
              arg_type="SingleOption",
              label="策略组合",
              order=0))
     self.add_trait(
         "BenchmarkPortfolio",
         Enum(*(["无"] + DefaultNumFactorList),
              arg_type="SingleOption",
              label="基准组合",
              order=1))
     self.add_trait(
         "AttributeFactors",
         ListStr(arg_type="MultiOption",
                 label="特征因子",
                 order=2,
                 option_range=tuple(DefaultNumFactorList)))
     self.AttributeFactors.append(DefaultNumFactorList[-1])
     self.add_trait(
         "IndustryFactor",
         Enum(*(["无"] + DefaultStrFactorList),
              arg_type="SingleOption",
              label="行业因子",
              order=3))
     self.add_trait(
         "PriceFactor",
         Enum(*DefaultNumFactorList,
              arg_type="SingleOption",
              label="价格因子",
              order=4))
     self.PriceFactor = searchNameInStrList(DefaultNumFactorList,
                                            ['价', 'Price', 'price'])
示例#6
0
class OLS(BaseModule):
    """时间序列 OLS"""
    TestFactors = ListStr(arg_type="MultiOption",
                          label="测试因子",
                          order=0,
                          option_range=())
    #PriceFactor = Enum(None, arg_type="SingleOption", label="价格因子", order=1)
    ReturnType = Enum("简单收益率",
                      "对数收益率",
                      "价格变化量",
                      arg_type="SingleOption",
                      label="收益率类型",
                      order=2)
    ForecastPeriod = Int(1, arg_type="Integer", label="预测期数", order=3)
    Lag = Int(0, arg_type="Integer", label="滞后期数", order=4)
    CalcDTs = List(dt.datetime, arg_type="DateList", label="计算时点", order=5)
    SummaryWindow = Float(np.inf, arg_type="Integer", label="统计窗口", order=6)
    MinSummaryWindow = Int(2, arg_type="Integer", label="最小统计窗口", order=7)
    Constant = Bool(True, arg_type="Bool", label="常数项", order=8)

    def __init__(self,
                 factor_table,
                 price_table,
                 name="时间序列OLS",
                 sys_args={},
                 **kwargs):
        self._FactorTable = factor_table
        self._PriceTable = price_table
        super().__init__(name=name, sys_args=sys_args, **kwargs)

    def __QS_initArgs__(self):
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._FactorTable.getFactorMetaData(key="DataType")))
        self.add_trait(
            "TestFactors",
            ListStr(arg_type="MultiOption",
                    label="测试因子",
                    order=0,
                    option_range=tuple(DefaultNumFactorList)))
        self.TestFactors.append(DefaultNumFactorList[0])
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._PriceTable.getFactorMetaData(key="DataType")))
        self.add_trait(
            "PriceFactor",
            Enum(*DefaultNumFactorList,
                 arg_type="SingleOption",
                 label="价格因子",
                 order=1))
        self.PriceFactor = searchNameInStrList(DefaultNumFactorList,
                                               ['价', 'Price', 'price'])

    def getViewItems(self, context_name=""):
        Items, Context = super().getViewItems(context_name=context_name)
        Items[0].editor = SetEditor(
            values=self.trait("TestFactors").option_range)
        return (Items, Context)

    def __QS_start__(self, mdl, dts, **kwargs):
        if self._isStarted: return ()
        super().__QS_start__(mdl=mdl, dts=dts, **kwargs)
        self._Output = {}
        self._Output["证券ID"] = self._PriceTable.getID()
        nID = len(self._Output["证券ID"])
        self._Output["收益率"] = np.zeros(shape=(0, nID))
        self._Output["滚动统计量"] = {
            "R平方": np.zeros((0, nID)),
            "调整R平方": np.zeros((0, nID)),
            "t统计量": {},
            "F统计量": np.zeros((0, nID))
        }
        self._Output["因子ID"] = self._FactorTable.getID()
        nFactorID = len(self._Output["因子ID"])
        self._Output["因子值"] = np.zeros((0, nFactorID * len(self.TestFactors)))
        self._CurCalcInd = 0
        self._nMinSample = (max(2, self.MinSummaryWindow) if np.isinf(
            self.MinSummaryWindow) else max(2, self.MinSummaryWindow))
        return (self._FactorTable, self._PriceTable)

    def __QS_move__(self, idt, **kwargs):
        if self._iDT == idt: return 0
        if self.CalcDTs:
            if idt not in self.CalcDTs[self._CurCalcInd:]: return 0
            self._CurCalcInd = self.CalcDTs[self._CurCalcInd:].index(
                idt) + self._CurCalcInd
            PreInd = self._CurCalcInd - self.ForecastPeriod - self.Lag
            LastInd = self._CurCalcInd - self.ForecastPeriod
            PreDateTime = self.CalcDTs[PreInd]
            LastDateTime = self.CalcDTs[LastInd]
        else:
            self._CurCalcInd = self._Model.DateTimeIndex
            PreInd = self._CurCalcInd - self.ForecastPeriod - self.Lag
            LastInd = self._CurCalcInd - self.ForecastPeriod
            PreDateTime = self._Model.DateTimeSeries[PreInd]
            LastDateTime = self._Model.DateTimeSeries[LastInd]
        if (PreInd < 0) or (LastInd < 0): return 0
        Price = self._PriceTable.readData(dts=[LastDateTime, idt],
                                          ids=self._Output["证券ID"],
                                          factor_names=[self.PriceFactor
                                                        ]).iloc[0, :, :].values
        self._Output["收益率"] = np.r_[
            self._Output["收益率"],
            _calcReturn(Price, return_type=self.ReturnType)]
        FactorData = self._FactorTable.readData(
            dts=[PreDateTime],
            ids=self._Output["因子ID"],
            factor_names=list(
                self.TestFactors)).iloc[:, 0, :].values.flatten(order="F")
        self._Output["因子值"] = np.r_[self._Output["因子值"],
                                    FactorData.reshape(
                                        (1, FactorData.shape[0]))]
        if self._Output["收益率"].shape[0] < self._nMinSample: return 0
        StartInd = int(
            max(0, self._Output["收益率"].shape[0] - self.SummaryWindow))
        X = self._Output["因子值"][StartInd:]
        if self.Constant: X = sm.add_constant(X, prepend=True)
        nID = len(self._Output["证券ID"])
        Statistics = {
            "R平方": np.full((1, nID), np.nan),
            "调整R平方": np.full((1, nID), np.nan),
            "t统计量": np.full((X.shape[1], nID), np.nan),
            "F统计量": np.full((1, nID), np.nan)
        }
        for i, iID in enumerate(self._Output["证券ID"]):
            Y = self._Output["收益率"][StartInd:, i]
            try:
                Result = sm.OLS(Y, X, missing="drop").fit()
                Statistics["R平方"][0, i] = Result.rsquared
                Statistics["调整R平方"][0, i] = Result.rsquared_adj
                Statistics["F统计量"][0, i] = Result.fvalue
                Statistics["t统计量"][:, i] = Result.tvalues
            except:
                pass
        self._Output["滚动统计量"]["R平方"] = np.r_[self._Output["滚动统计量"]["R平方"],
                                             Statistics["R平方"]]
        self._Output["滚动统计量"]["调整R平方"] = np.r_[self._Output["滚动统计量"]["调整R平方"],
                                               Statistics["调整R平方"]]
        self._Output["滚动统计量"]["F统计量"] = np.r_[self._Output["滚动统计量"]["F统计量"],
                                              Statistics["F统计量"]]
        self._Output["滚动统计量"]["t统计量"][idt] = Statistics["t统计量"]
        return 0

    def __QS_end__(self):
        if not self._isStarted: return 0
        FactorIDs, PriceIDs = self._Output.pop("因子ID"), self._Output.pop(
            "证券ID")
        DTs = sorted(self._Output["滚动统计量"]["t统计量"])
        self._Output["最后一期统计量"] = pd.DataFrame(
            {
                "R平方": self._Output["滚动统计量"]["R平方"][-1],
                "调整R平方": self._Output["滚动统计量"]["调整R平方"][-1],
                "F统计量": self._Output["滚动统计量"]["F统计量"][-1]
            },
            index=PriceIDs).loc[:, ["R平方", "调整R平方", "F统计量"]]
        Index = pd.MultiIndex.from_product([self.TestFactors, FactorIDs],
                                           names=["因子", "因子ID"])
        if self.Constant: Index = Index.insert(0, ("Constant", "Constant"))
        self._Output["最后一期t统计量"] = pd.DataFrame(
            self._Output["滚动统计量"]["t统计量"][DTs[-1]],
            index=Index,
            columns=PriceIDs).reset_index()
        self._Output["全样本统计量"], self._Output["全样本t统计量"] = pd.DataFrame(
            index=PriceIDs, columns=["R平方", "调整R平方",
                                     "F统计量"]), pd.DataFrame(index=Index,
                                                            columns=PriceIDs)
        X = self._Output["因子值"]
        if self.Constant: X = sm.add_constant(X, prepend=True)
        for i, iID in enumerate(PriceIDs):
            Y = self._Output["收益率"][:, i]
            try:
                Result = sm.OLS(Y, X, missing="drop").fit()
                self._Output["全样本统计量"].iloc[i, 0] = Result.rsquared
                self._Output["全样本统计量"].iloc[i, 1] = Result.rsquared_adj
                self._Output["全样本统计量"].iloc[i, 2] = Result.fvalue
                self._Output["全样本t统计量"].iloc[:, i] = Result.tvalues
            except:
                pass
        self._Output["滚动统计量"]["R平方"] = pd.DataFrame(
            self._Output["滚动统计量"]["R平方"], index=DTs, columns=PriceIDs)
        self._Output["滚动统计量"]["调整R平方"] = pd.DataFrame(
            self._Output["滚动统计量"]["调整R平方"], index=DTs, columns=PriceIDs)
        self._Output["滚动统计量"]["F统计量"] = pd.DataFrame(
            self._Output["滚动统计量"]["F统计量"], index=DTs, columns=PriceIDs)
        self._Output["滚动t统计量"] = pd.Panel(self._Output["滚动统计量"].pop("t统计量"),
                                          major_axis=Index,
                                          minor_axis=PriceIDs)
        self._Output["滚动t统计量"] = self._Output["滚动t统计量"].swapaxes(
            0, 2).to_frame(filter_observations=False).reset_index()
        self._Output["滚动t统计量"].columns = ["因子", "因子ID", "时点"] + PriceIDs
        self._Output.pop("收益率"), self._Output.pop("因子值")
        return 0
示例#7
0
class IndustryDistribution(BaseModule):
    """因子值行业分布"""
    TestFactors = ListStr(arg_type="MultiOption", label="测试因子", order=0, option_range=())
    #IndustryFactor = Enum(None, arg_type="SingleOption", label="行业因子", order=1)
    Threshold = Enum("中位数","平均数","25%分位数","75%分位数",arg_type="SingleOption", label="阈值", order=2)
    CalcDTs = List(dt.datetime, arg_type="DateList", label="计算时点", order=3)
    IDFilter = Str(arg_type="IDFilter", label="筛选条件", order=4)
    def __init__(self, factor_table, name="因子值行业分布", sys_args={}, **kwargs):
        self._FactorTable = factor_table
        return super().__init__(name=name, sys_args=sys_args, **kwargs)
    def __QS_initArgs__(self):
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(dict(self._FactorTable.getFactorMetaData(key="DataType")))
        self.add_trait("TestFactors", ListStr(arg_type="MultiOption", label="测试因子", order=0, option_range=tuple(DefaultNumFactorList)))
        self.TestFactors.append(DefaultNumFactorList[0])
        self.add_trait("IndustryFactor", Enum(*DefaultStrFactorList, arg_type="SingleOption", label="行业因子", order=1))
    def getViewItems(self, context_name=""):
        Items, Context = super().getViewItems(context_name=context_name)
        Items[0].editor = SetEditor(values=self.trait("TestFactors").option_range)
        return (Items, Context)
    def __QS_start__(self, mdl, dts, **kwargs):
        if self._isStarted: return ()
        super().__QS_start__(mdl=mdl, dts=dts, **kwargs)
        AllIndustries = pd.unique(self._FactorTable.readData(factor_names=[self.IndustryFactor], dts=self._FactorTable.getDateTime(ifactor_name=self.IndustryFactor), ids=self._FactorTable.getID(ifactor_name=self.IndustryFactor)).iloc[0].values.flatten())
        Mask = pd.isnull(AllIndustries)
        if np.sum(Mask)>0: AllIndustries = AllIndustries[~Mask].tolist()+[None]
        self._Output = {iFactorName:{iIndustry:[] for iIndustry in AllIndustries} for iFactorName in self.TestFactors}
        self._Output["历史平均值"] = {iFactorName:[] for iFactorName in self.TestFactors}
        self._Output["历史标准差"] = {iFactorName:[] for iFactorName in self.TestFactors}
        self._Output["行业分类"] = AllIndustries
        self._Output["时点"] = []
        self._CurCalcInd = 0
        return (self._FactorTable, )
    def __QS_move__(self, idt, **kwargs):
        if self._iDT==idt: return 0
        self._iDT = idt
        if self.CalcDTs:
            if idt not in self.CalcDTs[self._CurCalcInd:]: return 0
            self._CurCalcInd = self.CalcDTs[self._CurCalcInd:].index(idt) + self._CurCalcInd
        else:
            self._CurCalcInd = self._Model.DateTimeIndex
        IDs = self._FactorTable.getFilteredID(idt=idt, id_filter_str=self.IDFilter)
        FactorExpose = self._FactorTable.readData(dts=[idt], ids=IDs, factor_names=list(self.TestFactors)+[self.IndustryFactor]).iloc[:,0,:]
        IndustryData, FactorExpose = FactorExpose.iloc[:, -1], FactorExpose.iloc[:, :-1].astype("float")
        Threshold = {}
        Mask = {}
        for iFactorName in self.TestFactors:
            Mask[iFactorName] = pd.notnull(FactorExpose[iFactorName])
            if self.Threshold=="中位数":
                Threshold[iFactorName] = FactorExpose[iFactorName].median()
            elif self.Threshold=="平均值":
                Threshold[iFactorName] = FactorExpose[iFactorName].mean()
            elif self.Threshold=="25%分位数":
                Threshold[iFactorName] = FactorExpose[iFactorName].quantile(0.25)
            elif self.Threshold=="75%分位数":
                Threshold[iFactorName] = FactorExpose[iFactorName].quantile(0.75)
        for jIndustry in self._Output["行业分类"]:
            if pd.isnull(jIndustry): jMask = pd.isnull(IndustryData)
            else: jMask = (IndustryData==jIndustry)
            for iFactorName in self.TestFactors:
                ijMask = (jMask & Mask[iFactorName])
                ijNum = ijMask.sum()
                if ijNum!=0: self._Output[iFactorName][jIndustry].append((FactorExpose[iFactorName][ijMask]>=Threshold[iFactorName]).sum()/ijNum)
                else: self._Output[iFactorName][jIndustry].append(np.nan)
        self._Output["时点"].append(idt)
        return 0
    def __QS_end__(self):
        if not self._isStarted: return 0
        super().__QS_end__()
        for iFactorName in self.TestFactors:
            self._Output[iFactorName] = pd.DataFrame(self._Output[iFactorName], index=self._Output["时点"], columns=self._Output["行业分类"])
            self._Output["历史平均值"][iFactorName] = self._Output[iFactorName].mean()
            self._Output["历史标准差"][iFactorName] = self._Output[iFactorName].std()
        self._Output["历史平均值"] = pd.DataFrame(self._Output["历史平均值"], columns=list(self.TestFactors))
        self._Output["历史标准差"] = pd.DataFrame(self._Output["历史标准差"], columns=list(self.TestFactors))
        self._Output.pop("行业分类")
        self._Output.pop("时点")
        return 0
    def genMatplotlibFig(self, file_path=None):
        nRow, nCol = self._Output["历史平均值"].shape[1]//3+(self._Output["历史平均值"].shape[1]%3!=0), min(3, self._Output["历史平均值"].shape[1])
        Fig = Figure(figsize=(min(32, 16+(nCol-1)*8), 8*nRow))
        xData = np.arange(0, self._Output["历史平均值"].shape[0])
        xTickLabels = [str(iIndustry) for iIndustry in self._Output["历史平均值"].index]
        yMajorFormatter = FuncFormatter(_QS_formatMatplotlibPercentage)
        for i, iFactorName in enumerate(self._Output["历史平均值"].columns):
            iAxes = Fig.add_subplot(nRow, nCol, i+1)
            iAxes.yaxis.set_major_formatter(yMajorFormatter)
            iAxes.bar(xData, self._Output["历史平均值"].iloc[:, i].values, color="steelblue", label="历史平均值")
            iAxes.set_title(iFactorName+"-历史平均值")
            iAxes.set_xticks(xData)
            iAxes.set_xticklabels(xTickLabels)
        if file_path is not None: Fig.savefig(file_path, dpi=150, bbox_inches='tight')
        return Fig
    def _repr_html_(self):
        if len(self.ArgNames)>0:
            HTML = "参数设置: "
            HTML += '<ul align="left">'
            for iArgName in self.ArgNames:
                if iArgName!="计算时点":
                    HTML += "<li>"+iArgName+": "+str(self.Args[iArgName])+"</li>"
                elif self.Args[iArgName]:
                    HTML += "<li>"+iArgName+": 自定义时点</li>"
                else:
                    HTML += "<li>"+iArgName+": 所有时点</li>"
            HTML += "</ul>"
        else:
            HTML = ""
        Formatters = [_QS_formatPandasPercentage]*self._Output["历史平均值"].shape[1]
        iHTML = self._Output["历史平均值"].to_html(formatters=Formatters)
        Pos = iHTML.find(">")
        HTML += iHTML[:Pos]+' align="center"'+iHTML[Pos:]
        Fig = self.genMatplotlibFig()
        # figure 保存为二进制文件
        Buffer = BytesIO()
        Fig.savefig(Buffer, bbox_inches='tight')
        PlotData = Buffer.getvalue()
        # 图像数据转化为 HTML 格式
        ImgStr = "data:image/png;base64,"+base64.b64encode(PlotData).decode()
        HTML += ('<img src="%s">' % ImgStr)
        return HTML
示例#8
0
class QuantileTiming(BaseModule):
    """分位数择时"""
    #TestFactor = Enum(None, arg_type="SingleOption", label="测试因子", order=0)
    FactorIDs = ListStr(arg_type="List", label="因子ID", order=1)
    FactorOrder = Dict(key_trait=Str(),
                       value_trait=Enum("降序", "升序"),
                       arg_type="ArgDict",
                       label="排序方向",
                       order=2)
    #PriceFactor = Enum(None, arg_type="SingleOption", label="价格因子", order=3)
    CalcDTs = List(dt.datetime, arg_type="DateList", label="计算时点", order=4)
    SummaryWindow = Float(np.inf, arg_type="Integer", label="统计窗口", order=5)
    MinSummaryWindow = Int(2, arg_type="Integer", label="最小统计窗口", order=6)
    GroupNum = Int(3, arg_type="Integer", label="分组数", order=7)

    def __init__(self,
                 factor_table,
                 price_table,
                 name="分位数择时",
                 sys_args={},
                 **kwargs):
        self._FactorTable = factor_table
        self._PriceTable = price_table
        super().__init__(name=name, sys_args=sys_args, **kwargs)

    def __QS_initArgs__(self):
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._FactorTable.getFactorMetaData(key="DataType")))
        self.add_trait(
            "TestFactor",
            Enum(*DefaultNumFactorList,
                 arg_type="SingleOption",
                 label="测试因子",
                 order=0))
        self.FactorIDs = self._FactorTable.getID()
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._PriceTable.getFactorMetaData(key="DataType")))
        self.add_trait(
            "PriceFactor",
            Enum(*DefaultNumFactorList,
                 arg_type="SingleOption",
                 label="价格因子",
                 order=3))
        self.PriceFactor = searchNameInStrList(DefaultNumFactorList,
                                               ["价", "Price", "price"])

    def __QS_start__(self, mdl, dts, **kwargs):
        if self._isStarted: return ()
        super().__QS_start__(mdl=mdl, dts=dts, **kwargs)
        self._Output = {}
        SecurityIDs = self._PriceTable.getID()
        nDT, nSecurityID, nFactorID = len(dts), len(SecurityIDs), len(
            self.FactorIDs)
        self._Output["证券ID"] = SecurityIDs
        self._Output["证券收益率"] = np.zeros(shape=(nDT, nSecurityID))
        self._Output["因子值"] = np.full(shape=(nDT, nFactorID),
                                      fill_value=np.nan)
        self._Output["因子符号"] = 2 * np.array(
            [(self.FactorOrder.get(iID, "降序") == "升序")
             for i, iID in enumerate(self.FactorIDs)]).astype(float) - 1
        self._Output["最新信号"] = np.full(shape=(nFactorID, self.GroupNum),
                                       fill_value=np.nan)
        self._Output["信号"] = np.full(shape=(nDT, nFactorID, self.GroupNum),
                                     fill_value=np.nan)
        self._Output["信号收益率"] = np.full(shape=(nFactorID, nSecurityID, nDT,
                                               self.GroupNum),
                                        fill_value=np.nan)
        self._CurCalcInd = 0
        return (self._FactorTable, self._PriceTable)

    def __QS_move__(self, idt, **kwargs):
        if self._iDT == idt: return 0
        self._iDT = idt
        DTIdx = self._Model.DateTimeIndex
        if DTIdx > 0:
            Price = self._PriceTable.readData(
                dts=self._Model.DateTimeSeries[DTIdx - 1:DTIdx + 1],
                ids=self._Output["证券ID"],
                factor_names=[self.PriceFactor]).iloc[0, :, :].values
            Return = _calcReturn(Price, return_type="简单收益率")
            self._Output["证券收益率"][DTIdx:DTIdx + 1, :] = Return
            Mask = pd.notnull(self._Output["最新信号"])
            if np.any(Mask):
                self._Output["信号收益率"][
                    np.arange(len(self.FactorIDs))[Mask], :, DTIdx,
                    self._Output["最新信号"][Mask].astype(int)] = np.repeat(
                        Return, np.sum(Mask), axis=0)
        self._Output["因子值"][DTIdx, :] = self._FactorTable.readData(
            dts=self._Model.DateTimeSeries[DTIdx:DTIdx + 1],
            ids=list(self.FactorIDs),
            factor_names=[self.TestFactor]).iloc[0, 0, :].values
        if self.CalcDTs:
            if idt not in self.CalcDTs[self._CurCalcInd:]: return 0
            self._CurCalcInd = self.CalcDTs[self._CurCalcInd:].index(
                idt) + self._CurCalcInd
        else:
            self._CurCalcInd = self._Model.DateTimeIndex
        if DTIdx + 1 < self.MinSummaryWindow: return 0
        FactorData = self._Output["因子值"][
            int(max(0, DTIdx + 1 -
                    self.SummaryWindow)):DTIdx + 1, :] * self._Output["因子符号"]
        Quantiles = np.nanpercentile(FactorData,
                                     np.arange(1, self.GroupNum + 1) /
                                     self.GroupNum * 100,
                                     axis=0)
        Signal = np.sum(Quantiles < FactorData[-1, :], axis=0).astype(float)
        Signal[pd.isnull(FactorData[-1, :])] = np.nan
        self._Output["最新信号"] = Signal
        Mask = pd.notnull(Signal)
        if np.any(Mask):
            self._Output["信号"][DTIdx,
                               np.arange(len(self.FactorIDs))[Mask],
                               Signal[Mask].astype(int)] = 1
        return 0

    def __QS_end__(self):
        if not self._isStarted: return 0
        super().__QS_end__()
        DTs = self._Model.DateTimeSeries
        self._Output["证券收益率"] = pd.DataFrame(self._Output["证券收益率"],
                                             index=DTs,
                                             columns=self._Output["证券ID"])
        self._Output["证券净值"] = (self._Output["证券收益率"] + 1).cumprod()
        self._Output["因子值"] = pd.DataFrame(self._Output["因子值"],
                                           index=DTs,
                                           columns=self.FactorIDs)
        Groups = np.arange(1, self.GroupNum + 1).astype(str)
        Signal = self._Output["信号"]
        self._Output["信号"] = {}
        for i, iID in enumerate(self.FactorIDs):
            iSignal = pd.DataFrame(Signal[:, i, :], index=DTs, columns=Groups)
            iDTs = pd.notnull(iSignal).any(axis=1)
            iDTs = iDTs[iDTs].index
            if iDTs.shape[0] > 0:
                self._Output["信号"][iID] = iSignal.loc[iDTs[0]:]
            else:
                self._Output["信号"][iID] = pd.DataFrame(columns=Groups)
        SignalReturn = self._Output["信号收益率"]
        self._Output["信号收益率"] = {}
        self._Output["信号净值"] = {}
        self._Output["统计数据"] = {}
        nDate = len(DTs)
        nYear = (DTs[-1] - DTs[0]).days / 365
        for j, jSecurityID in enumerate(self._Output["证券ID"]):
            self._Output["信号收益率"][jSecurityID] = {}
            self._Output["信号净值"][jSecurityID] = {}
            self._Output["统计数据"][jSecurityID] = {}
            for i, iID in enumerate(self.FactorIDs):
                ijSignalReturn = pd.DataFrame(
                    SignalReturn[i, j, :, :], index=DTs,
                    columns=Groups).loc[self._Output["信号"][iID].index]
                if ijSignalReturn.shape[0] == 0:
                    self._Output["信号收益率"][jSecurityID][iID] = ijSignalReturn
                    self._Output["信号净值"][jSecurityID][iID] = ijSignalReturn
                    self._Output["统计数据"][jSecurityID][iID] = pd.DataFrame(
                        index=ijSignalReturn.columns,
                        columns=[
                            "总收益率", "年化收益率", "波动率", "Sharpe比率", "胜率", "最大回撤率",
                            "最大回撤开始时间", "最大回撤结束时间"
                        ])
                else:
                    ijNV = (1 + ijSignalReturn.fillna(0)).cumprod()
                    self._Output["信号收益率"][jSecurityID][iID] = ijSignalReturn
                    self._Output["信号净值"][jSecurityID][iID] = ijNV
                    ijStat = pd.DataFrame(index=ijNV.columns)
                    ijStat["总收益率"] = ijNV.iloc[-1, :] - 1
                    ijStat["年化收益率"] = ijNV.iloc[-1, :]**(1 / nYear) - 1
                    ijStat["波动率"] = ijSignalReturn.std() * np.sqrt(
                        nDate / nYear)
                    ijStat["Sharpe比率"] = ijStat["年化收益率"] / ijStat["波动率"]
                    ijStat["胜率"] = (ijSignalReturn > 0
                                    ).sum() / pd.notnull(ijSignalReturn).sum()
                    ijStat["最大回撤率"] = pd.Series(np.nan, index=ijStat.index)
                    ijStat["最大回撤开始时间"] = pd.Series(index=ijStat.index,
                                                   dtype="O")
                    ijStat["最大回撤结束时间"] = pd.Series(index=ijStat.index,
                                                   dtype="O")
                    for iCol in ijNV.columns:
                        iMaxDD, iStartPos, iEndPos = calcMaxDrawdownRate(
                            ijNV.loc[:, iCol].values)
                        ijStat.loc[iCol, "最大回撤率"] = abs(iMaxDD)
                        ijStat.loc[iCol, "最大回撤开始时间"] = (ijNV.index[iStartPos]
                                                        if iStartPos
                                                        is not None else None)
                        ijStat.loc[iCol, "最大回撤结束时间"] = (ijNV.index[iEndPos]
                                                        if iEndPos is not None
                                                        else None)
                    self._Output["统计数据"][jSecurityID][iID] = ijStat
        self._Output.pop("最新信号")
        self._Output.pop("因子符号")
        self._Output.pop("证券ID")
        return 0
示例#9
0
文件: IC.py 项目: rlcjj/QuantStudio
class IC(BaseModule):
    """IC"""
    TestFactors = ListStr(arg_type="MultiOption",
                          label="测试因子",
                          order=0,
                          option_range=())
    FactorOrder = Dict(key_trait=Str(),
                       value_trait=Enum("降序", "升序"),
                       arg_type="ArgDict",
                       label="排序方向",
                       order=1)
    #PriceFactor = Enum(None, arg_type="SingleOption", label="价格因子", order=2)
    #IndustryFactor = Enum("无", arg_type="SingleOption", label="行业因子", order=3)
    #WeightFactor = Enum("等权", arg_type="SingleOption", label="权重因子", order=4)
    CalcDTs = List(dt.datetime, arg_type="DateList", label="计算时点", order=5)
    LookBack = Int(1, arg_type="Integer", label="回溯期数", order=6)
    CorrMethod = Enum("spearman",
                      "pearson",
                      "kendall",
                      arg_type="SingleOption",
                      label="相关性算法",
                      order=7)
    IDFilter = Str(arg_type="IDFilter", label="筛选条件", order=8)
    RollAvgPeriod = Int(12, arg_type="Integer", label="滚动平均期数", order=9)

    def __init__(self, factor_table, name="IC", sys_args={}, **kwargs):
        self._FactorTable = factor_table
        super().__init__(name=name, sys_args=sys_args, **kwargs)

    def __QS_initArgs__(self):
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._FactorTable.getFactorMetaData(key="DataType")))
        self.add_trait(
            "TestFactors",
            ListStr(arg_type="MultiOption",
                    label="测试因子",
                    order=0,
                    option_range=tuple(DefaultNumFactorList)))
        self.TestFactors.append(DefaultNumFactorList[0])
        self.add_trait(
            "PriceFactor",
            Enum(*DefaultNumFactorList,
                 arg_type="SingleOption",
                 label="价格因子",
                 order=2))
        self.PriceFactor = searchNameInStrList(DefaultNumFactorList,
                                               ['价', 'Price', 'price'])
        self.add_trait(
            "IndustryFactor",
            Enum(*(["无"] + DefaultStrFactorList),
                 arg_type="SingleOption",
                 label="行业因子",
                 order=3))
        self.add_trait(
            "WeightFactor",
            Enum(*(["等权"] + DefaultNumFactorList),
                 arg_type="SingleOption",
                 label="权重因子",
                 order=4))

    @on_trait_change("TestFactors[]")
    def _on_TestFactors_changed(self, obj, name, old, new):
        self.FactorOrder = {
            iFactorName: self.FactorOrder.get(iFactorName, "降序")
            for iFactorName in self.TestFactors
        }

    def getViewItems(self, context_name=""):
        Items, Context = super().getViewItems(context_name=context_name)
        Items[0].editor = SetEditor(
            values=self.trait("TestFactors").option_range)
        return (Items, Context)

    def __QS_start__(self, mdl, dts, **kwargs):
        if self._isStarted: return ()
        super().__QS_start__(mdl=mdl, dts=dts, **kwargs)
        self._Output = {}
        self._Output["IC"] = {
            iFactorName: []
            for iFactorName in self.TestFactors
        }
        self._Output["股票数"] = {
            iFactorName: []
            for iFactorName in self.TestFactors
        }
        self._Output["时点"] = []
        self._CurCalcInd = 0
        return (self._FactorTable, )

    def __QS_move__(self, idt, **kwargs):
        if self._iDT == idt: return 0
        if self.CalcDTs:
            if idt not in self.CalcDTs[self._CurCalcInd:]: return 0
            self._CurCalcInd = self.CalcDTs[self._CurCalcInd:].index(
                idt) + self._CurCalcInd
            PreInd = self._CurCalcInd - self.LookBack
            LastInd = self._CurCalcInd - 1
            PreDateTime = self.CalcDTs[PreInd]
            LastDateTime = self.CalcDTs[LastInd]
        else:
            self._CurCalcInd = self._Model.DateTimeIndex
            PreInd = self._CurCalcInd - self.LookBack
            LastInd = self._CurCalcInd - 1
            PreDateTime = self._Model.DateTimeSeries[PreInd]
            LastDateTime = self._Model.DateTimeSeries[LastInd]
        if (PreInd < 0) or (LastInd < 0):
            for iFactorName in self.TestFactors:
                self._Output["IC"][iFactorName].append(np.nan)
                self._Output["股票数"][iFactorName].append(np.nan)
            self._Output["时点"].append(idt)
            return 0
        PreIDs = self._FactorTable.getFilteredID(idt=PreDateTime,
                                                 id_filter_str=self.IDFilter)
        FactorExpose = self._FactorTable.readData(
            dts=[PreDateTime], ids=PreIDs,
            factor_names=list(self.TestFactors)).iloc[:, 0, :]
        Price = self._FactorTable.readData(dts=[LastDateTime, idt],
                                           ids=PreIDs,
                                           factor_names=[self.PriceFactor
                                                         ]).iloc[0, :, :]
        Ret = Price.iloc[-1] / Price.iloc[0] - 1
        if self.IndustryFactor != "无":  # 进行收益率的行业调整
            IndustryData = self._FactorTable.readData(
                dts=[LastDateTime],
                ids=PreIDs,
                factor_names=[self.IndustryFactor]).iloc[0, 0, :]
            AllIndustry = IndustryData.unique()
            if self.WeightFactor == "等权":
                for iIndustry in AllIndustry:
                    iMask = (IndustryData == iIndustry)
                    Ret[iMask] -= Ret[iMask].mean()
            else:
                WeightData = self._FactorTable.readData(
                    dts=[LastDateTime],
                    ids=PreIDs,
                    factor_names=[self.WeightFactor]).iloc[0, 0, :]
                for iIndustry in AllIndustry:
                    iMask = (IndustryData == iIndustry)
                    iWeight = WeightData[iMask]
                    iRet = Ret[iMask]
                    Ret[iMask] -= (iRet * iWeight).sum() / iWeight[pd.notnull(
                        iWeight) & pd.notnull(iRet)].sum(skipna=False)
        for iFactorName in self.TestFactors:
            self._Output["IC"][iFactorName].append(
                FactorExpose[iFactorName].corr(Ret, method=self.CorrMethod))
            self._Output["股票数"][iFactorName].append(
                pd.notnull(FactorExpose[iFactorName]).sum())
        self._Output["时点"].append(idt)
        return 0

    def __QS_end__(self):
        if not self._isStarted: return 0
        CalcDateTimes = self._Output.pop("时点")
        self._Output["股票数"] = pd.DataFrame(self._Output["股票数"],
                                           index=CalcDateTimes)
        self._Output["IC"] = pd.DataFrame(self._Output["IC"],
                                          index=CalcDateTimes)
        for i, iFactorName in enumerate(self.TestFactors):
            if self.FactorOrder[iFactorName] == "升序":
                self._Output["IC"][
                    iFactorName] = -self._Output["IC"][iFactorName]
        self._Output["IC的移动平均"] = self._Output["IC"].copy()
        for i in range(len(CalcDateTimes)):
            if i < self.RollAvgPeriod - 1:
                self._Output["IC的移动平均"].iloc[i, :] = np.nan
            else:
                self._Output["IC的移动平均"].iloc[i, :] = self._Output["IC"].iloc[
                    i - self.RollAvgPeriod + 1:i + 1, :].mean()
        self._Output["统计数据"] = pd.DataFrame(index=self._Output["IC"].columns)
        self._Output["统计数据"]["平均值"] = self._Output["IC"].mean()
        self._Output["统计数据"]["标准差"] = self._Output["IC"].std()
        self._Output["统计数据"]["最小值"] = self._Output["IC"].min()
        self._Output["统计数据"]["最大值"] = self._Output["IC"].max()
        self._Output["统计数据"]["IC_IR"] = self._Output["统计数据"][
            "平均值"] / self._Output["统计数据"]["标准差"]
        self._Output["统计数据"]["t统计量"] = np.nan
        self._Output["统计数据"]["平均股票数"] = self._Output["股票数"].mean()
        self._Output["统计数据"]["IC×Sqrt(N)"] = self._Output["统计数据"][
            "平均值"] * np.sqrt(self._Output["统计数据"]["平均股票数"])
        self._Output["统计数据"]["有效期数"] = 0.0
        for iFactor in self._Output["IC"]:
            self._Output["统计数据"].loc[iFactor, "有效期数"] = pd.notnull(
                self._Output["IC"][iFactor]).sum()
        self._Output["统计数据"]["t统计量"] = (self._Output["统计数据"]["有效期数"]**
                                        0.5) * self._Output["统计数据"]["IC_IR"]
        return 0

    def genMatplotlibFig(self, file_path=None):
        nRow, nCol = self._Output["IC"].shape[1] // 3 + (
            self._Output["IC"].shape[1] % 3 != 0), min(
                3, self._Output["IC"].shape[1])
        Fig = plt.figure(figsize=(min(32, 16 + (nCol - 1) * 8), 8 * nRow))
        AxesGrid = gridspec.GridSpec(nRow, nCol)
        xData = np.arange(0, self._Output["IC"].shape[0])
        xTicks = np.arange(0, self._Output["IC"].shape[0],
                           max(1, int(self._Output["IC"].shape[0] / 10)))
        xTickLabels = [
            self._Output["IC"].index[i].strftime("%Y-%m-%d") for i in xTicks
        ]
        yMajorFormatter = FuncFormatter(_QS_formatMatplotlibPercentage)
        for i in range(self._Output["IC"].shape[1]):
            iAxes = plt.subplot(AxesGrid[i // nCol, i % nCol])
            iAxes.yaxis.set_major_formatter(yMajorFormatter)
            iAxes.plot(xData,
                       self._Output["IC的移动平均"].iloc[:, i].values,
                       label="IC的移动平均",
                       color="r",
                       alpha=0.6,
                       lw=3)
            iAxes.bar(xData,
                      self._Output["IC"].iloc[:, i].values,
                      label="IC",
                      color="b")
            iAxes.set_xticks(xTicks)
            iAxes.set_xticklabels(xTickLabels)
            iAxes.legend(loc='best')
            iAxes.set_title(self._Output["IC"].columns[i])
        if file_path is not None:
            Fig.savefig(file_path, dpi=150, bbox_inches='tight')
        return Fig

    def _repr_html_(self):
        if len(self.ArgNames) > 0:
            HTML = "参数设置: "
            HTML += '<ul align="left">'
            for iArgName in self.ArgNames:
                if iArgName != "计算时点":
                    HTML += "<li>" + iArgName + ": " + str(
                        self.Args[iArgName]) + "</li>"
                elif self.Args[iArgName]:
                    HTML += "<li>" + iArgName + ": 自定义时点</li>"
                else:
                    HTML += "<li>" + iArgName + ": 所有时点</li>"
            HTML += "</ul>"
        else:
            HTML = ""
        Formatters = [_QS_formatPandasPercentage] * 4 + [
            lambda x: '{0:.4f}'.format(x)
        ] + [lambda x: '{0:.2f}'.format(x)] * 3 + [
            lambda x: '{0:.0f}'.format(x)
        ]
        iHTML = self._Output["统计数据"].to_html(formatters=Formatters)
        Pos = iHTML.find(">")
        HTML += iHTML[:Pos] + ' align="center"' + iHTML[Pos:]
        Fig = self.genMatplotlibFig()
        # figure 保存为二进制文件
        Buffer = BytesIO()
        plt.savefig(Buffer, bbox_inches='tight')
        PlotData = Buffer.getvalue()
        # 图像数据转化为 HTML 格式
        ImgStr = "data:image/png;base64," + base64.b64encode(PlotData).decode()
        HTML += ('<img src="%s">' % ImgStr)
        return HTML
示例#10
0
class SectionCorrelation(BaseModule):
    """因子截面相关性"""
    TestFactors = ListStr(arg_type="MultiOption",
                          label="测试因子",
                          order=0,
                          option_range=())
    FactorOrder = Dict(key_trait=Str(),
                       value_trait=Enum("降序", "升序"),
                       arg_type="ArgDict",
                       label="排序方向",
                       order=1)
    CalcDTs = List(dt.datetime, arg_type="DateList", label="计算时点", order=2)
    CorrMethod = ListStr(["spearman"],
                         arg_type="MultiOption",
                         label="相关性算法",
                         order=3,
                         option_range=("spearman", "pearson", "kendall"))
    RiskTable = Instance(RiskTable, arg_type="RiskTable", label="风险表", order=4)
    IDFilter = Str(arg_type="IDFilter", label="筛选条件", order=5)

    def __init__(self, factor_table, name="因子截面相关性", sys_args={}, **kwargs):
        self._FactorTable = factor_table
        return super().__init__(name=name, sys_args=sys_args, **kwargs)

    def __QS_initArgs__(self):
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._FactorTable.getFactorMetaData(key="DataType")))
        self.add_trait(
            "TestFactors",
            ListStr(arg_type="MultiOption",
                    label="测试因子",
                    order=0,
                    option_range=tuple(DefaultNumFactorList)))
        self.TestFactors.append(DefaultNumFactorList[0])

    @on_trait_change("TestFactors[]")
    def _on_TestFactors_changed(self, obj, name, old, new):
        self.FactorOrder = {
            iFactorName: self.FactorOrder.get(iFactorName, "降序")
            for iFactorName in self.TestFactors
        }

    @on_trait_change("RiskTable")
    def _on_RiskDS_changed(self, obj, name, old, new):
        if new is None:
            self.add_trait(
                "CorrMethod",
                ListStr(arg_type="MultiOption",
                        label="相关性算法",
                        order=3,
                        option_range=("spearman", "pearson", "kendall")))
        else:
            self.add_trait(
                "CorrMethod",
                ListStr(arg_type="MultiOption",
                        label="相关性算法",
                        order=3,
                        option_range=("spearman", "pearson", "kendall",
                                      "factor-score correlation",
                                      "factor-portfolio correlation")))
        self.CorrMethod = list(
            set(self.CorrMethod).intersection(set(
                self.CorrMethod.option_range)))

    def getViewItems(self, context_name=""):
        Items, Context = super().getViewItems(context_name=context_name)
        Items[0].editor = SetEditor(
            values=self.trait("TestFactors").option_range)
        Items[3].editor = SetEditor(
            values=self.trait("CorrMethod").option_range)
        return (Items, Context)

    def __QS_start__(self, mdl, dts, **kwargs):
        if self._isStarted: return ()
        super().__QS_start__(mdl=mdl, dts=dts, **kwargs)
        self._Output = {"FactorPair": []}
        for i, iFactor in enumerate(self.TestFactors):
            for j, jFactor in enumerate(self.TestFactors):
                if j > i:
                    self._Output["FactorPair"].append(iFactor + "-" + jFactor)
        nPair = len(self._Output["FactorPair"])
        self._Output.update({
            iMethod: [[] for i in range(nPair)]
            for iMethod in self.CorrMethod
        })
        self._CorrMatrixNeeded = (
            ("factor-score correlation" in self.CorrMethod)
            or ("factor-portfolio correlation" in self.CorrMethod))
        if self._CorrMatrixNeeded and (self.RiskTable is not None):
            self.RiskTable.start(dts=dts)
        self._Output["时点"] = []
        self._CurCalcInd = 0
        return (self._FactorTable, )

    def __QS_move__(self, idt, **kwargs):
        if self._iDT == idt: return 0
        self._iDT = idt
        if self.CalcDTs:
            if idt not in self.CalcDTs[self._CurCalcInd:]: return 0
            self._CurCalcInd = self.CalcDTs[self._CurCalcInd:].index(
                idt) + self._CurCalcInd
        else:
            self._CurCalcInd = self._Model.DateTimeIndex
        IDs = self._FactorTable.getFilteredID(idt=idt,
                                              id_filter_str=self.IDFilter)
        FactorExpose = self._FactorTable.readData(
            dts=[idt], ids=IDs,
            factor_names=list(self.TestFactors)).iloc[:, 0, :].astype("float")
        if self._CorrMatrixNeeded and (self.RiskTable is not None):
            self.RiskTable.move(idt)
            CovMatrix = dropRiskMatrixNA(
                self.RiskTable.readCov(dts=[idt], ids=IDs).iloc[0])
            FactorIDs = {}
        else:
            CovMatrix = None
        PairInd = 0
        for i, iFactor in enumerate(self.TestFactors):
            iFactorExpose = FactorExpose[iFactor]
            if self._CorrMatrixNeeded:
                iIDs = FactorIDs.get(iFactor)
                if iIDs is None:
                    if CovMatrix is not None:
                        FactorIDs[iFactor] = list(
                            set(CovMatrix.index).intersection(
                                set(iFactorExpose[pd.notnull(
                                    iFactorExpose)].index)))
                    else:
                        FactorIDs[iFactor] = list(
                            iFactorExpose[pd.notnull(iFactorExpose)].index)
                    iIDs = FactorIDs[iFactor]
            for j, jFactor in enumerate(self.TestFactors):
                if j > i:
                    jFactorExpose = FactorExpose[jFactor]
                    if self._CorrMatrixNeeded:
                        jIDs = FactorIDs.get(jFactor)
                        if jIDs is None:
                            if CovMatrix is not None:
                                FactorIDs[jFactor] = list(
                                    set(CovMatrix.index).intersection(
                                        set(jFactorExpose[pd.notnull(
                                            jFactorExpose)].index)))
                            else:
                                FactorIDs[jFactor] = list(jFactorExpose[
                                    pd.notnull(jFactorExpose)].index)
                            jIDs = FactorIDs[jFactor]
                        IDs = list(set(iIDs).intersection(set(jIDs)))
                        iTempExpose = iFactorExpose.loc[IDs].values
                        jTempExpose = jFactorExpose.loc[IDs].values
                        if CovMatrix is not None:
                            TempCovMatrix = CovMatrix.loc[IDs, IDs].values
                        else:
                            nID = len(IDs)
                            TempCovMatrix = np.eye(nID, nID)
                    for kMethod in self.CorrMethod:
                        if kMethod == "factor-score correlation":
                            ijCov = np.dot(iTempExpose.T,
                                           np.dot(TempCovMatrix, jTempExpose))
                            iStd = np.sqrt(
                                np.dot(iTempExpose.T,
                                       np.dot(TempCovMatrix, iTempExpose)))
                            jStd = np.sqrt(
                                np.dot(jTempExpose.T,
                                       np.dot(TempCovMatrix, jTempExpose)))
                            self._Output[kMethod][PairInd].append(ijCov /
                                                                  iStd / jStd)
                        elif kMethod == "factor-portfolio correlation":
                            TempCovMatrixInv = np.linalg.inv(TempCovMatrix)
                            ijCov = np.dot(
                                iTempExpose.T,
                                np.dot(TempCovMatrixInv, jTempExpose))
                            iStd = np.sqrt(
                                np.dot(iTempExpose.T,
                                       np.dot(TempCovMatrixInv, iTempExpose)))
                            jStd = np.sqrt(
                                np.dot(jTempExpose.T,
                                       np.dot(TempCovMatrixInv, jTempExpose)))
                            self._Output[kMethod][PairInd].append(ijCov /
                                                                  iStd / jStd)
                        else:
                            self._Output[kMethod][PairInd].append(
                                FactorExpose[iFactor].corr(
                                    FactorExpose[jFactor], method=kMethod))
                    PairInd += 1
        self._Output["时点"].append(idt)
        return 0

    def __QS_end__(self):
        if not self._isStarted: return 0
        super().__QS_end__()
        for iMethod in self.CorrMethod:
            self._Output[iMethod] = pd.DataFrame(
                np.array(self._Output[iMethod]).T,
                columns=self._Output["FactorPair"],
                index=self._Output["时点"])
            iAvgName = iMethod + "均值"
            self._Output[iAvgName] = pd.DataFrame(index=list(self.TestFactors),
                                                  columns=list(
                                                      self.TestFactors),
                                                  dtype="float")
            for i, iFactor in enumerate(self.TestFactors):
                for j, jFactor in enumerate(self.TestFactors):
                    if j > i:
                        if self.FactorOrder[iFactor] != self.FactorOrder[
                                jFactor]:
                            self._Output[iMethod][
                                iFactor + "-" +
                                jFactor] = -self._Output[iMethod][iFactor + "-"
                                                                  + jFactor]
                        self._Output[iAvgName].loc[
                            iFactor,
                            jFactor] = self._Output[iMethod][iFactor + "-" +
                                                             jFactor].mean()
                    elif j < i:
                        self._Output[iAvgName].loc[
                            iFactor,
                            jFactor] = self._Output[iAvgName].loc[jFactor,
                                                                  iFactor]
                    else:
                        self._Output[iAvgName].loc[iFactor, jFactor] = 1
        self._Output.pop("FactorPair")
        self._Output.pop("时点")
        if (self.RiskTable is not None) and self._CorrMatrixNeeded:
            self.RiskTable.end()
        return 0

    def _plotHeatMap(self, axes, df, title):
        axes.pcolor(df.values, cmap=matplotlib.cm.Reds)
        axes.set_xticks(np.arange(df.shape[0]) + 0.5, minor=False)
        axes.set_yticks(np.arange(df.shape[1]) + 0.5, minor=False)
        axes.invert_yaxis()
        axes.xaxis.tick_top()
        axes.set_xticklabels(df.index.tolist(), minor=False)
        axes.set_yticklabels(df.columns.tolist(), minor=False)
        axes.set_title(title)
        return axes

    def genMatplotlibFig(self, file_path=None):
        nMethod = len(self.CorrMethod)
        nRow, nCol = nMethod // 3 + (nMethod % 3 != 0), min(3, nMethod)
        Fig = plt.figure(figsize=(min(32, 16 + (nCol - 1) * 8), 8 * nRow))
        AxesGrid = gridspec.GridSpec(nRow, nCol)
        for i, iMethod in enumerate(self.CorrMethod):
            iAvgName = iMethod + "均值"
            iAxes = plt.subplot(AxesGrid[i // nCol, i % nCol])
            self._plotHeatMap(iAxes, self._Output[iAvgName], iAvgName)
        if file_path is not None:
            Fig.savefig(file_path, dpi=150, bbox_inches='tight')
        return Fig

    def _repr_html_(self):
        if len(self.ArgNames) > 0:
            HTML = "参数设置: "
            HTML += '<ul align="left">'
            for iArgName in self.ArgNames:
                if iArgName == "风险表":
                    if self.RiskTable is None:
                        HTML += "<li>" + iArgName + ": None</li>"
                    else:
                        HTML += "<li>" + iArgName + ": " + self.RiskTable.Name + "</li>"
                elif iArgName != "计算时点":
                    HTML += "<li>" + iArgName + ": " + str(
                        self.Args[iArgName]) + "</li>"
                elif self.Args[iArgName]:
                    HTML += "<li>" + iArgName + ": 自定义时点</li>"
                else:
                    HTML += "<li>" + iArgName + ": 所有时点</li>"
            HTML += "</ul>"
        else:
            HTML = ""
        for i, iMethod in enumerate(self.CorrMethod):
            iAvgName = iMethod + "均值"
            iHTML = self._Output[iAvgName].style.background_gradient(
                cmap="Reds").set_precision(2).render()
            HTML += '<div align="left" style="font-size:1em"><strong>' + iAvgName + '</strong></div>' + iHTML
        return HTML
示例#11
0
class ExecutionContext(Controller):
    '''
    An execution context contains all the information necessary to start a 
    job. For instance, in order to use FSL, it is necessary to setup a few
    environment variables whose content depends on the location where FSL 
    is installed. The execution context contains the information about FSL 
    installation necessary to define these environment variable when a job 
    is started. The execution context is shared with every processing nodes 
    and used to build the execution environment of each job.
    '''
    
    python_path_first = ListStr()
    python_path_last = ListStr()
    environ = DictStrStr()
    
    #def __init__(self, python_path_first=[], python_path_last=[],
                 #environ = {}):
        #self.python_path_first = python_path_first
        #self.python_path_last = python_path_last
        #self.environ = environ
    
    def to_json(self):
        '''
        Returns a dictionary containing JSON compatible representation of
        the execution context.
        '''
        kwargs = {}
        if self.python_path_first:
            kwargs['python_path_first'] = self.python_path_first
        if self.python_path_last:
            kwargs['python_path_last'] = self.python_path_last
        if self.environ:
            kwargs['environ'] = self.environ
        return ['capsul.engine.execution_context.from_json', kwargs]
        
    def __enter__(self):
        self._sys_path_first = [osp.expandvars(osp.expanduser(i)) 
                                for i in self.python_path_first]
        self._sys_path_last = [osp.expandvars(osp.expanduser(i)) 
                                for i in self.python_path_last]
        sys.path = self._sys_path_first + sys.path + self._sys_path_last
        
        self._environ_backup = {}
        for n, v in self.environ.items():
            self._environ_backup[n] = os.environ.get(n)
            os.environ[n] = v

        # This code is specific to Nipype/SPM and should be
        # in a dedicated module. It is put here until 
        # modularity is added to this method.
        if os.environ.get('SPM_STANDALONE'):
            nipype = True
            try:
                from nipype.interfaces import spm
            except ImportError:
                nipype = False
            if nipype:
                import glob
                spm_directory = os.environ.get('SPM_DIRECTORY', '')
                spm_exec_glob = osp.join(spm_directory, 'mcr', 'v*')
                spm_exec = glob.glob(spm_exec_glob)
                if spm_exec:
                    spm_exec = spm_exec[0]
                    spm.SPMCommand.set_mlab_paths(
                        matlab_cmd=osp.join(spm_directory, 'run_spm%s.sh' % os.environ.get('SPM_VERSION','')) + ' ' + spm_exec + ' script',
                        use_mcr=True)

    def __exit__(self, exc_type, exc_value, traceback):
        sys_path_error = False
        if self._sys_path_first:
            if sys.path[0:len(self._sys_path_first)] == self._sys_path_first:
                del sys.path[0:len(self._sys_path_first)]
            else:
                sys_path_error = True
        if self._sys_path_last:
            if sys.path[-len(self._sys_path_last):] == self._sys_path_last:
                del sys.path[0:len(self._sys_path_last)]
            else:
                sys_path_error = True
        del self._sys_path_first
        del self._sys_path_last
        
        for n, v in self._environ_backup.items():
            if v is None:
                os.environ.pop(n, None)
            else:
                os.environ[n] = v
        del self._environ_backup
        
        if sys_path_error:
            raise ValueError('sys.path was modified and execution context modifications cannot be undone')
示例#12
0
class TimeBarAccount(Account):
    """基于 Bar 数据的期货账户"""
    Delay = Bool(True, arg_type="Bool", label="交易延迟", order=2)
    TargetIDs = ListStr(arg_type="IDList", label="目标ID", order=3)
    BuyLimit = Instance(_TradeLimit, allow_none=False, arg_type="ArgObject", label="买入限制", order=4)
    SellLimit = Instance(_TradeLimit, allow_none=False, arg_type="ArgObject", label="卖出限制", order=5)
    MarketInfo = Instance(_MarketBarInfo, arg_type="ArgObject", label="行情因子", order=6)
    SecurityInfo = Instance(_SecurityInfo, arg_type="ArgObject", label="证券信息", order=7)
    def __init__(self, market_ft, security_ft, sys_args={}, config_file=None, **kwargs):
        # 继承自 Account 的属性
        #self._Cash = None# 剩余现金, >=0,  array(shape=(nDT+1,))
        #self._FrozenCash = 0# 当前被冻结的现金, >=0, float
        #self._Debt = None# 负债, >=0, array(shape=(nDT+1,))
        #self._CashRecord = None# 现金流记录, 现金流入为正, 现金流出为负, DataFrame(columns=["时间点", "现金流", "备注"])
        #self._DebtRecord = None# 融资记录, 增加负债为正, 减少负债为负, DataFrame(columns=["时间点", "融资", "备注"])
        #self._TradingRecord = None# 交易记录, DataFrame(columns=["时间点", "ID", "买卖数量", "价格", "交易费", "现金收支", "类型"])
        self._IDs = []# 本账户支持交易的证券 ID, []
        self._Position = None# 持仓数量, DataFrame(index=[时间点]+1, columns=self._IDs)
        self._PositionAmount = None# 持仓金额, 保证金 - 浮动盈亏, DataFrame(index=[时间点]+1, columns=self._IDs)
        self._Orders = None# 当前接收到的订单, DataFrame(columns=["ID", "数量", "目标价"])
        self._OpenPosition = None# 当前未平仓的持仓信息, DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "保证金", "浮动盈亏"])
        self._ClosedPosition = None# 已平仓的交易信息, DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "保证金", "平仓时点", "平仓价格", "平仓交易费", "平仓盈亏"])
        self._LastPrice = None# 最新价, Series(index=self._IDs)
        self._MarketFT = market_ft# 行情因子表对象
        self._SecurityFT = security_ft# 证券信息因子表对象
        super().__init__(sys_args=sys_args, config_file=config_file, **kwargs)
        self.Name = "FutureAccount"
    def __QS_initArgs__(self):
        super().__QS_initArgs__()
        self.MarketInfo = _MarketBarInfo(self._MarketFT)
        self.SecurityInfo = _SecurityInfo(self._SecurityFT)
        self.BuyLimit = _TradeLimit(direction="Buy")
        self.SellLimit = _TradeLimit(direction="Sell")
    def __QS_start__(self, mdl, dts, **kwargs):
        Rslt = super().__QS_start__(mdl=mdl, dts=dts, **kwargs)
        self._IDs = list(self.TargetIDs)
        if not self._IDs: self._IDs = list(self._MarketFT.getID(ifactor_name=self.MarketInfo.TradePrice))
        nDT, nID = len(dts), len(self._IDs)
        #self._Cash = np.zeros(nDT+1)
        #self._Debt = np.zeros(nDT+1)
        self._Position = pd.DataFrame(np.zeros((nDT+1, nID)), index=[dts[0]-dt.timedelta(1)]+dts, columns=self._IDs)
        self._PositionAmount = self._Position.copy()
        self._Orders = pd.DataFrame(columns=["ID", "数量", "目标价"])
        self._LastPrice = pd.Series(np.nan, index=self._IDs)# 最新价
        self._OpenPosition = pd.DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "保证金", "浮动盈亏"])
        self._ClosedPosition = pd.DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "保证金", "平仓时点", "平仓价格", "平仓交易费", "平仓盈亏"])
        self._iTradingRecord = []# 暂存的交易记录
        self._nDT = nDT
        return Rslt + (self._MarketFT, self._SecurityFT)
    def __QS_move__(self, idt, **kwargs):
        super().__QS_move__(idt, **kwargs)
        self._LastPrice = self._MarketFT.readData(factor_names=[self.MarketInfo.Last], ids=self._IDs, dts=[idt]).iloc[0, 0]
        if self.Delay:# 撮合成交
            iIndex = self._Model.DateTimeIndex
            self._Position.iloc[iIndex+1] = self._Position.iloc[iIndex]
            TradingRecord = self._matchOrder(idt)
            TradingRecord = pd.DataFrame(TradingRecord, index=np.arange(self._TradingRecord.shape[0], self._TradingRecord.shape[0]+len(TradingRecord)), columns=self._TradingRecord.columns)
            self._TradingRecord = self._TradingRecord.append(TradingRecord)
        else:
            TradingRecord = self._iTradingRecord
        self._QS_updatePosition()
        return TradingRecord
    def __QS_after_move__(self, idt, **kwargs):
        super().__QS_after_move__(idt, **kwargs)
        iIndex = self._Model.DateTimeIndex
        if not self.Delay:# 撮合成交
            TradingRecord = self._matchOrder(idt)
            TradingRecord = pd.DataFrame(TradingRecord, index=np.arange(self._TradingRecord.shape[0], self._TradingRecord.shape[0]+len(TradingRecord)), columns=self._TradingRecord.columns)
            self._TradingRecord = self._TradingRecord.append(TradingRecord)
            self._iTradingRecord = TradingRecord
            self._QS_updatePosition()
        if iIndex<self._nDT-1:
            iNextDate = self._Model._QS_TestDateTimes[iIndex+1].date()
            if iNextDate!=idt.date():# 当前是该交易日的最后一个时点
                self._handleRollover(idt)# 处理展期
                self._handleSettlement(idt)# 处理结算
        return 0
    def __QS_end__(self):
        super().__QS_end__()
        self._Output["持仓"] = self.getPositionNumSeries()
        self._Output["持仓金额"] = self.getPositionAmountSeries()
        self._Output["平仓记录"] = self._ClosedPosition
        self._Output["未平仓持仓"] = self._OpenPosition
        return 0
    # 处理结算, 对于不满足保证金要求的仓位进行强平
    def _handleSettlement(self, idt):
        if self._OpenPosition.shape[0]==0: return 0
        idt = dt.datetime.combine(idt.date(), dt.time(0))
        SettlementPrice = self._SecurityFT.readData(factor_names=[self.SecurityInfo.SettlementPrice], dts=[idt], ids=self._IDs).iloc[0,0,:][self._OpenPosition["ID"].tolist()]
        self._OpenPosition["浮动盈亏"] = self._OpenPosition["数量"] * (SettlementPrice.values - self._OpenPosition["开仓价格"]) * self.SecurityInfo.Multiplier
        MaintenanceMargin = self._OpenPosition["数量"].abs() * self.SecurityInfo.Multiplier * SettlementPrice.values * self.SecurityInfo.MaintenanceMarginRate# 维持保证金
        iGap = (MaintenanceMargin - self._OpenPosition["保证金"] - self._OpenPosition["浮动盈亏"]).clip_lower(0)# 当前保证金和维持保证金之间的缺口
        iTotalGap = np.nansum(iGap)
        if iTotalGap<=0: return 0
        AvailableCash = self.AvailableCash
        iIndex = self._Model.DateTimeIndex
        if AvailableCash>=iTotalGap:
            self._updateAccount(-iTotalGap)
            self._OpenPosition["保证金"] += iGap
            iPositionAmount = pd.Series((self._OpenPosition["浮动盈亏"]+self._OpenPosition["保证金"]).values, index=self._OpenPosition["ID"].values).groupby(axis=0, level=0).sum()
            self._PositionAmount.iloc[iIndex+1][iPositionAmount.index] = iPositionAmount
            return 0
        self._OpenPosition["保证金"] += (iGap / iTotalGap) * AvailableCash# 将账户中的现金按照缺口金额大小分配给各个 ID
        iAllowedPosition = self._OpenPosition["保证金"] / self.SecurityInfo.Multiplier / SettlementPrice.values / self.MaintenanceMarginRate# 计算当前保证金水平下允许的持仓数量
        # 对于保证金不足的仓位进行平仓
        iNum = (self._OpenPosition["数量"].abs() - iAllowedPosition).clip_lower(0)
        iOrders = pd.Series(-iNum.values*np.sign(self._OpenPosition["数量"].values), index=self._OpenPosition["ID"].values).groupby(axis=0, level=0).sum()
        iOrders = iOrders[iOrders!=0]
        iBookOrders = self._Orders.groupby(by=["ID"])["数量"].sum()
        if iBookOrders.shape[0]>0: iBookOrders = iBookOrders.loc[iOrders.index].fillna(0)
        else: iBookOrders = pd.Series(0, index=iOrders.index)
        iMask = (iOrders>0)
        iOrders[iMask] = (iOrders[iMask] - iBookOrders[iMask]).clip_lower(0)
        iMask = (iOrders<0)
        iOrders[iMask] = (iOrders[iMask] - iBookOrders[iMask]).clip_upper(0)
        iOrders = iOrders[iOrders!=0]
        if iOrders.shape[0]==0: return 0
        iOrders = pd.DataFrame(iOrders, columns=["数量"]).reset_index()
        iOrders["目标价"] = np.nan
        self.order(combined_order=iOrders)
        return 0
    # 处理展期
    def _handleRollover(self, idt):
        if self._OpenPosition.shape[0]==0: return 0
        iIndex = self._Model.DateTimeIndex
        idt = dt.datetime.combine(idt.date(), dt.time(0))
        iNextDT = dt.datetime.combine(self._Model._QS_TestDateTimes[iIndex+1].date(), dt.time(0))
        IDMapping = self._SecurityFT.readData(factor_names=[self.SecurityInfo.ContractMapping], ids=self._IDs, dts=[idt, iNextDT]).iloc[0]
        SettlementPrice = self._SecurityFT.readData(factor_names=[self.SecurityInfo.SettlementPrice], dts=[idt], ids=self._IDs).iloc[0,0,:]
        isRollover = False
        for iID in self._OpenPosition["ID"].unique():
            iCurID, iNextID = IDMapping[iID].iloc[0], IDMapping[iID].iloc[1]
            if iCurID==iNextID: continue
            isRollover = True
            iNextIDPrice = SettlementPrice[IDMapping.iloc[0]==iNextID].iloc[0]
            iCurIDPrice = SettlementPrice[iID]
            iMask = (self._OpenPosition["ID"]==iID)
            iNewNum = self._OpenPosition["数量"][iMask] * iCurIDPrice / iNextIDPrice
            self._OpenPosition["开仓价格"][iMask] = self._OpenPosition["数量"][iMask] * self._OpenPosition["开仓价格"][iMask] / iNewNum
            self._OpenPosition["数量"][iMask] = iNewNum
        if isRollover: self._updateAccount(cash_changed=0.0)
        return 0
    # 当前账户价值
    @property
    def AccountValue(self):
        return super().AccountValue + np.nansum(self._PositionAmount.iloc[self._Model.DateTimeIndex+1])
    # 当前账户的持仓数量
    @property
    def Position(self):
        return self._Position.iloc[self._Model.DateTimeIndex+1]
    # 当前账户的持仓金额, 即保证金
    @property
    def PositionAmount(self):
        self.PositionAmount.iloc[self._Model.DateTimeIndex+1]
    # 本账户支持交易的证券 ID
    @property
    def IDs(self):
        return self._IDs
    # 当前账户中还未成交的订单, DataFrame(index=[int], columns=["ID", "数量", "目标价"])
    @property
    def Orders(self):
        return self._Orders
    # 当前最新价
    @property
    def LastPrice(self):
        return self._LastPrice
    # 获取持仓的历史序列, 以时间点为索引, 返回: pd.DataFrame(持仓, index=[时间点], columns=[ID])
    def getPositionNumSeries(self, dts=None, start_dt=None, end_dt=None):
        Data = self._Position.iloc[1:self._Model.DateTimeIndex+2]
        return cutDateTime(Data, dts=dts, start_dt=start_dt, end_dt=end_dt)
    # 获取持仓证券的金额历史序列, 以时间点为索引, 返回: pd.DataFrame(持仓金额, index=[时间点], columns=[ID])
    def getPositionAmountSeries(self, dts=None, start_dt=None, end_dt=None):
        Data = self._PositionAmount[1:self._Model.DateTimeIndex+2]
        return cutDateTime(Data, dts=dts, start_dt=start_dt, end_dt=end_dt)
    # 获取账户价值的历史序列, 以时间点为索引
    def getAccountValueSeries(self, dts=None, start_dt=None, end_dt=None):
        CashSeries = self.getCashSeries(dts=dts, start_dt=start_dt, end_dt=end_dt)
        PositionAmountSeries = self.getPositionAmountSeries(dts=dts, start_dt=start_dt, end_dt=end_dt).sum(axis=1)
        DebtSeries = self.getDebtSeries(dts=dts, start_dt=start_dt, end_dt=end_dt)
        return CashSeries + PositionAmountSeries - DebtSeries
    # 执行给定数量的证券委托单, target_id: 目标证券 ID, num: 待买卖的数量, target_price: nan 表示市价单, 
    # combined_order: 组合订单, DataFrame(index=[ID],columns=[数量, 目标价])
    # 基本的下单函数, 必须实现, 目前只实现了市价单
    def order(self, target_id=None, num=0, target_price=np.nan, combined_order=None):
        if target_id is not None:
            #if (num>0) and (self.BuyLimit.MinUnit>0):
                #num = np.fix(num / self.BuyLimit.MinUnit) * self.BuyLimit.MinUnit
            #elif (num<0) and (self.SellLimit.MinUnit>0):
                #num = np.fix(num / self.SellLimit.MinUnit) * self.SellLimit.MinUnit
            self._Orders.loc[self._Orders.shape[0]] = (target_id, num, target_price)
            return (target_id, num, target_price)
        if combined_order is not None:
            #if self.BuyLimit.MinUnit>0:
                #Mask = (combined_order["数量"]>0)
                #combined_order["数量"][Mask] = np.fix(combined_order["数量"][Mask] / self.BuyLimit.MinUnit) * self.BuyLimit.MinUnit
            #if self.SellLimit.MinUnit>0:
                #Mask = (combined_order["数量"]<0)
                #combined_order["数量"][Mask] = np.fix(combined_order["数量"][Mask] / self.SellLimit.MinUnit) * self.SellLimit.MinUnit
            combined_order.index = np.arange(self._Orders.shape[0], self._Orders.shape[0]+combined_order.shape[0])
            self._Orders = self._Orders.append(combined_order)
        return combined_order
    # 撤销订单, order_ids 是订单在 self.Orders 中的 index
    def cancelOrder(self, order_ids):
        self._Orders = self._Orders.loc[self._Orders.index.difference(set(order_ids))]
        self._Orders.sort_index(axis=0, inplace=True)
        self._Orders.index = np.arange(self._Orders.shape[0])
        return 0
    # 更新仓位信息
    def _QS_updatePosition(self):
        iIndex = self._Model.DateTimeIndex
        iNum = self._OpenPosition.groupby(by=["ID"])["数量"].sum()
        iPosition = pd.Series(0, index=self._IDs)
        iPosition[iNum.index] += iNum
        self._Position.iloc[iIndex+1] = iPosition.values
        self._OpenPosition["浮动盈亏"] = self._OpenPosition["数量"] * (self._LastPrice[self._OpenPosition["ID"].tolist()].values - self._OpenPosition["开仓价格"]) * self.SecurityInfo.Multiplier
        iPositionAmount = pd.Series((self._OpenPosition["浮动盈亏"]+self._OpenPosition["保证金"]).values, index=self._OpenPosition["ID"].values).groupby(axis=0, level=0).sum()
        self._PositionAmount.iloc[iIndex+1] = 0.0
        self._PositionAmount.iloc[iIndex+1][iPositionAmount.index] = iPositionAmount
        return 0
    # 撮合成交订单
    def _matchOrder(self, idt):
        MarketOrderMask = pd.isnull(self._Orders["目标价"])
        MarketOrders = self._Orders[MarketOrderMask]
        LimitOrders = self._Orders[~MarketOrderMask]
        MarketOrders = MarketOrders.groupby(by=["ID"]).sum()["数量"]# Series(数量, index=[ID])
        if LimitOrders.shape[0]>0:
            LimitOrders = LimitOrders.groupby(by=["ID", "目标价"]).sum()
            LimitOrders.sort_index(ascending=False)
            LimitOrders = LimitOrders.reset_index(level=1)
        else:
            LimitOrders = pd.DataFrame(columns=["ID", "数量", "目标价"])
        # 先执行平仓交易
        TradingRecord, MarketOpenOrders = self._matchMarketCloseOrder(idt, MarketOrders)
        iTradingRecord, LimitOrders = self._matchLimitCloseOrder(idt, LimitOrders)
        TradingRecord.extend(iTradingRecord)
        # 再执行开仓交易
        TradingRecord.extend(self._matchMarketOpenOrder(idt, MarketOpenOrders))
        iTradingRecord, LimitOrders = self._matchLimitOpenOrder(idt, LimitOrders)
        TradingRecord.extend(iTradingRecord)
        self._Orders = LimitOrders
        self._Orders.index = np.arange(self._Orders.shape[0])
        return TradingRecord
    # 撮合成交市价平仓单
    # 以成交价完成成交, 成交量满足交易限制要求
    # 未成交的市价单自动撤销
    def _matchMarketCloseOrder(self, idt, orders):
        orders = orders[orders!=0]
        if orders.shape[0]==0: return ([], pd.Series())
        IDs = orders.index.tolist()
        TradePrice = self._MarketFT.readData(dts=[idt], ids=IDs, factor_names=[self.MarketInfo.TradePrice]).iloc[0, 0]# 成交价
        # 过滤限制条件
        orders[pd.isnull(TradePrice)] = 0.0# 成交价缺失的不能交易
        #if self.SellLimit.LimitIDFilter:# 满足卖出禁止条件的不能卖出
            #Mask = (self._MarketFT.getIDMask(idt, ids=IDs, id_filter_str=self.SellLimit.LimitIDFilter) & (orders<0))
            #orders[Mask] = orders[Mask].clip_lower(0)
        #if self.BuyLimit.LimitIDFilter:# 满足买入禁止条件的不能买入
            #Mask = (self._MarketFT.getIDMask(idt, ids=IDs, id_filter_str=self.BuyLimit.LimitIDFilter) & (orders>0))
            #orders[Mask] = orders[Mask].clip_upper(0)
        #if self.MarketInfo.Vol:# 成交量限制
            #VolLimit = self._MarketFT.readData(factor_names=[self.MarketInfo.Vol], ids=IDs, dts=[idt]).iloc[0, 0]
            #orders = np.maximum(orders, -VolLimit * self.SellLimit.MarketOrderVolumeLimit)
            #orders = np.minimum(orders, VolLimit * self.BuyLimit.MarketOrderVolumeLimit)
        #if self.SellLimit.MinUnit!=0.0:# 最小卖出交易单位限制
            #Mask = (orders<0)
            #orders[Mask] = (orders[Mask] / self.SellLimit.MinUnit).astype("int") * self.SellLimit.MinUnit
        #if self.BuyLimit.MinUnit!=0.0:# 最小买入交易单位限制
            #Mask = (orders>0)
            #orders[Mask] = (orders[Mask] / self.BuyLimit.MinUnit).astype("int") * self.BuyLimit.MinUnit
        # 分离平仓单和开仓单
        Position = self.Position[IDs]
        CloseOrders = orders.clip(lower=(-Position).clip_upper(0), upper=(-Position).clip_lower(0))# 平仓单
        OpenOrders = orders - CloseOrders# 开仓单
        CloseOrders = CloseOrders[CloseOrders!=0]
        if CloseOrders.shape[0]==0: return ([], OpenOrders)
        # 处理平仓单
        TradePrice = TradePrice[CloseOrders.index]
        TradeAmounts = CloseOrders * TradePrice * self.SecurityInfo.Multiplier
        Fees = TradeAmounts.clip_lower(0) * self.BuyLimit.TradeFee + TradeAmounts.clip_upper(0).abs() * self.SellLimit.TradeFee
        iOpenPosition = self._OpenPosition.set_index(["ID"])
        iClosedPosition = pd.DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "保证金", "平仓时点", "平仓价格", "平仓交易费", "平仓盈亏"])
        iNewPosition = pd.DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "保证金", "浮动盈亏"])
        CashChanged = np.zeros(CloseOrders.shape[0])
        for j, jID in enumerate(CloseOrders.index):
            jNum = CloseOrders.iloc[j]
            ijPosition = iOpenPosition.loc[[jID]]
            if jNum<0: ijClosedNum = (ijPosition["数量"] - (ijPosition["数量"].cumsum()+jNum).clip_lower(0)).clip_lower(0)
            else: ijClosedNum = (ijPosition["数量"] - (ijPosition["数量"].cumsum()+jNum).clip_upper(0)).clip_upper(0)
            ijClosedPosition = ijPosition.copy()
            ijClosedPosition["数量"] = ijClosedNum
            ijClosedPosition["平仓时点"] = idt
            ijClosedPosition["平仓价格"] = TradePrice[jID]
            ijClosedPosition["平仓交易费"] = Fees[jID]
            ijClosedPosition["平仓盈亏"] = ijClosedNum * (TradePrice[jID] - ijPosition["开仓价格"]) * self.SecurityInfo.Multiplier
            ijClosedPosition["保证金"] = ijClosedPosition["保证金"] * (ijClosedPosition["数量"] / ijPosition["数量"])
            ijPosition["保证金"] = ijPosition["保证金"] - ijClosedPosition["保证金"]
            ijPosition["数量"] -= ijClosedNum
            CashChanged[j] = ijClosedPosition["平仓盈亏"].sum() - Fees[jID] + ijClosedPosition["保证金"].sum()
            ijClosedPosition[ijClosedPosition["数量"]!=0]
            ijClosedPosition.pop("浮动盈亏")
            iClosedPosition = iClosedPosition.append(ijClosedPosition.reset_index())
            iNewPosition = iNewPosition.append(ijPosition[ijPosition["数量"]!=0].reset_index())
        iClosedPosition.index = np.arange(self._ClosedPosition.shape[0], self._ClosedPosition.shape[0]+iClosedPosition.shape[0])
        self._ClosedPosition = self._ClosedPosition.append(iClosedPosition)
        iOpenPosition = iOpenPosition.loc[iOpenPosition.index.difference(CloseOrders.index)].reset_index()
        iNewPosition.index = np.arange(iOpenPosition.shape[0], iOpenPosition.shape[0]+iNewPosition.shape[0])
        self._OpenPosition = iOpenPosition.append(iNewPosition)
        TradingRecord = list(zip([idt]*CloseOrders.shape[0], CloseOrders.index, CloseOrders, TradePrice, Fees, CashChanged, ["close"]*CloseOrders.shape[0]))
        self._QS_updateCashDebt(CashChanged.sum())
        return (TradingRecord, OpenOrders)
    # 撮合成交市价开仓单
    # 以成交价完成成交, 成交量满足交易限制要求
    # 未成交的市价单自动撤销
    def _matchMarketOpenOrder(self, idt, orders):
        orders = orders[orders!=0]
        if orders.shape[0]==0: return []
        IDs = orders.index.tolist()
        TradePrice = self._MarketFT.readData(dts=[idt], ids=IDs, factor_names=[self.MarketInfo.TradePrice]).iloc[0, 0]# 成交价
        # 处理开仓单
        TradeAmounts = orders * TradePrice * self.SecurityInfo.Multiplier
        MarginAcquired = TradeAmounts.abs() * self.SecurityInfo.InitMarginRate
        FeesAcquired = TradeAmounts.clip_lower(0) * self.BuyLimit.TradeFee + TradeAmounts.clip_upper(0).abs() * self.SellLimit.TradeFee
        CashAcquired = MarginAcquired + FeesAcquired
        CashAllocated = min(CashAcquired.sum(), self.AvailableCash) * CashAcquired / CashAcquired.sum()
        orders = CashAllocated / TradePrice / self.SecurityInfo.Multiplier / (self.SecurityInfo.InitMarginRate + self.BuyLimit.TradeFee * (orders>0) + self.SellLimit.TradeFee * (orders<0)) * np.sign(orders)
        #if self.SellLimit.MinUnit!=0.0:# 最小卖出交易单位限制
            #Mask = (orders<0)
            #orders[Mask] = (orders[Mask] / self.SellLimit.MinUnit).astype("int") * self.SellLimit.MinUnit
        #if self.BuyLimit.MinUnit!=0.0:# 最小买入交易单位限制
            #Mask = (orders>0)
            #orders[Mask] = (orders[Mask] / self.BuyLimit.MinUnit).astype("int") * self.BuyLimit.MinUnit
        orders = orders[pd.notnull(orders) & (orders!=0)]
        if orders.shape[0]==0: return []
        TradePrice = TradePrice[orders.index]
        TradeAmounts = orders * TradePrice * self.SecurityInfo.Multiplier
        Fees = TradeAmounts.clip_lower(0) * self.BuyLimit.TradeFee + TradeAmounts.clip_upper(0).abs() * self.SellLimit.TradeFee
        MarginAcquired = TradeAmounts.abs() * self.SecurityInfo.InitMarginRate
        CashChanged = - (MarginAcquired + Fees).sum()
        TradingRecord = list(zip([idt]*orders.shape[0], orders.index, orders, TradePrice, Fees, -(MarginAcquired+Fees), ["open"]*orders.shape[0]))
        iNewPosition = pd.DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "保证金", "浮动盈亏"])
        iNewPosition["ID"] = orders.index
        iNewPosition["数量"] = orders.values
        iNewPosition["开仓时点"] = idt
        iNewPosition["开仓价格"] = TradePrice.values
        iNewPosition["开仓交易费"] = Fees.values
        iNewPosition["保证金"] = MarginAcquired.values
        iNewPosition["浮动盈亏"] = 0.0
        iNewPosition.index = np.arange(self._OpenPosition.shape[0], self._OpenPosition.shape[0]+iNewPosition.shape[0])
        self._OpenPosition = self._OpenPosition.append(iNewPosition)
        self._QS_updateCashDebt(CashChanged)# 更新账户信息
        return TradingRecord
    # 撮合成交卖出限价单
    # 如果最高价和最低价未指定, 则检验成交价是否优于目标价, 是就以目标价完成成交, 且成交量满足卖出限制中的限价单成交量限比, 否则无法成交
    # 如果指定了最高价和最低价, 则假设成交价服从[最低价, 最高价]中的均匀分布, 据此确定可完成成交的数量
    # 未成交的限价单继续保留, TODO
    def _matchLimitCloseOrder(self, idt, sell_orders):
        return ([], pd.DataFrame(columns=["ID", "数量", "目标价"]))
    # 撮合成交买入限价单
    # 如果最高价和最低价未指定, 则检验成交价是否优于目标价, 是就以目标价完成成交, 且成交量满足买入限制要求
    # 如果指定了最高价和最低价, 则假设成交价服从[最低价, 最高价]中的均匀分布, 据此确定可完成成交的数量, 同时满足买入限制要求
    # 未成交的限价单继续保留, TODO
    def _matchLimitOpenOrder(self, idt, buy_orders):
        return ([], pd.DataFrame(columns=["ID", "数量", "目标价"]))
示例#13
0
class TimeBarAccount(Account):
    """基于 Bar 数据的债券账户"""
    Delay = Bool(True, arg_type="Bool", label="交易延迟", order=2)
    TargetIDs = ListStr(arg_type="IDList", label="目标ID", order=3)
    BuyLimit = Instance(_TradeLimit, allow_none=False, arg_type="ArgObject", label="买入限制", order=4)
    SellLimit = Instance(_TradeLimit, allow_none=False, arg_type="ArgObject", label="卖出限制", order=5)
    MarketInfo = Instance(_BarFactorMap, arg_type="ArgObject", label="行情信息", order=6)
    PaymentInfo = Instance(_SecurityInfo, arg_type="ArgObject", label="支付信息", order=7)
    def __init__(self, market_ft, payment_ft, sys_args={}, config_file=None, **kwargs):
        # 继承自 Account 的属性
        #self._Cash = None# 剩余现金, >=0,  array(shape=(nDT+1,))
        #self._Debt = None# 负债, >=0, array(shape=(nDT+1,))
        #self._CashRecord = None# 现金流记录, 现金流入为正, 现金流出为负, DataFrame(columns=["时间点", "现金流", "备注"])
        #self._DebtRecord = None# 融资记录, 增加负债为正, 减少负债为负, DataFrame(columns=["时间点", "融资", "备注"])
        #self._TradingRecord = None# 交易记录, DataFrame(columns=["时间点", "ID", "买卖数量", "价格", "交易费", "现金收支", "类型"])
        self._IDs = []# 本账户支持交易的证券 ID, []
        self._Position = None# 持仓数量, DataFrame(index=[时间点]+1, columns=self._IDs)
        self._PositionAmount = None# 持仓金额, DataFrame(index=[时间点]+1, columns=self._IDs)
        self._Orders = None# 当前接收到的订单, DataFrame(columns=["ID", "数量", "目标价"])
        self._OpenPosition = None# 当前未平仓的持仓信息, DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "开仓应计利息", "持有应计利息", "浮动盈亏"])
        self._ClosedPosition = None# 已平仓的交易信息, DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "开仓应计利息", "平仓时点", "平仓价格", "平仓交易费", "平仓应计利息", "持有应计利息", "平仓盈亏"])
        self._LastPrice = None# 最新价, Series(index=self._IDs)
        self._AI = None# 应计利息, Series(index=self._IDs)
        self._MarketFT = market_ft# 行情信息因子表对象
        self._PaymentFT = payment_ft# 支付信息因子表对象
        self._UnExInterest = None# 待除权利息, DataFrame(index=[ID], columns=[除息日])
        self._UnpaidInterest = None# 待支付利息, DataFrame(index=[ID], columns=[付息日])
        super().__init__(sys_args=sys_args, config_file=config_file, **kwargs)
        self.Name = "BondAccount"
    def __QS_initArgs__(self):
        super().__QS_initArgs__()
        self.MarketInfo = _BarFactorMap(self._MarketFT)
        self.PaymentInfo = _Payment(self._PaymentFT)
        self.BuyLimit = _TradeLimit(direction="Buy")
        self.SellLimit = _TradeLimit(direction="Sell")
    def __QS_start__(self, mdl, dts, **kwargs):
        Rslt = super().__QS_start__(mdl=mdl, dts=dts, **kwargs)
        self._IDs = list(self.TargetIDs)
        if not self._IDs: self._IDs = list(self._MarketFT.getID(ifactor_name=self.MarketInfo.TradePrice))
        nDT, nID = len(dts), len(self._IDs)
        #self._Cash = np.zeros(nDT+1)
        #self._Debt = np.zeros(nDT+1)
        self._Position = pd.DataFrame(np.zeros((nDT+1, nID)), index=[dts[0]-dt.timedelta(1)]+dts, columns=self._IDs)
        self._PositionAmount = self._Position.copy()
        self._Orders = pd.DataFrame(columns=["ID", "数量", "目标价"])
        self._LastPrice = pd.Series(np.nan, index=self._IDs)
        self._AI = pd.Series(np.nan, index=self._IDs)
        self._OpenPosition = pd.DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "开仓支付利息", "持有所得利息", "持有应计利息", "浮动盈亏"])
        self._ClosedPosition = pd.DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "开仓支付利息", "平仓时点", "平仓价格", "平仓交易费", "平仓收入利息", "持有所得利息", "平仓盈亏"])
        self._UnpaidInterest = pd.DataFrame(index=self._IDs)
        self._iTradingRecord = []# 暂存的交易记录
        self._nDT = nDT
        return Rslt + (self._MarketFT, self._SecurityFT)
    def __QS_move__(self, idt, **kwargs):
        super().__QS_move__(idt, **kwargs)
        # 更新当前的账户信息
        iIndex = self._Model.DateTimeIndex
        self._LastPrice = self._MarketFT.readData(factor_names=[self.MarketInfo.Last, self.MarketInfo.AI], ids=self._IDs, dts=[idt]).iloc[:, 0]
        self._AI, self._LastPrice = self._LastPrice.iloc[:, 1], self._LastPrice.iloc[:, 0]
        if iIndex>0:
            iPreDate, iDate = self._Model._QS_TestDateTimes[iIndex-1].date(), idt.date()
            if iPreDate!=iDate:# 进入新的一天, 更新应计利息, 接受支付
                self._handlePayment(iDate)
                self._OpenPosition["持有应计利息"] = self._OpenPosition["数量"] * self._AI[self._OpenPosition["ID"].tolist()].values
        if self.Delay:# 撮合成交
            self._Position.iloc[iIndex+1] = self._Position.iloc[iIndex]# 初始化持仓
            TradingRecord = self._matchOrder(idt)
            TradingRecord = pd.DataFrame(TradingRecord, index=np.arange(self._TradingRecord.shape[0], self._TradingRecord.shape[0]+len(TradingRecord)), columns=self._TradingRecord.columns)
            self._TradingRecord = self._TradingRecord.append(TradingRecord)
        else:
            TradingRecord = self._iTradingRecord
        self._QS_updatePosition()
        return TradingRecord
    def __QS_after_move__(self, idt, **kwargs):
        super().__QS_after_move__(self, idt, **kwargs)
        iIndex = self._Model.DateTimeIndex
        if not self.Delay:# 撮合成交
            TradingRecord = self._matchOrder(idt)
            TradingRecord = pd.DataFrame(TradingRecord, index=np.arange(self._TradingRecord.shape[0], self._TradingRecord.shape[0]+len(TradingRecord)), columns=self._TradingRecord.columns)
            self._TradingRecord = self._TradingRecord.append(TradingRecord)
            self._iTradingRecord = TradingRecord
            self._QS_updatePosition()
        if iIndex<self._nDT-1:
            iNextDate, iDate = self._Model._QS_TestDateTimes[iIndex+1].date(), idt.date()
            if iNextDate!=iDate:# 当前是该交易日的最后一个时点
                self._addPaymentInfo(iDate)# 添加新的支付信息
        return 0
    def __QS_end__(self):
        super().__QS_end__()
        self._Output["持仓"] = self.getPositionNumSeries()
        self._Output["持仓金额"] = self.getPositionAmountSeries()
        self._Output["平仓记录"] = self._ClosedPosition
        self._Output["未平仓持仓"] = self._OpenPosition
        return 0
    # 添加新的支付信息
    def _addPaymentInfo(self, idate):
        if self._OpenPosition.shape[0]==0: return 0
        iDT = dt.datetime.combine(idate, dt.time(0))
        Interest = self._PaymentFT.readData(factor_names=[self.PaymentInfo.Interest, self.PaymentInfo.Principal], ids=self._OpenPosition["ID"].unique().tolist(), dts=[iDT]).iloc[:, 0, :].sum(axis=1)
        IDs = Interest[Interest>0].index.tolist()
        if not IDs: return 0
        iPosition = self.Position
        DateInfo = self._PaymentFT.readData(factor_names=[self.PaymentInfo.ExDate, self.PaymentInfo.PayDate], ids=IDs, dts=[iDT]).iloc[:, 0, :]
        for i, iID in IDs:
            iExDate, iPayDate = DateInfo.iloc[i, 0], DateInfo.iloc[i, 1]
            if pd.notnull(iExDate) and pd.notnull(iPayDate):
                iExDate, iPayDate = dt.date(int(iExDate[:4]), int(iExDate[4:6]), int(iExDate[6:8])), dt.date(int(iPayDate[:4]), int(iPayDate[4:6]), int(iPayDate[6:8]))
                iInterest = Interest[iID] * iPosition[iID]
                self._UnExInterest[iPayDate] = self._UnExInterest.get(iPayDate, 0)
                self._UnExInterest.loc[iID, iPayDate] += iInterest
                self._UnpaidInterest[iPayDate] = self._UnpaidInterest.get(iPayDate, 0)
                self._UnpaidInterest.loc[iID, iPayDate] += iInterest
        return 0
    # 处理付息
    def _handlePayment(self, idate):
        if idate in self._UnExInterest:
            iInterest = self._UnExInterest.pop(idate)
            iInterest = iInterest[iInterest>0]
            for i, iID in enumerate(iInterest.index):
                self.addCash(iInterest.iloc[i], "%s: 除息" % iID)
                self._FrozenCash += iInterest.iloc[i]
        if idate in self._UnpaidInterest:
            iInterest = self._UnpaidInterest.pop(idate)
            iInterest = iInterest[iInterest>0]
            for i, iID in enumerate(iInterest.index):
                self._FrozenCash -= iInterest.iloc[i]
        return 0
    # 当前账户价值
    @property
    def AccountValue(self):
        return super().AccountValue + np.nansum(self._PositionAmount.iloc[self._Model.DateTimeIndex+1])
    # 当前账户的持仓数量
    @property
    def Position(self):
        return self._Position.iloc[self._Model.DateTimeIndex+1]
    # 当前账户的持仓金额, 即保证金
    @property
    def PositionAmount(self):
        self.PositionAmount.iloc[self._Model.DateTimeIndex+1]
    # 本账户支持交易的证券 ID
    @property
    def IDs(self):
        return self._IDs
    # 当前账户中还未成交的订单, DataFrame(index=[int], columns=["ID", "数量", "目标价"])
    @property
    def Orders(self):
        return self._Orders
    # 当前最新价
    @property
    def LastPrice(self):
        return self._LastPrice
    # 获取持仓的历史序列, 以时间点为索引, 返回: pd.DataFrame(持仓, index=[时间点], columns=[ID])
    def getPositionNumSeries(self, dts=None, start_dt=None, end_dt=None):
        Data = self._Position.iloc[1:self._Model.DateTimeIndex+2]
        return cutDateTime(Data, dts=dts, start_dt=start_dt, end_dt=end_dt)
    # 获取持仓证券的金额历史序列, 以时间点为索引, 返回: pd.DataFrame(持仓金额, index=[时间点], columns=[ID])
    def getPositionAmountSeries(self, dts=None, start_dt=None, end_dt=None):
        Data = self._PositionAmount[1:self._Model.DateTimeIndex+2]
        return cutDateTime(Data, dts=dts, start_dt=start_dt, end_dt=end_dt)
    # 获取账户价值的历史序列, 以时间点为索引
    def getAccountValueSeries(self, dts=None, start_dt=None, end_dt=None):
        CashSeries = self.getCashSeries(dts=dts, start_dt=start_dt, end_dt=end_dt)
        PositionAmountSeries = self.getPositionAmountSeries(dts=dts, start_dt=start_dt, end_dt=end_dt).sum(axis=1)
        DebtSeries = self.getDebtSeries(dts=dts, start_dt=start_dt, end_dt=end_dt)
        return CashSeries + PositionAmountSeries - DebtSeries
    # 执行给定数量的证券委托单, target_id: 目标证券 ID, num: 待买卖的数量, target_price: nan 表示市价单, 
    # combined_order: 组合订单, DataFrame(index=[ID],columns=[数量, 目标价])
    # 基本的下单函数, 必须实现, 目前只实现了市价单
    def order(self, target_id=None, num=0, target_price=np.nan, combined_order=None):
        if target_id is not None:
            #if (num>0) and (self.BuyLimit.MinUnit>0):
                #num = np.fix(num / self.BuyLimit.MinUnit) * self.BuyLimit.MinUnit
            #elif (num<0) and (self.SellLimit.MinUnit>0):
                #num = np.fix(num / self.SellLimit.MinUnit) * self.SellLimit.MinUnit
            self._Orders.loc[self._Orders.shape[0]] = (target_id, num, target_price)
            return (target_id, num, target_price)
        if combined_order is not None:
            #if self.BuyLimit.MinUnit>0:
                #Mask = (combined_order["数量"]>0)
                #combined_order["数量"][Mask] = np.fix(combined_order["数量"][Mask] / self.BuyLimit.MinUnit) * self.BuyLimit.MinUnit
            #if self.SellLimit.MinUnit>0:
                #Mask = (combined_order["数量"]<0)
                #combined_order["数量"][Mask] = np.fix(combined_order["数量"][Mask] / self.SellLimit.MinUnit) * self.SellLimit.MinUnit
            combined_order.index = np.arange(self._Orders.shape[0], self._Orders.shape[0]+combined_order.shape[0])
            self._Orders = self._Orders.append(combined_order)
        return combined_order
    # 撤销订单, order_ids 是订单在 self.Orders 中的 index
    def cancelOrder(self, order_ids):
        self._Orders = self._Orders.loc[self._Orders.index.difference(set(order_ids))]
        self._Orders.sort_index(axis=0, inplace=True)
        self._Orders.index = np.arange(self._Orders.shape[0])
        return 0
    # 更新仓位信息
    def _QS_updatePosition(self):
        iIndex = self._Model.DateTimeIndex
        iNum = self._OpenPosition.groupby(by=["ID"])["数量"].sum()
        iPosition = pd.Series(0, index=self._IDs)
        iPosition[iNum.index] += iNum
        self._Position.iloc[iIndex+1] = iPosition.values
        LastPrice = self._LastPrice[self._OpenPosition["ID"].tolist()].values
        self._OpenPosition["浮动盈亏"] = self._OpenPosition["数量"] * (LastPrice - self._OpenPosition["开仓价格"]) + self._OpenPosition["持有应计利息"] - self._OpenPosition["开仓支付利息"] + self._OpenPosition["持有所得利息"]
        iPositionAmount = pd.Series(self._OpenPosition["数量"].values * LastPrice + self._OpenPosition["持有应计利息"].values, index=self._OpenPosition["ID"].values).groupby(axis=0, level=0).sum()
        self._PositionAmount.iloc[iIndex+1] = 0.0
        self._PositionAmount.iloc[iIndex+1][iPositionAmount.index] = iPositionAmount
        return 0
    # 撮合成交订单
    def _matchOrder(self, idt):
        MarketOrderMask = pd.isnull(self._Orders["目标价"])
        MarketOrders = self._Orders[MarketOrderMask]
        LimitOrders = self._Orders[~MarketOrderMask]
        MarketOrders = MarketOrders.groupby(by=["ID"]).sum()["数量"]# Series(数量, index=[ID])
        if LimitOrders.shape[0]>0:
            LimitOrders = LimitOrders.groupby(by=["ID", "目标价"]).sum()
            LimitOrders.sort_index(ascending=False)
            LimitOrders = LimitOrders.reset_index(level=1)
        else:
            LimitOrders = pd.DataFrame(columns=["ID", "数量", "目标价"])
        # 先执行平仓交易
        TradingRecord, MarketOpenOrders = self._matchMarketCloseOrder(idt, MarketOrders)
        iTradingRecord, LimitOrders = self._matchLimitCloseOrder(idt, LimitOrders)
        TradingRecord.extend(iTradingRecord)
        # 再执行开仓交易
        TradingRecord.extend(self._matchMarketOpenOrder(idt, MarketOpenOrders))
        iTradingRecord, LimitOrders = self._matchLimitOpenOrder(idt, LimitOrders)
        TradingRecord.extend(iTradingRecord)
        self._Orders = LimitOrders
        self._Orders.index = np.arange(self._Orders.shape[0])
        return TradingRecord
    # 撮合成交市价平仓单
    # 以成交价完成成交, 成交量满足交易限制要求
    # 未成交的市价单自动撤销
    def _matchMarketCloseOrder(self, idt, orders):
        orders = orders[orders!=0]
        if orders.shape[0]==0: return ([], pd.Series())
        IDs = orders.index.tolist()
        TradePrice = self._MarketFT.readData(dts=[idt], ids=IDs, factor_names=[self.MarketInfo.TradePrice]).iloc[0, 0]# 成交价
        # 过滤限制条件
        orders[pd.isnull(TradePrice)] = 0.0# 成交价缺失的不能交易
        #if self.SellLimit.LimitIDFilter:# 满足卖出禁止条件的不能卖出
            #Mask = (self._MarketFT.getIDMask(idt, ids=IDs, id_filter_str=self.SellLimit.LimitIDFilter) & (orders<0))
            #orders[Mask] = orders[Mask].clip_lower(0)
        #if self.BuyLimit.LimitIDFilter:# 满足买入禁止条件的不能买入
            #Mask = (self._MarketFT.getIDMask(idt, ids=IDs, id_filter_str=self.BuyLimit.LimitIDFilter) & (orders>0))
            #orders[Mask] = orders[Mask].clip_upper(0)
        #if self.MarketInfo.Vol:# 成交量限制
            #VolLimit = self._MarketFT.readData(factor_names=[self.MarketInfo.Vol], ids=IDs, dts=[idt]).iloc[0, 0]
            #orders = np.maximum(orders, -VolLimit * self.SellLimit.MarketOrderVolumeLimit)
            #orders = np.minimum(orders, VolLimit * self.BuyLimit.MarketOrderVolumeLimit)
        #if self.SellLimit.MinUnit!=0.0:# 最小卖出交易单位限制
            #Mask = (orders<0)
            #orders[Mask] = (orders[Mask] / self.SellLimit.MinUnit).astype("int") * self.SellLimit.MinUnit
        #if self.BuyLimit.MinUnit!=0.0:# 最小买入交易单位限制
            #Mask = (orders>0)
            #orders[Mask] = (orders[Mask] / self.BuyLimit.MinUnit).astype("int") * self.BuyLimit.MinUnit
        # 分离平仓单和开仓单
        Position = self.Position[IDs]
        CloseOrders = orders.clip(lower=(-Position).clip_upper(0), upper=(-Position).clip_lower(0))# 平仓单
        OpenOrders = orders - CloseOrders# 开仓单
        CloseOrders = CloseOrders[CloseOrders!=0]
        if CloseOrders.shape[0]==0: return ([], OpenOrders)
        # 处理平仓单
        TradePrice = TradePrice[CloseOrders.index]
        TradeAmounts = CloseOrders * TradePrice * self.SecurityInfo.Multiplier
        Fees = TradeAmounts.clip_lower(0) * self.BuyLimit.TradeFee + TradeAmounts.clip_upper(0).abs() * self.SellLimit.TradeFee
        iOpenPosition = self._OpenPosition.set_index(["ID"])
        iClosedPosition = pd.DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "保证金", "平仓时点", "平仓价格", "平仓交易费", "平仓盈亏"])
        iNewPosition = pd.DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "保证金", "浮动盈亏"])
        CashChanged = np.zeros(CloseOrders.shape[0])
        for j, jID in enumerate(CloseOrders.index):
            jNum = CloseOrders.iloc[j]
            ijPosition = iOpenPosition.loc[[jID]]
            if jNum<0: ijClosedNum = (ijPosition["数量"] - (ijPosition["数量"].cumsum()+jNum).clip_lower(0)).clip_lower(0)
            else: ijClosedNum = (ijPosition["数量"] - (ijPosition["数量"].cumsum()+jNum).clip_upper(0)).clip_upper(0)
            CashChanged[j] = ijClosedNum * (TradePrice[jID] - ijPosition["开仓价格"]) + ijPosition["保证金"] - Fees[jID]
            ijClosedPosition = ijPosition.copy()
            ijClosedPosition["数量"] = ijClosedNum
            ijClosedPosition["平仓时点"] = idt
            ijClosedPosition["平仓价格"] = TradePrice[jID]
            ijClosedPosition["平仓交易费"] = Fees[jID]
            ijClosedPosition["平仓盈亏"] = ijClosedNum * (TradePrice[jID] - ijPosition["开仓价格"]) * self.SecurityInfo.Multiplier
            ijClosedPosition["保证金"] = ijClosedPosition["保证金"] * (ijClosedPosition["数量"] / ijPosition["数量"])
            ijPosition["保证金"] = ijPosition["保证金"] - ijClosedPosition["保证金"]
            ijPosition["数量"] -= ijClosedNum
            CashChanged[j] = ijClosedPosition["平仓盈亏"].sum() - Fees[jID] + ijClosedPosition["保证金"].sum()
            ijClosedPosition[ijClosedPosition["数量"]!=0]
            ijClosedPosition.pop("浮动盈亏")
            iClosedPosition = iClosedPosition.append(ijClosedPosition.reset_index())
            iNewPosition = iNewPosition.append(ijPosition[ijPosition["数量"]!=0].reset_index())
        iClosedPosition.index = np.arange(self._ClosedPosition.shape[0], self._ClosedPosition.shape[0]+iClosedPosition.shape[0])
        self._ClosedPosition = self._ClosedPosition.append(iClosedPosition)
        iOpenPosition = iOpenPosition.loc[iOpenPosition.index.difference(CloseOrders.index)].reset_index()
        iNewPosition.index = np.arange(iOpenPosition.shape[0], iOpenPosition.shape[0]+iNewPosition.shape[0])
        self._OpenPosition = iOpenPosition.append(iNewPosition)
        TradingRecord = list(zip([idt]*CloseOrders.shape[0], CloseOrders.index, CloseOrders, TradePrice, Fees, CashChanged, ["close"]*CloseOrders.shape[0]))
        if TradingRecord: self._updateAccount(CashChanged.sum())# 更新账户信息
        return (TradingRecord, OpenOrders)
    # 撮合成交市价开仓单
    # 以成交价完成成交, 成交量满足交易限制要求
    # 未成交的市价单自动撤销
    def _matchMarketOpenOrder(self, idt, orders):
        orders = orders[orders!=0]
        if orders.shape[0]==0: return []
        IDs = orders.index.tolist()
        TradePrice = self._MarketFT.readData(dts=[idt], ids=IDs, factor_names=[self.MarketInfo.TradePrice]).iloc[0, 0]# 成交价
        # 处理开仓单
        TradeAmounts = orders * TradePrice * self.SecurityInfo.Multiplier
        MarginAcquired = TradeAmounts.abs() * self.SecurityInfo.InitMarginRate
        FeesAcquired = TradeAmounts.clip_lower(0) * self.BuyLimit.TradeFee + TradeAmounts.clip_upper(0).abs() * self.SellLimit.TradeFee
        CashAcquired = MarginAcquired + FeesAcquired
        CashAllocated = min(CashAcquired.sum(), self.AvailableCash) * CashAcquired / CashAcquired.sum()
        orders = CashAllocated / TradePrice / self.SecurityInfo.Multiplier / (self.SecurityInfo.InitMarginRate + self.BuyLimit.TradeFee * (orders>0) + self.SellLimit.TradeFee * (orders<0)) * np.sign(orders)
        #if self.SellLimit.MinUnit!=0.0:# 最小卖出交易单位限制
            #Mask = (orders<0)
            #orders[Mask] = (orders[Mask] / self.SellLimit.MinUnit).astype("int") * self.SellLimit.MinUnit
        #if self.BuyLimit.MinUnit!=0.0:# 最小买入交易单位限制
            #Mask = (orders>0)
            #orders[Mask] = (orders[Mask] / self.BuyLimit.MinUnit).astype("int") * self.BuyLimit.MinUnit
        orders = orders[pd.notnull(orders) & (orders!=0)]
        if orders.shape[0]==0: return []
        TradePrice = TradePrice[orders.index]
        TradeAmounts = orders * TradePrice * self.SecurityInfo.Multiplier
        Fees = TradeAmounts.clip_lower(0) * self.BuyLimit.TradeFee + TradeAmounts.clip_upper(0).abs() * self.SellLimit.TradeFee
        MarginAcquired = TradeAmounts.abs() * self.SecurityInfo.InitMarginRate
        CashChanged = - (MarginAcquired + Fees).sum()
        TradingRecord = list(zip([idt]*orders.shape[0], orders.index, orders, TradePrice, Fees, -(MarginAcquired+Fees), ["open"]*orders.shape[0]))
        iNewPosition = pd.DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "保证金", "浮动盈亏"])
        iNewPosition["ID"] = orders.index
        iNewPosition["数量"] = orders.values
        iNewPosition["开仓时点"] = idt
        iNewPosition["开仓价格"] = TradePrice.values
        iNewPosition["开仓交易费"] = Fees.values
        iNewPosition["保证金"] = MarginAcquired.values
        iNewPosition["浮动盈亏"] = 0.0
        iNewPosition.index = np.arange(self._OpenPosition.shape[0], self._OpenPosition.shape[0]+iNewPosition.shape[0])
        self._OpenPosition = self._OpenPosition.append(iNewPosition)
        self._updateAccount(CashChanged)# 更新账户信息
        return TradingRecord
    # 撮合成交卖出限价单
    # 如果最高价和最低价未指定, 则检验成交价是否优于目标价, 是就以目标价完成成交, 且成交量满足卖出限制中的限价单成交量限比, 否则无法成交
    # 如果指定了最高价和最低价, 则假设成交价服从[最低价, 最高价]中的均匀分布, 据此确定可完成成交的数量
    # 未成交的限价单继续保留, TODO
    def _matchLimitCloseOrder(self, idt, sell_orders):
        return ([], pd.DataFrame(columns=["ID", "数量", "目标价"]))
    # 撮合成交买入限价单
    # 如果最高价和最低价未指定, 则检验成交价是否优于目标价, 是就以目标价完成成交, 且成交量满足买入限制要求
    # 如果指定了最高价和最低价, 则假设成交价服从[最低价, 最高价]中的均匀分布, 据此确定可完成成交的数量, 同时满足买入限制要求
    # 未成交的限价单继续保留, TODO
    def _matchLimitOpenOrder(self, idt, buy_orders):
        return ([], pd.DataFrame(columns=["ID", "数量", "目标价"]))
示例#14
0
class SQLDB(QSSQLObject, WritableFactorDB):
    """SQLDB"""
    CheckWriteData = Bool(False, arg_type="Bool", label="检查写入值", order=100)
    IgnoreFields = ListStr(arg_type="List", label="忽略字段", order=101)
    InnerPrefix = Str("qs_", arg_type="String", label="内部前缀", order=102)

    def __init__(self, sys_args={}, config_file=None, **kwargs):
        super().__init__(sys_args=sys_args,
                         config_file=(__QS_ConfigPath__ + os.sep +
                                      "SQLDBConfig.json"
                                      if config_file is None else config_file),
                         **kwargs)
        self._TableFactorDict = {}  # {表名: pd.Series(数据类型, index=[因子名])}
        self._TableFieldDataType = {
        }  # {表名: pd.Series(        数据库数据类型, index=[因子名])}
        self.Name = "SQLDB"
        return

    def connect(self):
        super().connect()
        nPrefix = len(self.InnerPrefix)
        if self._Connector == "sqlite3":
            SQLStr = "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%s%%' ORDER BY name"
            Cursor = self.cursor(SQLStr % self.InnerPrefix)
            AllTables = Cursor.fetchall()
            self._TableFactorDict = {}
            self._TableFieldDataType = {}
            IgnoreFields = ["code", "datetime"] + list(self.IgnoreFields)
            for iTableName in AllTables:
                iTableName = iTableName[0][nPrefix:]
                Cursor.execute("PRAGMA table_info([%s])" % self.InnerPrefix +
                               iTableName)
                iDataType = np.array(Cursor.fetchall())
                iDataType = pd.Series(iDataType[:, 2], index=iDataType[:, 1])
                iDataType = iDataType[iDataType.index.difference(IgnoreFields)]
                if iDataType.shape[0] > 0:
                    self._TableFieldDataType[iTableName] = iDataType.copy()
                    iDataType[iDataType == "text"] = "string"
                    iDataType[iDataType == "real"] = "double"
                    self._TableFactorDict[iTableName] = iDataType
        elif self.DBType == "MySQL":
            SQLStr = (
                "SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE FROM information_schema.COLUMNS WHERE table_schema='%s' "
                % self.DBName)
            SQLStr += ("AND TABLE_NAME LIKE '%s%%' " % self.InnerPrefix)
            SQLStr += "AND COLUMN_NAME NOT IN ('code', 'datetime'"
            if len(self.IgnoreFields) > 0:
                SQLStr += ",'" + "','".join(self.IgnoreFields) + "') "
            else:
                SQLStr += ") "
            SQLStr += "ORDER BY TABLE_NAME, COLUMN_NAME"
            Rslt = self.fetchall(SQLStr)
            if not Rslt:
                self._TableFieldDataType = {}
                self._TableFactorDict = {}
            else:
                self._TableFieldDataType = pd.DataFrame(
                    np.array(Rslt),
                    columns=["表", "因子",
                             "DataType"]).set_index(["表", "因子"])["DataType"]
                self._TableFactorDict = self._TableFieldDataType.copy()
                Mask = (self._TableFactorDict.str.contains("char")
                        | self._TableFactorDict.str.contains("date"))
                self._TableFactorDict[Mask] = "string"
                self._TableFactorDict[~Mask] = "double"
                self._TableFactorDict = {
                    iTable[nPrefix:]: self._TableFactorDict.loc[iTable]
                    for iTable in self._TableFactorDict.index.levels[0]
                }
                self._TableFieldDataType = {
                    iTable[nPrefix:]: self._TableFieldDataType.loc[iTable]
                    for iTable in self._TableFieldDataType.index.levels[0]
                }
        return 0

    @property
    def TableNames(self):
        return sorted(self._TableFactorDict)

    def getTable(self, table_name, args={}):
        if table_name not in self._TableFactorDict:
            Msg = ("因子库 '%s' 调用方法 getTable 错误: 不存在因子表: '%s'!" %
                   (self.Name, table_name))
            self._QS_Logger.error(Msg)
            raise __QS_Error__(Msg)
        if args.get("因子表类型", "宽表") == "宽表":
            return _WideTable(name=table_name,
                              fdb=self,
                              sys_args=args,
                              logger=self._QS_Logger)
        else:
            return _NarrowTable(name=table_name,
                                fdb=self,
                                sys_args=args,
                                logger=self._QS_Logger)

    def renameTable(self, old_table_name, new_table_name):
        if old_table_name not in self._TableFactorDict:
            Msg = ("因子库 '%s' 调用方法 renameTable 错误: 不存在因子表 '%s'!" %
                   (self.Name, old_table_name))
            self._QS_Logger.error(Msg)
            raise __QS_Error__(Msg)
        if (new_table_name != old_table_name) and (new_table_name
                                                   in self._TableFactorDict):
            Msg = ("因子库 '%s' 调用方法 renameTable 错误: 新因子表名 '%s' 已经存在于库中!" %
                   (self.Name, new_table_name))
            self._QS_Logger.error(Msg)
            raise __QS_Error__(Msg)
        self.renameDBTable(self.InnerPrefix + old_table_name,
                           self.InnerPrefix + new_table_name)
        self._TableFactorDict[new_table_name] = self._TableFactorDict.pop(
            old_table_name)
        self._TableFieldDataType[
            new_table_name] = self._TableFieldDataType.pop(old_table_name)
        return 0

    # 创建表, field_types: {字段名: 数据库数据类型}
    def createTable(self, table_name, field_types):
        FieldTypes = field_types.copy()
        if self.DBType == "MySQL":
            FieldTypes["datetime"] = field_types.pop("datetime",
                                                     "DATETIME(6) NOT NULL")
            FieldTypes["code"] = field_types.pop("code",
                                                 "VARCHAR(40) NOT NULL")
        elif self.DBType == "sqlite3":
            FieldTypes["datetime"] = field_types.pop("datetime",
                                                     "text NOT NULL")
            FieldTypes["code"] = field_types.pop("code", "text NOT NULL")
        self.createDBTable(self.InnerPrefix + table_name,
                           FieldTypes,
                           primary_keys=["datetime", "code"],
                           index_fields=["datetime", "code"])
        self._TableFactorDict[table_name] = pd.Series({
            iFactorName:
            ("string"
             if field_types[iFactorName].find("char") != -1 else "double")
            for iFactorName in field_types
        })
        self._TableFieldDataType[table_name] = pd.Series(field_types)
        return 0

    # 增加因子,field_types: {字段名: 数据库数据类型}
    def addFactor(self, table_name, field_types):
        if table_name not in self._TableFactorDict:
            return self.createTable(table_name, field_types)
        self.addField(self.InnerPrefix + table_name, field_types)
        NewDataType = pd.Series({
            iFactorName:
            ("string"
             if field_types[iFactorName].find("char") != -1 else "double")
            for iFactorName in field_types
        })
        self._TableFactorDict[table_name] = self._TableFactorDict[
            table_name].append(NewDataType)
        self._TableFieldDataType[table_name] = self._TableFieldDataType[
            table_name].append(pd.Series(field_types))
        return 0

    def deleteTable(self, table_name):
        if table_name not in self._TableFactorDict: return 0
        self.deleteDBTable(self.InnerPrefix + table_name)
        self._TableFactorDict.pop(table_name, None)
        self._TableFieldDataType.pop(table_name, None)
        return 0

    # ----------------------------因子操作---------------------------------
    def renameFactor(self, table_name, old_factor_name, new_factor_name):
        if old_factor_name not in self._TableFactorDict[table_name]:
            Msg = ("因子库 '%s' 调用方法 renameFactor 错误: 因子表 '%s' 中不存在因子 '%s'!" %
                   (self.Name, table_name, old_factor_name))
            self._QS_Logger.error(Msg)
            raise __QS_Error__(Msg)
        if (new_factor_name != old_factor_name) and (
                new_factor_name in self._TableFactorDict[table_name]):
            Msg = (
                "因子库 '%s' 调用方法 renameFactor 错误: 新因子名 '%s' 已经存在于因子表 '%s' 中!" %
                (self.Name, new_factor_name, table_name))
            self._QS_Logger.error(Msg)
            raise __QS_Error__(Msg)
        self.renameField(self.InnerPrefix + table_name, old_factor_name,
                         new_factor_name)
        self._TableFactorDict[table_name][
            new_factor_name] = self._TableFactorDict[table_name].pop(
                old_factor_name)
        self._TableFieldDataType[table_name][
            new_factor_name] = self._TableFieldDataType[table_name].pop(
                old_factor_name)
        return 0

    def deleteFactor(self, table_name, factor_names):
        if not factor_names: return 0
        FactorIndex = self._TableFactorDict.get(
            table_name, pd.Series()).index.difference(factor_names).tolist()
        if not FactorIndex: return self.deleteTable(table_name)
        self.deleteField(self.InnerPrefix + table_name, factor_names)
        self._TableFactorDict[table_name] = self._TableFactorDict[table_name][
            FactorIndex]
        self._TableFieldDataType[table_name] = self._TableFieldDataType[
            table_name][FactorIndex]
        return 0

    def deleteData(self, table_name, ids=None, dts=None):
        if table_name not in self._TableFactorDict:
            Msg = ("因子库 '%s' 调用方法 deleteData 错误: 不存在因子表 '%s'!" %
                   (self.Name, table_name))
            self._QS_Logger.error(Msg)
            raise __QS_Error__(Msg)
        if (ids is None) and (dts is None):
            return self.truncateDBTable(self.InnerPrefix + table_name)
        DBTableName = self.TablePrefix + self.InnerPrefix + table_name
        SQLStr = "DELETE FROM " + DBTableName
        if dts is not None:
            DTs = [iDT.strftime("%Y-%m-%d %H:%M:%S.%f") for iDT in dts]
            SQLStr += "WHERE " + genSQLInCondition(
                DBTableName + ".datetime", DTs, is_str=True,
                max_num=1000) + " "
        else:
            SQLStr += "WHERE " + DBTableName + ".datetime IS NOT NULL "
        if ids is not None:
            SQLStr += "AND " + genSQLInCondition(
                DBTableName + ".code", ids, is_str=True, max_num=1000)
        try:
            self.execute(SQLStr)
        except Exception as e:
            Msg = ("'%s' 调用方法 deleteData 删除表 '%s' 中数据时错误: %s" %
                   (self.Name, table_name, str(e)))
            self._QS_Logger.error(Msg)
            raise e
        return 0

    def _adjustWriteData(self, data):
        NewData = []
        DataLen = data.applymap(lambda x: len(x) if isinstance(x, list) else 1)
        DataLenMax = DataLen.max(axis=1)
        DataLenMin = DataLen.min(axis=1)
        if (DataLenMax != DataLenMin).sum() > 0:
            self._QS_Logger.warning("'%s' 在写入因子 '%s' 时出现因子值长度不一致的情况, 将填充缺失!" %
                                    (self.Name, str(data.columns.tolist())))
        for i in range(data.shape[0]):
            iDataLen = DataLen.iloc[i]
            if iDataLen > 0:
                iData = data.iloc[i].apply(
                    lambda x: [None] * (iDataLen - len(x)) + x
                    if isinstance(x, list) else [x] * iDataLen).tolist()
                NewData.extend(zip(*iData))
        NewData = pd.DataFrame(NewData, dtype="O")
        return NewData.where(pd.notnull(NewData),
                             None).to_records(index=False).tolist()

    def writeData(self,
                  data,
                  table_name,
                  if_exists="update",
                  data_type={},
                  **kwargs):
        if table_name not in self._TableFactorDict:
            FieldTypes = {
                iFactorName: _identifyDataType(self.DBType,
                                               data.iloc[i].dtypes)
                for i, iFactorName in enumerate(data.items)
            }
            self.createTable(table_name, field_types=FieldTypes)
            SQLStr = "INSERT INTO " + self.TablePrefix + self.InnerPrefix + table_name + " (`datetime`, `code`, "
        else:
            NewFactorNames = data.items.difference(
                self._TableFactorDict[table_name].index).tolist()
            if NewFactorNames:
                FieldTypes = {
                    iFactorName: _identifyDataType(self.DBType,
                                                   data.iloc[i].dtypes)
                    for i, iFactorName in enumerate(NewFactorNames)
                }
                self.addFactor(table_name, FieldTypes)
            AllFactorNames = self._TableFactorDict[table_name].index.tolist()
            if self.CheckWriteData:
                OldData = self.getTable(table_name,
                                        args={
                                            "因子值类型": "list",
                                            "时间转字符串": True
                                        }).readData(
                                            factor_names=AllFactorNames,
                                            ids=data.minor_axis.tolist(),
                                            dts=data.major_axis.tolist())
            else:
                OldData = self.getTable(table_name, args={
                    "时间转字符串": True
                }).readData(factor_names=AllFactorNames,
                            ids=data.minor_axis.tolist(),
                            dts=data.major_axis.tolist())
            if if_exists == "append":
                for iFactorName in AllFactorNames:
                    if iFactorName in data:
                        data[iFactorName] = OldData[iFactorName].where(
                            pd.notnull(OldData[iFactorName]),
                            data[iFactorName])
                    else:
                        data[iFactorName] = OldData[iFactorName]
            elif if_exists == "update":
                for iFactorName in AllFactorNames:
                    if iFactorName in data:
                        data[iFactorName] = data[iFactorName].where(
                            pd.notnull(data[iFactorName]),
                            OldData[iFactorName])
                    else:
                        data[iFactorName] = OldData[iFactorName]
            SQLStr = "REPLACE INTO " + self.TablePrefix + self.InnerPrefix + table_name + " (`datetime`, `code`, "
        data.major_axis = [
            iDT.strftime("%Y-%m-%d %H:%M:%S.%f") for iDT in data.major_axis
        ]
        NewData = {}
        for iFactorName in data.items:
            iData = data.loc[iFactorName].stack(dropna=False)
            NewData[iFactorName] = iData
            SQLStr += "`" + iFactorName + "`, "
        NewData = pd.DataFrame(NewData).loc[:, data.items]
        NewData = NewData[pd.notnull(NewData).any(axis=1)]
        if NewData.shape[0] == 0: return 0
        if self._Connector in ("pyodbc", "sqlite3"):
            SQLStr = SQLStr[:-2] + ") VALUES (" + "?, " * (NewData.shape[1] +
                                                           2)
        else:
            SQLStr = SQLStr[:-2] + ") VALUES (" + "%s, " * (NewData.shape[1] +
                                                            2)
        SQLStr = SQLStr[:-2] + ") "
        Cursor = self._Connection.cursor()
        if self.CheckWriteData:
            NewData = self._adjustWriteData(NewData.reset_index())
            Cursor.executemany(SQLStr, NewData)
        else:
            NewData = NewData.astype("O").where(pd.notnull(NewData), None)
            Cursor.executemany(SQLStr, NewData.reset_index().values.tolist())
        self._Connection.commit()
        Cursor.close()
        return 0
示例#15
0
class FMPModel(BaseModule):
    """基于特征因子模拟组合的绩效分析模型"""
    #Portfolio = Enum(None, arg_type="SingleOption", label="策略组合", order=0)
    #BenchmarkPortfolio = Enum("无", arg_type="SingleOption", label="基准组合", order=1)
    AttributeFactors = ListStr(arg_type="MultiOption",
                               label="特征因子",
                               order=2,
                               option_range=())
    #IndustryFactor = Enum("无", arg_type="SingleOption", label="行业因子", order=3)
    #PriceFactor = Enum(None, arg_type="SingleOption", label="价格因子", order=4)
    RiskTable = Instance(RiskTable, arg_type="RiskTable", label="风险表", order=5)
    CalcDTs = List(dt.datetime, arg_type="DateList", label="计算时点", order=6)

    def __init__(self,
                 factor_table,
                 name="因子模拟组合绩效分析模型",
                 sys_args={},
                 **kwargs):
        self._FactorTable = factor_table
        return super().__init__(name=name,
                                sys_args=sys_args,
                                config_file=None,
                                **kwargs)

    def __QS_initArgs__(self):
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._FactorTable.getFactorMetaData(key="DataType")))
        self.add_trait(
            "Portfolio",
            Enum(*DefaultNumFactorList,
                 arg_type="SingleOption",
                 label="策略组合",
                 order=0))
        self.add_trait(
            "BenchmarkPortfolio",
            Enum(*(["无"] + DefaultNumFactorList),
                 arg_type="SingleOption",
                 label="基准组合",
                 order=1))
        self.add_trait(
            "AttributeFactors",
            ListStr(arg_type="MultiOption",
                    label="特征因子",
                    order=2,
                    option_range=tuple(DefaultNumFactorList)))
        self.AttributeFactors.append(DefaultNumFactorList[-1])
        self.add_trait(
            "IndustryFactor",
            Enum(*(["无"] + DefaultStrFactorList),
                 arg_type="SingleOption",
                 label="行业因子",
                 order=3))
        self.add_trait(
            "PriceFactor",
            Enum(*DefaultNumFactorList,
                 arg_type="SingleOption",
                 label="价格因子",
                 order=4))
        self.PriceFactor = searchNameInStrList(DefaultNumFactorList,
                                               ['价', 'Price', 'price'])

    def _normalizePortfolio(self, portfolio):
        NegMask = (portfolio < 0)
        TotalNegWeight = portfolio[NegMask].sum()
        if TotalNegWeight != 0:
            portfolio[NegMask] = portfolio[NegMask] / TotalNegWeight
        PosMask = (portfolio > 0)
        TotalPosWeight = portfolio[PosMask].sum()
        if TotalPosWeight != 0:
            portfolio[PosMask] = portfolio[PosMask] / TotalPosWeight
            portfolio[
                NegMask] = portfolio[NegMask] * TotalNegWeight / TotalPosWeight
        return portfolio

    def __QS_start__(self, mdl, dts, **kwargs):
        if self._isStarted: return ()
        super().__QS_start__(mdl=mdl, dts=dts, **kwargs)
        self.RiskTable.start(dts=dts)
        self._Output = {}
        self._Output["因子暴露"] = pd.DataFrame(columns=self.AttributeFactors)
        self._Output["风险调整的因子暴露"] = pd.DataFrame(columns=self.AttributeFactors)
        self._Output["风险贡献"] = pd.DataFrame(columns=self.AttributeFactors +
                                            ["Alpha"])
        self._Output["收益贡献"] = pd.DataFrame(columns=self.AttributeFactors +
                                            ["Alpha"])
        self._Output["因子收益"] = pd.DataFrame(columns=self.AttributeFactors)
        self._CurCalcInd = 0
        self._IDs = self._FactorTable.getID()
        return (self._FactorTable, )

    def __QS_move__(self, idt, **kwargs):
        if self._iDT == idt: return 0
        self._iDT = idt
        PreDT = None
        if self.CalcDTs:
            if idt not in self.CalcDTs[self._CurCalcInd:]: return 0
            self._CurCalcInd = self.CalcDTs[self._CurCalcInd:].index(
                idt) + self._CurCalcInd
            if self._CurCalcInd > 0: PreDT = self.CalcDTs[self._CurCalcInd - 1]
        else:
            self._CurCalcInd = self._Model.DateTimeIndex
            if self._CurCalcInd > 0:
                PreDT = self._Model.DateTimeSeries[self._CurCalcInd - 1]
        if PreDT is None: return 0
        Portfolio = self._FactorTable.readData(factor_names=[self.Portfolio],
                                               dts=[PreDT],
                                               ids=self._IDs).iloc[0, 0]
        Portfolio = self._normalizePortfolio(Portfolio[pd.notnull(Portfolio)
                                                       & (Portfolio != 0)])
        if self.BenchmarkPortfolio != "无":
            BenchmarkPortfolio = self._FactorTable.readData(
                factor_names=[self.BenchmarkPortfolio],
                dts=[PreDT],
                ids=self._IDs).iloc[0, 0]
            BenchmarkPortfolio = self._normalizePortfolio(
                BenchmarkPortfolio[pd.notnull(BenchmarkPortfolio)
                                   & (BenchmarkPortfolio != 0)])
            IDs = Portfolio.index.union(BenchmarkPortfolio.index)
            if Portfolio.shape[0] > 0:
                Portfolio = Portfolio.loc[IDs]
                Portfolio.fillna(0.0, inplace=True)
            else:
                Portfolio = pd.Series(0.0, index=IDs)
            if BenchmarkPortfolio.shape[0] > 0:
                BenchmarkPortfolio = BenchmarkPortfolio.loc[IDs]
                BenchmarkPortfolio.fillna(0.0, inplace=True)
            else:
                BenchmarkPortfolio = pd.Series(0.0, index=IDs)
            Portfolio = Portfolio - BenchmarkPortfolio
        # 计算因子模拟组合
        self.RiskTable.move(PreDT, **kwargs)
        CovMatrix = dropRiskMatrixNA(
            self.RiskTable.readCov(dts=[PreDT],
                                   ids=Portfolio.index.tolist()).iloc[0])
        FactorExpose = self._FactorTable.readData(
            factor_names=list(self.AttributeFactors), ids=IDs,
            dts=[PreDT]).iloc[:, 0].dropna(axis=0)
        IDs = FactorExpose.index.intersection(CovMatrix.index).tolist()
        CovMatrix, FactorExpose = CovMatrix.loc[IDs,
                                                IDs], FactorExpose.loc[IDs, :]
        if self.IndustryFactor != "无":
            IndustryData = self._FactorTable.readData(
                factor_names=[self.IndustryFactor], ids=IDs,
                dts=[PreDT]).iloc[0, 0, :]
            DummyData = DummyVarTo01Var(IndustryData, ignore_nonstring=True)
            DummyData.columns.values[pd.isnull(DummyData.columns)] = "None"
            FactorExpose = pd.merge(FactorExpose,
                                    DummyData,
                                    left_index=True,
                                    right_index=True)
        CovMatrixInv = np.linalg.inv(CovMatrix.values)
        FMPHolding = np.dot(
            np.dot(
                np.linalg.inv(
                    np.dot(np.dot(FactorExpose.values.T, CovMatrixInv),
                           FactorExpose.values)), FactorExpose.values.T),
            CovMatrixInv)
        # 计算持仓对因子模拟组合的投资组合
        Portfolio = self._normalizePortfolio(Portfolio.loc[IDs])
        Beta = np.dot(
            np.dot(
                np.dot(
                    np.linalg.inv(
                        np.dot(np.dot(FMPHolding, CovMatrix.values),
                               FMPHolding.T)), FMPHolding), CovMatrix.values),
            Portfolio.values)
        Price = self._FactorTable.readData(factor_names=[self.PriceFactor],
                                           dts=[PreDT, idt],
                                           ids=IDs).iloc[0]
        Return = Price.iloc[1] / Price.iloc[0] - 1
        # 计算各统计指标
        if FactorExpose.shape[1] > self._Output["因子暴露"].shape[1]:
            FactorNames = FactorExpose.columns.tolist()
            self._Output["因子暴露"] = self._Output["因子暴露"].loc[:, FactorNames]
            self._Output["风险调整的因子暴露"] = self._Output[
                "风险调整的因子暴露"].loc[:, FactorNames]
            self._Output["风险贡献"] = self._Output["风险贡献"].loc[:, FactorNames +
                                                            ["Alpha"]]
            self._Output["收益贡献"] = self._Output["收益贡献"].loc[:, FactorNames +
                                                            ["Alpha"]]
            self._Output["因子收益"] = self._Output["因子收益"].loc[:, FactorNames]
        self._Output["因子暴露"].loc[PreDT, FactorExpose.columns] = Beta
        self._Output["风险调整的因子暴露"].loc[PreDT, FactorExpose.columns] = np.sqrt(
            np.diag(np.dot(np.dot(FMPHolding, CovMatrix.values),
                           FMPHolding.T))) * Beta
        RiskContribution = np.dot(np.dot(
            FMPHolding, CovMatrix.values), Portfolio.values) / np.sqrt(
                np.dot(np.dot(Portfolio.values, CovMatrix.values),
                       Portfolio.values)) * Beta
        self._Output["风险贡献"].loc[idt, FactorExpose.columns] = RiskContribution
        self._Output["风险贡献"].loc[idt, "Alpha"] = np.sqrt(
            np.dot(np.dot(Portfolio.values, CovMatrix),
                   Portfolio.values)) - np.nansum(RiskContribution)
        self._Output["因子收益"].loc[idt, FactorExpose.columns] = np.nansum(
            Return.values * FMPHolding, axis=1)
        self._Output["收益贡献"].loc[idt, FactorExpose.columns] = self._Output[
            "因子收益"].loc[idt, FactorExpose.columns] * self._Output["因子暴露"].loc[
                PreDT, FactorExpose.columns]
        self._Output["收益贡献"].loc[idt, "Alpha"] = (Portfolio * Return).sum(
        ) - self._Output["收益贡献"].loc[idt, FactorExpose.columns].sum()
        return 0

    def __QS_end__(self):
        if not self._isStarted: return 0
        super().__QS_end__()
        self.RiskTable.end()
        self._Output["风险贡献占比"] = self._Output["风险贡献"].divide(
            self._Output["风险贡献"].sum(axis=1), axis=0)
        self._Output["历史均值"] = pd.DataFrame(
            columns=["因子暴露", "风险调整的因子暴露", "风险贡献", "风险贡献占比", "收益贡献"],
            index=self._Output["收益贡献"].columns)
        self._Output["历史均值"]["因子暴露"] = self._Output["因子暴露"].mean(axis=0)
        self._Output["历史均值"]["风险调整的因子暴露"] = self._Output["风险调整的因子暴露"].mean(
            axis=0)
        self._Output["历史均值"]["风险贡献"] = self._Output["风险贡献"].mean(axis=0)
        self._Output["历史均值"]["风险贡献占比"] = self._Output["风险贡献占比"].mean(axis=0)
        self._Output["历史均值"]["收益贡献"] = self._Output["收益贡献"].mean(axis=0)
        self._IDs = None
        return 0

    def genMatplotlibFig(self, file_path=None):
        nRow, nCol = 2, 3
        Fig = plt.figure(figsize=(min(32, 16 + (nCol - 1) * 8), 8 * nRow))
        AxesGrid = gridspec.GridSpec(nRow, nCol)
        xData = np.arange(1, self._Output["历史均值"].shape[0])
        xTickLabels = [str(iInd) for iInd in self._Output["历史均值"].index]
        PercentageFormatter = FuncFormatter(_QS_formatMatplotlibPercentage)
        FloatFormatter = FuncFormatter(lambda x, pos: '%.2f' % (x, ))
        _QS_plotStatistics(plt.subplot(AxesGrid[0, 0]), xData[:-1],
                           xTickLabels[:-1],
                           self._Output["历史均值"]["因子暴露"].iloc[:-1],
                           FloatFormatter)
        _QS_plotStatistics(plt.subplot(AxesGrid[0, 1]), xData[:-1],
                           xTickLabels[:-1],
                           self._Output["历史均值"]["风险调整的因子暴露"].iloc[:-1],
                           FloatFormatter)
        _QS_plotStatistics(plt.subplot(AxesGrid[0, 2]), xData[:-1],
                           xTickLabels[:-1],
                           self._Output["历史均值"]["因子收益"].iloc[:-1],
                           PercentageFormatter)
        _QS_plotStatistics(plt.subplot(AxesGrid[1, 0]), xData, xTickLabels,
                           self._Output["历史均值"]["收益贡献"], PercentageFormatter)
        _QS_plotStatistics(plt.subplot(AxesGrid[1, 1]), xData, xTickLabels,
                           self._Output["历史均值"]["风险贡献"], FloatFormatter)
        _QS_plotStatistics(plt.subplot(AxesGrid[1, 2]), xData, xTickLabels,
                           self._Output["历史均值"]["风险贡献占比"], PercentageFormatter)
        if file_path is not None:
            Fig.savefig(file_path, dpi=150, bbox_inches='tight')
        return Fig

    def _repr_html_(self):
        if len(self.ArgNames) > 0:
            HTML = "参数设置: "
            HTML += '<ul align="left">'
            for iArgName in self.ArgNames:
                if iArgName == "风险表":
                    HTML += "<li>" + iArgName + ": " + self.RiskTable.Name + "</li>"
                elif iArgName != "计算时点":
                    HTML += "<li>" + iArgName + ": " + str(
                        self.Args[iArgName]) + "</li>"
                elif self.Args[iArgName]:
                    HTML += "<li>" + iArgName + ": 自定义时点</li>"
                else:
                    HTML += "<li>" + iArgName + ": 所有时点</li>"
            HTML += "</ul>"
        else:
            HTML = ""
        Formatters = [lambda x: '{0:.4f}'.format(x)] * 2 + [
            _QS_formatPandasPercentage
        ] * 2 + [lambda x: '{0:.4f}'.format(x), _QS_formatPandasPercentage]
        iHTML = self._Output["历史均值"].to_html(formatters=Formatters)
        Pos = iHTML.find(">")
        HTML += iHTML[:Pos] + ' align="center"' + iHTML[Pos:]
        Fig = self.genMatplotlibFig()
        # figure 保存为二进制文件
        Buffer = BytesIO()
        plt.savefig(Buffer, bbox_inches='tight')
        PlotData = Buffer.getvalue()
        # 图像数据转化为 HTML 格式
        ImgStr = "data:image/png;base64," + base64.b64encode(PlotData).decode()
        HTML += ('<img src="%s">' % ImgStr)
        return HTML
示例#16
0
class FamaMacBethRegression(BaseModule):
    """Fama-MacBeth 回归"""
    TestFactors = ListStr(arg_type="MultiOption",
                          label="测试因子",
                          order=0,
                          option_range=())
    #PriceFactor = Enum(None, arg_type="SingleOption", label="价格因子", order=1)
    #ClassFactor = Enum("无", arg_type="SingleOption", label="类别因子", order=2)
    CalcDTs = List(dt.datetime, arg_type="DateList", label="计算时点", order=3)
    IDFilter = Str(arg_type="IDFilter", label="筛选条件", order=4)
    RollAvgPeriod = Int(12, arg_type="Integer", label="滚动平均期数", order=5)

    def __init__(self,
                 factor_table,
                 name="Fama-MacBeth 回归",
                 sys_args={},
                 **kwargs):
        self._FactorTable = factor_table
        return super().__init__(name=name, sys_args=sys_args, **kwargs)

    def __QS_initArgs__(self):
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._FactorTable.getFactorMetaData(key="DataType")))
        self.add_trait(
            "TestFactors",
            ListStr(arg_type="MultiOption",
                    label="测试因子",
                    order=0,
                    option_range=tuple(DefaultNumFactorList)))
        self.TestFactors.append(DefaultNumFactorList[0])
        self.add_trait(
            "PriceFactor",
            Enum(*DefaultNumFactorList,
                 arg_type="SingleOption",
                 label="价格因子",
                 order=1))
        self.PriceFactor = searchNameInStrList(DefaultNumFactorList,
                                               ['价', 'Price', 'price'])
        self.add_trait(
            "ClassFactor",
            Enum(*(["无"] + DefaultStrFactorList),
                 arg_type="SingleOption",
                 label="类别因子",
                 order=2))

    def getViewItems(self, context_name=""):
        Items, Context = super().getViewItems(context_name=context_name)
        Items[0].editor = SetEditor(
            values=self.trait("TestFactors").option_range)
        return (Items, Context)

    def __QS_start__(self, mdl, dts, **kwargs):
        if self._isStarted: return ()
        super().__QS_start__(mdl=mdl, dts=dts, **kwargs)
        self._Output = {
            "Pure Return": [],
            "Raw Return": [],
            "时点": [],
            "回归R平方": [],
            "回归调整R平方": [],
            "回归F统计量": [],
            "回归t统计量(Raw Return)": [],
            "回归t统计量(Pure Return)": []
        }
        self._CurCalcInd = 0
        return (self._FactorTable, )

    def __QS_move__(self, idt, **kwargs):
        if self._iDT == idt: return 0
        self._iDT = idt
        if self.CalcDTs:
            if idt not in self.CalcDTs[self._CurCalcInd:]: return 0
            self._CurCalcInd = self.CalcDTs[self._CurCalcInd:].index(
                idt) + self._CurCalcInd
            LastInd = self._CurCalcInd - 1
            LastDateTime = self.CalcDTs[LastInd]
        else:
            self._CurCalcInd = self._Model.DateTimeIndex
            LastInd = self._CurCalcInd - 1
            LastDateTime = self._Model.DateTimeSeries[LastInd]
        nFactor = len(self.TestFactors)
        self._Output["Pure Return"].append(
            np.full(shape=(nFactor, ), fill_value=np.nan))
        self._Output["Raw Return"].append(
            np.full(shape=(nFactor, ), fill_value=np.nan))
        self._Output["回归t统计量(Pure Return)"].append(
            np.full(shape=(nFactor, ), fill_value=np.nan))
        self._Output["回归t统计量(Raw Return)"].append(
            np.full(shape=(nFactor, ), fill_value=np.nan))
        self._Output["回归F统计量"].append(
            np.full(shape=(nFactor + 1, ), fill_value=np.nan))
        self._Output["回归R平方"].append(
            np.full(shape=(nFactor + 1, ), fill_value=np.nan))
        self._Output["回归调整R平方"].append(
            np.full(shape=(nFactor + 1, ), fill_value=np.nan))
        self._Output["时点"].append(idt)
        if LastInd < 0: return 0
        LastIDs = self._FactorTable.getFilteredID(idt=LastDateTime,
                                                  id_filter_str=self.IDFilter)
        FactorData = self._FactorTable.readData(
            dts=[LastDateTime],
            ids=LastIDs,
            factor_names=list(self.TestFactors)).iloc[:, 0, :]
        Price = self._FactorTable.readData(dts=[LastDateTime, idt],
                                           ids=LastIDs,
                                           factor_names=[self.PriceFactor
                                                         ]).iloc[0]
        Ret = Price.iloc[1] / Price.iloc[0] - 1
        # 展开Dummy因子
        if self.ClassFactor != "无":
            DummyFactorData = self._FactorTable.readData(
                dts=[LastDateTime],
                ids=LastIDs,
                factor_names=[self.ClassFactor]).iloc[0, 0, :]
            Mask = pd.notnull(DummyFactorData)
            DummyFactorData = DummyVarTo01Var(DummyFactorData[Mask],
                                              ignore_na=True)
            FactorData = pd.merge(FactorData.loc[Mask],
                                  DummyFactorData,
                                  left_index=True,
                                  right_index=True)
        # 回归
        yData = Ret[FactorData.index].values
        xData = FactorData.values
        if self.ClassFactor == "无":
            xData = sm.add_constant(xData, prepend=False)
            LastInds = [nFactor]
        else:
            LastInds = [nFactor + i for i in range(xData.shape[1] - nFactor)]
        try:
            Result = sm.OLS(yData, xData, missing="drop").fit()
            self._Output["Pure Return"][-1] = Result.params[0:nFactor]
            self._Output["回归t统计量(Pure Return)"][-1] = Result.tvalues[0:nFactor]
            self._Output["回归F统计量"][-1][-1] = Result.fvalue
            self._Output["回归R平方"][-1][-1] = Result.rsquared
            self._Output["回归调整R平方"][-1][-1] = Result.rsquared_adj
        except:
            pass
        for i, iFactorName in enumerate(self.TestFactors):
            iXData = xData[:, [i] + LastInds]
            try:
                Result = sm.OLS(yData, iXData, missing="drop").fit()
                self._Output["Raw Return"][-1][i] = Result.params[0]
                self._Output["回归t统计量(Raw Return)"][-1][i] = Result.tvalues[0]
                self._Output["回归F统计量"][-1][i] = Result.fvalue
                self._Output["回归R平方"][-1][i] = Result.rsquared
                self._Output["回归调整R平方"][-1][i] = Result.rsquared_adj
            except:
                pass
        return 0

    def __QS_end__(self):
        if not self._isStarted: return 0
        super().__QS_end__()
        FactorNames = list(self.TestFactors)
        self._Output["Pure Return"] = pd.DataFrame(self._Output["Pure Return"],
                                                   index=self._Output["时点"],
                                                   columns=FactorNames)
        self._Output["Raw Return"] = pd.DataFrame(self._Output["Raw Return"],
                                                  index=self._Output["时点"],
                                                  columns=FactorNames)
        self._Output["滚动t统计量_Pure"] = pd.DataFrame(np.nan,
                                                   index=self._Output["时点"],
                                                   columns=FactorNames)
        self._Output["滚动t统计量_Raw"] = pd.DataFrame(np.nan,
                                                  index=self._Output["时点"],
                                                  columns=FactorNames)
        self._Output["回归t统计量(Raw Return)"] = pd.DataFrame(
            self._Output["回归t统计量(Raw Return)"],
            index=self._Output["时点"],
            columns=FactorNames)
        self._Output["回归t统计量(Pure Return)"] = pd.DataFrame(
            self._Output["回归t统计量(Pure Return)"],
            index=self._Output["时点"],
            columns=FactorNames)
        self._Output["回归F统计量"] = pd.DataFrame(self._Output["回归F统计量"],
                                              index=self._Output["时点"],
                                              columns=FactorNames + ["所有因子"])
        self._Output["回归R平方"] = pd.DataFrame(self._Output["回归R平方"],
                                             index=self._Output["时点"],
                                             columns=FactorNames + ["所有因子"])
        self._Output["回归调整R平方"] = pd.DataFrame(self._Output["回归调整R平方"],
                                               index=self._Output["时点"],
                                               columns=FactorNames + ["所有因子"])
        nDT = self._Output["Raw Return"].shape[0]
        # 计算滚动t统计量
        for i in range(nDT):
            if i < self.RollAvgPeriod - 1: continue
            iReturn = self._Output["Pure Return"].iloc[i - self.RollAvgPeriod +
                                                       1:i + 1, :]
            self._Output["滚动t统计量_Pure"].iloc[i] = iReturn.mean(
                axis=0) / iReturn.std(axis=0) * pd.notnull(iReturn).sum(
                    axis=0)**0.5
            iReturn = self._Output["Raw Return"].iloc[i - self.RollAvgPeriod +
                                                      1:i + 1, :]
            self._Output["滚动t统计量_Raw"].iloc[i] = iReturn.mean(
                axis=0) / iReturn.std(axis=0) * pd.notnull(iReturn).sum(
                    axis=0)**0.5
        nYear = (self._Output["时点"][-1] - self._Output["时点"][0]).days / 365
        self._Output["统计数据"] = pd.DataFrame(
            index=self._Output["Pure Return"].columns)
        self._Output["统计数据"]["年化收益率(Pure)"] = (
            (1 + self._Output["Pure Return"]).prod())**(1 / nYear) - 1
        self._Output["统计数据"]["跟踪误差(Pure)"] = self._Output["Pure Return"].std(
        ) * np.sqrt(nDT / nYear)
        self._Output["统计数据"]["信息比率(Pure)"] = self._Output["统计数据"][
            "年化收益率(Pure)"] / self._Output["统计数据"]["跟踪误差(Pure)"]
        self._Output["统计数据"]["胜率(Pure)"] = (self._Output["Pure Return"] >
                                            0).sum() / nDT
        self._Output["统计数据"]["t统计量(Pure)"] = self._Output["Pure Return"].mean(
        ) / self._Output["Pure Return"].std() * np.sqrt(nDT)
        self._Output["统计数据"]["年化收益率(Raw)"] = (
            1 + self._Output["Raw Return"]).prod()**(1 / nYear) - 1
        self._Output["统计数据"]["跟踪误差(Raw)"] = self._Output["Raw Return"].std(
        ) * np.sqrt(nDT / nYear)
        self._Output["统计数据"]["信息比率(Raw)"] = self._Output["统计数据"][
            "年化收益率(Raw)"] / self._Output["统计数据"]["跟踪误差(Raw)"]
        self._Output["统计数据"]["胜率(Raw)"] = (self._Output["Raw Return"] >
                                           0).sum() / nDT
        self._Output["统计数据"]["t统计量(Raw)"] = self._Output["Raw Return"].mean(
        ) / self._Output["Raw Return"].std() * np.sqrt(nDT)
        self._Output["统计数据"]["年化收益率(Pure-Naive)"] = (
            1 + self._Output["Pure Return"] -
            self._Output["Raw Return"]).prod()**(1 / nYear) - 1
        self._Output["统计数据"]["跟踪误差(Pure-Naive)"] = (
            self._Output["Pure Return"] -
            self._Output["Raw Return"]).std() * np.sqrt(nDT / nYear)
        self._Output["统计数据"]["信息比率(Pure-Naive)"] = self._Output["统计数据"][
            "年化收益率(Pure-Naive)"] / self._Output["统计数据"]["跟踪误差(Pure-Naive)"]
        self._Output["统计数据"]["胜率(Pure-Naive)"] = (
            self._Output["Pure Return"] - self._Output["Raw Return"] >
            0).sum() / nDT
        self._Output["统计数据"]["t统计量(Pure-Naive)"] = (
            self._Output["Pure Return"] - self._Output["Raw Return"]
        ).mean() / (self._Output["Pure Return"] -
                    self._Output["Raw Return"]).std() * np.sqrt(nDT)
        self._Output["回归统计量均值"] = pd.DataFrame(index=FactorNames + ["所有因子"])
        self._Output["回归统计量均值"]["t统计量(Raw Return)"] = self._Output[
            "回归t统计量(Raw Return)"].mean()
        self._Output["回归统计量均值"]["t统计量(Pure Return)"] = self._Output[
            "回归t统计量(Pure Return)"].mean()
        self._Output["回归统计量均值"]["F统计量"] = self._Output["回归F统计量"].mean()
        self._Output["回归统计量均值"]["R平方"] = self._Output["回归R平方"].mean()
        self._Output["回归统计量均值"]["调整R平方"] = self._Output["回归调整R平方"].mean()
        self._Output.pop("时点")
        return 0

    def _plotStatistics(self,
                        axes,
                        x_data,
                        x_ticklabels,
                        left_data,
                        left_formatter,
                        right_data=None,
                        right_formatter=None,
                        right_axes=True):
        axes.yaxis.set_major_formatter(left_formatter)
        axes.bar(x_data,
                 left_data.values,
                 label=left_data.name,
                 color="steelblue")
        if right_data is not None:
            if right_axes:
                axes.legend(loc='upper left')
                right_axes = axes.twinx()
                right_axes.yaxis.set_major_formatter(right_formatter)
                right_axes.plot(x_data,
                                right_data.values,
                                label=right_data.name,
                                color="indianred",
                                lw=2.5)
                right_axes.legend(loc="upper right")
            else:
                axes.plot(x_data,
                          right_data.values,
                          label=right_data.name,
                          color="indianred",
                          lw=2.5)
                axes.legend(loc='best')
        else:
            axes.legend(loc='best')
        axes.set_xticks(x_data)
        axes.set_xticklabels(x_ticklabels)
        return axes

    def genMatplotlibFig(self, file_path=None):
        nRow, nCol = 1, 3
        Fig = Figure(figsize=(min(32, 16 + (nCol - 1) * 8), 8 * nRow))
        PercentageFormatter = FuncFormatter(_QS_formatMatplotlibPercentage)
        FloatFormatter = FuncFormatter(lambda x, pos: '%.2f' % (x, ))
        xData = np.arange(0, self._Output["统计数据"].shape[0])
        xTickLabels = [str(iInd) for iInd in self._Output["统计数据"].index]
        iAxes = Fig.add_subplot(nRow, nCol, 1)
        iAxes.yaxis.set_major_formatter(PercentageFormatter)
        iAxes.bar(xData,
                  self._Output["统计数据"]["年化收益率(Raw)"].values,
                  width=-0.25,
                  align="edge",
                  color="indianred",
                  label="年化收益率(Raw)")
        iAxes.bar(xData,
                  self._Output["统计数据"]["年化收益率(Pure)"].values,
                  width=0.25,
                  align="edge",
                  color="steelblue",
                  label="年化收益率(Pure)")
        iAxes.set_xticks(xData)
        iAxes.set_xticklabels(xTickLabels)
        iAxes.legend(loc='best')
        iAxes.set_title("年化收益率")
        iAxes = Fig.add_subplot(nRow, nCol, 2)
        iAxes.yaxis.set_major_formatter(FloatFormatter)
        iAxes.bar(xData,
                  self._Output["统计数据"]["t统计量(Raw)"].values,
                  width=-0.25,
                  align="edge",
                  color="indianred",
                  label="t统计量(Raw)")
        iAxes.bar(xData,
                  self._Output["统计数据"]["t统计量(Pure)"].values,
                  width=0.25,
                  align="edge",
                  color="steelblue",
                  label="t统计量(Pure)")
        iAxes.set_xticks(xData)
        iAxes.set_xticklabels(xTickLabels)
        iAxes.legend(loc='best')
        iAxes.set_title("t统计量")
        iAxes = Fig.add_subplot(nRow, nCol, 3)
        iAxes.yaxis.set_major_formatter(PercentageFormatter)
        iAxes.bar(xData,
                  self._Output["统计数据"]["年化收益率(Pure-Naive)"].values,
                  color="steelblue",
                  label="年化收益率(Pure-Naive)")
        iAxes.set_xticks(xData)
        iAxes.set_xticklabels(xTickLabels)
        iAxes.legend(loc='upper left')
        iAxes.set_title("Pure-Naive")
        RAxes = iAxes.twinx()
        RAxes.yaxis.set_major_formatter(FloatFormatter)
        RAxes.plot(xData,
                   self._Output["统计数据"]["t统计量(Pure-Naive)"].values,
                   color="indianred",
                   lw=2.5,
                   label="t统计量(Pure-Naive)")
        RAxes.legend(loc='upper right')
        if file_path is not None:
            Fig.savefig(file_path, dpi=150, bbox_inches='tight')
        return Fig

    def _repr_html_(self):
        if len(self.ArgNames) > 0:
            HTML = "参数设置: "
            HTML += '<ul align="left">'
            for iArgName in self.ArgNames:
                if iArgName != "计算时点":
                    HTML += "<li>" + iArgName + ": " + str(
                        self.Args[iArgName]) + "</li>"
                elif self.Args[iArgName]:
                    HTML += "<li>" + iArgName + ": 自定义时点</li>"
                else:
                    HTML += "<li>" + iArgName + ": 所有时点</li>"
            HTML += "</ul>"
        else:
            HTML = ""
        FloatFormatFun = lambda x: '{0:.2f}'.format(x)
        Formatters = [_QS_formatPandasPercentage] * 2 + [
            FloatFormatFun, _QS_formatPandasPercentage, FloatFormatFun
        ]
        Formatters += [_QS_formatPandasPercentage] * 2 + [
            FloatFormatFun, _QS_formatPandasPercentage, FloatFormatFun
        ]
        Formatters += [_QS_formatPandasPercentage] * 2 + [
            FloatFormatFun, _QS_formatPandasPercentage, FloatFormatFun
        ]
        iHTML = self._Output["统计数据"].to_html(formatters=Formatters)
        Pos = iHTML.find(">")
        HTML += iHTML[:Pos] + ' align="center"' + iHTML[Pos:]
        HTML += '<div align="left" style="font-size:1em"><strong>回归统计量</strong></div>'
        iHTML = self._Output["回归统计量均值"].to_html(formatters=[FloatFormatFun] *
                                                5)
        Pos = iHTML.find(">")
        HTML += iHTML[:Pos] + ' align="center"' + iHTML[Pos:]
        Fig = self.genMatplotlibFig()
        # figure 保存为二进制文件
        Buffer = BytesIO()
        Fig.savefig(Buffer, bbox_inches='tight')
        PlotData = Buffer.getvalue()
        # 图像数据转化为 HTML 格式
        ImgStr = "data:image/png;base64," + base64.b64encode(PlotData).decode()
        HTML += ('<img src="%s">' % ImgStr)
        return HTML
示例#17
0
class TimeSeriesCorrelation(BaseModule):
    """时间序列相关性"""
    TestFactors = ListStr(arg_type="MultiOption",
                          label="测试因子",
                          order=0,
                          option_range=())
    #PriceFactor = Enum(None, arg_type="SingleOption", label="价格因子", order=1)
    ReturnType = Enum("简单收益率",
                      "对数收益率",
                      "价格变化量",
                      arg_type="SingleOption",
                      label="收益率类型",
                      order=2)
    ForecastPeriod = Int(1, arg_type="Integer", label="预测期数", order=3)
    Lag = Int(0, arg_type="Integer", label="滞后期数", order=4)
    CalcDTs = List(dt.datetime, arg_type="DateList", label="计算时点", order=5)
    CorrMethod = Enum("pearson",
                      "spearman",
                      "kendall",
                      arg_type="SingleOption",
                      label="相关性算法",
                      order=6)
    SummaryWindow = Float(np.inf, arg_type="Integer", label="统计窗口", order=7)
    MinSummaryWindow = Int(2, arg_type="Integer", label="最小统计窗口", order=8)

    def __init__(self,
                 factor_table,
                 price_table,
                 name="时间序列相关性",
                 sys_args={},
                 **kwargs):
        self._FactorTable = factor_table
        self._PriceTable = price_table
        super().__init__(name=name, sys_args=sys_args, **kwargs)

    def __QS_initArgs__(self):
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._FactorTable.getFactorMetaData(key="DataType")))
        self.add_trait(
            "TestFactors",
            ListStr(arg_type="MultiOption",
                    label="测试因子",
                    order=0,
                    option_range=tuple(DefaultNumFactorList)))
        self.TestFactors.append(DefaultNumFactorList[0])
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._PriceTable.getFactorMetaData(key="DataType")))
        self.add_trait(
            "PriceFactor",
            Enum(*DefaultNumFactorList,
                 arg_type="SingleOption",
                 label="价格因子",
                 order=1))
        self.PriceFactor = searchNameInStrList(DefaultNumFactorList,
                                               ['价', 'Price', 'price'])

    def getViewItems(self, context_name=""):
        Items, Context = super().getViewItems(context_name=context_name)
        Items[0].editor = SetEditor(
            values=self.trait("TestFactors").option_range)
        return (Items, Context)

    def __QS_start__(self, mdl, dts, **kwargs):
        if self._isStarted: return ()
        super().__QS_start__(mdl=mdl, dts=dts, **kwargs)
        self._Output = {}
        self._Output["滚动相关性"] = {
            iFactorName: {}
            for iFactorName in self.TestFactors
        }  # {因子: {时点: DataFrame(index=[因子ID], columns=[证券ID])}},
        self._Output["证券ID"] = self._PriceTable.getID()
        self._Output["收益率"] = np.zeros(shape=(0, len(self._Output["证券ID"])))
        self._Output["因子ID"] = self._FactorTable.getID()
        nFactorID = len(self._Output["因子ID"])
        self._Output["因子值"] = {
            iFactorName: np.zeros(shape=(0, nFactorID))
            for iFactorName in self.TestFactors
        }
        self._CurCalcInd = 0
        self._nMinSample = (max(2, self.MinSummaryWindow) if np.isinf(
            self.MinSummaryWindow) else max(2, self.MinSummaryWindow))
        return (self._FactorTable, self._PriceTable)

    def __QS_move__(self, idt, **kwargs):
        if self._iDT == idt: return 0
        if self.CalcDTs:
            if idt not in self.CalcDTs[self._CurCalcInd:]: return 0
            self._CurCalcInd = self.CalcDTs[self._CurCalcInd:].index(
                idt) + self._CurCalcInd
            PreInd = self._CurCalcInd - self.ForecastPeriod - self.Lag
            LastInd = self._CurCalcInd - self.ForecastPeriod
            PreDateTime = self.CalcDTs[PreInd]
            LastDateTime = self.CalcDTs[LastInd]
        else:
            self._CurCalcInd = self._Model.DateTimeIndex
            PreInd = self._CurCalcInd - self.ForecastPeriod - self.Lag
            LastInd = self._CurCalcInd - self.ForecastPeriod
            PreDateTime = self._Model.DateTimeSeries[PreInd]
            LastDateTime = self._Model.DateTimeSeries[LastInd]
        if (PreInd < 0) or (LastInd < 0): return 0
        Price = self._PriceTable.readData(dts=[LastDateTime, idt],
                                          ids=self._Output["证券ID"],
                                          factor_names=[self.PriceFactor
                                                        ]).iloc[0, :, :].values
        self._Output["收益率"] = np.r_[
            self._Output["收益率"],
            _calcReturn(Price, return_type=self.ReturnType)]
        FactorData = self._FactorTable.readData(
            dts=[PreDateTime],
            ids=self._Output["因子ID"],
            factor_names=list(self.TestFactors)).iloc[:, 0, :].values.T
        StartInd = int(
            max(0, self._Output["收益率"].shape[0] - self.SummaryWindow))
        for i, iFactorName in enumerate(self.TestFactors):
            self._Output["因子值"][iFactorName] = np.r_[
                self._Output["因子值"][iFactorName], FactorData[i:i + 1]]
            if self._Output["收益率"].shape[0] >= self._nMinSample:
                self._Output["滚动相关性"][iFactorName][idt] = pd.DataFrame(
                    np.c_[self._Output["因子值"][iFactorName][StartInd:],
                          self._Output["收益率"][StartInd:]]).corr(
                              method=self.CorrMethod,
                              min_periods=self._nMinSample
                          ).values[:FactorData.shape[1], FactorData.shape[1]:]
        return 0

    def __QS_end__(self):
        if not self._isStarted: return 0
        FactorIDs, PriceIDs = self._Output.pop("因子ID"), self._Output.pop(
            "证券ID")
        LastDT = max(self._Output["滚动相关性"][self.TestFactors[0]])
        self._Output["最后一期相关性"], self._Output["全样本相关性"] = {}, {}
        for iFactorName in self.TestFactors:
            self._Output["最后一期相关性"][iFactorName] = self._Output["滚动相关性"][
                iFactorName][LastDT].T
            self._Output["全样本相关性"][iFactorName] = pd.DataFrame(np.c_[
                self._Output["因子值"][iFactorName], self._Output["收益率"]]).corr(
                    method=self.CorrMethod,
                    min_periods=self._nMinSample).values[:len(FactorIDs),
                                                         len(FactorIDs):].T
            self._Output["滚动相关性"][iFactorName] = pd.Panel(
                self._Output["滚动相关性"][iFactorName],
                major_axis=FactorIDs,
                minor_axis=PriceIDs).swapaxes(
                    0, 2).to_frame(filter_observations=False).reset_index()
            self._Output["滚动相关性"][iFactorName].columns = ["因子ID", "时点"
                                                          ] + PriceIDs
        self._Output["最后一期相关性"] = pd.Panel(
            self._Output["最后一期相关性"], major_axis=PriceIDs,
            minor_axis=FactorIDs).swapaxes(
                0, 1).to_frame(filter_observations=False).reset_index()
        self._Output["最后一期相关性"].columns = ["因子", "因子ID"] + PriceIDs
        self._Output["全样本相关性"] = pd.Panel(
            self._Output["全样本相关性"], major_axis=PriceIDs,
            minor_axis=FactorIDs).swapaxes(
                0, 1).to_frame(filter_observations=False).reset_index()
        self._Output["全样本相关性"].columns = ["因子", "因子ID"] + PriceIDs
        self._Output.pop("收益率"), self._Output.pop("因子值")
        return 0
示例#18
0
class FactorTurnover(BaseModule):
    """因子换手率"""
    TestFactors = ListStr(arg_type="MultiOption",
                          label="测试因子",
                          order=0,
                          option_range=())
    CalcDTs = List(dt.datetime, arg_type="DateList", label="计算时点", order=1)
    IDFilter = Str(arg_type="IDFilter", label="筛选条件", order=2)

    def __init__(self, factor_table, name="因子换手率", sys_args={}, **kwargs):
        self._FactorTable = factor_table
        return super().__init__(name=name, sys_args=sys_args, **kwargs)

    def __QS_initArgs__(self):
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._FactorTable.getFactorMetaData(key="DataType")))
        self.add_trait(
            "TestFactors",
            ListStr(arg_type="MultiOption",
                    label="测试因子",
                    order=0,
                    option_range=tuple(DefaultNumFactorList)))
        self.TestFactors.append(DefaultNumFactorList[0])

    def getViewItems(self, context_name=""):
        Items, Context = super().getViewItems(context_name=context_name)
        Items[0].editor = SetEditor(
            values=self.trait("TestFactors").option_range)
        return (Items, Context)

    def __QS_start__(self, mdl, dts=None, dates=None, times=None):
        if self._isStarted: return ()
        super().__QS_start__(mdl=mdl, dts=dts, dates=dates, times=times)
        self._Output = {iFactorName: [] for iFactorName in self.TestFactors}
        self._Output["时点"] = []
        self._CurCalcInd = 0
        return (self._FactorTable, )

    def __QS_move__(self, idt):
        if self._iDT == idt: return 0
        self._iDT = idt
        if self.CalcDTs:
            if idt not in self.CalcDTs[self._CurCalcInd:]: return 0
            self._CurCalcInd = self.CalcDTs[self._CurCalcInd:].index(
                idt) + self._CurCalcInd
            LastInd = self._CurCalcInd - 1
            LastDateTime = self.CalcDTs[LastInd]
        else:
            self._CurCalcInd = self._Model.DateTimeIndex
            LastInd = self._CurCalcInd - 1
            LastDateTime = self._Model.DateTimeSeries[LastInd]
        if LastInd < 0:
            for iFactorName in self.TestFactors:
                self._Output[iFactorName].append(0.0)
            self._Output["时点"].append(idt)
            return 0
        LastIDs = self._FactorTable.getFilteredID(idt=LastDateTime,
                                                  id_filter_str=self.IDFilter)
        PreFactorExpose = self._FactorTable.readData(
            dts=[LastDateTime],
            ids=LastIDs,
            factor_names=list(self.TestFactors)).iloc[:, 0, :].astype("float")
        CurFactorExpose = self._FactorTable.readData(
            dts=[idt], ids=LastIDs,
            factor_names=list(self.TestFactors)).iloc[:, 0, :].astype("float")
        for iFactorName in self.TestFactors:
            self._Output[iFactorName].append(CurFactorExpose[iFactorName].corr(
                PreFactorExpose[iFactorName]))
        self._Output["时点"].append(idt)
        return 0

    def __QS_end__(self):
        if not self._isStarted: return 0
        self._Output = {
            "因子换手率": pd.DataFrame(self._Output, index=self._Output.pop("时点"))
        }
        self._Output["统计数据"] = pd.DataFrame(self._Output["因子换手率"].mean(),
                                            columns=["平均值"])
        self._Output["统计数据"]["标准差"] = self._Output["因子换手率"].std()
        self._Output["统计数据"]["最小值"] = self._Output["因子换手率"].min()
        self._Output["统计数据"]["最大值"] = self._Output["因子换手率"].max()
        self._Output["统计数据"]["中位数"] = self._Output["因子换手率"].median()
        return 0

    def genMatplotlibFig(self, file_path=None):
        nRow, nCol = self._Output["因子换手率"].shape[1] // 3 + (
            self._Output["因子换手率"].shape[1] % 3 != 0), min(
                3, self._Output["因子换手率"].shape[1])
        Fig = plt.figure(figsize=(min(32, 16 + (nCol - 1) * 8), 8 * nRow))
        AxesGrid = gridspec.GridSpec(nRow, nCol)
        yMajorFormatter = FuncFormatter(_QS_formatMatplotlibPercentage)
        for i in range(self._Output["因子换手率"].shape[1]):
            iAxes = plt.subplot(AxesGrid[i // nCol, i % nCol])
            iAxes.yaxis.set_major_formatter(yMajorFormatter)
            iAxes.xaxis_date()
            iAxes.xaxis.set_major_formatter(mdate.DateFormatter('%Y-%m-%d'))
            iAxes.stackplot(self._Output["因子换手率"].index,
                            self._Output["因子换手率"].iloc[:, i].values,
                            color="b")
            iAxes.set_title(self._Output["因子换手率"].columns[i])
        if file_path is not None:
            Fig.savefig(file_path, dpi=150, bbox_inches='tight')
        return Fig

    def _repr_html_(self):
        if len(self.ArgNames) > 0:
            HTML = "参数设置: "
            HTML += '<ul align="left">'
            for iArgName in self.ArgNames:
                if iArgName != "计算时点":
                    HTML += "<li>" + iArgName + ": " + str(
                        self.Args[iArgName]) + "</li>"
                elif self.Args[iArgName]:
                    HTML += "<li>" + iArgName + ": 自定义时点</li>"
                else:
                    HTML += "<li>" + iArgName + ": 所有时点</li>"
            HTML += "</ul>"
        else:
            HTML = ""
        iHTML = self._Output["统计数据"].to_html(
            formatters=[_QS_formatPandasPercentage] * 5)
        Pos = iHTML.find(">")
        HTML += iHTML[:Pos] + ' align="center"' + iHTML[Pos:]
        Fig = self.genMatplotlibFig()
        # figure 保存为二进制文件
        Buffer = BytesIO()
        plt.savefig(Buffer, bbox_inches='tight')
        PlotData = Buffer.getvalue()
        # 图像数据转化为 HTML 格式
        ImgStr = "data:image/png;base64," + base64.b64encode(PlotData).decode()
        HTML += ('<img src="%s">' % ImgStr)
        return HTML
示例#19
0
class DefaultAccount(Account):
    """默认证券账户"""
    Delay = Bool(True, arg_type="Bool", label="交易延迟", order=2)
    TargetIDs = ListStr(arg_type="IDList", label="目标ID", order=3)
    BuyLimit = Instance(_TradeLimit,
                        allow_none=False,
                        arg_type="ArgObject",
                        label="买入限制",
                        order=4)
    SellLimit = Instance(_TradeLimit,
                         allow_none=False,
                         arg_type="ArgObject",
                         label="卖出限制",
                         order=5)

    #Last = Enum(None, arg_type="SingleOption", label="最新价", order=6)
    def __init__(self,
                 market_ft,
                 name="默认证券账户",
                 sys_args={},
                 config_file=None,
                 **kwargs):
        # 继承自 Account 的属性
        #self._Cash = None# 剩余现金, >=0,  array(shape=(nDT+1,))
        #self._FrozenCash = 0# 当前被冻结的现金, >=0, float
        #self._Debt = None# 负债, >=0, array(shape=(nDT+1,))
        #self._CashRecord = None# 现金流记录, 现金流入为正, 现金流出为负, DataFrame(columns=["时间点", "现金流", "备注"])
        #self._DebtRecord = None# 融资记录, 增加负债为正, 减少负债为负, DataFrame(columns=["时间点", "融资", "备注"])
        #self._TradingRecord = None# 交易记录, DataFrame(columns=["时间点", "ID", "买卖数量", "价格", "交易费", "现金收支", "类型"])
        self._IDs = []  # 本账户支持交易的证券 ID, []
        self._PositionNum = None  # 持仓数量, DataFrame(index=[时间点]+1, columns=self._IDs)
        self._PositionAmount = None  # 持仓金额, DataFrame(index=[时间点]+1, columns=self._IDs)
        self._Turnover = None  # 换手率, Series(index=[时间点])
        self._Orders = None  # 当前接收到的订单, DataFrame(columns=["ID", "数量", "目标价"])
        self._LastPrice = None  # 最新价, Series(index=self._IDs)
        self._BuyPrice = None  # 买入成交价, Series(index=self._IDs)
        self._SellPrice = None  # 卖出成交价, Series(index=self._IDs)
        self._SellVolLimit = None  # 卖出成交量限制, Series(index=self._IDs)
        self._BuyVolLimit = None  # 买入成交量限制, Series(index=self._IDs)
        self._MarketFT = market_ft  # 提供净值或者收益率数据的因子表
        self._TempData = {}  # 临时数据
        return super().__init__(name=name,
                                sys_args=sys_args,
                                config_file=config_file,
                                **kwargs)

    def __QS_initArgs__(self):
        super().__QS_initArgs__()
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._MarketFT.getFactorMetaData(key="DataType")))
        self.add_trait(
            "Last",
            Enum(*DefaultNumFactorList,
                 arg_type="SingleOption",
                 label="最新价",
                 order=6,
                 option_range=DefaultNumFactorList))
        self.Last = searchNameInStrList(
            DefaultNumFactorList, ['新', '收', 'Last', 'last', 'close', 'Close'])
        self.BuyLimit = _TradeLimit(account=self, direction="Buy")
        self.SellLimit = _TradeLimit(account=self, direction="Sell")

    def __QS_start__(self, mdl, dts, **kwargs):
        if self._isStarted: return ()
        Rslt = super().__QS_start__(mdl=mdl, dts=dts, **kwargs)
        self._IDs = list(self.TargetIDs)
        if not self._IDs:
            self._IDs = self._MarketFT.getID(ifactor_name=self.Last)
        nDT, nID = len(dts), len(self._IDs)
        #self._Cash = np.zeros(nDT+1)
        #self._FrozenCash = 0
        #self._Debt = np.zeros(nDT+1)
        self._PositionNum = pd.DataFrame(np.zeros((nDT + 1, nID)),
                                         index=[dts[0] - dt.timedelta(1)] +
                                         dts,
                                         columns=self._IDs)
        self._PositionAmount = self._PositionNum.copy()
        self._Turnover = pd.Series(np.zeros((nDT, )), index=dts)
        self._Orders = pd.DataFrame(columns=["ID", "数量", "目标价"])
        self._TempData = {}
        if self.BuyLimit.TradePrice is None:
            self.BuyLimit.TradePrice, self._TempData[
                "BuyPrice"] = self.Last, None
        if self.SellLimit.TradePrice is None:
            self.SellLimit.TradePrice, self._TempData[
                "SellPrice"] = self.Last, None
        self._LastPrice = self._BuyPrice = self._SellPrice = None
        self._iTradingRecord = pd.DataFrame(
            columns=["时间点", "ID", "买卖数量", "价格", "交易费", "现金收支",
                     "类型"])  # 暂存的交易记录
        self._SellVolLimit, self._BuyVolLimit = pd.Series(
            np.inf, index=self._IDs), pd.Series(np.inf, index=self._IDs)
        self._nDT = nDT
        return Rslt + (self._MarketFT, )

    def __QS_move__(self, idt, **kwargs):
        if self._iDT == idt: return self._iTradingRecord
        super().__QS_move__(idt, **kwargs)
        # 更新当前的账户信息
        iIndex = self._Model.DateTimeIndex
        self._LastPrice = self._MarketFT.readData(factor_names=[self.Last],
                                                  ids=self._IDs,
                                                  dts=[idt]).iloc[0, 0]
        self._PositionNum.iloc[iIndex +
                               1] = self._PositionNum.iloc[iIndex]  # 初始化持仓
        if self.Delay:  # 撮合成交
            self._iTradingRecord = self._matchOrder(idt)
            self._iTradingRecord = pd.DataFrame(
                self._iTradingRecord,
                index=np.arange(
                    self._TradingRecord.shape[0],
                    self._TradingRecord.shape[0] + len(self._iTradingRecord)),
                columns=self._TradingRecord.columns)
            self._TradingRecord = self._TradingRecord.append(
                self._iTradingRecord)
        self._PositionAmount.iloc[
            iIndex + 1] = self._PositionNum.iloc[iIndex + 1] * self._LastPrice
        return self._iTradingRecord

    def __QS_after_move__(self, idt, **kwargs):
        if self._iDT == idt: return 0
        super().__QS_after_move__(idt, **kwargs)
        if not self.Delay:  # 撮合成交
            self._iTradingRecord = self._matchOrder(idt)
            self._iTradingRecord = pd.DataFrame(
                self._iTradingRecord,
                index=np.arange(
                    self._TradingRecord.shape[0],
                    self._TradingRecord.shape[0] + len(self._iTradingRecord)),
                columns=self._TradingRecord.columns)
            self._TradingRecord = self._TradingRecord.append(
                self._iTradingRecord)
            iIndex = self._Model.DateTimeIndex
            self._PositionAmount.iloc[
                iIndex +
                1] = self._PositionNum.iloc[iIndex + 1] * self._LastPrice
        return 0

    def __QS_end__(self):
        if not self._isStarted: return 0
        super().__QS_end__()
        self.BuyLimit.TradePrice = self._TempData.pop("BuyPrice",
                                                      self.BuyLimit.TradePrice)
        self.SellLimit.TradePrice = self._TempData.pop(
            "SellPrice", self.SellLimit.TradePrice)
        self._Output["持仓数量"] = self.getPositionNumSeries()
        self._Output["持仓金额"] = self.getPositionAmountSeries()
        self._Output["换手率"] = pd.DataFrame(self._Turnover.values,
                                           index=self._Turnover.index,
                                           columns=["换手率"])
        return 0

    # 当前账户价值
    @property
    def AccountValue(self):
        return super().AccountValue + np.nansum(
            self._PositionAmount.iloc[self._Model.DateTimeIndex + 1])

    # 当前账户的持仓数量
    @property
    def PositionNum(self):
        return self._PositionNum.iloc[self._Model.DateTimeIndex + 1]

    # 当前账户的持仓金额
    @property
    def PositionAmount(self):
        return self._PositionAmount.iloc[self._Model.DateTimeIndex + 1]

    # 本账户支持交易的证券 ID
    @property
    def IDs(self):
        return self._IDs

    # 当前最新价
    @property
    def LastPrice(self):
        return self._LastPrice

    # 当前账户中还未成交的订单, DataFrame(index=[int], columns=["ID", "数量", "目标价"])
    @property
    def Orders(self):
        return self._Orders

    # 获取持仓的历史序列, 以时间点为索引, 返回: pd.DataFrame(持仓, index=[时间点], columns=[ID])
    def getPositionNumSeries(self, dts=None, start_dt=None, end_dt=None):
        Data = self._PositionNum.iloc[1:self._Model.DateTimeIndex + 2]
        return cutDateTime(Data, dts=dts, start_dt=start_dt, end_dt=end_dt)

    # 获取持仓证券的金额历史序列, 以时间点为索引, 返回: pd.DataFrame(持仓金额, index=[时间点], columns=[ID])
    def getPositionAmountSeries(self, dts=None, start_dt=None, end_dt=None):
        Data = self._PositionAmount[1:self._Model.DateTimeIndex + 2]
        return cutDateTime(Data, dts=dts, start_dt=start_dt, end_dt=end_dt)

    # 获取账户价值的历史序列, 以时间点为索引
    def getAccountValueSeries(self, dts=None, start_dt=None, end_dt=None):
        CashSeries = self.getCashSeries(dts=dts,
                                        start_dt=start_dt,
                                        end_dt=end_dt)
        PositionAmountSeries = self.getPositionAmountSeries(
            dts=dts, start_dt=start_dt, end_dt=end_dt).sum(axis=1)
        DebtSeries = self.getDebtSeries(dts=dts,
                                        start_dt=start_dt,
                                        end_dt=end_dt)
        return CashSeries + PositionAmountSeries - DebtSeries

    # 执行给定数量的证券委托单, target_id: 目标证券 ID, num: 待买卖的数量, target_price: nan 表示市价单, 返回: (订单ID,
    # combined_order: 组合订单, DataFrame(index=[ID], columns=[数量, 目标价])
    # 基本的下单函数, 必须实现
    def order(self,
              target_id=None,
              num=0,
              target_price=np.nan,
              combined_order=None):
        if target_id is not None:
            self._Orders.loc[self._Orders.shape[0]] = (target_id, num,
                                                       target_price)
            if pd.notnull(target_price):
                print("警告: 本账户: '%s' 不支持限价单, 限价单将自动转为市价单!" % self.Name)
            return (self._Orders.shape[0], target_id, num, target_price)
        if combined_order is not None:
            if pd.notnull(combined_order["目标价"]).sum() > 0:
                print("警告: 本账户: '%s' 不支持限价单, 限价单将自动转为市价单!" % self.Name)
            combined_order.index.name = "ID"
            combined_order = combined_order.reset_index()
            combined_order.index = np.arange(
                self._Orders.shape[0],
                self._Orders.shape[0] + combined_order.shape[0])
            self._Orders = self._Orders.append(combined_order)
        return combined_order

    # 撤销订单, order_ids 是订单在 self.Orders 中的 index
    def cancelOrder(self, order_ids):
        self._Orders = self._Orders.loc[self._Orders.index.difference(
            set(order_ids))]
        self._Orders.sort_index(axis=0, inplace=True)
        self._Orders.index = np.arange(self._Orders.shape[0])
        return 0

    # 更新交易限制条件
    def _updateTradeLimit(self, idt):
        self._SellVolLimit[:] = self._BuyVolLimit[:] = np.inf
        TradePrice = self._MarketFT.readData(
            factor_names=[self.BuyLimit.TradePrice, self.SellLimit.TradePrice],
            ids=self._IDs,
            dts=[idt]).iloc[:, 0, :]
        self._BuyPrice, self._SellPrice = TradePrice.iloc[:,
                                                          0], TradePrice.iloc[:,
                                                                              1]
        self._BuyVolLimit[pd.isnull(self._BuyPrice) |
                          (self._BuyPrice <= 0)] = 0.0  # 买入成交价缺失的不能买入
        self._SellVolLimit[pd.isnull(self._SellPrice) |
                           (self._SellPrice <= 0)] = 0.0  # 卖出成交价缺失的不能卖出
        if self.SellLimit.LimitIDFilter:  # 满足卖出禁止条件的不能卖出
            Mask = self._MarketFT.getIDMask(
                idt, ids=self._IDs, id_filter_str=self.SellLimit.LimitIDFilter)
            self._SellVolLimit[Mask] = 0.0
        if self.BuyLimit.LimitIDFilter:  # 满足买入禁止条件的不能买入
            Mask = self._MarketFT.getIDMask(
                idt, ids=self._IDs, id_filter_str=self.BuyLimit.LimitIDFilter)
            self._BuyVolLimit[Mask] = 0.0
        if self.BuyLimit.Amt is not None:  # 指定了买入成交额, 成交额满足限制要求
            Amount = self._MarketFT.readData(factor_names=[self.BuyLimit.Amt],
                                             ids=self._IDs,
                                             dts=[idt]).iloc[0, 0, :]
            self._BuyVolLimit = self._BuyVolLimit.clip_upper(
                Amount * self.BuyLimit.AmtLimitRatio / self._BuyPrice)
        if self.SellLimit.Amt is not None:  # 指定了卖出成交额, 成交额满足限制要求
            Amount = self._MarketFT.readData(factor_names=[self.SellLimit.Amt],
                                             ids=self._IDs,
                                             dts=[idt]).iloc[0, 0, :]
            self._SellVolLimit = self._SellVolLimit.clip_upper(
                Amount * self.SellLimit.AmtLimitRatio / self._SellPrice)
        if not self.SellLimit.ShortAllowed:
            PositionNum = self._PositionNum.iloc[self._Model.DateTimeIndex + 1]
            self._SellVolLimit = self._SellVolLimit.clip_upper(
                PositionNum.clip_lower(0.0))
        return 0

    # 撮合成交订单
    def _matchOrder(self, idt):
        if self._Orders.shape[0] == 0: return []
        self._updateTradeLimit(idt)
        MarketOrders = self._Orders.groupby(
            by=["ID"]).sum()["数量"]  # Series(数量, index=[ID])
        self._Orders = pd.DataFrame(columns=["ID", "数量", "目标价"])
        return self._matchMarketOrder(idt, MarketOrders)

    # 撮合成交市价单
    # 以成交价完成成交, 满足交易限制要求
    # 未成交的市价单自动撤销
    def _matchMarketOrder(self, idt, orders):
        IDs = orders.index.tolist()
        orders = orders.clip(upper=self._BuyVolLimit.loc[IDs],
                             lower=-self._SellVolLimit.loc[IDs])  # 过滤限制条件
        orders = orders[orders != 0]
        if orders.shape[0] == 0: return []
        # 先执行卖出交易
        SellPrice = self._SellPrice[orders.index]
        SellAmounts = (SellPrice * orders).clip_upper(0).abs()
        Fees = SellAmounts * self.SellLimit.TradeFee  # 卖出交易费
        CashChanged = SellAmounts - Fees
        Mask = (SellAmounts > 0)
        SellNums = orders.clip_upper(0)
        TradingRecord = list(
            zip([idt] * Mask.sum(), orders.index[Mask], SellNums[Mask],
                SellPrice[Mask], Fees[Mask], CashChanged[Mask],
                ["sell"] * Mask.sum()))
        # 再执行买入交易
        BuyPrice = self._BuyPrice[orders.index]
        BuyAmounts = (BuyPrice * orders).clip_lower(0)
        CashAcquired = BuyAmounts * (1 + self.BuyLimit.TradeFee)
        TotalCashAcquired = CashAcquired.sum()
        if TotalCashAcquired > 0:
            AvailableCash = self.AvailableCash + CashChanged.sum()
            CashAllocated = min(
                AvailableCash,
                TotalCashAcquired) * CashAcquired / TotalCashAcquired
            BuyAmounts = CashAllocated / (1 + self.BuyLimit.TradeFee)
            Fees = BuyAmounts * self.BuyLimit.TradeFee
            BuyNums = BuyAmounts / BuyPrice
            Mask = (BuyAmounts > 0)
            TradingRecord.extend(
                (zip([idt] * Mask.sum(), orders.index[Mask], BuyNums[Mask],
                     BuyPrice[Mask], Fees[Mask], -CashAllocated[Mask],
                     ["buy"] * Mask.sum())))
        else:
            CashAllocated = BuyNums = pd.Series(0, index=BuyAmounts.index)
        # 更新持仓数量和现金
        iIndex = self._Model.DateTimeIndex
        iPosition = self._PositionNum.iloc[iIndex + 1].copy()
        TotalAmount = (self._BuyPrice * iPosition.clip_upper(0) +
                       self._SellPrice * iPosition.clip_lower(0)).sum()
        self._Turnover.iloc[iIndex] = (SellAmounts.sum() + BuyAmounts.sum()
                                       ) / (TotalAmount + super().AccountValue)
        iPosition[orders.index] += BuyNums + SellNums
        self._PositionNum.iloc[iIndex + 1] = iPosition
        self._QS_updateCashDebt(CashChanged.sum() - CashAllocated.sum())
        return TradingRecord
示例#20
0
class EMFields(HasTraits):
    """ A collection of EM fields output from Sim4Life EM simulations
    """

    # pylint: disable=too-many-instance-attributes

    #: Configuration parser.
    configuration = Instance(ConfigParser)

    #: Current participant ID.
    participant_id = Str()

    #: Path to field data file.
    data_path = File()

    #: Dictionary of data in `data_path`.
    data_dict = Dict()

    #: List of field keys that can be displayed.
    field_keys = ListStr()

    #: The currently selected field key.
    selected_field_key = Str()

    #: X values of grid in data file.
    x_vals = Array()

    #: Y values of grid in data file.
    y_vals = Array()

    #: Z values of grid in data file.
    z_vals = Array()

    #: Raw data of currently selected field from data file.
    data_arr = Array()

    #: X values of regular grid for current field.
    masked_gr_x = Array()

    #: Y values of regular grid for current field.
    masked_gr_y = Array()

    #: Z values of regular grid for current field.
    masked_gr_z = Array()

    #: Data on regular grid for current field.
    masked_grid_data = Array()

    @observe('data_path')
    def _update_data_path(self, event):
        self.data_dict = loadmat(event.new)

        g_z = self.data_dict['Axis0'][0] * 1000
        g_y = self.data_dict['Axis1'][0] * 1000
        g_x = self.data_dict['Axis2'][0] * 1000

        self.x_vals = np.array([(g_x[i] + g_x[i + 1]) / 2
                                for i in range(g_x.size - 1)])
        self.y_vals = np.array([(g_y[i] + g_y[i + 1]) / 2
                                for i in range(g_y.size - 1)])
        self.z_vals = np.array([(g_z[i] + g_z[i + 1]) / 2
                                for i in range(g_z.size - 1)])

        tmp = self.x_vals
        self.x_vals = self.z_vals
        self.z_vals = tmp

        self.field_keys = [
            key for key in self.data_dict.keys()
            if 'Snapshot' in key and not any(field in key
                                             for field in scalar_fields)
        ]

        if self.selected_field_key not in self.field_keys:
            self.selected_field_key = self.field_keys[0]
            for key in self.field_keys:
                if key.lower().startswith(
                        self._get_default_value('initial_field').lower()):
                    self.selected_field_key = key
                    break

        self.calculate_field()

    @observe('selected_field_key', post_init=True)
    def calculate_field(self, event=None):  # pylint: disable=unused-argument, too-many-locals
        """
        Calculate the current selected field values and set the grid locations and values

        Parameters
        ----------
        event : A :py:class:`traits.observation.events.TraitChangeEvent` instance
            The trait change event for selected_field_key
        """
        data_x, data_y, data_z = abs(self.data_dict[self.selected_field_key]).T

        self.data_arr = np.sqrt(data_x**2 + data_y**2 + data_z**2).reshape(
            self.z_vals.size, self.y_vals.size, self.x_vals.size)
        self.data_arr[self.data_arr == 0] = np.nan

        self.data_arr = np.swapaxes(self.data_arr, 0, 2)

        x_min = int(np.ceil(self.x_vals.min()))
        x_max = int(np.floor(self.x_vals.max()))
        y_min = int(np.ceil(self.y_vals.min()))
        y_max = int(np.floor(self.y_vals.max()))
        z_min = int(np.ceil(self.z_vals.min()))
        z_max = int(np.floor(self.z_vals.max()))

        gr_x, gr_y, gr_z = np.mgrid[x_min:x_max:len(self.x_vals) * 1j,
                                    y_min:y_max:len(self.y_vals) * 1j,
                                    z_min:z_max:len(self.z_vals) * 1j]

        points = np.array([[gr_x[i, j, k], gr_y[i, j, k], gr_z[i, j, k]]
                           for i in range(gr_x.shape[0])
                           for j in range(gr_x.shape[1])
                           for k in range(gr_x.shape[2])])

        interp_func = RegularGridInterpolator(
            (self.x_vals, self.y_vals, self.z_vals), self.data_arr)
        grid_data = interp_func(points).reshape(self.data_arr.shape)

        mask = np.all(np.isnan(grid_data), axis=(0, 1))
        masked_grid_data = grid_data[:, :, ~mask]
        masked_gr_x = gr_x[:, :, ~mask]
        masked_gr_y = gr_y[:, :, ~mask]
        masked_gr_z = gr_z[:, :, ~mask]

        maskx = np.all(np.isnan(masked_grid_data), axis=(1, 2))
        masked_grid_data = masked_grid_data[~maskx, :, :]
        masked_gr_x = masked_gr_x[~maskx, :, :]
        masked_gr_y = masked_gr_y[~maskx, :, :]
        masked_gr_z = masked_gr_z[~maskx, :, :]

        masky = np.all(np.isnan(masked_grid_data), axis=(0, 2))
        self.masked_grid_data = masked_grid_data[:, ~masky, :]
        self.masked_gr_x = masked_gr_x[:, ~masky, :]
        self.masked_gr_y = masked_gr_y[:, ~masky, :]
        self.masked_gr_z = masked_gr_z[:, ~masky, :]

    def _get_default_value(self, option):
        if self.participant_id is not None:
            if self.participant_id not in self.configuration:
                self.configuration[self.participant_id] = {}
            val = self.configuration[self.participant_id][option]
        else:
            val = self.configuration[self.participant_id][option]
        return val
示例#21
0
class MongoDB(WritableFactorDB):
    """MongoDB"""
    Name = Str("MongoDB")
    DBType = Enum("Mongo", arg_type="SingleOption", label="数据库类型", order=0)
    DBName = Str("Scorpion", arg_type="String", label="数据库名", order=1)
    IPAddr = Str("127.0.0.1", arg_type="String", label="IP地址", order=2)
    Port = Range(low=0,
                 high=65535,
                 value=27017,
                 arg_type="Integer",
                 label="端口",
                 order=3)
    User = Str("root", arg_type="String", label="用户名", order=4)
    Pwd = Password("", arg_type="String", label="密码", order=5)
    CharSet = Enum("utf8",
                   "gbk",
                   "gb2312",
                   "gb18030",
                   "cp936",
                   "big5",
                   arg_type="SingleOption",
                   label="字符集",
                   order=6)
    Connector = Enum("default",
                     "pymongo",
                     arg_type="SingleOption",
                     label="连接器",
                     order=7)
    IgnoreFields = ListStr(arg_type="List", label="忽略字段", order=8)
    InnerPrefix = Str("qs_", arg_type="String", label="内部前缀", order=9)

    def __init__(self, sys_args={}, config_file=None, **kwargs):
        super().__init__(sys_args=sys_args,
                         config_file=(__QS_ConfigPath__ + os.sep +
                                      "MongoDBConfig.json"
                                      if config_file is None else config_file),
                         **kwargs)
        self._TableFactorDict = {}  # {表名: pd.Series(数据类型, index=[因子名])}
        self._TableFieldDataType = {}  # {表名: pd.Series(数据库数据类型, index=[因子名])}
        self.Name = "MongoDB"
        return

    def __getstate__(self):
        state = self.__dict__.copy()
        state["_Connection"] = (True if self.isAvailable() else False)
        state["_DB"] = None
        return state

    def __setstate__(self, state):
        super().__setstate__(state)
        if self._Connection: self._connect()
        else: self._Connection = None

    @property
    def Connection(self):
        if self._Connection is not None:
            if os.getpid() != self._PID: self._connect()  # 如果进程号发生变化, 重连
        return self._Connection

    def _connect(self):
        self._Connection = None
        if (self.Connector == "pymongo") or ((self.Connector == "default") and
                                             (self.DBType == "Mongo")):
            try:
                import pymongo
                self._Connection = pymongo.MongoClient(host=self.IPAddr,
                                                       port=self.Port)
            except Exception as e:
                Msg = ("'%s' 尝试使用 pymongo 连接(%s@%s:%d)数据库 '%s' 失败: %s" %
                       (self.Name, self.User, self.IPAddr, self.Port,
                        self.DBName, str(e)))
                self._QS_Logger.error(Msg)
                if self.Connector != "default": raise e
            else:
                self._Connector = "pymongo"
        else:
            Msg = ("'%s' 连接(%s@%s:%d)数据库 '%s' 失败: %s" %
                   (self.Name, self.User, self.IPAddr, self.Port, self.DBName,
                    str(e)))
            self._QS_Logger.error(Msg)
            raise e
        self._PID = os.getpid()
        self._DB = self._Connection[self.DBName]
        return 0

    def connect(self):
        self._connect()
        nPrefix = len(self.InnerPrefix)
        if self.DBType == "Mongo":
            self._TableFactorDict = {}
            for iTableName in self._DB.collection_names():
                if iTableName[:nPrefix] == self.InnerPrefix:
                    iTableInfo = self._DB[iTableName].find_one(
                        {"code": "_TableInfo"}, {
                            "datetime": 0,
                            "code": 0,
                            "_id": 0
                        })
                    if iTableInfo:
                        self._TableFactorDict[
                            iTableName[nPrefix:]] = pd.Series({
                                iFactorName: iInfo["DataType"]
                                for iFactorName, iInfo in iTableInfo.items()
                                if iFactorName not in self.IgnoreFields
                            })
        return 0

    @property
    def TableNames(self):
        return sorted(self._TableFactorDict)

    def getTable(self, table_name, args={}):
        if table_name not in self._TableFactorDict:
            Msg = ("因子库 '%s' 调用方法 getTable 错误: 不存在因子表: '%s'!" %
                   (self.Name, table_name))
            self._QS_Logger.error(Msg)
            raise __QS_Error__(Msg)
        TableType = args.get("因子表类型", "宽表")
        if TableType == "宽表":
            return _WideTable(name=table_name,
                              fdb=self,
                              sys_args=args,
                              logger=self._QS_Logger)
        else:
            Msg = ("因子库 '%s' 调用方法 getTable 错误: 不支持的因子表类型: '%s'" %
                   (self.Name, TableType))
            self._QS_Logger.error(Msg)
            raise __QS_Error__(Msg)

    def renameTable(self, old_table_name, new_table_name):
        if old_table_name not in self._TableFactorDict:
            Msg = ("因子库 '%s' 调用方法 renameTable 错误: 不存在因子表 '%s'!" %
                   (self.Name, old_table_name))
            self._QS_Logger.error(Msg)
            raise __QS_Error__(Msg)
        if (new_table_name != old_table_name) and (new_table_name
                                                   in self._TableFactorDict):
            Msg = ("因子库 '%s' 调用方法 renameTable 错误: 新因子表名 '%s' 已经存在于库中!" %
                   (self.Name, new_table_name))
            self._QS_Logger.error(Msg)
            raise __QS_Error__(Msg)
        self._DB[self.InnerPrefix + old_table_name].rename(self.InnerPrefix +
                                                           new_table_name)
        self._TableFactorDict[new_table_name] = self._TableFactorDict.pop(
            old_table_name)
        return 0

    def deleteTable(self, table_name):
        if table_name not in self._TableFactorDict: return 0
        self._DB.drop_collection(self.InnerPrefix + table_name)
        self._TableFactorDict.pop(table_name, None)
        return 0

    # 创建表, field_types: {字段名: 数据类型}
    def createTable(self, table_name, field_types):
        if self.InnerPrefix + table_name not in self._DB.collection_names():
            Doc = {
                iField: {
                    "DataType": iDataType
                }
                for iField, iDataType in field_types.items()
            }
            Doc.update({"datetime": None, "code": "_TableInfo"})
            Collection = self._DB[self.InnerPrefix + table_name]
            Collection.insert(Doc)
            # 添加索引
            if self._Connector == "pymongo":
                import pymongo
                Index1 = pymongo.IndexModel([("datetime", pymongo.ASCENDING),
                                             ("code", pymongo.ASCENDING)],
                                            name=self.InnerPrefix +
                                            "datetime_code")
                Index2 = pymongo.IndexModel([("code", pymongo.HASHED)],
                                            name=self.InnerPrefix + "code")
                try:
                    Collection.create_indexes([Index1, Index2])
                except Exception as e:
                    self._QS_Logger.warning(
                        "'%s' 调用方法 createTable 在数据库中创建表 '%s' 的索引时错误: %s" %
                        (self.Name, table_name, str(e)))
        self._TableFactorDict[table_name] = pd.Series(field_types)
        return 0

    # ----------------------------因子操作---------------------------------
    def renameFactor(self, table_name, old_factor_name, new_factor_name):
        if old_factor_name not in self._TableFactorDict[table_name]:
            Msg = ("因子库 '%s' 调用方法 renameFactor 错误: 因子表 '%s' 中不存在因子 '%s'!" %
                   (self.Name, table_name, old_factor_name))
            self._QS_Logger.error(Msg)
            raise __QS_Error__(Msg)
        if (new_factor_name != old_factor_name) and (
                new_factor_name in self._TableFactorDict[table_name]):
            Msg = (
                "因子库 '%s' 调用方法 renameFactor 错误: 新因子名 '%s' 已经存在于因子表 '%s' 中!" %
                (self.Name, new_factor_name, table_name))
            self._QS_Logger.error(Msg)
            raise __QS_Error__(Msg)
        self._DB[self.InnerPrefix + table_name].update_many(
            {}, {"$rename": {
                old_factor_name: new_factor_name
            }})
        self._TableFactorDict[table_name][
            new_factor_name] = self._TableFactorDict[table_name].pop(
                old_factor_name)
        return 0

    def deleteFactor(self, table_name, factor_names):
        if not factor_names: return 0
        FactorIndex = self._TableFactorDict.get(
            table_name, pd.Series()).index.difference(factor_names).tolist()
        if not FactorIndex: return self.deleteTable(table_name)
        self.deleteField(self.InnerPrefix + table_name, factor_names)
        for iFactorName in factor_names:
            self._DB[self.InnerPrefix + table_name].update_many(
                {}, {'$unset': {
                    iFactorName: 1
                }})
        self._TableFactorDict[table_name] = self._TableFactorDict[table_name][
            FactorIndex]
        return 0

    # 增加因子,field_types: {字段名: 数据类型}
    def addFactor(self, table_name, field_types):
        if table_name not in self._TableFactorDict:
            return self.createTable(table_name, field_types)
        Doc = {
            iField: {
                "DataType": iDataType
            }
            for iField, iDataType in field_types.items()
        }
        self._DB[self.InnerPrefix + table_name].update({"code": "_TableInfo"},
                                                       {"$set": Doc})
        self._TableFactorDict[table_name] = self._TableFactorDict[
            table_name].append(field_types)
        return 0

    def deleteData(self, table_name, ids=None, dts=None):
        Doc = {}
        if dts is not None:
            Doc["datetime"] = {"$in": dts}
        if ids is not None:
            Doc["code"] = {"$in": ids}
        if Doc:
            self._DB[self.InnerPrefix + table_name].delete_many(Doc)
        else:
            self._DB.drop_collection(self.InnerPrefix + table_name)
            self._TableFactorDict.pop(table_name)
        return 0

    def writeData(self,
                  data,
                  table_name,
                  if_exists="update",
                  data_type={},
                  **kwargs):
        if table_name not in self._TableFactorDict:
            FieldTypes = {
                iFactorName: _identifyDataType(data.iloc[i].dtypes)
                for i, iFactorName in enumerate(data.items)
            }
            self.createTable(table_name, field_types=FieldTypes)
        else:
            NewFactorNames = data.items.difference(
                self._TableFactorDict[table_name].index).tolist()
            if NewFactorNames:
                FieldTypes = {
                    iFactorName: _identifyDataType(data.iloc[i].dtypes)
                    for i, iFactorName in enumerate(NewFactorNames)
                }
                self.addFactor(table_name, FieldTypes)
            AllFactorNames = self._TableFactorDict[table_name].index.tolist()
            OldData = self.getTable(table_name).readData(
                factor_names=AllFactorNames,
                ids=data.minor_axis.tolist(),
                dts=data.major_axis.tolist())
            if if_exists == "append":
                for iFactorName in AllFactorNames:
                    if iFactorName in data:
                        data[iFactorName] = OldData[iFactorName].where(
                            pd.notnull(OldData[iFactorName]),
                            data[iFactorName])
                    else:
                        data[iFactorName] = OldData[iFactorName]
            elif if_exists == "update":
                for iFactorName in AllFactorNames:
                    if iFactorName in data:
                        data[iFactorName] = data[iFactorName].where(
                            pd.notnull(data[iFactorName]),
                            OldData[iFactorName])
                    else:
                        data[iFactorName] = OldData[iFactorName]
            else:
                Msg = ("因子库 '%s' 调用方法 writeData 错误: 不支持的写入方式 '%s'!" %
                       (self.Name, if_exists))
                self._QS_Logger.error(Msg)
                raise __QS_Error__(Msg)
        NewData = {}
        for iFactorName in data.items:
            iData = data.loc[iFactorName].stack(dropna=False)
            NewData[iFactorName] = iData
        NewData = pd.DataFrame(NewData).loc[:, data.items]
        Mask = pd.notnull(NewData).any(axis=1)
        NewData = NewData[Mask]
        if NewData.shape[0] == 0: return 0
        self.deleteData(table_name,
                        ids=data.minor_axis.tolist(),
                        dts=data.major_axis.tolist())
        NewData = NewData.reset_index()
        NewData.columns = ["datetime", "code"] + NewData.columns[2:].tolist()
        self._DB[self.InnerPrefix + table_name].insert_many(
            NewData.to_dict(orient="records"))
        return 0
示例#22
0
文件: IC.py 项目: rlcjj/QuantStudio
class RiskAdjustedIC(IC):
    """风险调整的 IC"""
    RiskFactors = ListStr(arg_type="MultiOption",
                          label="风险因子",
                          order=2.5,
                          option_range=())

    def __init__(self, factor_table, name="风险调整的 IC", sys_args={}, **kwargs):
        return super().__init__(factor_table=factor_table,
                                name=name,
                                sys_args=sys_args,
                                **kwargs)

    def __QS_initArgs__(self):
        super().__QS_initArgs__()
        self.remove_trait("WeightFactor")

    def __QS_move__(self, idt, **kwargs):
        if self._iDT == idt: return 0
        if self.CalcDTs:
            if idt not in self.CalcDTs[self._CurCalcInd:]: return 0
            self._CurCalcInd = self.CalcDTs[self._CurCalcInd:].index(
                idt) + self._CurCalcInd
            PreInd = self._CurCalcInd - self.LookBack
            LastInd = self._CurCalcInd - 1
            PreDateTime = self.CalcDTs[PreInd]
            LastDateTime = self.CalcDTs[LastInd]
        else:
            self._CurCalcInd = self._Model.DateTimeIndex
            PreInd = self._CurCalcInd - self.LookBack
            LastInd = self._CurCalcInd - 1
            PreDateTime = self._Model.DateTimeSeries[PreInd]
            LastDateTime = self._Model.DateTimeSeries[LastInd]
        if (PreInd < 0) or (LastInd < 0):
            for iFactorName in self.TestFactors:
                self._Output["IC"][iFactorName].append(np.nan)
                self._Output["股票数"][iFactorName].append(np.nan)
            self._Output["时点"].append(idt)
            return 0
        PreIDs = self._FactorTable.getFilteredID(idt=PreDateTime,
                                                 id_filter_str=self.IDFilter)
        FactorExpose = self._FactorTable.readData(
            dts=[PreDateTime], ids=PreIDs,
            factor_names=list(self.TestFactors)).iloc[:, 0, :]
        if self.RiskFactors:
            RiskExpose = self._FactorTable.readData(
                dts=[PreDateTime],
                ids=PreIDs,
                factor_names=list(self.RiskFactors)).iloc[:, 0, :]
            RiskExpose["constant"] = 1.0
        else:
            RiskExpose = pd.DataFrame(1.0, index=PreIDs, columns=["constant"])
        CurPrice = self._FactorTable.readData(dts=[idt],
                                              ids=PreIDs,
                                              factor_names=[self.PriceFactor
                                                            ]).iloc[0, 0, :]
        LastPrice = self._FactorTable.readData(dts=[LastDateTime],
                                               ids=PreIDs,
                                               factor_names=[self.PriceFactor
                                                             ]).iloc[0, 0, :]
        Ret = CurPrice / LastPrice - 1
        Mask = (pd.isnull(RiskExpose).sum(axis=1) == 0)
        # 展开Dummy因子
        if self.IndustryFactor != "无":
            DummyFactorData = self._FactorTable.readData(
                dts=[PreDateTime],
                ids=PreIDs,
                factor_names=[self.IndustryFactor]).iloc[0, 0, :]
            _, _, _, DummyFactorData = prepareRegressData(
                np.ones(DummyFactorData.shape[0]),
                dummy_data=DummyFactorData.values)
        iMask = (pd.notnull(Ret) & Mask)
        Ret = Ret[iMask]
        iX = RiskExpose.loc[iMask].values
        if self.IndustryFactor != "无":
            iDummy = DummyFactorData[iMask.values]
            iDummy = iDummy[:, (np.sum(iDummy == 0, axis=0) < iDummy.shape[0])]
            iX = np.hstack((iX, iDummy[:, :-1]))
        try:
            Result = sm.OLS(Ret.values, iX, missing="drop").fit()
        except:
            return self._moveNone(idt)
        RiskAdjustedRet = pd.Series(Result.resid, index=Ret.index)
        for iFactorName in self.TestFactors:
            iFactorExpose = FactorExpose[iFactorName]
            iMask = (Mask & pd.notnull(iFactorExpose))
            iFactorExpose = iFactorExpose[iMask]
            iX = RiskExpose.loc[iMask].values
            if self.IndustryFactor != "无":
                iDummy = DummyFactorData[iMask.values]
                iDummy = iDummy[:, (
                    np.sum(iDummy == 0, axis=0) < iDummy.shape[0])]
                iX = np.hstack((iX, iDummy[:, :-1]))
            try:
                Result = sm.OLS(iFactorExpose.values, iX, missing="drop").fit()
            except:
                self._Output["IC"][iFactorName].append(np.nan)
                self._Output["股票数"][iFactorName].append(0)
                continue
            iFactorExpose = pd.Series(Result.resid, index=iFactorExpose.index)
            self._Output["IC"][iFactorName].append(
                iFactorExpose.corr(RiskAdjustedRet, method=self.CorrMethod))
            self._Output["股票数"][iFactorName].append(
                pd.notnull(iFactorExpose).sum())
        self._Output["时点"].append(idt)
        return 0
示例#23
0
class SQLDB(WritableFactorDB):
    """SQLDB"""
    DBType = Enum("MySQL", "SQL Server", "Oracle", "sqlite3", arg_type="SingleOption", label="数据库类型", order=0)
    DBName = Str("Scorpion", arg_type="String", label="数据库名", order=1)
    IPAddr = Str("127.0.0.1", arg_type="String", label="IP地址", order=2)
    Port = Range(low=0, high=65535, value=3306, arg_type="Integer", label="端口", order=3)
    User = Str("root", arg_type="String", label="用户名", order=4)
    Pwd = Password("", arg_type="String", label="密码", order=5)
    TablePrefix = Str("", arg_type="String", label="表名前缀", order=6)
    CharSet = Enum("utf8", "gbk", "gb2312", "gb18030", "cp936", "big5", arg_type="SingleOption", label="字符集", order=7)
    Connector = Enum("default", "cx_Oracle", "pymssql", "mysql.connector", "pymysql", "sqlite3", "pyodbc", arg_type="SingleOption", label="连接器", order=8)
    DSN = Str("", arg_type="String", label="数据源", order=9)
    SQLite3File = File(label="sqlite3文件", arg_type="File", order=10)
    CheckWriteData = Bool(False, arg_type="Bool", label="检查写入值", order=11)
    IgnoreFields = ListStr(arg_type="List", label="忽略字段", order=12)
    InnerPrefix = Str("qs_", arg_type="String", label="内部前缀", order=13)
    def __init__(self, sys_args={}, config_file=None, **kwargs):
        self._Connection = None# 数据库链接
        self._Connector = None# 实际使用的数据库链接器
        self._TableFactorDict = {}# {表名: pd.Series(数据类型, index=[因子名])}
        self._TableFieldDataType = {}# {表名: pd.Series(数据库数据类型, index=[因子名])}
        super().__init__(sys_args=sys_args, config_file=(__QS_ConfigPath__+os.sep+"SQLDBConfig.json" if config_file is None else config_file), **kwargs)
        self._PID = None# 保存数据库连接创建时的进程号
        self.Name = "SQLDB"
        return
    def __getstate__(self):
        state = self.__dict__.copy()
        state["_Connection"] = (True if self.isAvailable() else False)
        return state
    def __setstate__(self, state):
        super().__setstate__(state)
        if self._Connection: self._connect()
        else: self._Connection = None
    # -------------------------------------------数据库相关---------------------------
    def _connect(self):
        self._Connection = None
        if (self.Connector=="cx_Oracle") or ((self.Connector=="default") and (self.DBType=="Oracle")):
            try:
                import cx_Oracle
                self._Connection = cx_Oracle.connect(self.User, self.Pwd, cx_Oracle.makedsn(self.IPAddr, str(self.Port), self.DBName))
            except Exception as e:
                if self.Connector!="default": raise e
            else:
                self._Connector = "cx_Oracle"
        elif (self.Connector=="pymssql") or ((self.Connector=="default") and (self.DBType=="SQL Server")):
            try:
                import pymssql
                self._Connection = pymssql.connect(server=self.IPAddr, port=str(self.Port), user=self.User, password=self.Pwd, database=self.DBName, charset=self.CharSet)
            except Exception as e:
                if self.Connector!="default": raise e
            else:
                self._Connector = "pymssql"
        elif (self.Connector=="mysql.connector") or ((self.Connector=="default") and (self.DBType=="MySQL")):
            try:
                import mysql.connector
                self._Connection = mysql.connector.connect(host=self.IPAddr, port=str(self.Port), user=self.User, password=self.Pwd, database=self.DBName, charset=self.CharSet, autocommit=True)
            except Exception as e:
                if self.Connector!="default": raise e
            else:
                self._Connector = "mysql.connector"
        elif self.Connector=='pymysql':
            try:
                import pymysql
                self._Connection = pymysql.connect(host=self.IPAddr, port=self.Port, user=self.User, password=self.Pwd, db=self.DBName, charset=self.CharSet)
            except Exception as e:
                if self.Connector!='default': raise e
        elif (self.Connector=="sqlite3") or ((self.Connector=="default") and (self.DBType=="sqlite3")):
            import sqlite3
            self._Connection = sqlite3.connect(self.SQLite3File)
            self._Connector = "sqlite3"
        if self._Connection is None:
            if self.Connector not in ("default", "pyodbc"):
                self._Connection = None
                raise __QS_Error__("不支持该连接器(connector) : "+self.Connector)
            else:
                import pyodbc
                if self.DSN: self._Connection = pyodbc.connect("DSN=%s;PWD=%s" % (self.DSN, self.Pwd))
                else: self._Connection = pyodbc.connect("DRIVER={%s};DATABASE=%s;SERVER=%s;UID=%s;PWD=%s" % (self.DBType, self.DBName, self.IPAddr+","+str(self.Port), self.User, self.Pwd))
                self._Connector = "pyodbc"
        self._PID = os.getpid()
        return 0
    def connect(self):
        self._connect()
        nPrefix = len(self.InnerPrefix)
        if self._Connector=="sqlite3":
            SQLStr = "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%s%%' ORDER BY name"
            Cursor = self.cursor(SQLStr % self.InnerPrefix)
            AllTables = Cursor.fetchall()
            self._TableFactorDict = {}
            self._TableFieldDataType = {}
            IgnoreFields = ["code", "datetime"]+list(self.IgnoreFields)
            for iTableName in AllTables:
                iTableName = iTableName[0][nPrefix:]
                Cursor.execute("PRAGMA table_info([%s])" % self.InnerPrefix+iTableName)
                iDataType = np.array(Cursor.fetchall())
                iDataType = pd.Series(iDataType[:, 2], index=iDataType[:, 1])
                iDataType = iDataType[iDataType.index.difference(IgnoreFields)]
                if iDataType.shape[0]>0:
                    self._TableFieldDataType[iTableName] = iDataType.copy()
                    iDataType[iDataType=="text"] = "string"
                    iDataType[iDataType=="real"] = "double"
                    self._TableFactorDict[iTableName] = iDataType
        elif self.DBType=="MySQL":
            SQLStr = ("SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE FROM information_schema.COLUMNS WHERE table_schema='%s' " % self.DBName)
            SQLStr += ("AND TABLE_NAME LIKE '%s%%' " % self.InnerPrefix)
            SQLStr += "AND COLUMN_NAME NOT IN ('code', 'datetime'"
            if len(self.IgnoreFields)>0:
                SQLStr += ",'"+"','".join(self.IgnoreFields)+"') "
            else:
                SQLStr += ") "
            SQLStr += "ORDER BY TABLE_NAME, COLUMN_NAME"
            Rslt = self.fetchall(SQLStr)
            if not Rslt:
                self._TableFieldDataType = {}
                self._TableFactorDict = {}
            else:
                self._TableFieldDataType = pd.DataFrame(np.array(Rslt), columns=["表", "因子", "DataType"]).set_index(["表", "因子"])["DataType"]
                self._TableFactorDict = self._TableFieldDataType.copy()
                Mask = (self._TableFactorDict.str.contains("char") | self._TableFactorDict.str.contains("date"))
                self._TableFactorDict[Mask] = "string"
                self._TableFactorDict[~Mask] = "double"
                self._TableFactorDict = {iTable[nPrefix:]:self._TableFactorDict.loc[iTable] for iTable in self._TableFactorDict.index.levels[0]}
                self._TableFieldDataType = {iTable[nPrefix:]:self._TableFieldDataType.loc[iTable] for iTable in self._TableFieldDataType.index.levels[0]}
        return 0
    def disconnect(self):
        if self._Connection is not None:
            try:
                self._Connection.close()
            except Exception as e:
                self._QS_Logger.error("因子库 '%s' 断开错误: %s" % (self.Name, str(e)))
            finally:
                self._Connection = None
        return 0
    def isAvailable(self):
        return (self._Connection is not None)
    def cursor(self, sql_str=None):
        if self._Connection is None: raise __QS_Error__("%s尚未连接!" % self.__doc__)
        if os.getpid()!=self._PID: self._connect()# 如果进程号发生变化, 重连
        try:# 连接断开后重连
            Cursor = self._Connection.cursor()
        except:
            self._connect()
            Cursor = self._Connection.cursor()
        if sql_str is None: return Cursor
        Cursor.execute(sql_str)
        return Cursor
    def fetchall(self, sql_str):
        Cursor = self.cursor(sql_str=sql_str)
        Data = Cursor.fetchall()
        Cursor.close()
        return Data
    def execute(self, sql_str):
        if self._Connection is None: raise __QS_Error__("%s尚未连接!" % self.__doc__)
        if os.getpid()!=self._PID: self._connect()# 如果进程号发生变化, 重连
        try:
            Cursor = self._Connection.cursor()
        except:
            self._connect()
            Cursor = self._Connection.cursor()
        Cursor.execute(sql_str)
        self._Connection.commit()
        Cursor.close()
        return 0
    # -------------------------------表的操作---------------------------------
    @property
    def TableNames(self):
        return sorted(self._TableFactorDict)
    def getTable(self, table_name, args={}):
        if table_name not in self._TableFactorDict: raise __QS_Error__("表 '%s' 不存在!" % table_name)
        if args.get("因子表类型", "宽表")=="宽表":
            return _WideTable(name=table_name, fdb=self, sys_args=args, logger=self._QS_Logger)
        else:
            return _NarrowTable(name=table_name, fdb=self, sys_args=args, logger=self._QS_Logger)
    def renameTable(self, old_table_name, new_table_name):
        if old_table_name not in self._TableFactorDict: raise __QS_Error__("表: '%s' 不存在!" % old_table_name)
        if (new_table_name!=old_table_name) and (new_table_name in self._TableFactorDict): raise __QS_Error__("表: '"+new_table_name+"' 已存在!")
        SQLStr = "ALTER TABLE "+self.TablePrefix+self.InnerPrefix+old_table_name+" RENAME TO "+self.TablePrefix+self.InnerPrefix+new_table_name
        self.execute(SQLStr)
        self._TableFactorDict[new_table_name] = self._TableFactorDict.pop(old_table_name)
        self._TableFieldDataType[new_table_name] = self._TableFieldDataType.pop(old_table_name)
        return 0
    # 为某张表增加索引
    def addIndex(self, index_name, table_name, fields=["datetime", "code"], index_type="BTREE"):
        if index_type is not None:
            SQLStr = "CREATE INDEX "+index_name+" USING "+index_type+" ON "+self.TablePrefix+self.InnerPrefix+table_name+"("+", ".join(fields)+")"
        else:
            SQLStr = "CREATE INDEX "+index_name+" ON "+self.TablePrefix+self.InnerPrefix+table_name+"("+", ".join(fields)+")"
        return self.execute(SQLStr)
    # 创建表, field_types: {字段名: 数据类型}
    def createTable(self, table_name, field_types):
        if self.DBType=="MySQL":
            SQLStr = "CREATE TABLE IF NOT EXISTS %s (`datetime` DATETIME(6) NOT NULL, `code` VARCHAR(40) NOT NULL, " % (self.TablePrefix+self.InnerPrefix+table_name)
            for iField in field_types: SQLStr += "`%s` %s, " % (iField, field_types[iField])
            SQLStr += "PRIMARY KEY (`datetime`, `code`)) ENGINE=InnoDB DEFAULT CHARSET=utf8"
            IndexType = "BTREE"
        elif self.DBType=="sqlite3":
            SQLStr = "CREATE TABLE IF NOT EXISTS %s (`datetime` text NOT NULL, `code` text NOT NULL, " % (self.TablePrefix+self.InnerPrefix+table_name)
            for iField in field_types: SQLStr += "`%s` %s, " % (iField, field_types[iField])
            SQLStr += "PRIMARY KEY (`datetime`, `code`))"
            IndexType = None
        self.execute(SQLStr)
        try:
            self.addIndex(table_name+"_index", table_name, index_type=IndexType)
        except Exception as e:
            self._QS_Logger.warning("因子表 '%s' 索引创建失败: %s" % (table_name, str(e)))
        return 0
    # 增加字段,field_types: {字段名: 数据类型}
    def addField(self, table_name, field_types):
        if table_name not in self._TableFactorDict: return self.createTable(table_name, field_types)
        SQLStr = "ALTER TABLE %s " % (self.TablePrefix+self.InnerPrefix+table_name)
        SQLStr += "ADD COLUMN ("
        for iField in field_types: SQLStr += "%s %s," % (iField, field_types[iField])
        SQLStr = SQLStr[:-1]+")"
        self.execute(SQLStr)
        return 0
    def deleteTable(self, table_name):
        if table_name not in self._TableFactorDict: return 0
        SQLStr = 'DROP TABLE %s' % (self.TablePrefix+self.InnerPrefix+table_name)
        self.execute(SQLStr)
        self._TableFactorDict.pop(table_name, None)
        self._TableFieldDataType.pop(table_name, None)
        return 0
    # 清空表
    def truncateTable(self, table_name):
        if table_name not in self._TableFactorDict: raise __QS_Error__("表: '%s' 不存在!" % table_name)
        SQLStr = "TRUNCATE TABLE %s" % (self.TablePrefix+self.InnerPrefix+table_name)
        self.execute(SQLStr)
        return 0
    # ----------------------------因子操作---------------------------------
    def renameFactor(self, table_name, old_factor_name, new_factor_name):
        if old_factor_name not in self._TableFactorDict[table_name]: raise __QS_Error__("因子: '%s' 不存在!" % old_factor_name)
        if (new_factor_name!=old_factor_name) and (new_factor_name in self._TableFactorDict[table_name]): raise __QS_Error__("表中的因子: '%s' 已存在!" % new_factor_name)
        if self.DBType!="sqlite3":
            SQLStr = "ALTER TABLE "+self.TablePrefix+self.InnerPrefix+table_name
            SQLStr += " CHANGE COLUMN `"+old_factor_name+"` `"+new_factor_name+"`"
            self.execute(SQLStr)
        else:
            # 将表名改为临时表
            SQLStr = "ALTER TABLE %s RENAME TO %s"
            TempTableName = genAvailableName("TempTable", self.TableNames)
            self.execute(SQLStr % (self.TablePrefix+self.InnerPrefix+table_name, self.TablePrefix+self.InnerPrefix+TempTableName))
            # 创建新表
            FieldTypes = OrderedDict()
            for iFactorName, iDataType in self._TableFactorDict[table_name].items():
                iDataType = ("text" if iDataType=="string" else "real")
                if iFactorName==old_factor_name: FieldTypes[new_factor_name] = iDataType
                else: FieldTypes[iFactorName] = iDataType
            self.createTable(table_name, field_types=FieldTypes)
            # 导入数据
            OldFactorNames = ", ".join(self._TableFactorDict[table_name].index)
            NewFactorNames = ", ".join(FieldTypes)
            SQLStr = "INSERT INTO %s (datetime, code, %s) SELECT datetime, code, %s FROM %s"
            Cursor = self.cursor(SQLStr % (self.TablePrefix+self.InnerPrefix+table_name, NewFactorNames, OldFactorNames, self.TablePrefix+self.InnerPrefix+TempTableName))
            self._Connection.commit()
            # 删除临时表
            Cursor.execute("DROP TABLE %s" % (self.TablePrefix+self.InnerPrefix+TempTableName, ))
            self._Connection.commit()
            Cursor.close()
        self._TableFactorDict[table_name][new_factor_name] = self._TableFactorDict[table_name].pop(old_factor_name)
        self._TableFieldDataType[table_name][new_factor_name] = self._TableFieldDataType[table_name].pop(old_factor_name)
        return 0
    def deleteFactor(self, table_name, factor_names):
        if not factor_names: return 0
        FactorIndex = self._TableFactorDict.get(table_name, pd.Series()).index.difference(factor_names).tolist()
        if not FactorIndex: return self.deleteTable(table_name)
        if self.DBType!="sqlite3":
            SQLStr = "ALTER TABLE "+self.TablePrefix+self.InnerPrefix+table_name
            for iFactorName in factor_names: SQLStr += " DROP COLUMN `"+iFactorName+"`,"
            self.execute(SQLStr[:-1])
        else:
            # 将表名改为临时表
            SQLStr = "ALTER TABLE %s RENAME TO %s"
            TempTableName = genAvailableName("TempTable", self.TableNames)
            self.execute(SQLStr % (self.TablePrefix+self.InnerPrefix+table_name, self.TablePrefix+self.InnerPrefix+TempTableName))
            # 创建新表
            FieldTypes = OrderedDict()
            for iFactorName in FactorIndex:
                FieldTypes[iFactorName] = ("text" if self._TableFactorDict[table_name].loc[iFactorName]=="string" else "real")
            self.createTable(table_name, field_types=FieldTypes)
            # 导入数据
            FactorNameStr = ", ".join(FactorIndex)
            SQLStr = "INSERT INTO %s (datetime, code, %s) SELECT datetime, code, %s FROM %s"
            Cursor = self.cursor(SQLStr % (self.TablePrefix+self.InnerPrefix+table_name, FactorNameStr, FactorNameStr, self.TablePrefix+self.InnerPrefix+TempTableName))
            self._Connection.commit()
            # 删除临时表
            Cursor.execute("DROP TABLE %s" % (self.TablePrefix+self.InnerPrefix+TempTableName, ))
            self._Connection.commit()
            Cursor.close()
        self._TableFactorDict[table_name] = self._TableFactorDict[table_name][FactorIndex]
        self._TableFieldDataType[table_name] = self._TableFieldDataType[table_name][FactorIndex]
        return 0
    def deleteData(self, table_name, ids=None, dts=None):
        DBTableName = self.TablePrefix+self.InnerPrefix+table_name
        if (self.DBType!="sqlite3") and (ids is None) and (dts is None):
            SQLStr = "TRUNCATE TABLE "+DBTableName
            return self.execute(SQLStr)
        SQLStr = "DELETE * FROM "+DBTableName
        if dts is not None:
            DTs = [iDT.strftime("%Y-%m-%d %H:%M:%S.%f") for iDT in dts]
            SQLStr += "WHERE "+genSQLInCondition(DBTableName+".datetime", DTs, is_str=True, max_num=1000)+" "
        else:
            SQLStr += "WHERE "+DBTableName+".datetime IS NOT NULL "
        if ids is not None:
            SQLStr += "AND "+genSQLInCondition(DBTableName+".code", ids, is_str=True, max_num=1000)
        return self.execute(SQLStr)
    def _adjustWriteData(self, data):
        NewData = []
        DataLen = data.applymap(lambda x: len(x) if isinstance(x, list) else 1).max(axis=1)
        for i in range(data.shape[0]):
            iDataLen = DataLen.iloc[i]
            iData = data.iloc[i].apply(lambda x: x * int(np.ceil(iDataLen / len(x))) if isinstance(x, list) else [x]*iDataLen).tolist()
            NewData.extend(zip(*iData))
        return NewData
    def writeData(self, data, table_name, if_exists="update", data_type={}, **kwargs):
        FieldTypes = {iFactorName:_identifyDataType(self.DBType, data.iloc[i].dtypes) for i, iFactorName in enumerate(data.items)}
        if table_name not in self._TableFactorDict:
            self.createTable(table_name, field_types=FieldTypes)
            self._TableFactorDict[table_name] = pd.Series({iFactorName: ("string" if FieldTypes[iFactorName].find("char")!=-1 else "double") for iFactorName in FieldTypes})
            self._TableFieldDataType[table_name] = pd.Series(FieldTypes)
            SQLStr = "INSERT INTO "+self.TablePrefix+self.InnerPrefix+table_name+" (`datetime`, `code`, "
        else:
            NewFactorNames = data.items.difference(self._TableFactorDict[table_name].index).tolist()
            if NewFactorNames:
                self.addField(table_name, {iFactorName:FieldTypes[iFactorName] for iFactorName in NewFactorNames})
                NewDataType = pd.Series({iFactorName: ("string" if FieldTypes[iFactorName].find("char")!=-1 else "double") for iFactorName in NewFactorNames})
                self._TableFactorDict[table_name] = self._TableFactorDict[table_name].append(NewDataType)
                self._TableFieldDataType[table_name] = self._TableFieldDataType[table_name].append(pd.Series(FieldTypes))
            AllFactorNames = self._TableFactorDict[table_name].index.tolist()
            if self.CheckWriteData:
                OldData = self.getTable(table_name, args={"因子值类型":"list", "时间转字符串":True}).readData(factor_names=AllFactorNames, ids=data.minor_axis.tolist(), dts=data.major_axis.tolist())
            else:
                OldData = self.getTable(table_name, args={"时间转字符串":True}).readData(factor_names=AllFactorNames, ids=data.minor_axis.tolist(), dts=data.major_axis.tolist())
            if if_exists=="append":
                for iFactorName in AllFactorNames:
                    if iFactorName in data:
                        data[iFactorName] = OldData[iFactorName].where(pd.notnull(OldData[iFactorName]), data[iFactorName])
                    else:
                        data[iFactorName] = OldData[iFactorName]
            elif if_exists=="update":
                for iFactorName in AllFactorNames:
                    if iFactorName in data:
                        data[iFactorName] = data[iFactorName].where(pd.notnull(data[iFactorName]), OldData[iFactorName])
                    else:
                        data[iFactorName] = OldData[iFactorName]
            SQLStr = "REPLACE INTO "+self.TablePrefix+self.InnerPrefix+table_name+" (`datetime`, `code`, "
        data.major_axis = [iDT.strftime("%Y-%m-%d %H:%M:%S.%f") for iDT in data.major_axis]
        NewData = {}
        for iFactorName in data.items:
            iData = data.loc[iFactorName].stack(dropna=False)
            NewData[iFactorName] = iData
            SQLStr += "`"+iFactorName+"`, "
        NewData = pd.DataFrame(NewData).loc[:, data.items]
        NewData = NewData[pd.notnull(NewData).any(axis=1)]
        if NewData.shape[0]==0: return 0
        NewData = NewData.astype("O").where(pd.notnull(NewData), None)
        if self._Connector in ("pyodbc", "sqlite3"):
            SQLStr = SQLStr[:-2] + ") VALUES (" + "?, " * (NewData.shape[1]+2)
        else:
            SQLStr = SQLStr[:-2] + ") VALUES (" + "%s, " * (NewData.shape[1]+2)
        SQLStr = SQLStr[:-2]+") "
        Cursor = self._Connection.cursor()
        if self.CheckWriteData:
            NewData = self._adjustWriteData(NewData.reset_index())
            Cursor.executemany(SQLStr, NewData)
        else:
            Cursor.executemany(SQLStr, NewData.reset_index().values.tolist())
        self._Connection.commit()
        Cursor.close()
        return 0
示例#24
0
class ReturnBasedStyleModel(BaseModule):
    """基于收益率回归的风格分析模型"""
    # TargetNAV = Enum(None, arg_type="SingleOption", label="目标净值", order=0)
    TargetIDs = ListStr(arg_type="StrList", label="目标ID", order=1)
    # StyleNAV = Enum(None, arg_type="SingleOption", label="风格净值", order=2)
    StyleIDs = ListStr(arg_type="StrList", label="风格ID", order=3)
    ReturnType = Enum("简单收益率",
                      "对数收益率",
                      "价格变化量",
                      arg_type="SingleOption",
                      label="收益率类型",
                      order=4)
    CalcDTs = List(dt.datetime, arg_type="DateList", label="计算时点", order=5)
    SummaryWindow = Float(240, arg_type="Integer", label="统计窗口", order=6)
    MinSummaryWindow = Int(20, arg_type="Integer", label="最小统计窗口", order=7)

    def __init__(self,
                 target_table,
                 style_table,
                 name="基于收益率回归的风格分析模型",
                 sys_args={},
                 **kwargs):
        self._TargetTable = target_table
        self._StyleTable = style_table
        return super().__init__(name=name,
                                sys_args=sys_args,
                                config_file=None,
                                **kwargs)

    def __QS_initArgs__(self):
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._TargetTable.getFactorMetaData(key="DataType")))
        self.add_trait(
            "TargetNAV",
            Enum(*DefaultNumFactorList,
                 arg_type="SingleOption",
                 label="目标净值",
                 order=0))
        DefaultNumFactorList, DefaultStrFactorList = getFactorList(
            dict(self._StyleTable.getFactorMetaData(key="DataType")))
        self.add_trait(
            "StyleNAV",
            Enum(*DefaultNumFactorList,
                 arg_type="SingleOption",
                 label="风格净值",
                 order=2))

    def __QS_start__(self, mdl, dts, **kwargs):
        if self._isStarted: return ()
        super().__QS_start__(mdl=mdl, dts=dts, **kwargs)
        self._Output = {}
        if not self.TargetIDs:
            self._Output["目标ID"] = self._TargetTable.getID()
        else:
            self._Output["目标ID"] = list(self.TargetIDs)
        nTargetID = len(self._Output["目标ID"])
        self._Output["目标净值"] = np.zeros(shape=(0, nTargetID))
        if not self.StyleIDs:
            self._Output["风格ID"] = self._StyleTable.getID()
        else:
            self._Output["风格ID"] = list(self.StyleIDs)
        nStyleID = len(self._Output["风格ID"])
        self._Output["风格指数净值"] = np.zeros((0, nStyleID))
        self._Output["滚动回归系数"] = {iID: [] for iID in self._Output["目标ID"]}
        self._Output["滚动回归R平方"] = []
        self._Output["时点"] = []
        self._CurCalcInd = 0
        return (self._StyleTable, self._TargetTable)

    def __QS_move__(self, idt, **kwargs):
        if self._iDT == idt: return 0
        self._iDT = idt
        TargetNAV = self._TargetTable.readData(
            dts=[idt], ids=self._Output["目标ID"],
            factor_names=[self.TargetNAV]).iloc[0, :, :].values
        self._Output["目标净值"] = np.r_[self._Output["目标净值"], TargetNAV]
        StyleNAV = self._StyleTable.readData(
            dts=[idt], ids=self._Output["风格ID"],
            factor_names=[self.StyleNAV]).iloc[0, :, :].values
        self._Output["风格指数净值"] = np.r_[self._Output["风格指数净值"], StyleNAV]
        if self.CalcDTs:
            if idt not in self.CalcDTs[self._CurCalcInd:]: return 0
            self._CurCalcInd = self.CalcDTs[self._CurCalcInd:].index(
                idt) + self._CurCalcInd
        else:
            self._CurCalcInd = self._Model.DateTimeIndex
        if self._Output["目标净值"].shape[0] - 1 < self.MinSummaryWindow: return 0
        StartInd = int(
            max(0, self._Output["目标净值"].shape[0] - 1 - self.SummaryWindow))
        X = _calcReturn(self._Output["风格指数净值"][StartInd:, :],
                        return_type=self.ReturnType)
        Y = _calcReturn(self._Output["目标净值"][StartInd:, :],
                        return_type=self.ReturnType)
        nTargetID, nStyleID = len(self._Output["目标ID"]), len(
            self._Output["风格ID"])
        Rsquared = np.full((nTargetID, ), np.nan)
        for i, iID in enumerate(self._Output["目标ID"]):
            iMask = ((np.sum(pd.isnull(X), axis=1) == 0) &
                     (pd.notnull(Y[:, i])))
            try:
                iBeta = regressByCVX(Y[:, i],
                                     X,
                                     weight=None,
                                     constraints={
                                         "Box": {
                                             "ub": np.ones((nStyleID, )),
                                             "lb": np.zeros((nStyleID, ))
                                         },
                                         "LinearEq": {
                                             "Aeq": np.ones((1, nStyleID)),
                                             "beq": 1
                                         }
                                     })
            except:
                iBeta = None
            if iBeta is None:
                self._Output["滚动回归系数"][iID].append(
                    np.full((nStyleID, ), np.nan))
            else:
                self._Output["滚动回归系数"][iID].append(iBeta)
                Rsquared[i] = 1 - np.nansum(
                    (Y[:, i][iMask] - np.dot(X[iMask], iBeta))**2) / np.nansum(
                        (Y[:, i][iMask] - np.nanmean(Y[:, i][iMask]))**2)
        self._Output["滚动回归R平方"].append(Rsquared)
        self._Output["时点"].append(idt)
        return 0

    def __QS_end__(self):
        if not self._isStarted: return 0
        super().__QS_end__()
        DTs, StyleIDs, TargetIDs = self._Output.pop("时点"), self._Output.pop(
            "风格ID"), self._Output.pop("目标ID")
        nTargetID, nStyleID = len(TargetIDs), len(StyleIDs)
        X = _calcReturn(self._Output["风格指数净值"], return_type=self.ReturnType)
        Y = _calcReturn(self._Output["目标净值"], return_type=self.ReturnType)
        self._Output["全样本回归系数"] = np.full(shape=(nStyleID, nTargetID),
                                          fill_value=np.nan)
        self._Output["全样本回归R平方"] = np.full(shape=(nTargetID, ),
                                           fill_value=np.nan)
        for i, iID in enumerate(TargetIDs):
            iMask = ((np.sum(pd.isnull(X), axis=1) == 0) &
                     (pd.notnull(Y[:, i])))
            try:
                iBeta = regressByCVX(Y[:, i],
                                     X,
                                     weight=None,
                                     constraints={
                                         "Box": {
                                             "ub": np.ones((nStyleID, )),
                                             "lb": np.zeros((nStyleID, ))
                                         },
                                         "LinearEq": {
                                             "Aeq": np.ones((1, nStyleID)),
                                             "beq": 1
                                         }
                                     })
            except:
                iBeta = None
            if iBeta is not None:
                self._Output["全样本回归系数"][:, i] = iBeta
                self._Output["全样本回归R平方"][i] = 1 - np.nansum(
                    (Y[:, i][iMask] - np.dot(X[iMask], iBeta))**2) / np.nansum(
                        (Y[:, i][iMask] - np.nanmean(Y[:, i][iMask]))**2)
            self._Output["滚动回归系数"][iID] = pd.DataFrame(
                self._Output["滚动回归系数"][iID], index=DTs, columns=self.StyleIDs)
        self._Output["全样本回归系数"] = pd.DataFrame(self._Output["全样本回归系数"],
                                               index=StyleIDs,
                                               columns=TargetIDs)
        self._Output["全样本回归R平方"] = pd.DataFrame(self._Output["全样本回归R平方"],
                                                index=TargetIDs,
                                                columns=["全样本回归R平方"])
        self._Output["滚动回归R平方"] = pd.DataFrame(self._Output["滚动回归R平方"],
                                               index=DTs,
                                               columns=TargetIDs)
        self._Output["目标净值"] = pd.DataFrame(self._Output["目标净值"],
                                            index=self._Model.DateTimeSeries,
                                            columns=self.TargetIDs)
        self._Output["风格指数净值"] = pd.DataFrame(self._Output["风格指数净值"],
                                              index=self._Model.DateTimeSeries,
                                              columns=self.StyleIDs)
        return 0
示例#25
0
class SimpleAccount(Account):
    """简单证券账户"""
    Delay = Bool(True, arg_type="Bool", label="交易延迟", order=2)
    TargetIDs = ListStr(arg_type="IDList", label="目标ID", order=3)
    BuyLimit = Instance(_TradeLimit,
                        allow_none=False,
                        arg_type="ArgObject",
                        label="买入限制",
                        order=4)
    SellLimit = Instance(_TradeLimit,
                         allow_none=False,
                         arg_type="ArgObject",
                         label="卖出限制",
                         order=5)
    MarketInfo = Instance(_MarketInfo,
                          allow_none=False,
                          arg_type="ArgObject",
                          label="行情信息",
                          order=6)

    def __init__(self,
                 market_ft,
                 name="简单证券账户",
                 sys_args={},
                 config_file=None,
                 **kwargs):
        # 继承自 Account 的属性
        #self._Cash = None# 剩余现金, >=0,  array(shape=(nDT+1,))
        #self._FrozenCash = 0# 当前被冻结的现金, >=0, float
        #self._Debt = None# 负债, >=0, array(shape=(nDT+1,))
        #self._CashRecord = None# 现金流记录, 现金流入为正, 现金流出为负, DataFrame(columns=["时间点", "现金流", "备注"])
        #self._DebtRecord = None# 融资记录, 增加负债为正, 减少负债为负, DataFrame(columns=["时间点", "融资", "备注"])
        #self._TradingRecord = None# 交易记录, DataFrame(columns=["时间点", "ID", "买卖数量", "价格", "交易费", "现金收支", "类型"])
        self._IDs = []  # 本账户支持交易的证券 ID, []
        self._PositionNum = None  # 持仓数量, DataFrame(index=[时间点]+1, columns=self._IDs)
        self._PositionAmount = None  # 持仓金额, DataFrame(index=[时间点]+1, columns=self._IDs)
        self._Orders = None  # 当前接收到的订单, DataFrame(columns=["ID", "数量", "目标价"])
        self._OpenPosition = None  # 当前未平仓的持仓信息, DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "浮动盈亏"])
        self._ClosedPosition = None  # 已平仓的交易信息, DataFrame(columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "平仓时点", "平仓价格", "平仓交易费", "平仓盈亏"])
        self._LastPrice = None  # 最新价, Series(index=self._IDs)
        self._TradePrice = None  # 成交价, Series(index=self._IDs)
        self._SellVolLimit = None  # 卖出成交量限制, Series(index=self._IDs)
        self._BuyVolLimit = None  # 买入成交量限制, Series(index=self._IDs)
        self._MarketFT = market_ft  # 提供净值或者收益率数据的因子表
        return super().__init__(name=name,
                                sys_args=sys_args,
                                config_file=config_file,
                                **kwargs)

    def __QS_initArgs__(self):
        super().__QS_initArgs__()
        self.BuyLimit = _TradeLimit(direction="Buy")
        self.SellLimit = _TradeLimit(direction="Sell")
        self.MarketInfo = _MarketInfo(ft=self._MarketFT)

    def __QS_start__(self, mdl, dts, **kwargs):
        Rslt = super().__QS_start__(mdl=mdl, dts=dts, **kwargs)
        self._IDs = list(self.TargetIDs)
        if not self._IDs:
            self._IDs = self._MarketFT.getID(ifactor_name=self.MarketInfo.Last)
        nDT, nID = len(dts), len(self._IDs)
        #self._Cash = np.zeros(nDT+1)
        #self._FrozenCash = 0
        #self._Debt = np.zeros(nDT+1)
        self._PositionNum = pd.DataFrame(np.zeros((nDT + 1, nID)),
                                         index=[dts[0] - dt.timedelta(1)] +
                                         dts,
                                         columns=self._IDs)
        self._PositionAmount = self._PositionNum.copy()
        self._Orders = pd.DataFrame(columns=["ID", "数量", "目标价"])
        self._LastPrice = self._TradePrice = None
        self._OpenPosition = pd.DataFrame(
            columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "浮动盈亏"])
        self._ClosedPosition = pd.DataFrame(columns=[
            "ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "平仓时点", "平仓价格", "平仓交易费",
            "平仓盈亏"
        ])
        self._iTradingRecord = []  # 暂存的交易记录
        self._SellVolLimit, self._BuyVolLimit = pd.Series(
            np.inf, index=self._IDs), pd.Series(np.inf, index=self._IDs)
        self._nDT = nDT
        return Rslt + (self._MarketFT, )

    def __QS_move__(self, idt, **kwargs):
        super().__QS_move__(idt, **kwargs)
        # 更新当前的账户信息
        iIndex = self._Model.DateTimeIndex
        self._LastPrice = self._MarketFT.readData(factor_names=[
            self.MarketInfo.Last
        ],
                                                  ids=self._IDs,
                                                  dts=[idt]).iloc[0, 0]
        self._TradePrice = self._MarketFT.readData(
            factor_names=[self.MarketInfo.TradePrice],
            ids=self._IDs,
            dts=[idt]).iloc[0, 0]
        self._PositionNum.iloc[iIndex +
                               1] = self._PositionNum.iloc[iIndex]  # 初始化持仓
        if self.Delay:  # 撮合成交
            TradingRecord = self._matchOrder(idt)
            TradingRecord = pd.DataFrame(
                TradingRecord,
                index=np.arange(
                    self._TradingRecord.shape[0],
                    self._TradingRecord.shape[0] + len(TradingRecord)),
                columns=self._TradingRecord.columns)
            self._TradingRecord = self._TradingRecord.append(TradingRecord)
        else:
            TradingRecord = self._iTradingRecord
        self._QS_updatePosition()
        return TradingRecord

    def __QS_after_move__(self, idt, **kwargs):
        super().__QS_after_move__(idt, **kwargs)
        if not self.Delay:  # 撮合成交
            TradingRecord = self._matchOrder(idt)
            TradingRecord = pd.DataFrame(
                TradingRecord,
                index=np.arange(
                    self._TradingRecord.shape[0],
                    self._TradingRecord.shape[0] + len(TradingRecord)),
                columns=self._TradingRecord.columns)
            self._TradingRecord = self._TradingRecord.append(TradingRecord)
            self._iTradingRecord = TradingRecord
            self._QS_updatePosition()
        return 0

    def __QS_end__(self):
        super().__QS_end__()
        self._Output["持仓数量"] = self.getPositionNumSeries()
        self._Output["持仓金额"] = self.getPositionAmountSeries()
        self._Output["平仓记录"] = self._ClosedPosition
        self._Output["未平仓持仓"] = self._OpenPosition
        return 0

    # 当前账户价值
    @property
    def AccountValue(self):
        return super().AccountValue + np.nansum(
            self._PositionAmount.iloc[self._Model.DateTimeIndex + 1])

    # 当前账户的持仓数量
    @property
    def PositionNum(self):
        return self._PositionNum.iloc[self._Model.DateTimeIndex + 1]

    # 当前账户的持仓金额
    @property
    def PositionAmount(self):
        return self._PositionAmount.iloc[self._Model.DateTimeIndex + 1]

    # 本账户支持交易的证券 ID
    @property
    def IDs(self):
        return self._IDs

    # 当前最新价
    @property
    def LastPrice(self):
        return self._LastPrice

    # 当前账户中还未成交的订单, DataFrame(index=[int], columns=["ID", "数量", "目标价"])
    @property
    def Orders(self):
        return self._Orders

    # 获取持仓的历史序列, 以时间点为索引, 返回: pd.DataFrame(持仓, index=[时间点], columns=[ID])
    def getPositionNumSeries(self, dts=None, start_dt=None, end_dt=None):
        Data = self._PositionNum.iloc[1:self._Model.DateTimeIndex + 2]
        return cutDateTime(Data, dts=dts, start_dt=start_dt, end_dt=end_dt)

    # 获取持仓证券的金额历史序列, 以时间点为索引, 返回: pd.DataFrame(持仓金额, index=[时间点], columns=[ID])
    def getPositionAmountSeries(self, dts=None, start_dt=None, end_dt=None):
        Data = self._PositionAmount[1:self._Model.DateTimeIndex + 2]
        return cutDateTime(Data, dts=dts, start_dt=start_dt, end_dt=end_dt)

    # 获取账户价值的历史序列, 以时间点为索引
    def getAccountValueSeries(self, dts=None, start_dt=None, end_dt=None):
        CashSeries = self.getCashSeries(dts=dts,
                                        start_dt=start_dt,
                                        end_dt=end_dt)
        PositionAmountSeries = self.getPositionAmountSeries(
            dts=dts, start_dt=start_dt, end_dt=end_dt).sum(axis=1)
        DebtSeries = self.getDebtSeries(dts=dts,
                                        start_dt=start_dt,
                                        end_dt=end_dt)
        return CashSeries + PositionAmountSeries - DebtSeries

    # 执行给定数量的证券委托单, target_id: 目标证券 ID, num: 待买卖的数量, target_price: nan 表示市价单, 返回: (订单ID,
    # combined_order: 组合订单, DataFrame(index=[ID], columns=[数量, 目标价])
    # 基本的下单函数, 必须实现
    def order(self,
              target_id=None,
              num=0,
              target_price=np.nan,
              combined_order=None):
        if target_id is not None:
            self._Orders.loc[self._Orders.shape[0]] = (target_id, num,
                                                       target_price)
            return (self._Orders.shape[0], target_id, num, target_price)
        if combined_order is not None:
            combined_order.index.name = "ID"
            combined_order = combined_order.reset_index()
            combined_order.index = np.arange(
                self._Orders.shape[0],
                self._Orders.shape[0] + combined_order.shape[0])
            self._Orders = self._Orders.append(combined_order)
        return combined_order

    # 撤销订单, order_ids 是订单在 self.Orders 中的 index
    def cancelOrder(self, order_ids):
        self._Orders = self._Orders.loc[self._Orders.index.difference(
            set(order_ids))]
        self._Orders.sort_index(axis=0, inplace=True)
        self._Orders.index = np.arange(self._Orders.shape[0])
        return 0

    # 更新仓位信息
    def _QS_updatePosition(self):
        iIndex = self._Model.DateTimeIndex
        iNum = self._OpenPosition.groupby(by=["ID"])["数量"].sum()
        iPosition = pd.Series(0, index=self._IDs)
        iPosition[iNum.index] += iNum
        self._PositionNum.iloc[iIndex + 1] = iPosition.values
        LastPrice = self._LastPrice[self._OpenPosition["ID"].tolist()].values
        self._OpenPosition["浮动盈亏"] = self._OpenPosition["数量"] * (
            LastPrice - self._OpenPosition["开仓价格"])
        iPositionAmount = pd.Series(
            self._OpenPosition["数量"].values * LastPrice,
            index=self._OpenPosition["ID"].values).groupby(axis=0,
                                                           level=0).sum()
        self._PositionAmount.iloc[iIndex + 1] = 0.0
        self._PositionAmount.iloc[iIndex +
                                  1][iPositionAmount.index] = iPositionAmount
        return 0

    # 更新交易限制条件
    def _updateTradeLimit(self, idt):
        self._SellVolLimit[:] = self._BuyVolLimit[:] = np.inf
        if self.SellLimit.LimitIDFilter:  # 满足卖出禁止条件的不能卖出
            Mask = self._MarketFT.getIDMask(
                idt, ids=self._IDs, id_filter_str=self.SellLimit.LimitIDFilter)
            self._SellVolLimit[Mask] = 0.0
        if self.BuyLimit.LimitIDFilter:  # 满足买入禁止条件的不能买入
            Mask = self._MarketFT.getIDMask(
                idt, ids=self._IDs, id_filter_str=self.BuyLimit.LimitIDFilter)
            self._BuyVolLimit[Mask] = 0.0
        if self.MarketInfo.Amt is not None:  # 指定了成交额, 成交额满足限制要求
            Amount = self._MarketFT.readData(factor_names=[
                self.MarketInfo.Amt
            ],
                                             ids=self._IDs,
                                             dts=[idt]).iloc[0, 0, :]
            self._SellVolLimit = self._SellVolLimit.clip_upper(
                Amount * self.SellLimit.AmtLimitRatio / self._TradePrice)
            self._BuyVolLimit = self._BuyVolLimit.clip_upper(
                Amount * self.BuyLimit.AmtLimitRatio / self._TradePrice)
        Mask = (pd.isnull(self._TradePrice) | (self._TradePrice <= 0))
        self._SellVolLimit[Mask] = self._BuyVolLimit[Mask] = 0.0  # 成交价缺失的不能交易
        if not self.SellLimit.ShortAllowed:
            PositionNum = self._PositionNum.iloc[self._Model.DateTimeIndex + 1]
            self._SellVolLimit = self._SellVolLimit.clip_upper(
                PositionNum.clip_lower(0.0))
        return 0

    # 撮合成交订单
    def _matchOrder(self, idt):
        if self._Orders.shape[0] == 0: return []
        self._updateTradeLimit(idt)
        MarketOrderMask = pd.isnull(self._Orders["目标价"])
        MarketOrders = self._Orders[MarketOrderMask]
        MarketOrders = MarketOrders.groupby(
            by=["ID"]).sum()["数量"]  # Series(数量, index=[ID])
        # 先执行平仓交易
        TradingRecord, MarketOpenOrders = self._matchMarketCloseOrder(
            idt, MarketOrders)
        LimitOrders = self._Orders[~MarketOrderMask]
        #if LimitOrders.shape[0]>0:
        #LimitOrders = LimitOrders.groupby(by=["ID", "目标价"]).sum()
        #LimitOrders = LimitOrders.reset_index(level=1)
        #iTradingRecord, LimitOrders = self._matchLimitCloseOrder(idt, LimitOrders)
        #TradingRecord.extend(iTradingRecord)
        # 再执行开仓交易
        TradingRecord.extend(self._matchMarketOpenOrder(idt, MarketOpenOrders))
        #if LimitOrders.shape[0]>0:
        #iTradingRecord, LimitOrders = self._matchLimitOpenOrder(idt, LimitOrders)
        #TradingRecord.extend(iTradingRecord)
        #LimitOrders = LimitOrders.reset_index()
        self._Orders = LimitOrders
        self._Orders.index = np.arange(self._Orders.shape[0])
        return TradingRecord

    # 撮合成交市价平仓单
    # 以成交价完成成交, 满足交易限制要求
    # 未成交的市价单自动撤销
    def _matchMarketCloseOrder(self, idt, orders):
        IDs = orders.index.tolist()
        orders = orders.clip(upper=self._BuyVolLimit.loc[IDs],
                             lower=-self._SellVolLimit.loc[IDs])  # 过滤限制条件
        orders = orders[orders != 0]
        if orders.shape[0] == 0: return ([], pd.Series())
        # 分离平仓单和开仓单
        PositionNum = self.PositionNum[orders.index]
        CloseOrders = orders.clip(lower=(-PositionNum).clip_upper(0),
                                  upper=(-PositionNum).clip_lower(0))  # 平仓单
        OpenOrders = orders - CloseOrders  # 开仓单
        CloseOrders = CloseOrders[CloseOrders != 0]
        if CloseOrders.shape[0] == 0: return ([], OpenOrders)
        # 处理平仓单
        TradePrice = self._TradePrice[CloseOrders.index]
        TradeAmounts = CloseOrders * TradePrice
        Fees = TradeAmounts.clip_lower(
            0) * self.BuyLimit.TradeFee + TradeAmounts.clip_upper(
                0).abs() * self.SellLimit.TradeFee
        OpenPosition = self._OpenPosition.set_index(["ID"])
        ClosedPosition = pd.DataFrame(columns=[
            "ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "平仓时点", "平仓价格", "平仓交易费",
            "平仓盈亏"
        ])
        RemainderPosition = pd.DataFrame(
            columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "浮动盈亏"])
        CashChanged = np.zeros(CloseOrders.shape[0])
        for i, iID in enumerate(CloseOrders.index):
            iNum = CloseOrders.iloc[i]
            iOpenPosition = OpenPosition.loc[[iID]]
            if iNum < 0:
                iClosedNum = (iOpenPosition["数量"] -
                              (iOpenPosition["数量"].cumsum() +
                               iNum).clip_lower(0)).clip_lower(0)
            else:
                iClosedNum = (iOpenPosition["数量"] -
                              (iOpenPosition["数量"].cumsum() +
                               iNum).clip_upper(0)).clip_upper(0)
            iPNL = iClosedNum * (TradePrice[iID] - iOpenPosition["开仓价格"])
            CashChanged[i] = (np.abs(iClosedNum) * iOpenPosition["开仓价格"] +
                              iPNL).sum() - Fees[iID]
            iClosedPosition = iOpenPosition.copy()
            iClosedPosition["数量"] = iClosedNum
            iClosedPosition["平仓时点"] = idt
            iClosedPosition["平仓价格"] = TradePrice[iID]
            iClosedPosition["平仓交易费"] = Fees[iID]
            iClosedPosition["平仓盈亏"] = iPNL
            iOpenPosition["数量"] -= iClosedNum
            iClosedPosition = iClosedPosition[iClosedPosition["数量"] != 0]
            iClosedPosition.pop("浮动盈亏")
            ClosedPosition = ClosedPosition.append(
                iClosedPosition.reset_index())
            self._SellVolLimit.loc[iID] -= np.clip(iClosedNum.sum(), 0,
                                                   np.inf)  # 调整卖出限制条件
            self._BuyVolLimit.loc[iID] += np.clip(iClosedNum.sum(), -np.inf,
                                                  0)  # 调整买入限制条件
            RemainderPosition = RemainderPosition.append(
                iOpenPosition[iOpenPosition["数量"] != 0].reset_index())
        ClosedPosition.index = np.arange(
            self._ClosedPosition.shape[0],
            self._ClosedPosition.shape[0] + ClosedPosition.shape[0])
        self._ClosedPosition = self._ClosedPosition.append(ClosedPosition)
        OpenPosition = OpenPosition.loc[OpenPosition.index.difference(
            CloseOrders.index)].reset_index()
        RemainderPosition.index = np.arange(
            OpenPosition.shape[0],
            OpenPosition.shape[0] + RemainderPosition.shape[0])
        self._OpenPosition = OpenPosition.append(RemainderPosition)
        if self.SellLimit.ShortAllowed:
            self._OpenPosition = self._OpenPosition[
                self._OpenPosition["数量"].abs() > _QS_MinPositionNum]
        else:
            self._OpenPosition = self._OpenPosition[
                self._OpenPosition["数量"] > _QS_MinPositionNum]
        TradingRecord = list(
            zip([idt] * CloseOrders.shape[0], CloseOrders.index, CloseOrders,
                TradePrice, Fees, CashChanged,
                ["close"] * CloseOrders.shape[0]))
        if TradingRecord: self._QS_updateCashDebt(CashChanged.sum())
        return (TradingRecord, OpenOrders)

    # 撮合成交市价开仓单
    # 以成交价完成成交, 满足交易限制要求
    # 未成交的市价单自动撤销
    def _matchMarketOpenOrder(self, idt, orders):
        orders = orders[orders != 0]
        if orders.shape[0] == 0: return []
        TradePrice = self._TradePrice[orders.index]
        TradeAmounts = orders * TradePrice
        FeesAcquired = TradeAmounts.clip_lower(
            0) * self.BuyLimit.TradeFee + TradeAmounts.clip_upper(
                0).abs() * self.SellLimit.TradeFee
        CashAcquired = TradeAmounts.abs() + FeesAcquired
        CashAllocated = min(
            CashAcquired.sum(),
            self.AvailableCash) * CashAcquired / CashAcquired.sum()
        orders = CashAllocated / TradePrice / (1 + self.BuyLimit.TradeFee *
                                               (orders > 0) +
                                               self.SellLimit.TradeFee *
                                               (orders < 0)) * np.sign(orders)
        orders = orders[pd.notnull(orders) & (orders != 0)]
        if orders.shape[0] == 0: return []
        TradePrice = TradePrice[orders.index]
        TradeAmounts = orders * TradePrice
        Fees = TradeAmounts.clip_lower(
            0) * self.BuyLimit.TradeFee + TradeAmounts.clip_upper(
                0).abs() * self.SellLimit.TradeFee
        CashChanged = -TradeAmounts.abs() - Fees
        TradingRecord = list(
            zip([idt] * orders.shape[0], orders.index, orders, TradePrice,
                Fees, CashChanged, ["open"] * orders.shape[0]))
        NewPosition = pd.DataFrame(
            columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "浮动盈亏"])
        NewPosition["ID"] = orders.index
        NewPosition["数量"] = orders.values
        NewPosition["开仓时点"] = idt
        NewPosition["开仓价格"] = TradePrice.values
        NewPosition["开仓交易费"] = Fees.values
        NewPosition["浮动盈亏"] = 0.0
        NewPosition.index = np.arange(
            self._OpenPosition.shape[0],
            self._OpenPosition.shape[0] + NewPosition.shape[0])
        self._OpenPosition = self._OpenPosition.append(NewPosition)
        self._QS_updateCashDebt(CashChanged.sum())
        return TradingRecord

    # 撮合成交限价平仓单, TODO
    # 限价单的报价如果优于当前时段的成交价则可成交, 否则继续挂单
    def _matchLimitCloseOrder(self, idt, orders):
        if orders.shape[0] == 0: return ([], orders)
        IDs = orders.index.unique().tolist()
        PositionNum = self.PositionNum
        RemainderOrders = pd.DataFrame(columns=["目标价", "数量"])
        OpenPosition = self._OpenPosition.set_index(["ID"])
        CashChanged = zeros(len(IDs))
        ClosedPosition = pd.DataFrame(columns=[
            "ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "平仓时点", "平仓价格", "平仓交易费",
            "平仓盈亏"
        ])
        NewPosition = pd.DataFrame(
            columns=["ID", "数量", "开仓时点", "开仓价格", "开仓交易费", "浮动盈亏"])
        for i, iID in enumerate(IDs):
            iOpenPosition = OpenPosition.loc[[iID]]
            iPositionNum = iOpenPosition["数量"].sum()
            iOrders = orders.loc[[iID]]
            iTradePrice = self._TradePrice.loc[iID]
            if (iPositionNum == 0) or pd.isnull(iTradePrice):  # 当前无仓位
                RemainderOrders = RemainderOrders.append(iOrders)
                continue
            iSellOrders = iOrders[iOrders["数量"] < 0].sort_values(
                by=["目标价"], ascending=True)
            iBuyOrders = iOrders[iOrders["数量"] > 0].sort_values(
                by=["目标价"], ascending=False)
            iClosedPosition = iOpenPosition.copy()
            if (iPositionNum > 0) and (iSellOrders.shape[0] >
                                       0):  # 当前多头仓位, 卖出订单为平仓单
                iClosedNum = min(
                    iSellOrders["数量"][
                        iSellOrders["目标价"] <= iTradePrice].abs().sum(),
                    self._SellVolLimit.loc[iID], iPositionNum)

                iRemainder = (iSellOrders["数量"].cumsum() +
                              iClosedNum).clip_upper(0.0)
                iRemainder.iloc[1:] = iRemainder.diff().iloc[1:]
                iSellOrders["数量"] = iRemainder.values
                RemainderOrders = RemainderOrders.append(
                    iSellOrders[iSellOrders["数量"] != 0])
                self._SellVolLimit.loc[iID] -= iClosedNum  # 调整卖出限制条件
                iClosedNum = (iOpenPosition["数量"] -
                              (iOpenPosition["数量"].cumsum() -
                               iClosedNum).clip_lower(0)).clip_lower(0)
                iFee = iClosedNum * iTradePrice * self.SellLimit.TradeFee
            elif (iPositionNum < 0) and (iBuyOrders.shape[0] > 0):
                iClosedNum = min(
                    iBuyOrders["数量"][iBuyOrders["目标价"] >= iTradePrice].sum(),
                    self._BuyVolLimit.loc[iID], -iPositionNum)
                iRemainder = (iBuyOrders["数量"].cumsum() -
                              iClosedNum).clip_lower(0.0)
                iRemainder.iloc[1:] = iRemainder.diff().iloc[1:]
                iBuyOrders["数量"] = iRemainder.values
                RemainderOrders = RemainderOrders.append(
                    iBuyOrders[iBuyOrders["数量"] != 0])
                self._BuyVolLimit.loc[iID] -= iClosedNum  # 调整卖出限制条件
                iClosedNum = (iOpenPosition["数量"] -
                              (iOpenPosition["数量"].cumsum() +
                               iClosedNum).clip_upper(0)).clip_upper(0)
                iFee = iClosedNum.abs() * iTradePrice * self.BuyLimit.TradeFee
            iPNL = iClosedNum * (iTradePrice - iOpenPosition["开仓价格"])
            CashChanged[i] = (np.abs(iClosedNum) * iOpenPosition["开仓价格"] +
                              iPNL).sum() - iFee
            iClosedPosition["数量"] = iClosedNum
            iClosedPosition["平仓时点"] = idt
            iClosedPosition["平仓价格"] = iTradePrice
            iClosedPosition["平仓交易费"] = iFee
            iClosedPosition["平仓盈亏"] = iPNL
            iOpenPosition["数量"] -= iClosedNum
            iClosedPosition = iClosedPosition[iClosedPosition["数量"] != 0]
            iClosedPosition.pop("浮动盈亏")
            ClosedPosition = ClosedPosition.append(
                iClosedPosition.reset_index())
            NewPosition = NewPosition.append(
                iOpenPosition[iOpenPosition["数量"] != 0].reset_index())
        ClosedPosition.index = np.arange(
            self._ClosedPosition.shape[0],
            self._ClosedPosition.shape[0] + ClosedPosition.shape[0])
        self._ClosedPosition = self._ClosedPosition.append(ClosedPosition)
        OpenPosition = OpenPosition.loc[OpenPosition.index.difference(
            set(IDs))].reset_index()
        NewPosition.index = np.arange(
            OpenPosition.shape[0],
            OpenPosition.shape[0] + NewPosition.shape[0])
        self._OpenPosition = OpenPosition.append(NewPosition)
        if self.SellLimit.ShortAllowed:
            self._OpenPosition = self._OpenPosition[
                self._OpenPosition["数量"].abs() > _QS_MinPositionNum]
        else:
            self._OpenPosition = self._OpenPosition[
                self._OpenPosition["数量"] > _QS_MinPositionNum]
        TradingRecord = list(
            zip([idt] * CloseOrders.shape[0], CloseOrders.index, CloseOrders,
                TradePrice, Fees, CashChanged,
                ["close"] * CloseOrders.shape[0]))
        if TradingRecord: self._QS_updateCashDebt(CashChanged.sum())
        return (TradingRecord, RemainderOrders)

    # 撮合成交限价开仓单, TODO
    # 限价单的报价如果优于当前时段的成交价则可成交, 否则继续挂单
    def _matchLimitOpenOrder(self, idt, orders):
        return ([], pd.DataFrame(columns=["ID", "数量", "目标价"]))