【小白量化Qbuddy】用AI设计miniQMT指标公式计算量化平台

【小白量化Qbuddy】用AI设计miniQMT指标公式计算量化平台

Qbuddy(中文名:小白量化伴侣)是一款基于Windows 桌面框架开发的智能化量化辅助软件,面向量化交易学习者与开发者,将大语言模型(LLM)能力与本地工具链深度整合,覆盖从策略构思、公式编写、代码生成、回测验证到自动执行的完整链路。

我们花几十分钟设计一个miniQMT指标公式计算量化平台,可以通过修改通达信专家公式实现全自动交易。

一、【指标公式智能体】(Indicator Formula Agent)

用【指标公式智能体】,选择【写通达信股票交易公式话术】

输入指标公式策略:macd金叉买入,死叉卖出。

下面是生成的通达信专家公式,100%准确。

python 复制代码
DIF:=EMA(CLOSE,12)-EMA(CLOSE,26);
DEA:=EMA(DIF,9);
MACD:=(DIF-DEA)*2;
ENTERLONG:CROSS(DIF,DEA);
EXITLONG:CROSS(DEA,DIF);

二、【标准公式转Python】

【标准公式转Python】画面选【生成miniQMT交易Python程序】

生成的Python自动交易源码如下:

python 复制代码
# 生成miniQMT交易Python程序
# 生成时间:2026年08月27日 09:24:51
gs='''
DIF:=EMA(CLOSE,12)-EMA(CLOSE,26);
DEA:=EMA(DIF,9);
MACD:=(DIF-DEA)*2;
ENTERLONG:CROSS(DIF,DEA);
EXITLONG:CROSS(DEA,DIF);
'''

##########修改帐户信息########################
miniQMT路径 = r'D:\国金QMT交易端模拟'  # 路径
账号 = ""  # 账号
#############################################

global 允许,不允许
允许 = True
不允许 = False
import numpy as np
import pandas as pd
import pickle
import time, os, threading
import datetime as dt
import HP_tdx as htdx
import random, time, logging, datetime, json, akshare
from xtquant import xttrader, xtconstant
from xtquant.xttrader import XtQuantTrader, XtQuantTraderCallback
from xtquant.xttype import StockAccount
from xtquant import xtdata
import time
import HP_tdx as htdx
import HP_formula as hgs  #通达信公式库
from HP_formula import *   #股票指标公式函数库
import HP_plt as hplt

hq = htdx.TdxInit(ip='222.90.136.119',port=7709)
global CLOSE, LOW, HIGH, OPEN, VOL
global C, L, H, O, V
global 股票池, 每笔金额, 可用余额, 买入数量, 运行次数, 持仓, 最大买股数量, 买股数量
global 滑点
global cwsj, 合约信息, jjtj, wtsj
wtsj = {}
滑点 = 0.001
运行次数 = 0
可用余额 = {}
每笔金额 = 20000
买股数量 = 0
买入数量 = 400
最大买股数量 = 7
#股票池 = r'D:\new_tdx\T0002\blocknew\zxg88.blk'
股票池 =r'block/沪深3000107592.blk'
codes = htdx.getzxgfile(股票池)  # 获取自选股


股票池 = []
for m, c in codes:
    code = htdx.ttom(m, c)
    股票池.append(code)
    可用余额[code] = 0
print("股票池数量:", len(股票池))

class Singleton(object):
    _instance = None

    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = object.__new__(class_, *args, **kwargs)
        return class_._instance

class Trader(Singleton):
    xt_trader = None
    account = None

    def set_trader(self, qmt_dir, session_id):
        self.xt_trader = XtQuantTrader(qmt_dir, session_id)
        self.xt_trader.start()
        connect_result = self.xt_trader.connect()
        return connect_result

    def set_account(self, account_id, account_type):
        self.account = StockAccount(account_id, account_type=account_type)
        return self.account

    def get_account(self):
        return self.account

    def get_trader(self):
        return self.xt_trader

def query_holding(trader):
    '''
    查询当前持仓
    '''
    holding = []
    for p in trader.xt_trader.query_stock_positions(trader.account):
        holding.append(
            [{'股票代码': p.stock_code, '持仓': p.volume, '可用持仓': p.can_use_volume, '成本': p.open_price,
              '持仓市值': p.market_value}])
    return holding

def query_order(trader, order_id=''):
    '''
    查询当日委托
    '''
    if order_id == '':
        orders = trader.xt_trader.query_stock_orders(trader.account)
    else:
        orders = trader.xt_trader.query_stock_order(trader.account, int(order_id))
        # 订单不存在,下单失败
        if orders is None:
            return []
        orders = [orders]
    result = []
    for order in orders:
        result.append(
            {'股票代码': order.stock_code, '委托数量': order.order_volume, '成交数量': order.traded_volume,
             '委托价格': order.price, '委托类型': order.order_type, '委托状态': order.order_status,
             "订单编号": order.order_id, "柜台合同编号": order.order_sysid, "报单时间": order.order_time})
    return result

def trade_cancel_order(trader, order_id=''):
    '''
    撤单
    '''
    cancel_order_result = trader.xt_trader.cancel_order_stock(trader.account, order_id)
    return cancel_order_result

# 获取tick行情
def get_tick_data(code='000776.SZ'):
    tick = xtdata.get_full_tick([code])
    return tick[code]

xtdata.enable_hello = False
trader = Trader()
session_id = random.randint(20000, 60000)
qmt_dir = miniQMT路径+ '\\userdata_mini'
account_type = "STOCK"  # 账号类型,可选STOCK、CREDIT
connect_result = trader.set_trader(qmt_dir, session_id)
trader.set_account(账号, account_type=account_type)
print('路径:', miniQMT路径)
print('账号: ', 账号)
print("交易连接成功!") if connect_result == 0 else print("交易连接失败!")

print('获取可用资金')
asset = trader.xt_trader.query_stock_asset(trader.account)
总资产 = asset.total_asset  # 总资产
可用金额 = asset.cash  # '可用金额'
股票市值 = asset.market_value  # '股票市值'
冻结金额 = asset.frozen_cash  # 冻结金额
print('可用金额:', 可用金额)

print('获取持仓')
持仓股票 = []
持仓 = {}
holding = query_holding(trader)
for i in range(len(holding)):
    股票代码 = holding[i][0]['股票代码']
    持仓股票.append(股票代码)
    持仓[股票代码] = holding[i][0]
    可用余额[股票代码] = 持仓[股票代码]['可用持仓']
    if 股票代码 not in 股票池:
        股票池.insert(0, 股票代码)
        wtsj[股票代码] = 0

print('获取合约基础信息。')
合约信息 = {}  # 财务数据
for code in 股票池:
    wtsj[code] = 0
    try:
        cw = xtdata.get_instrument_detail(code)
        合约信息[code] = cw
    except:
        pass
cwsj = 合约信息

tgs1=hgs.Tdxgs()
# 主函数
def aaa():
    global 股票池, 每笔金额, 可用余额, 买入数量, 运行次数, 持仓, 最大买股数量, 买股数量
    global 滑点
    global cwsj, 合约信息, jjtj, wtsj

    临时信息 = ''
    starttime = time.perf_counter()
    运行次数 = 运行次数 + 1

    nowtime = MACHINETIME()
    if nowtime >= 91500 and nowtime <= 92000:
        pass

    # 9点20分钟到9点25分钟,竞价成交金额大于昨天最大分成交金额的百分之十
    if nowtime >= 92000 and nowtime <= 92500:
        pass

    # wtc=query_order(trader,'') #获取委托池
    # for i in range(len(wtc)):
    #     wt=wtc[i]
    #     #print(wt)
    #     code=wt['股票代码']
    #     订单编号=wt['订单编号']
    #     if wt['委托状态'] !=56 and wt['委托状态'] !=54 and (time.perf_counter()-wtsj[code])>=3:
    #         try:
    #             trade_cancel_order(trader,订单编号)
    #         except:
    #             print('撤单失败:',订单编号)

    ticks = xtdata.get_full_tick(['SH', 'SZ','BJ'])
    if nowtime >= 93000 and nowtime < 240000:
        for code in 股票池:
            try:
                tick = ticks[code]
                当前涨幅 = (tick['lastPrice'] - tick['lastClose']) / (tick['lastClose'] + 0.0000001)
                开盘涨幅 = (tick['open'] - tick['lastClose']) / (tick['lastClose'] + 0.0000001)
                最低涨幅 = (tick['low'] - tick['lastClose']) / (tick['lastClose'] + 0.0000001)
                换手率 = tick['volume'] * 10000 / (cwsj[code]['FloatVolume'] + 0.0000001)
                x = 0
                m,c=htdx.mtot(code)   #miniQMT代码转TDX
                #(nCategory, nMarket, sStockCode, nStart, nCount) 
                #获取市场内指定范围的证券K 线, 
                #指定开始位置和指定K 线数量,指定数量最大值为800。 
                #参数: 
                #nCategory -> K 线种类 
                #0 5 分钟K 线 
                #1 15 分钟K 线 
                #2 30 分钟K 线 
                #3 1 小时K 线 
                #4 日K 线 
                #5 周K 线 
                #6 月K 线 
                #7 1 分钟 
                #8 1 分钟K 线 
                #9 日K 线 
                #10 季K 线 
                #11 年K 线 
                #nMarket -> 市场代码0:深圳,1:上海 
                #sStockCode -> 证券代码; 
                #nStart -> 指定的范围开始位置; 
                #nCount -> 用户要请求的K 线数目,最大值为800。                           
                df = htdx.get_security_bars(nCategory=4, nMarket=m, code=c,nStart=0, nCount=300)  # 获取通达信行情指定范围的证券K线
                print(code, len(df))
                
                ## 数据规格化
                df.dropna(inplace=True)
                mydf = hgs.initmydf(df)
                tgs1.loaddf(mydf)
                mydf=tgs1.rungs(gs)
                if 'ENTERLONG' in mydf.columns:
                    ENTERLONG=mydf['ENTERLONG']
                else:
                    mydf['ENTERLONG']=0
                    ENTERLONG=mydf['ENTERLONG']
                    
                if 'EXITLONG' in mydf.columns:
                    EXITLONG=mydf['EXITLONG']
                else:
                    mydf['EXITLONG']=0
                    EXITLONG=mydf['EXITLONG']
                
                C = CLOSE = mydf['close']
                L = LOW = mydf['low']
                H = HIGH = mydf['high']
                O = OPEN = mydf['open']
                V = VOL = mydf['volume']
                AMO=AMOUNT=mydf['amount']

                # 自编公式计算和选股

             
                if ENTERLONG.iloc[-1] > 0 and 买股数量 < 最大买股数量:
                    买股数量 = 买股数量 + 1
                    price2 = round(tick['lastPrice'] * (1 + 滑点), 2)
                    if price2 > cwsj[code]['UpStopPrice']:
                        price2 = cwsj[code]['UpStopPrice']
                    amount = 每笔金额 / price2
                    买入数量 = int(amount / 100) * 100
                    print('买入股票:', code, '  价格:', price2, '  买入数量:', 买入数量)
                    order_id = trader.xt_trader.order_stock(trader.account, code, xtconstant.STOCK_BUY, 买入数量,
                                                            xtconstant.FIX_PRICE, price2, '买入', '买入')

                if EXITLONG.iloc[-1] > 0 and 可用余额[code] > 0:
                    print('卖出股票:', code, '  价格:', C.iloc[-1], '  卖出数量:', 可用余额[code])
                    order_id = trader.xt_trader.order_stock(trader.account, code, xtconstant.STOCK_SELL, 可用余额[code],
                                                            xtconstant.FIX_PRICE, C.iloc[-1], '卖出', '卖出')
                    可用余额[code] = 0

                if 可用余额[code] > 0:  #止损功能
                    成本价 = 持仓[code]['成本']
                    盈亏率 = (C.iloc[-1] - 成本价) / (成本价 + 0.00000001)
                    if 盈亏率 < -0.03:  # 止损
                        print('止损:', code, '  价格:', C.iloc[-1], '  卖出数量:', 可用余额[code])
                        order_id = trader.xt_trader.order_stock(trader.account, code, xtconstant.STOCK_SELL,
                                                                可用余额[code], xtconstant.FIX_PRICE, C.iloc[-1], '卖出',
                                                                '卖出')
                        可用余额[code] = 0

            except:
                print(code, '计算出错!')
                pass
    endtime = time.perf_counter()
    costtime = (endtime - starttime)
    t1 = costtime - int(costtime / 60) * 60
    t2 = int(costtime / 60) - int(int(costtime / 60) / 60) * 60
    t3 = int(int(costtime / 60) / 60)
    qhcsj2 = time.strftime('%Y%m%d %H:%M:%S', time.localtime(time.time()))
    print(qhcsj2, '花费时间:%d:%d:%f' % (t3, t2, t1), '\n')

def aaa2():
    while True:
        aaa()
        time.sleep(0.1)

def main():
    aaa2()

# 运行程序
if __name__ == '__main__':
    main()

上面代码简单输入策略参数,100%准确。

支持通达信、大智慧股票池板块文件。

我们保存为Python文件:miniQMT策略macd交易.py

三、【Python智能体】

【Python智能体】画面,把文件【miniQMT策略macd交易.py*】作为附件。

输入提示词:

python 复制代码
修改附件程序,使用pyside6窗口,生成一个miniQMT指标公式交易系统。要求用户可以设置必要参数,例如账户,路径,股票池、指标公式源码等。其他由AI考虑,实现用户输入不同公式,实现指标公式miniQMT交易系统。

【Python智能体】会自动生成代码,自动运行,自动根据运行错误自动修改,直到代码生成完成。

我们对外观不满意可以继续修改代码。

继续输入话术:

python 复制代码
程序运行正常,我需要窗口外观,美观时尚,采用量化蓝色调。功能继续完善。

【小白量化Qbuddy】的【Python智能体】会继续修改,直到代码无错误。

如果满意保存为:miniQMT指标公式交易系统.py

全部源代码如下:

python 复制代码
# -*- coding: utf-8 -*-
'''
miniQMT 指标公式交易系统 - PySide6 量化蓝调版
功能说明:
1. 支持通达信指标公式解析,生成买卖信号
2. 模拟交易与miniQMT实盘交易双模式
3. 美观时尚的量化蓝色调界面
4. 参数可配置:账户、路径、股票池、轮询间隔、交易数量等
5. 实时日志、持仓、信号监控
'''

import sys
import os
import re
import json
import random
import threading
import time
import datetime as dt

import numpy as np
import pandas as pd

from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                               QHBoxLayout, QLineEdit, QTextEdit, QPushButton,
                               QLabel, QGridLayout, QGroupBox, QFileDialog,
                               QMessageBox, QCheckBox, QTabWidget, QSplitter,
                               QComboBox, QSpinBox, QScrollArea, QFrame,
                               QStatusBar)
from PySide6.QtCore import Qt, QThread, Signal
from PySide6.QtGui import QFont, QColor, QPalette

# ------------------ 可选依赖引入 ------------------
try:
    from xtquant import xtdata, xtconstant
    from xtquant.xttrader import XtQuantTrader
    from xtquant.xttype import StockAccount
    QMT_AVAILABLE = True
except ImportError:
    QMT_AVAILABLE = False
    print('警告: 未找到xtquant,仅支持模拟模式')

try:
    import HP_tdx as htdx
    HP_AVAILABLE = True
except ImportError:
    HP_AVAILABLE = False

# ------------------ 量化蓝调 QSS 样式 ------------------
QSS = """
QMainWindow {
    background-color: #0d1b2a;
}
QWidget {
    background-color: transparent;
    color: #e0f7fa;
    font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
}
QLabel {
    color: #b0c4de;
    font-size: 13px;
}
QLabel#titleLabel {
    color: #4fc3f7;
    font-size: 22px;
    font-weight: bold;
    padding: 8px 0;
}
QLabel#sectionTitle {
    color: #4fc3f7;
    font-size: 16px;
    font-weight: bold;
    padding: 4px 0;
}
QLineEdit, QTextEdit, QComboBox, QSpinBox {
    background-color: #1b263b;
    border: 1px solid #415a77;
    border-radius: 6px;
    padding: 5px;
    color: #ffffff;
    selection-background-color: #1f6feb;
}
QLineEdit:focus, QTextEdit:focus, QComboBox:focus, QSpinBox:focus {
    border: 1px solid #1f6feb;
}
QPushButton {
    background-color: #1f6feb;
    color: white;
    border: none;
    border-radius: 6px;
    padding: 8px 16px;
    font-size: 14px;
    font-weight: bold;
}
QPushButton:hover {
    background-color: #3d8bfd;
}
QPushButton:pressed {
    background-color: #185abc;
}
QPushButton:disabled {
    background-color: #39506b;
    color: #9aa7b8;
}
QGroupBox {
    border: 1px solid #2c3e50;
    border-radius: 8px;
    margin-top: 12px;
    padding-top: 12px;
    background-color: #10243e;
    font-weight: bold;
}
QGroupBox::title {
    subcontrol-origin: margin;
    left: 10px;
    padding: 0 4px;
    color: #4fc3f7;
}
QTabWidget::pane {
    border: 1px solid #2c3e50;
    border-radius: 6px;
    background-color: #0d1b2a;
}
QTabBar::tab {
    background-color: #10243e;
    padding: 8px 16px;
    margin-right: 4px;
    border-top-left-radius: 6px;
    border-top-right-radius: 6px;
    color: #b0c4de;
}
QTabBar::tab:selected {
    background-color: #1f6feb;
    color: white;
}
QTextEdit#logEdit {
    background-color: #0a1420;
    border: 1px solid #2c3e50;
    font-family: 'Consolas', 'Courier New', monospace;
    font-size: 12px;
}
QScrollArea {
    border: none;
}
QStatusBar {
    background-color: #10243e;
    color: #b0c4de;
}
QSplitter::handle {
    background-color: #1b263b;
    width: 2px;
}
"""

# ------------------ 通达信公式引擎 ------------------
class FormulaEngine:
    '''通达信指标公式解析与信号计算'''
    def __init__(self, text):
        self.text = text
        self.statements = []
        self.signals = []
        self._parse()

    def _parse(self):
        for line in self.text.replace(';', chr(10)).split(chr(10)):
            line = line.strip()
            if not line:
                continue
            m = re.match(r'^([A-Za-z_][A-Za-z0-9_]*)[ ]*(:=|:)[ ]*(.+)$', line)
            if m:
                var, op, expr = m.groups()
                self.statements.append((var, expr))
                if op == ':':
                    self.signals.append(var)

    def calculate(self, df):
        ns = {}
        for key in ['close', 'open', 'high', 'low', 'volume']:
            ns[key.upper()] = df[key]
            ns[key] = df[key]
        ns['pd'] = pd
        ns['EMA'] = lambda x, n: pd.Series(x).ewm(span=int(n), adjust=False).mean()
        ns['MA'] = lambda x, n: pd.Series(x).rolling(int(n)).mean()
        ns['REF'] = lambda x, n: pd.Series(x).shift(int(n))
        ns['CROSS'] = lambda a, b: (pd.Series(a) > pd.Series(b)) & (pd.Series(a).shift(1) <= pd.Series(b).shift(1))
        ns['HHV'] = lambda x, n: pd.Series(x).rolling(int(n)).max()
        ns['LLV'] = lambda x, n: pd.Series(x).rolling(int(n)).min()
        ns['ABS'] = lambda x: pd.Series(x).abs()
        ns['MAX'] = lambda a, b: pd.concat([pd.Series(a), pd.Series(b)], axis=1).max(axis=1)
        ns['MIN'] = lambda a, b: pd.concat([pd.Series(a), pd.Series(b)], axis=1).min(axis=1)
        ns['COUNT'] = lambda cond, n: pd.Series(cond).rolling(int(n)).sum()
        ns['BARSLAST'] = lambda cond: pd.Series(cond).groupby(pd.Series(cond).cumsum()).cumcount()
        ns['VALUEWHEN'] = lambda cond, x: pd.Series(x).where(pd.Series(cond), np.nan).ffill()

        for var, expr in self.statements:
            try:
                val = eval(expr, {'__builtins__': None}, ns)
                ns[var] = val
            except Exception as e:
                raise ValueError(f'变量 {var} 计算失败: {e}')

        res = {}
        for s in self.signals:
            if s in ns:
                series = ns[s]
                try:
                    res[s] = bool(pd.Series(series).iloc[-1])
                except Exception:
                    res[s] = False
        return res

# ------------------ 交易接口(支持实盘与模拟) ------------------
class Trader:
    '''miniQMT 实盘交易接口(模拟时忽略)'''
    def __init__(self):
        self.xt_trader = None
        self.account = None
        self.connected = False

    def connect(self, qmt_dir, account_id):
        if not QMT_AVAILABLE:
            return -1
        try:
            session_id = random.randint(20000, 60000)
            self.xt_trader = XtQuantTrader(qmt_dir, session_id)
            self.xt_trader.start()
            conn = self.xt_trader.connect()
            if conn == 0:
                self.account = StockAccount(account_id, 'STOCK')
                self.connected = True
            return conn
        except Exception as e:
            print(f'connect error: {e}')
            return -1

    def query_asset(self):
        if self.connected:
            return self.xt_trader.query_stock_asset(self.account)
        return None

    def query_positions(self):
        if not self.connected:
            return {}
        try:
            positions = self.xt_trader.query_stock_positions(self.account)
            return {p.stock_code: {'volume': p.volume, 'can_use': p.can_use_volume} for p in positions}
        except Exception:
            return {}

    def order(self, code, amount, buy=True):
        if not self.connected:
            return -1
        try:
            side = xtconstant.STOCK_BUY if buy else xtconstant.STOCK_SELL
            return self.xt_trader.order_stock(self.account, code, side, amount,
                                              xtconstant.LATEST_PRICE, 0, 'ai_strategy', '')
        except Exception as e:
            print(f'order error: {e}')
            return -1

# ------------------ 交易工作线程 ------------------
class TradingWorker(QThread):
    log_signal = Signal(str)
    status_signal = Signal(str)
    trade_signal = Signal(str)
    position_signal = Signal(str)

    def __init__(self, params, parent=None):
        super().__init__(parent)
        self.params = params
        self.stop_event = threading.Event()
        self.trader = Trader()
        self.last_signals = {}
        self.positions = {}   # 模拟持仓
        self.cash = 1000000.0 # 模拟资金
        self.lock = threading.Lock()

    def run(self):
        self.log_signal.emit('系统启动')
        if self.params.get('simulation', True):
            self.log_signal.emit('模拟模式启用')
        else:
            conn = self.trader.connect(self.params['qmt_path'], self.params['account'])
            if conn != 0:
                self.log_signal.emit(f'miniQMT连接失败,错误码 {conn}')
                self.status_signal.emit('连接失败')
                return
            self.log_signal.emit('miniQMT连接成功')

        codes = self.load_stock_pool()
        self.log_signal.emit(f'股票池: {len(codes)} 只')
        if not codes:
            self.log_signal.emit('股票池为空,程序退出')
            return

        try:
            engine = FormulaEngine(self.params['formula'])
            self.log_signal.emit('公式解析成功')
        except Exception as e:
            self.log_signal.emit(f'公式错误: {e}')
            return

        # 初始持仓显示
        self.update_position_display()

        while not self.stop_event.is_set():
            for code in codes:
                if self.stop_event.is_set():
                    break
                df = self.get_data(code)
                if df is None or len(df) < 30:
                    continue
                signals = engine.calculate(df)
                price = df['close'].iloc[-1]
                self.process_signals(code, signals, price)
            self.status_signal.emit(f'运行中 - {dt.datetime.now().strftime("%H:%M:%S")}')
            self.log_signal.emit(f'--- 轮询完成 {dt.datetime.now().strftime("%H:%M:%S")} ---')
            time.sleep(self.params.get('interval', 10))

        self.status_signal.emit('已停止')
        self.log_signal.emit('线程退出')

    def load_stock_pool(self):
        raw = self.params.get('stock_pool', '')
        codes = []
        for part in re.split(r'[,\s;]+', raw):
            part = part.strip()
            if part and ('.' in part or len(part) == 6):
                # 补全后缀规则:6开头为SH,0/3开头为SZ
                if len(part) == 6 and '.' not in part:
                    if part[0] in '600601603605688':
                        part += '.SH'
                    else:
                        part += '.SZ'
                codes.append(part.upper())
        return list(dict.fromkeys(codes))

    def get_data(self, code):
        if self.params.get('simulation', True):
            return self._generate_sim_data(code)
        else:
            # 尝试从xtdata获取历史数据
            try:
                if QMT_AVAILABLE:
                    xtdata.subscribe_quote(code, period='1d', count=100)
                    data = xtdata.get_market_data_ex([], [code], period='1d', count=100)
                    if code in data and len(data[code]) > 30:
                        df = data[code].copy()
                        df = df.rename(columns={'time':'date'})
                        return df
            except Exception as e:
                self.log_signal.emit(f'获取实盘数据失败 {code}: {e}')
            return self._generate_sim_data(code)

    def _generate_sim_data(self, code, days=120):
        """生成模拟K线"""
        seed = abs(hash(code)) % 100000
        np.random.seed(seed)
        base = 10 + seed % 100
        returns = np.random.randn(days) * 0.02
        price = base * np.exp(np.cumsum(returns))
        close = price
        open_ = np.concatenate([[base], price[:-1]]) * (1 + np.random.randn(days) * 0.01)
        high = np.maximum(open_, close) * (1 + np.random.randn(days) * 0.015 + 0.005)
        low = np.minimum(open_, close) * (1 - np.random.randn(days) * 0.015 - 0.005)
        volume = np.random.randint(100000, 5000000, days).astype(float)
        df = pd.DataFrame({'open': open_, 'high': high, 'low': low, 'close': close, 'volume': volume})
        return df

    def process_signals(self, code, signals, price):
        buy = False
        sell = False
        for name, flag in signals.items():
            if flag:
                if 'sell' in name.lower() or '卖出' in name:
                    sell = True
                else:
                    buy = True

        with self.lock:
            shares = self.positions.get(code, 0)

        if sell and shares > 0:
            self.execute_order(code, price, shares, buy=False, reason='卖出信号')
        elif buy and shares == 0:
            volume = self.params.get('trade_volume', 1000)
            self.execute_order(code, price, volume, buy=True, reason='买入信号')
        elif buy and shares > 0:
            # 已有持仓,信号再现时忽略
            self.log_signal.emit(f'{code} 已有持仓,忽略买入信号 @ {price:.2f}')

    def execute_order(self, code, price, volume, buy=True, reason=''):
        with self.lock:
            shares = self.positions.get(code, 0)
            if buy:
                cost = price * volume
                if cost > self.cash:
                    self.log_signal.emit(f'{code} 资金不足,无法买入 {volume}股,需 {cost:.0f},可用 {self.cash:.0f}')
                    return
                self.cash -= cost
                self.positions[code] = shares + volume
                self.log_signal.emit(f'[买入] {code} 数量{volume} 价格{price:.2f} 原因:{reason} 剩余资金:{self.cash:.2f}')
                self.trade_signal.emit(f'[买入] {code} @ {price:.2f} x{volume} | {reason}')
            else:
                volume = min(volume, shares)
                if volume <= 0:
                    return
                revenue = price * volume
                self.cash += revenue
                self.positions[code] = shares - volume
                if self.positions[code] == 0:
                    del self.positions[code]
                self.log_signal.emit(f'[卖出] {code} 数量{volume} 价格{price:.2f} 原因:{reason} 资金:{self.cash:.2f}')
                self.trade_signal.emit(f'[卖出] {code} @ {price:.2f} x{volume} | {reason}')
            self.update_position_display()

    def update_position_display(self):
        with self.lock:
            info = []
            info.append(f'可用资金: {self.cash:,.2f}\n')
            info.append('当前持仓:')
            for code, vol in self.positions.items():
                info.append(f'  {code}: {vol}股')
            text = '\n'.join(info)
        self.position_signal.emit(text)

    def stop(self):
        self.stop_event.set()
        self.wait(2000)
        if self.trader.connected:
            self.trader.xt_trader.stop()

# ------------------ 主窗口 ------------------
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.worker = None
        self.setWindowTitle('miniQMT 量化指标交易系统')
        self.setMinimumSize(1100, 700)
        self._init_ui()
        self._load_defaults()

    def _init_ui(self):
        # 中央部件
        central = QWidget()
        self.setCentralWidget(central)
        main_layout = QVBoxLayout(central)
        main_layout.setContentsMargins(10, 10, 10, 10)
        main_layout.setSpacing(8)

        # 标题
        title = QLabel('✧ miniQMT 指标公式智能交易系统 ✧')
        title.setObjectName('titleLabel')
        title.setAlignment(Qt.AlignCenter)
        main_layout.addWidget(title)

        # 主分割器
        splitter = QSplitter(Qt.Horizontal)
        main_layout.addWidget(splitter, stretch=1)

        # 左侧参数区
        left_panel = QScrollArea()
        left_panel.setWidgetResizable(True)
        left_panel.setFrameShape(QFrame.NoFrame)
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)
        left_layout.setContentsMargins(10, 10, 10, 10)
        left_panel.setWidget(left_widget)
        splitter.addWidget(left_panel)
        splitter.setStretchFactor(0, 0)

        # 右侧信息区
        right_tabs = QTabWidget()
        splitter.addWidget(right_tabs)
        splitter.setStretchFactor(1, 1)
        splitter.setSizes([420, 700])

        # 参数分组
        # 1. 连接设置
        connect_group = QGroupBox('连接设置')
        grid = QGridLayout()
        self.account_edit = QLineEdit()
        self.account_edit.setPlaceholderText('例如: 123456789')
        self.qmt_path_edit = QLineEdit()
        self.qmt_path_edit.setPlaceholderText('QMT安装目录,如 C:/QMT/bin.x64')
        browse_btn = QPushButton('浏览...')
        browse_btn.setFixedWidth(80)
        browse_btn.clicked.connect(self._browse_qmt_path)
        grid.addWidget(QLabel('资金账号:'), 0, 0)
        grid.addWidget(self.account_edit, 0, 1, 1, 2)
        grid.addWidget(QLabel('QMT路径:'), 1, 0)
        grid.addWidget(self.qmt_path_edit, 1, 1)
        grid.addWidget(browse_btn, 1, 2)
        self.simulation_check = QCheckBox('模拟交易(不连接实盘)')
        self.simulation_check.setChecked(True)
        grid.addWidget(self.simulation_check, 2, 0, 1, 3)
        connect_group.setLayout(grid)
        left_layout.addWidget(connect_group)

        # 2. 股票池与交易参数
        pool_group = QGroupBox('股票池 & 交易参数')
        grid = QGridLayout()
        grid.addWidget(QLabel('股票池(逗号/空格分隔):'), 0, 0, 1, 2)
        self.pool_edit = QTextEdit()
        self.pool_edit.setFixedHeight(80)
        self.pool_edit.setPlaceholderText('600000.SH,000001.SZ,600519.SH')
        grid.addWidget(self.pool_edit, 1, 0, 1, 2)
        grid.addWidget(QLabel('轮询间隔(秒):'), 2, 0)
        self.interval_spin = QSpinBox()
        self.interval_spin.setRange(1, 300)
        self.interval_spin.setValue(10)
        grid.addWidget(self.interval_spin, 2, 1)
        grid.addWidget(QLabel('每次交易数量(股):'), 3, 0)
        self.volume_spin = QSpinBox()
        self.volume_spin.setRange(100, 100000)
        self.volume_spin.setValue(1000)
        self.volume_spin.setStepType(QSpinBox.AdaptiveDecimalStepType)
        grid.addWidget(self.volume_spin, 3, 1)
        self.auto_stop_check = QCheckBox('信号后自动停止该股票本轮轮询')
        self.auto_stop_check.setChecked(False)
        grid.addWidget(self.auto_stop_check, 4, 0, 1, 2)
        pool_group.setLayout(grid)
        left_layout.addWidget(pool_group)

        # 3. 公式编辑
        formula_group = QGroupBox('指标公式 (通达信语法)')
        vbox = QVBoxLayout()
        self.formula_edit = QTextEdit()
        self.formula_edit.setPlaceholderText('''MA5:=MA(CLOSE,5);\nMA10:=MA(CLOSE,10);\n买入: CROSS(MA5,MA10);\n卖出: CROSS(MA10,MA5);''')
        vbox.addWidget(self.formula_edit)
        formula_group.setLayout(vbox)
        left_layout.addWidget(formula_group)

        # 4. 控制按钮
        btn_group = QGroupBox('控制')
        btn_layout = QHBoxLayout()
        self.start_btn = QPushButton('▶ 启动')
        self.start_btn.clicked.connect(self._start_trading)
        self.stop_btn = QPushButton('■ 停止')
        self.stop_btn.setEnabled(False)
        self.stop_btn.clicked.connect(self._stop_trading)
        self.save_btn = QPushButton('💾 保存配置')
        self.save_btn.clicked.connect(self._save_config)
        self.load_btn = QPushButton('📂 加载配置')
        self.load_btn.clicked.connect(self._load_config)
        btn_layout.addWidget(self.start_btn)
        btn_layout.addWidget(self.stop_btn)
        btn_layout.addWidget(self.save_btn)
        btn_layout.addWidget(self.load_btn)
        btn_group.setLayout(btn_layout)
        left_layout.addWidget(btn_group)
        left_layout.addStretch()

        # 右侧标签页
        # 日志
        log_tab = QWidget()
        log_layout = QVBoxLayout(log_tab)
        self.log_edit = QTextEdit()
        self.log_edit.setObjectName('logEdit')
        self.log_edit.setReadOnly(True)
        log_layout.addWidget(self.log_edit)
        right_tabs.addTab(log_tab, '📋 运行日志')

        # 持仓
        pos_tab = QWidget()
        pos_layout = QVBoxLayout(pos_tab)
        self.pos_edit = QTextEdit()
        self.pos_edit.setObjectName('logEdit')
        self.pos_edit.setReadOnly(True)
        pos_layout.addWidget(self.pos_edit)
        right_tabs.addTab(pos_tab, '📊 持仓/资金')

        # 交易信号
        signal_tab = QWidget()
        sig_layout = QVBoxLayout(signal_tab)
        self.signal_edit = QTextEdit()
        self.signal_edit.setObjectName('logEdit')
        self.signal_edit.setReadOnly(True)
        sig_layout.addWidget(self.signal_edit)
        right_tabs.addTab(signal_tab, '🔥 交易信号')

        # 状态栏
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        self.status_bar.showMessage('就绪')
        self.status_label = QLabel(' 状态: 停止')
        self.status_bar.addPermanentWidget(self.status_label)

    # ---------- 默认参数 ----------
    def _load_defaults(self):
        self.account_edit.setText('')
        self.qmt_path_edit.setText('')
        self.pool_edit.setPlainText('600519.SH,000001.SZ,600036.SH')
        self.interval_spin.setValue(10)
        self.volume_spin.setValue(1000)
        self.simulation_check.setChecked(True)
        self.formula_edit.setPlainText('''MA5:=MA(CLOSE,5);
MA10:=MA(CLOSE,10);
买入: CROSS(MA5,MA10);
卖出: CROSS(MA10,MA5);''')

    # ---------- 槽函数 ----------
    def _browse_qmt_path(self):
        path = QFileDialog.getExistingDirectory(self, '选择QMT安装目录')
        if path:
            self.qmt_path_edit.setText(path)

    def _collect_params(self):
        params = {
            'account': self.account_edit.text().strip(),
            'qmt_path': self.qmt_path_edit.text().strip(),
            'stock_pool': self.pool_edit.toPlainText().strip(),
            'interval': self.interval_spin.value(),
            'trade_volume': self.volume_spin.value(),
            'simulation': self.simulation_check.isChecked(),
            'formula': self.formula_edit.toPlainText(),
        }
        return params

    def _start_trading(self):
        if self.worker and self.worker.isRunning():
            QMessageBox.warning(self, '提示', '交易已启动')
            return
        params = self._collect_params()
        if not params['stock_pool']:
            QMessageBox.warning(self, '参数错误', '请输入股票池')
            return
        if not params['formula'].strip():
            QMessageBox.warning(self, '参数错误', '请输入公式源码')
            return
        if not params['simulation'] and (not params['qmt_path'] or not params['account']):
            QMessageBox.warning(self, '参数错误', '实盘模式必须填写QMT路径和资金账号')
            return

        self.worker = TradingWorker(params)
        self.worker.log_signal.connect(self._append_log)
        self.worker.status_signal.connect(self._update_status)
        self.worker.trade_signal.connect(self._append_signal)
        self.worker.position_signal.connect(self._update_position)
        self.worker.finished.connect(self._on_worker_finished)
        self.worker.start()

        self.start_btn.setEnabled(False)
        self.stop_btn.setEnabled(True)
        self.status_label.setText(' 状态: 运行中')
        self.signal_edit.clear()

    def _stop_trading(self):
        if self.worker:
            self.worker.stop()
            self.stop_btn.setEnabled(False)
            self.status_label.setText(' 状态: 停止中...')

    def _on_worker_finished(self):
        self.start_btn.setEnabled(True)
        self.stop_btn.setEnabled(False)
        self.status_label.setText(' 状态: 已停止')

    def _append_log(self, msg):
        timestamp = dt.datetime.now().strftime('%H:%M:%S')
        self.log_edit.append(f'[{timestamp}] {msg}')
        # 自动滚动到底部
        self.log_edit.moveCursor(self.log_edit.textCursor().MoveOperation.End)

    def _append_signal(self, msg):
        timestamp = dt.datetime.now().strftime('%H:%M:%S')
        self.signal_edit.append(f'[{timestamp}] {msg}')
        self.signal_edit.moveCursor(self.signal_edit.textCursor().MoveOperation.End)

    def _update_status(self, msg):
        self.status_label.setText(f' 状态: {msg}')
        self.status_bar.showMessage(msg)

    def _update_position(self, text):
        self.pos_edit.setPlainText(text)

    def _save_config(self):
        params = self._collect_params()
        file_path, _ = QFileDialog.getSaveFileName(self, '保存配置', 'miniQMT_config.json', 'JSON (*.json)')
        if file_path:
            with open(file_path, 'w', encoding='utf-8') as f:
                json.dump(params, f, ensure_ascii=False, indent=2)
            self._append_log(f'配置已保存到 {file_path}')

    def _load_config(self):
        file_path, _ = QFileDialog.getOpenFileName(self, '加载配置', '', 'JSON (*.json)')
        if file_path:
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    params = json.load(f)
                self.account_edit.setText(params.get('account', ''))
                self.qmt_path_edit.setText(params.get('qmt_path', ''))
                self.pool_edit.setPlainText(params.get('stock_pool', ''))
                self.interval_spin.setValue(params.get('interval', 10))
                self.volume_spin.setValue(params.get('trade_volume', 1000))
                self.simulation_check.setChecked(params.get('simulation', True))
                self.formula_edit.setPlainText(params.get('formula', ''))
                self._append_log(f'配置已从 {file_path} 加载')
            except Exception as e:
                QMessageBox.critical(self, '错误', f'加载配置失败: {e}')

    def closeEvent(self, event):
        if self.worker and self.worker.isRunning():
            reply = QMessageBox.question(self, '确认退出', '交易线程仍在运行,确定退出吗?',
                                         QMessageBox.Yes | QMessageBox.No)
            if reply == QMessageBox.Yes:
                self.worker.stop()
                event.accept()
            else:
                event.ignore()
        else:
            event.accept()

# ------------------ 自测试 ------------------
def self_test():
    """验证公式引擎基本功能"""
    text = """
MA5:=MA(CLOSE,5);
MA10:=MA(CLOSE,10);
买入: CROSS(MA5,MA10);
卖出: CROSS(MA10,MA5);
"""
    engine = FormulaEngine(text)
    # 生成简单上升趋势数据
    dates = pd.date_range('2024-01-01', periods=50)
    close = np.linspace(10, 20, 50) + np.random.randn(50) * 0.2
    df = pd.DataFrame({'close': close,
                       'open': close * 0.99,
                       'high': close * 1.02,
                       'low': close * 0.98,
                       'volume': np.random.randint(10000, 100000, 50)}
                      , index=dates)
    signals = engine.calculate(df)
    print('信号结果:', signals)
    assert isinstance(signals, dict)
    assert '买入' in signals and '卖出' in signals
    print('公式引擎自测试通过')

# ------------------ 主函数 ------------------
def main():
    # 运行自测试(可选)
    try:
        self_test()
    except Exception as e:
        print(f'自测试未通过: {e}')

    app = QApplication(sys.argv)
    app.setStyleSheet(QSS)
    # 设置深色调色板
    palette = QPalette()
    palette.setColor(QPalette.Window, QColor('#0d1b2a'))
    palette.setColor(QPalette.WindowText, QColor('#e0f7fa'))
    palette.setColor(QPalette.Base, QColor('#1b263b'))
    palette.setColor(QPalette.Text, QColor('#ffffff'))
    palette.setColor(QPalette.Button, QColor('#1f6feb'))
    palette.setColor(QPalette.ButtonText, QColor('#ffffff'))
    palette.setColor(QPalette.Highlight, QColor('#1f6feb'))
    palette.setColor(QPalette.HighlightedText, QColor('#ffffff'))
    app.setPalette(palette)

    window = MainWindow()
    window.show()
    sys.exit(app.exec())

if __name__ == '__main__':
    main()

网上设计指标,设计Python策略网上报价合计不止5K,

现在用【小白量化Qbuddy】都自动做好了。能省就是赚。

你现在开发出了设计【miniQMT指标公式计算量化平台】代码,用户修改不同指标公式,实现不同交易策略。这个代码值多少钱?

下面是【小白量化Qbuddy】免费下载网盘。

通过百度网盘分享的文件:Qbuddy

链接: https://pan.baidu.com/s/1kfVL3A1-_EVhXGiKrLBXpA?pwd=xblh 提取码: xblh

点赞和关注我的博客!
超越自己是我的每一步,我的进步就是你的进步!

相关推荐
猎头南楼14 分钟前
VLA 模型在双臂机器人操作中的工程落地:从 pi0 到 diffusion policy 的实践思考
人工智能·机器人
阿童木写作17 分钟前
跨境电商图片翻译工具,批量翻译视频字幕一键抠图
人工智能·python·音视频
ITmaster073131 分钟前
从零开始实现一个 AI Agent CLI
人工智能
user-猴子1 小时前
钛媒体测五款、光锥智能测WorkBuddy、用户测AiPy——三组实测交叉对比,哪款AI办公工具最值得下载?
人工智能
IT古董1 小时前
AI 资讯日报 | 2026年8月29日:开源大模型三连发,DeepSeek 500 亿融资落地
人工智能·开源
何以解忧,唯有..2 小时前
Pydantic 介绍与使用:Python 数据校验的现代方案
数据库·python·microsoft
IT_陈寒2 小时前
Vite打包时的静态资源坑,我帮你踩过了
前端·人工智能·后端
IJCAST2 小时前
IJCAST最新一期已经发布
人工智能·深度学习·神经网络
又幸福了哥2 小时前
Python入门到高级(知识点七)
python