利用儲存的成交訂單,生成實盤交易分析報表

張國平發表於2022-10-19

利用之前文章寫的儲存成交order,借鑑vnpy已有的回測分析報表,生成一個實盤交易分析報表。

截圖如下。

這裡有個統計按鈕,點選輸出統計介面

因為我的程式碼改動不少,直接拿來用估計不合適,只做參考。


首先要自行修改database的程式碼,新增按照strategy名稱,和時間段讀取儲存到資料的order,按照時許正向返回。


讀取出來是一個order的佇列,下面程式碼可以放在cta_engine.py, 是把開倉價和開倉時間作為屬性附加到平倉單,輸出平倉單佇列。對於沒有成對的,或者跨期的品種配對要拋棄;這裡在做移倉操作時候,要針對策略,生成虛擬的平倉和開新倉的訂單,具體實現程式碼後面再移倉程式碼中說。

def convert_strategy_triggered_order(self,strategy_name,open_date = datetime(2001,10,10), close_date = datetime(2100,10,10)):
    result = database_manager.load_triggered_stop_order_data(strategy_name,open_date,close_date)
    if result:
        matchedOrderList = []
        last_order = None
        for order in result:
            if order.offset == Offset.OPEN:
                last_order = copy(order)
            elif last_order != None and order.offset == Offset.CLOSE:
                if last_order.vt_symbol != order.vt_symbol:
                    last_order = None
                    continue
                order.open_datetime= last_order.datetime
                order.open_price = last_order.average_price
                matchedOrderList.append(copy(order))
                last_order = None
        return matchedOrderList


然後下面程式碼是處理的平倉單佇列進行分析,計算出各種指標,輸出為DataFrame格式,這裡包括收益金額,收益率,單位收益率,回撤,賬面價值等。

def get_strategy_triggered_order(self,strategy_name):
    # result = database_manager.load_close_triggered_stop_order_data(strategy_name)
    result = self.convert_strategy_triggered_order(strategy_name)
    objectDF = DataFrame(data=None,columns=["開倉日期","平倉日期", "開倉方向", "開倉價", "手數", "平倉價", "收益"],dtype=object)
    if result:
        parameters = self.get_strategy_parameters(strategy_name)
        # 策略中定義的合約價值
        HeYueJiaZhi = parameters["HeYueJiaZhi"] 
        # 策略中定義的品種每筆數量
        HeYueChengShu = parameters["HeYueChengShu"]
        for close_data in result:
            if close_data.direction == Direction.LONG:
                close_data.direction = Direction.SHORT
            else:
                close_data.direction = Direction.LONG
            objectDF.loc[len(objectDF) + 1] = [close_data.vt_orderids[0].replace(tzinfo=None),close_data.datetime.replace(tzinfo=None), close_data.direction, close_data.open_price,close_data.volume, close_data.average_price,0.0]
        objectDF["收益"] = objectDF.apply(lambda x: x['開倉價'] - x['平倉價'] if x['開倉方向'] == Direction.SHORT else x['平倉價'] - x['開倉價'],
                                    axis=1)
        objectDF["UnitReturn"] = objectDF["收益"] * 100 / objectDF['開倉價']
        objectDF["收益"] = objectDF["收益"]*HeYueChengShu*objectDF["手數"]
        objectDF["balance"] = objectDF["收益"].cumsum() + HeYueJiaZhi
        objectDF["return"] = objectDF["收益"]*100/HeYueJiaZhi
        objectDF.loc[0] = copy(objectDF.iloc[0])
        objectDF = objectDF.sort_index()
        objectDF.loc[0,"balance"] = HeYueJiaZhi
        objectDF["highlevel"] = (
            objectDF["balance"].rolling(
                min_periods=1, window=len(objectDF), center=False).max()
        )
        objectDF.drop(index=0, inplace=True)
        objectDF["drawdown"] = objectDF["balance"] - objectDF["highlevel"]
        objectDF["ddpercent"] = objectDF["drawdown"] / objectDF["highlevel"] * 100
    return objectDF


下面程式碼是分析dataframe,計算指標資料

    def calculate_statistics(self, objectDF):
        """
        """
        data = {}
        # end_balance = df["balance"].iloc[-1]
        # max_drawdown = df["drawdown"].min()
        # max_ddpercent = df["ddpercent"].min()
        HeYueJiaZhi = self._data["parameters"]["HeYueJiaZhi"]
        data["capital"]  = HeYueJiaZhi
        data["total_net_pnl"] = objectDF["收益"].sum()
        data["end_balance"] = objectDF["balance"].iloc[-1]
        data["total_return"] = data["total_net_pnl"]*100/max(HeYueJiaZhi,1)
        data["max_drawdown"] = objectDF["drawdown"].min()
        data["max_ddpercent"] = objectDF["ddpercent"].min()
        data["total_trade_count"] = len(objectDF)
        data["winningResult"] = len(objectDF[objectDF["收益"] >0])
        data["losingResult"] = len(objectDF[objectDF["收益"] <0])
        data["winningRate"] = data["winningResult"] *100/ data["total_trade_count"]
        data["totalWinning"] = objectDF[objectDF["收益"] >0]["收益"].sum()
        data["totalLosing"] = objectDF[objectDF["收益"] <0]["收益"].sum()
        data["averageWinning"] = data["totalWinning"]/max(1,data["winningResult"])
        data["averageLosing"] = data["totalLosing"]/max(1,data["losingResult"])
        data["perprofitLoss"] = data["total_net_pnl"] / data["total_trade_count"]
        data["profitLossRatio"] = data["averageWinning"] / max(1,abs(data["averageLosing"]))
        return data

然後在cta_widget.py 中,加入顯示程式碼,這裡直接使用回撤模組的BacktesterChart

    def analyze_strategy(self):
        objectDF = self.cta_engine.get_strategy_triggered_order(self.strategy_name)
        if not objectDF.empty:
            triggerd_statistics_monitor = Triggered_OrderStatisticsMonitor()
            triggerd_statistics_monitor.set_data(self.calculate_statistics(objectDF))
            triggerd_statistics_monitor.setMinimumHeight(400)
            triggerd_view = TriggeredMonitor(self.cta_manager.main_engine, self.cta_manager.main_engine.event_engine)
            triggerd_view.set_df(objectDF)
            triggerd_view.setMinimumHeight(400)
            triggerd_view.setMinimumWidth(600)
            objectDF["net_pnl"] = objectDF["收益"]
            objectDF= objectDF.set_index("平倉日期")
            chart = BacktesterChart()
            chart.set_data(objectDF)
            analyz_dialog = QDialog()
            analyz_dialog.setWindowTitle(self.strategy_name)
            analyz_dialog.setWindowModality(Qt.NonModal)
            analyz_dialog.setWindowFlags(Qt.Dialog | Qt.WindowMinMaxButtonsHint | Qt.WindowCloseButtonHint)
            gbox = QtWidgets.QGridLayout()
            analyz_dialog.resize(1200, 800)
            gbox.addWidget(triggerd_statistics_monitor,0,0)
            gbox.addWidget(triggerd_view,1,0)
            gbox.addWidget(chart,0,1,2,1)
            analyz_dialog.setLayout(gbox)
            analyz_dialog.exec_()
class PercentCell(BaseCell):
    def __init__(self, content: Any, data: Any):
        super(PercentCell, self).__init__(content, data)
    def set_content(self, content: Any, data: Any) -> None:
        self.setText(f"{content:,.2f}%")
        self._data = data
class fullDatetimeCell(BaseCell):
    def __init__(self, content: Any, data: Any):
        super(fullDatetimeCell, self).__init__(content, data)
    def set_content(self, content: Any, data: Any) -> None:
        if content is None:
            return
        timestamp = content.strftime("%Y%m%d %H:%M:%S")
        self.setText(timestamp)
        self._data = data
class TriggeredMonitor(BaseMonitor):
    event_type = ""
    data_key = ""
    sorting = False
    # ["平倉日期", "方向", "開倉價", "手數", "平倉價", "收益"]
    headers = {
        "開倉日期": {"display": "開倉日期", "cell": fullDatetimeCell, "update": False},
        "平倉日期": {"display": "平倉日期", "cell": fullDatetimeCell, "update": False},
        "開倉方向": {"display": "開倉方向", "cell": DirectionCell, "update": False},
        "開倉價": {"display": "開倉價", "cell": BaseCell, "update": False},
        "手數": {"display": "手數", "cell": BaseCell, "update": False},
        "平倉價": {"display": "平倉價", "cell": BaseCell, "update": False},
        "收益": {"display": "收益", "cell": PnlCell, "update": False},
        "return": {"display": "收益率", "cell": PercentCell, "update": False},
        "UnitReturn": {"display": "單位收益率", "cell": PercentCell, "update": False},
        "balance": {"display": "當前資金", "cell": BaseCell, "update": False},
        "drawdown": {"display": "回撤", "cell": BaseCell, "update": False}
    }
    def set_df(self,objectDF):
        objectDF_list = objectDF.to_dict(orient='records')
        for record_item in objectDF_list:
            self.insert_data(record_item)
    def insert_data(self, data):
        self.insertRow(0)
        for column, header in enumerate(self.headers.keys()):
            setting = self.headers[header]
            content = data[header]
            cell = setting["cell"](content, data)
            self.setItem(0, column, cell)
    def __del__(self) -> None:
        pass
class Triggered_OrderStatisticsMonitor(QtWidgets.QTableWidget):
 
    KEY_NAME_MAP = {
        "capital": "策略定義資金",
        "end_balance": "歷史結算資金",
        "total_net_pnl": "總盈虧",
        "total_return": "總收益率",
        "total_trade_count": "總成交筆數",
        "winningResult": "盈利次數",
        "losingResult" : "虧損次數",
        "winningRate": "筆數勝率",
        "max_drawdown": "最大回撤",
        "max_ddpercent": "最大回撤比率",
        "totalWinning": "總盈利金額",
        "totalLosing": "總虧損金額",
        "perprofitLoss": "平均單筆損益",
        "averageWinning": "盈利平均每筆",
        "averageLosing" : "虧損平均每筆",
        "profitLossRatio" : "盈虧比",
    }
    def __init__(self):
        super().__init__()
        self.cells = {}
        self.init_ui()
    def init_ui(self):
        self.setRowCount(len(self.KEY_NAME_MAP))
        self.setVerticalHeaderLabels(list(self.KEY_NAME_MAP.values()))
        self.setColumnCount(1)
        self.horizontalHeader().setVisible(False)
        self.horizontalHeader().setSectionResizeMode(
            QtWidgets.QHeaderView.Stretch
        )
        self.setEditTriggers(self.NoEditTriggers)
        for row, key in enumerate(self.KEY_NAME_MAP.keys()):
            cell = QtWidgets.QTableWidgetItem()
            self.setItem(row, 0, cell)
            self.cells[key] = cell
    def clear_data(self):
        for cell in self.cells.values():
            cell.setText("")
    def set_data(self, data: dict):
        data["capital"] = f"{data['capital']:,.2f}"
        data["end_balance"] = f"{data['end_balance']:,.2f}"
        data["total_net_pnl"] = f"{data['total_net_pnl']:,.2f}"
        data["total_return"] = f"{data['total_return']:,.2f}%"
        data["total_trade_count"] = f"{data['total_trade_count']}"
        data["winningResult"] = f"{data['winningResult']}"
        data["losingResult"] = f"{data['losingResult']}"
        data["winningRate"] = f"{data['winningRate']:,.2f}%"
        data["max_drawdown"] = f"{data['max_drawdown']:,.2f}"
        data["max_ddpercent"] = f"{data['max_ddpercent']:,.2f}%"
        data["totalWinning"] = f"{data['totalWinning']:,.2f}"
        data["totalLosing"] = f"{data['totalLosing']:,.2f}"
        data["averageWinning"] = f"{data['averageWinning']:,.2f}"
        data["averageLosing"] = f"{data['averageLosing']:,.2f}"
        data["perprofitLoss"] = f"{data['perprofitLoss']:,.2f}"
        data["profitLossRatio"] = f"{data['profitLossRatio']:,.2f}"
        for key, cell in self.cells.items():
            value = data.get(key, "")
            cell.setText(str(value))


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/22259926/viewspace-2919132/,如需轉載,請註明出處,否則將追究法律責任。

相關文章