text: """
  Widget for algo trading.
  """
  
  import csv
  from functools import partial
  from datetime import datetime
  
  from vnpy.event import EventEngine, Event
  from vnpy.trader.engine import MainEngine
  from vnpy.trader.ui import QtWidgets, QtCore
  
  from ..engine import (
      AlgoEngine,
      AlgoTemplate,
      APP_NAME,
      EVENT_ALGO_LOG,
      EVENT_ALGO_PARAMETERS,
      EVENT_ALGO_VARIABLES,
      EVENT_ALGO_SETTING
  )
  from .display import NAME_DISPLAY_MAP
  
  
  class AlgoWidget(QtWidgets.QWidget):
      """
      Start connection of a certain gateway.
      """
  
      def __init__(
          self,
          algo_engine: AlgoEngine,
          algo_template: AlgoTemplate
      ):
          """"""
          super().__init__()
  
          self.algo_engine = algo_engine
          self.template_name = algo_template.__name__
          self.default_setting = algo_template.default_setting
  
          self.widgets = {}
  
          self.init_ui()
  
      def init_ui(self):
          """
          Initialize line edits and form layout based on setting.
          """
          self.setMaximumWidth(400)
  
          form = QtWidgets.QFormLayout()
  
          for field_name, field_value in self.default_setting.items():
              field_type = type(field_value)
  
              if field_type == list:
                  widget = QtWidgets.QComboBox()
                  widget.addItems(field_value)
              else:
                  widget = QtWidgets.QLineEdit()
  
              display_name = NAME_DISPLAY_MAP.get(field_name, field_name)
  
              form.addRow(display_name, widget)
              self.widgets[field_name] = (widget, field_type)
  
          start_algo_button = QtWidgets.QPushButton("启动算法")
          start_algo_button.clicked.connect(self.start_algo)
          form.addRow(start_algo_button)
  
          load_csv_button = QtWidgets.QPushButton("CSV启动")
          load_csv_button.clicked.connect(self.load_csv)
          form.addRow(load_csv_button)
  
          form.addRow(QtWidgets.QLabel(""))
          form.addRow(QtWidgets.QLabel(""))
          form.addRow(QtWidgets.QLabel(""))
  
          self.setting_name_line = QtWidgets.QLineEdit()
          form.addRow("配置名称", self.setting_name_line)
  
          save_setting_button = QtWidgets.QPushButton("保存配置")
          save_setting_button.clicked.connect(self.save_setting)
          form.addRow(save_setting_button)
  
          for button in [
              start_algo_button,
              load_csv_button,
              save_setting_button
          ]:
              button.setFixedHeight(button.sizeHint().height() * 2)
  
          self.setLayout(form)
  
      def load_csv(self):
          """"""
          # Get csv file path from dialog
          path, type_ = QtWidgets.QFileDialog.getOpenFileName(
              self,
              u"加载算法配置",
              "",
              "CSV(*.csv)"
          )
  
          if not path:
              return
  
          # Create csv DictReader
          with open(path, "r") as f:
              buf = [line for line in f]
              reader = csv.DictReader(buf)
  
          # Check whether the csv file has all fields
          for field_name in self.widgets.keys():
              if field_name not in reader.fieldnames:
                  QtWidgets.QMessageBox.warning(
                      self,
                      "字段缺失",
                      f"CSV文件缺失算法{self.template_name}所需字段{field_name}"
                  )
                  return
  
          # Read setting
          settings = []
  
          for d in reader:
              # Initialize algo setting with template name
              setting = {
                  "template_name": self.template_name
              }
  
              # Read field from each dict of csv line
              for field_name, tp in self.widgets.items():
                  field_type = tp[-1]
                  field_text = d[field_name]
  
                  if field_type == list:
                      field_value = field_text
                  else:
                      try:
                          field_value = field_type(field_text)
                      except ValueError:
                          QtWidgets.QMessageBox.warning(
                              self,
                              "参数错误",
                              f"{field_name}参数类型应为{field_type}，请检查！"
                          )
                          return
  
                  setting[field_name] = field_value
  
              # Add setting into list
              settings.append(setting)
  
          # Only start algos if no exception/error occured
          for setting in settings:
              self.algo_engine.start_algo(setting)
  
      def get_setting(self):
          """
          Get setting value from line edits.
          """
          setting = {"template_name": self.template_name}
  
          for field_name, tp in self.widgets.items():
              widget, field_type = tp
              if field_type == list:
                  field_value = str(widget.currentText())
              else:
                  try:
                      field_value = field_type(widget.text())
                  except ValueError:
                      display_name = NAME_DISPLAY_MAP.get(field_name, field_name)
                      QtWidgets.QMessageBox.warning(
                          self,
                          "参数错误",
                          f"{display_name}参数类型应为{field_type}，请检查！"
                      )
                      return None
  
              setting[field_name] = field_value
  
          return setting
  
      def start_algo(self):
          """
          Start algo trading.
          """
          setting = self.get_setting()
          if setting:
              self.algo_engine.start_algo(setting)
  
      def update_setting(self, setting_name: str, setting: dict):
          """
          Update setting into widgets.
          """
          self.setting_name_line.setText(setting_name)
  
          for name, tp in self.widgets.items():
              widget, _ = tp
              value = setting[name]
  
              if isinstance(widget, QtWidgets.QLineEdit):
                  widget.setText(str(value))
              elif isinstance(widget, QtWidgets.QComboBox):
                  ix = widget.findText(value)
                  widget.setCurrentIndex(ix)
  
      def save_setting(self):
          """
          Save algo setting
          """
          setting_name = self.setting_name_line.text()
          if not setting_name:
              return
  
          setting = self.get_setting()
          if setting:
              self.algo_engine.update_algo_setting(setting_name, setting)
  
  
  class AlgoMonitor(QtWidgets.QTableWidget):
      """"""
      parameters_signal = QtCore.pyqtSignal(Event)
      variables_signal = QtCore.pyqtSignal(Event)
  
      def __init__(
          self,
          algo_engine: AlgoEngine,
          event_engine: EventEngine,
          mode_active: bool
      ):
          """"""
          super().__init__()
  
          self.algo_engine = algo_engine
          self.event_engine = event_engine
          self.mode_active = mode_active
  
          self.algo_cells = {}
  
          self.init_ui()
          self.register_event()
  
      def init_ui(self):
          """"""
          labels = [
              "",
              "算法",
              "参数",
              "状态"
          ]
          self.setColumnCount(len(labels))
          self.setHorizontalHeaderLabels(labels)
          self.verticalHeader().setVisible(False)
          self.setEditTriggers(self.NoEditTriggers)
  
          self.verticalHeader().setSectionResizeMode(
              QtWidgets.QHeaderView.ResizeToContents
          )
  
          for column in range(2, 4):
              self.horizontalHeader().setSectionResizeMode(
                  column,
                  QtWidgets.QHeaderView.Stretch
              )
          self.setWordWrap(True)
  
          if not self.mode_active:
              self.hideColumn(0)
  
      def register_event(self):
          """"""
          self.parameters_signal.connect(self.process_parameters_event)
          self.variables_signal.connect(self.process_variables_event)
  
          self.event_engine.register(
              EVENT_ALGO_PARAMETERS, self.parameters_signal.emit)
          self.event_engine.register(
              EVENT_ALGO_VARIABLES, self.variables_signal.emit)
  
      def process_parameters_event(self, event):
          """"""
          data = event.data
          algo_name = data["algo_name"]
          parameters = data["parameters"]
  
          cells = self.get_algo_cells(algo_name)
          text = to_text(parameters)
          cells["parameters"].setText(text)
  
      def process_variables_event(self, event):
          """"""
          data = event.data
          algo_name = data["algo_name"]
          variables = data["variables"]
  
          cells = self.get_algo_cells(algo_name)
          variables_cell = cells["variables"]
          text = to_text(variables)
          variables_cell.setText(text)
  
          row = self.row(variables_cell)
          active = variables["active"]
  
          if self.mode_active:
              if active:
                  self.showRow(row)
              else:
                  self.hideRow(row)
          else:
              if active:
                  self.hideRow(row)
              else:
                  self.showRow(row)
  
      def stop_algo(self, algo_name: str):
          """"""
          self.algo_engine.stop_algo(algo_name)
  
      def get_algo_cells(self, algo_name: str):
          """"""
          cells = self.algo_cells.get(algo_name, None)
  
          if not cells:
              stop_func = partial(self.stop_algo, algo_name=algo_name)
              stop_button = QtWidgets.QPushButton("停止")
              stop_button.clicked.connect(stop_func)
  
              name_cell = QtWidgets.QTableWidgetItem(algo_name)
              parameters_cell = QtWidgets.QTableWidgetItem()
              variables_cell = QtWidgets.QTableWidgetItem()
  
              self.insertRow(0)
              self.setCellWidget(0, 0, stop_button)
              self.setItem(0, 1, name_cell)
              self.setItem(0, 2, parameters_cell)
              self.setItem(0, 3, variables_cell)
  
              cells = {
                  "name": name_cell,
                  "parameters": parameters_cell,
                  "variables": variables_cell
              }
              self.algo_cells[algo_name] = cells
  
          return cells
  
  
  class ActiveAlgoMonitor(AlgoMonitor):
      """
      Monitor for active algos.
      """
  
      def __init__(self, algo_engine: AlgoEngine, event_engine: EventEngine):
          """"""
          super().__init__(algo_engine, event_engine, True)
  
  
  class InactiveAlgoMonitor(AlgoMonitor):
      """
      Monitor for inactive algos.
      """
  
      def __init__(self, algo_engine: AlgoEngine, event_engine: EventEngine):
          """"""
          super().__init__(algo_engine, event_engine, False)
  
  
  class SettingMonitor(QtWidgets.QTableWidget):
      """"""
      setting_signal = QtCore.pyqtSignal(Event)
      use_signal = QtCore.pyqtSignal(dict)
  
      def __init__(self, algo_engine: AlgoEngine, event_engine: EventEngine):
          """"""
          super().__init__()
  
          self.algo_engine = algo_engine
          self.event_engine = event_engine
  
          self.settings = {}
          self.setting_cells = {}
  
          self.init_ui()
          self.register_event()
  
      def init_ui(self):
          """"""
          labels = [
              "",
              "",
              "名称",
              "配置"
          ]
          self.setColumnCount(len(labels))
          self.setHorizontalHeaderLabels(labels)
          self.verticalHeader().setVisible(False)
          self.setEditTriggers(self.NoEditTriggers)
  
          self.verticalHeader().setSectionResizeMode(
              QtWidgets.QHeaderView.ResizeToContents
          )
  
          self.horizontalHeader().setSectionResizeMode(
              3,
              QtWidgets.QHeaderView.Stretch
          )
          self.setWordWrap(True)
  
      def register_event(self):
          """"""
          self.setting_signal.connect(self.process_setting_event)
  
          self.event_engine.register(
              EVENT_ALGO_SETTING, self.setting_signal.emit)
  
      def process_setting_event(self, event):
          """"""
          data = event.data
          setting_name = data["setting_name"]
          setting = data["setting"]
          cells = self.get_setting_cells(setting_name)
  
          if setting:
              self.settings[setting_name] = setting
  
              cells["setting"].setText(to_text(setting))
          else:
              if setting_name in self.settings:
                  self.settings.pop(setting_name)
  
              row = self.row(cells["setting"])
              self.removeRow(row)
  
              self.setting_cells.pop(setting_name)
  
      def get_setting_cells(self, setting_name: str):
          """"""
          cells = self.setting_cells.get(setting_name, None)
  
          if not cells:
              use_func = partial(self.use_setting, setting_name=setting_name)
              use_button = QtWidgets.QPushButton("使用")
              use_button.clicked.connect(use_func)
  
              remove_func = partial(self.remove_setting,
                                    setting_name=setting_name)
              remove_button = QtWidgets.QPushButton("移除")
              remove_button.clicked.connect(remove_func)
  
              name_cell = QtWidgets.QTableWidgetItem(setting_name)
              setting_cell = QtWidgets.QTableWidgetItem()
  
              self.insertRow(0)
              self.setCellWidget(0, 0, use_button)
              self.setCellWidget(0, 1, remove_button)
              self.setItem(0, 2, name_cell)
              self.setItem(0, 3, setting_cell)
  
              cells = {
                  "name": name_cell,
                  "setting": setting_cell
              }
              self.setting_cells[setting_name] = cells
  
          return cells
  
      def use_setting(self, setting_name: str):
          """"""
          setting = self.settings[setting_name]
          setting["setting_name"] = setting_name
          self.use_signal.emit(setting)
  
      def remove_setting(self, setting_name: str):
          """"""
          self.algo_engine.remove_algo_setting(setting_name)
  
  
  class LogMonitor(QtWidgets.QTableWidget):
      """"""
      signal = QtCore.pyqtSignal(Event)
  
      def __init__(self, event_engine: EventEngine):
          """"""
          super().__init__()
  
          self.event_engine = event_engine
  
          self.init_ui()
          self.register_event()
  
      def init_ui(self):
          """"""
          labels = [
              "时间",
              "信息"
          ]
          self.setColumnCount(len(labels))
          self.setHorizontalHeaderLabels(labels)
          self.verticalHeader().setVisible(False)
          self.setEditTriggers(self.NoEditTriggers)
  
          self.verticalHeader().setSectionResizeMode(
              QtWidgets.QHeaderView.ResizeToContents
          )
  
          self.horizontalHeader().setSectionResizeMode(
              1,
              QtWidgets.QHeaderView.Stretch
          )
          self.setWordWrap(True)
  
      def register_event(self):
          """"""
          self.signal.connect(self.process_log_event)
  
          self.event_engine.register(EVENT_ALGO_LOG, self.signal.emit)
  
      def process_log_event(self, event):
          """"""
          log = event.data
          msg = log.msg
          timestamp = datetime.now().strftime("%H:%M:%S")
  
          timestamp_cell = QtWidgets.QTableWidgetItem(timestamp)
          msg_cell = QtWidgets.QTableWidgetItem(msg)
  
          self.insertRow(0)
          self.setItem(0, 0, timestamp_cell)
          self.setItem(0, 1, msg_cell)
  
  
  class AlgoManager(QtWidgets.QWidget):
      """"""
  
      def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
          """"""
          super().__init__()
  
          self.main_engine = main_engine
          self.event_engine = event_engine
          self.algo_engine = main_engine.get_engine(APP_NAME)
  
          self.algo_widgets = {}
  
          self.init_ui()
          self.algo_engine.init_engine()
  
      def init_ui(self):
          """"""
          self.setWindowTitle("算法交易")
  
          # Left side control widgets
          self.template_combo = QtWidgets.QComboBox()
          self.template_combo.currentIndexChanged.connect(self.show_algo_widget)
  
          form = QtWidgets.QFormLayout()
          form.addRow("算法", self.template_combo)
          widget = QtWidgets.QWidget()
          widget.setLayout(form)
  
          vbox = QtWidgets.QVBoxLayout()
          vbox.addWidget(widget)
  
          for algo_template in self.algo_engine.algo_templates.values():
              widget = AlgoWidget(self.algo_engine, algo_template)
              vbox.addWidget(widget)
  
              template_name = algo_template.__name__
              display_name = algo_template.display_name
  
              self.algo_widgets[template_name] = widget
              self.template_combo.addItem(display_name, template_name)
  
          vbox.addStretch()
  
          stop_all_button = QtWidgets.QPushButton("全部停止")
          stop_all_button.setFixedHeight(stop_all_button.sizeHint().height() * 2)
          stop_all_button.clicked.connect(self.algo_engine.stop_all)
  
          vbox.addWidget(stop_all_button)
  
          # Right side monitor widgets
          active_algo_monitor = ActiveAlgoMonitor(
              self.algo_engine, self.event_engine
          )
          inactive_algo_monitor = InactiveAlgoMonitor(
              self.algo_engine, self.event_engine
          )
          tab1 = QtWidgets.QTabWidget()
          tab1.addTab(active_algo_monitor, "执行中")
          tab1.addTab(inactive_algo_monitor, "已结束")
  
          log_monitor = LogMonitor(self.event_engine)
          tab2 = QtWidgets.QTabWidget()
          tab2.addTab(log_monitor, "日志")
  
          setting_monitor = SettingMonitor(self.algo_engine, self.event_engine)
          setting_monitor.use_signal.connect(self.use_setting)
          tab3 = QtWidgets.QTabWidget()
          tab3.addTab(setting_monitor, "配置")
  
          grid = QtWidgets.QGridLayout()
          grid.addWidget(tab1, 0, 0, 1, 2)
          grid.addWidget(tab2, 1, 0)
          grid.addWidget(tab3, 1, 1)
  
          hbox2 = QtWidgets.QHBoxLayout()
          hbox2.addLayout(vbox)
          hbox2.addLayout(grid)
          self.setLayout(hbox2)
  
          self.show_algo_widget()
  
      def show_algo_widget(self):
          """"""
          ix = self.template_combo.currentIndex()
          current_name = self.template_combo.itemData(ix)
  
          for template_name, widget in self.algo_widgets.items():
              if template_name == current_name:
                  widget.show()
              else:
                  widget.hide()
  
      def use_setting(self, setting: dict):
          """"""
          setting_name = setting["setting_name"]
          template_name = setting["template_name"]
  
          widget = self.algo_widgets[template_name]
          widget.update_setting(setting_name, setting)
  
          ix = self.template_combo.findData(template_name)
          self.template_combo.setCurrentIndex(ix)
          self.show_algo_widget()
  
      def show(self):
          """"""
          self.showMaximized()
  
  
  def to_text(data: dict):
      """
      Convert dict data into string.
      """
      buf = []
      for key, value in data.items():
          key = NAME_DISPLAY_MAP.get(key, key)
          buf.append(f"{key}：{value}")
      text = "，".join(buf)
      return text
  