VeighNa量化社区
你的开源社区量化交易平台
Member
avatar
加入于:
帖子: 11
声望: 1

[知乎专栏经验发布] (https://www.zhihu.com/column/c_1760768090802171904)

提取地址

本软件基于 VNPY 3.9.0 开源量化交易平台制作。VNPY是由  "上海韦纳软件科技有限公司" 发布的一款 "基于Python的开源量化交易平台开发框架"
本软件可能有未经发现的缺陷,只供学习使用,一切使用后果由您自行承担。

软件是已经编译好的exe可执行文件,策略代码放于strategies文件中
使用其它的python库,可在自己的环境中pip装好,复制到 _internal 目录中

链接:https://pan.baidu.com/s/1Sw95-GuaXv7hn-O9WyQHLw?pwd=agmj 
提取码:agmj

主窗口
description

多账户管理,自动登录

description

合约管理,收藏,手续费设置(策略中算手续费),定期从外部获取后计算最大持仓定为主力

description

交易持仓
description

绩效查看,按账户统计
description

数据管理,一键批量导入通达信数据(日线、分钟线)
description

策略执行
使用数据模拟定义参数和状态显示,配合限制输入框,对输入内容格式、最大最小值做限制
界面参数、状态值,使用模型指定的中文标题显示
Params、State 显示在窗口
Variable 仅做为中间变量存储,但不显示
扩展 on_ready,在在 on_start 后触发,不同的是,交易状态 self.trading == True
扩展 on_reset,在画面点击重置时触发

description

description

description

Administrator
avatar
加入于:
帖子: 4502
声望: 322

很漂亮啊,是用QFluentWidgets开发的吗?帮你加个精华

Member
avatar
加入于:
帖子: 11
声望: 1

用Python的交易员 wrote:

很漂亮啊,是用QFluentWidgets开发的吗?帮你加个精华
是的,一眼就认出来了

Member
avatar
加入于:
帖子: 1
声望: 0

很好

Member
avatar
加入于:
帖子: 15
声望: 0

非常赞 能分享吗?谢谢

Member
avatar
加入于:
帖子: 1472
声望: 105

海外社区那边也有用户开发了一套基于QFluentWidgets的版本,有兴趣的同学可以参考看看:https://github.com/veighna-global/vnpy_evo

Member
avatar
加入于:
帖子: 11
声望: 1

其实美化VNPY比较简单,可以看另外一个 批量导入通达信分钟数据

基本上,就是把VNPY下载回来,找到每一个模块的ui目录,把里面的代码复制过来
然后把 QWidgets.Q**,改为成QFluentWidgets.**控件代替

可以独立使用的像data_recorder,data_manager,可以独立出来,用的时候优先找编辑好的版本,没有的话运行py

def run_modules(name: str):

    exe = f"{name}.exe"
    py = f"{name}.py"

    if os.path.exists(exe):

        try:
            os.startfile(exe)
        except FileNotFoundError as err:
            infobar.error(title="启动错误", content=f"{err.strerror}", duration=-1)

    elif os.path.exists(py):

        shell = ["python", py]
        subprocess.Popen(shell, shell=True)
Member
avatar
加入于:
帖子: 11
声望: 1

数据记录模块,也是比较容易单拎出来的
我的逻辑是:账户里面找标识为行情的号,再加入标记为收藏的品种
增加了关闭窗口隐藏,托盘图标后台记录


import sys
from datetime import datetime
from typing import Any, List

from PySide6 import QtCore, QtWidgets
from PySide6.QtCore import Qt
from PySide6.QtGui import QIcon, QAction
from PySide6.QtWidgets import QSpacerItem, QApplication, QSystemTrayIcon, QMenu
from qfluentwidgets import LineEdit, SpinBox, TextEdit, BodyLabel, PrimaryPushButton, setTheme, Theme, Action, \
    FluentIcon, RoundMenu

from vnpy_ctp import CtpGateway
from vnpy.trader.engine import MainEngine
from guanlan_futures.common.modules import accounts
from vnpy.event import Event, EventEngine
from vnpy_datarecoder.engine import RecorderEngine, APP_NAME, EVENT_RECORDER_LOG, EVENT_RECORDER_UPDATE, \
    EVENT_RECORDER_EXCEPTION
from vnpy.trader.event import EVENT_CONTRACT
from vnpy.trader.object import ContractData
from guanlan_futures.common import resource
from guanlan_widgets import MicaWindow


# from guanlan_futures.common import resource


class DataRecoderWindow(MicaWindow):
    signal_log: QtCore.Signal = QtCore.Signal(Event)
    signal_update: QtCore.Signal = QtCore.Signal(Event)
    signal_contract: QtCore.Signal = QtCore.Signal(Event)
    signal_exception: QtCore.Signal = QtCore.Signal(Event)

    def __init__(self):
        super().__init__()

        self.setWindowIcon(QIcon(':/futures/icon/recorder.png'))
        self.setWindowTitle("行情记录")
        self.titleBar.minBtn.hide()
        self.titleBar.maxBtn.hide()
        self.setMicaEffectEnabled(True)

        self.event_engine = EventEngine()
        self.main_engine = MainEngine(self.event_engine)
        self.main_engine.add_engine(RecorderEngine)

        self.recorder_engine: RecorderEngine = self.main_engine.get_engine(APP_NAME)

        self.init_ui()
        self.init_tray_icon()
        self.register_event()
        self.recorder_engine.put_event()

        # 连接服务器
        self.connect_ctp()

    def closeEvent(self, event):
        # 忽略退出事件,而是隐藏到托盘
        event.ignore()
        self.hide()

    def init_tray_icon(self):
        # 配置系统托盘
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(QIcon(':/futures/icon/recorder.png'))
        self.tray_icon.setToolTip("观澜量化 - 行情记录")
        self.tray_icon.show()

        # 创建托盘的右键菜单
        self.tray_menu = RoundMenu(parent=self)
        self.tray_menu.addAction(Action(FluentIcon.ZOOM, "显示", triggered=self.show))
        self.tray_menu.addAction(Action(FluentIcon.CLOSE, "退出", triggered=self.quit))

        # 配置菜单并显示托盘
        self.tray_icon.setContextMenu(self.tray_menu)  # 把tpMenu设定为托盘的右键菜单

    def quit(self):
        # 真正的退出
        self.recorder_engine.close()
        self.main_engine.close()
        sys.exit()

    def connect_ctp(self):
        # 找账户配置为行情的
        self.gateway_name = ""
        setting = {}

        for key, value in accounts.account_data.items():
            if value['md_data'] == "1":
                self.gateway_name = key
                setting = value
                break

        gateway = self.main_engine.add_gateway(CtpGateway, self.gateway_name)
        self.main_engine.connect(setting, self.gateway_name)

    def init_ui(self) -> None:
        """"""

        self.symbol_line: LineEdit = LineEdit()

        self.interval_spin: SpinBox = SpinBox()
        self.interval_spin.setMinimum(1)
        self.interval_spin.setMaximum(60)
        self.interval_spin.setValue(self.recorder_engine.timer_interval)
        self.interval_spin.setSuffix("  秒")
        self.interval_spin.valueChanged.connect(self.set_interval)

        contracts: List[ContractData] = self.main_engine.get_all_contracts()
        self.vt_symbols: list = [contract.vt_symbol for contract in contracts]

        self.symbol_completer: QtWidgets.QCompleter = QtWidgets.QCompleter(self.vt_symbols)
        self.symbol_completer.setFilterMode(Qt.MatchContains)
        self.symbol_line.setCompleter(self.symbol_completer)

        add_bar_button: PrimaryPushButton = PrimaryPushButton(text="添加", parent=self)
        add_bar_button.clicked.connect(self.add_bar_recording)

        remove_bar_button: PrimaryPushButton = PrimaryPushButton(text="移除", parent=self)
        remove_bar_button.clicked.connect(self.remove_bar_recording)

        add_tick_button: PrimaryPushButton = PrimaryPushButton(text="添加", parent=self)
        add_tick_button.clicked.connect(self.add_tick_recording)

        remove_tick_button: PrimaryPushButton = PrimaryPushButton(text="移除", parent=self)
        remove_tick_button.clicked.connect(self.remove_tick_recording)

        self.bar_recording_edit: TextEdit = TextEdit()
        self.bar_recording_edit.setReadOnly(True)

        self.tick_recording_edit: TextEdit = TextEdit()
        self.tick_recording_edit.setReadOnly(True)

        self.log_edit: TextEdit = TextEdit()
        self.log_edit.setReadOnly(True)

        # Set layout
        grid: QtWidgets.QGridLayout = QtWidgets.QGridLayout()
        grid.addWidget(BodyLabel(text="K线记录", parent=self), 0, 0)
        grid.addWidget(add_bar_button, 0, 1)
        grid.addWidget(remove_bar_button, 0, 2)
        grid.addWidget(BodyLabel(text="Tick记录", parent=self), 1, 0)
        grid.addWidget(add_tick_button, 1, 1)
        grid.addWidget(remove_tick_button, 1, 2)

        self.symbol_line.setMinimumWidth(300)
        form: QtWidgets.QFormLayout = QtWidgets.QFormLayout()
        form.addRow(BodyLabel(text="本地代码", parent=self), self.symbol_line)
        form.addRow(BodyLabel(text="写入间隔", parent=self), self.interval_spin)

        hbox: QtWidgets.QHBoxLayout = QtWidgets.QHBoxLayout()
        hbox.addLayout(form)
        # hbox.addWidget(BodyLabel(text="     ", parent=self))
        hbox.addItem(QSpacerItem(50, 0))
        hbox.addLayout(grid)
        hbox.addStretch(1)
        hbox.setContentsMargins(0, 10, 0, 0)

        grid2: QtWidgets.QGridLayout = QtWidgets.QGridLayout()
        grid2.addWidget(BodyLabel(text="K线记录列表", parent=self), 0, 0)
        grid2.addWidget(BodyLabel(text="Tick记录列表", parent=self), 0, 1)
        grid2.addWidget(self.bar_recording_edit, 1, 0)
        grid2.addWidget(self.tick_recording_edit, 1, 1)
        grid2.addWidget(self.log_edit, 2, 0, 1, 2)
        grid2.setContentsMargins(0, 10, 0, 0)

        vbox: QtWidgets.QVBoxLayout = QtWidgets.QVBoxLayout()
        vbox.addLayout(hbox)
        vbox.addLayout(grid2)
        vbox.setContentsMargins(20, 40, 12, 12)

        self.setLayout(vbox)

    def register_event(self) -> None:
        """"""
        self.signal_log.connect(self.process_log_event)
        self.signal_contract.connect(self.process_contract_event)
        self.signal_update.connect(self.process_update_event)
        self.signal_exception.connect(self.process_exception_event)

        self.event_engine.register(EVENT_CONTRACT, self.signal_contract.emit)
        self.event_engine.register(EVENT_RECORDER_LOG, self.signal_log.emit)
        self.event_engine.register(EVENT_RECORDER_UPDATE, self.signal_update.emit)
        self.event_engine.register(EVENT_RECORDER_EXCEPTION, self.signal_exception.emit)

    def process_log_event(self, event: Event) -> None:
        """"""
        timestamp: str = datetime.now().strftime("%H:%M:%S")
        msg: str = f"{timestamp}\t{event.data}"
        self.log_edit.append(msg)

    def process_update_event(self, event: Event) -> None:
        """"""
        data: Any = event.data

        self.bar_recording_edit.clear()
        bar_text: str = "\n".join(data["bar"])
        self.bar_recording_edit.setText(bar_text)

        self.tick_recording_edit.clear()
        tick_text: str = "\n".join(data["tick"])
        self.tick_recording_edit.setText(tick_text)

    def process_contract_event(self, event: Event) -> None:
        """"""
        contract: ContractData = event.data
        self.vt_symbols.append(contract.vt_symbol)

        model: QtCore.QAbstractItemModel = self.symbol_completer.model()
        model.setStringList(self.vt_symbols)

    def process_exception_event(self, event: Event) -> None:
        """"""
        exc_info = event.data
        raise exc_info[1].with_traceback(exc_info[2])

    def add_bar_recording(self) -> None:
        """"""
        vt_symbol: str = self.symbol_line.text()
        self.recorder_engine.add_bar_recording(vt_symbol, self.gateway_name)


    def add_tick_recording(self) -> None:
        """"""
        vt_symbol: str = self.symbol_line.text()
        self.recorder_engine.add_tick_recording(vt_symbol)

    def remove_bar_recording(self) -> None:
        """"""
        vt_symbol: str = self.symbol_line.text()
        self.recorder_engine.remove_bar_recording(vt_symbol)

    def remove_tick_recording(self) -> None:
        """"""
        vt_symbol: str = self.symbol_line.text()
        self.recorder_engine.remove_tick_recording(vt_symbol)

    def set_interval(self, interval) -> None:
        """"""
        self.recorder_engine.timer_interval = interval


if __name__ == '__main__':

    setTheme(Theme.DARK)

    # 初始化应用和窗口
    app = QApplication(sys.argv)
    win = DataRecoderWindow()
    win.show()

    # 运行应用
    sys.exit(app.exec())
Member
avatar
加入于:
帖子: 11
声望: 1

可执行版本已发,学习研究用,一切使用后果由您自行承担。

Member
avatar
加入于:
帖子: 15
声望: 0

厉害!赞 赞 赞

© 2015-2022 上海韦纳软件科技有限公司
备案服务号:沪ICP备18006526号

沪公网安备 31011502017034号

【用户协议】
【隐私政策】
【免责条款】