🚀 AI 一键生成 qmt 策略代码

QMT 策略中如何读取本地文件或保存自定义日志?

qmt | 发布时间: 2025/12/2
以下内容由 EasyQuant 生成。

问题描述

如何在策略中读取一个本地 txt 文件里的股票列表,并将策略的交易记录保存到另一个 csv 文件里?

解决方案

这是一个非常实用的需求。在 QMT 策略中,我们可以利用 Python 标准库中的文件操作功能(如 open 函数)或第三方库(如 pandas)来实现本地文件的读写。

以下是一个完整的策略示例代码。该策略在初始化时读取本地 TXT 文件中的股票代码设置股票池,并在盘中产生交易信号时将记录追加写入到本地 CSV 文件中。

策略代码实现

# -*- coding: gbk -*-
import pandas as pd
import os
import datetime

def init(ContextInfo):
    """
    初始化函数:读取本地股票列表,初始化CSV文件
    """
    # ================= 配置路径 =================
    # 请确保文件夹存在,或者手动创建 D:\qmt_data\ 目录
    # 注意:路径字符串前加 r 表示原始字符串,防止转义字符错误
    ContextInfo.stock_list_path = r'D:\qmt_data\stock_list.txt'
    ContextInfo.record_save_path = r'D:\qmt_data\trade_records.csv'
    
    # ================= 1. 读取本地 TXT 股票列表 =================
    stock_list = []
    if os.path.exists(ContextInfo.stock_list_path):
        try:
            with open(ContextInfo.stock_list_path, 'r', encoding='utf-8') as f:
                # 逐行读取,去除空白符,并过滤空行
                lines = f.readlines()
                for line in lines:
                    code = line.strip()
                    if code:
                        stock_list.append(code)
            
            print(f"成功读取股票列表,共 {len(stock_list)} 只: {stock_list}")
            
            # 设定股票池
            ContextInfo.set_universe(stock_list)
            ContextInfo.my_stocks = stock_list # 存入ContextInfo以便handlebar使用
            
        except Exception as e:
            print(f"读取股票列表文件出错: {e}")
    else:
        print(f"错误:找不到文件 {ContextInfo.stock_list_path},请先创建该文件。")
        ContextInfo.my_stocks = []

    # ================= 2. 初始化 CSV 记录文件 =================
    # 如果文件不存在,则创建并写入表头
    if not os.path.exists(ContextInfo.record_save_path):
        try:
            with open(ContextInfo.record_save_path, 'w', encoding='gbk') as f:
                # 写入表头:时间,代码,操作,价格,数量
                f.write("Time,StockCode,Action,Price,Volume\n")
            print(f"已创建新的交易记录文件: {ContextInfo.record_save_path}")
        except Exception as e:
            print(f"创建CSV文件失败: {e}")

    # 设置账号(请替换为您自己的账号)
    ContextInfo.account_id = '6000000000' 
    ContextInfo.set_account(ContextInfo.account_id)


def handlebar(ContextInfo):
    """
    K线周期运行函数:执行策略并记录交易
    """
    # 获取当前K线对应的时间
    index = ContextInfo.barpos
    timetag = ContextInfo.get_bar_timetag(index)
    # 将时间戳转换为易读的字符串格式
    current_time = timetag_to_datetime(timetag, '%Y-%m-%d %H:%M:%S')
    
    # 遍历我们在 init 中读取到的股票列表
    for stock in ContextInfo.my_stocks:
        # 获取最新收盘价
        # 注意:get_market_data_ex 返回的是字典 {code: dataframe}
        market_data = ContextInfo.get_market_data_ex(
            ['close', 'open'], 
            [stock], 
            period=ContextInfo.period, 
            count=1, 
            subscribe=True
        )
        
        if stock in market_data and not market_data[stock].empty:
            close_price = market_data[stock].iloc[-1]['close']
            open_price = market_data[stock].iloc[-1]['open']
            
            # === 示例策略逻辑:如果收盘价大于开盘价,模拟买入 ===
            # (这里仅为演示写入CSV功能,非真实盈利策略)
            if close_price > open_price:
                action = "BUY"
                volume = 100
                
                # 执行下单 (此处注释掉,仅演示记录功能)
                # order_shares(stock, volume, 'fix', close_price, ContextInfo, ContextInfo.account_id)
                
                # === 3. 将交易记录写入 CSV 文件 ===
                write_trade_record(ContextInfo.record_save_path, current_time, stock, action, close_price, volume)
                
                # 为了防止一根K线重复写入,实际策略中通常需要控制信号触发频率
                # 这里简单打印日志
                print(f"[{current_time}] 记录写入: {stock} {action} {close_price}")


def write_trade_record(filepath, time_str, code, action, price, volume):
    """
    辅助函数:追加写入一行记录到CSV
    """
    try:
        # 使用 'a' 模式 (append) 追加内容
        with open(filepath, 'a', encoding='gbk') as f:
            line = f"{time_str},{code},{action},{price},{volume}\n"
            f.write(line)
    except Exception as e:
        print(f"写入CSV失败: {e}")

代码详解

  1. 文件路径设置

    • 使用了 r'...' 原始字符串格式来定义路径(例如 r'D:\qmt_data\...'),这在 Windows 系统中非常重要,可以避免反斜杠 \ 被识别为转义字符。
  2. 读取 TXT 文件 (init 函数中)

    • 使用 open(..., 'r') 模式打开文件。
    • 使用 readlines() 读取所有行,并配合 strip() 去除每行末尾的换行符和空格。
    • 数据准备:您需要在 D:\qmt_data\ 下创建一个 stock_list.txt,内容格式如下(一行一个代码):
      600000.SH
      000001.SZ
      600519.SH
      
  3. 写入 CSV 文件 (write_trade_record 函数)

    • 初始化:在 init 中检查文件是否存在。如果不存在,使用 'w' (write) 模式创建文件并写入表头(Header)。
    • 追加写入:在交易发生时,使用 'a' (append) 模式打开文件。这确保了新的交易记录会添加到文件末尾,而不会覆盖旧数据。
    • 编码:示例中使用了 encoding='gbk',这是为了确保生成的 CSV 文件在 Excel 中打开时中文不乱码。如果您主要用于程序处理,也可以改为 utf-8
  4. ContextInfo 的作用

    • 我们将读取到的 stock_list 和文件路径保存在 ContextInfo 对象中(如 ContextInfo.my_stocks)。这样,在 handlebar 函数中就可以直接访问这些变量,而不需要重新读取文件或重新定义路径。

注意事项

  • 文件权限:请确保 QMT 软件有权限读写您设置的文件夹(如 D 盘根目录有时需要管理员权限,建议放在子文件夹中)。
  • 性能:频繁打开和关闭文件(IO操作)会消耗一定时间。在回测模式下影响不大,但在高频实盘策略中,建议使用内存缓存数据,定期批量写入,或者使用 Python 的 logging 模块。
  • 数据格式get_market_data_ex 返回的是 DataFrame,取值时要注意判空。

Q&A

Q: 如果我的 txt 文件里股票代码是用逗号分隔的怎么办?
A: 可以修改读取逻辑,读取整个文件内容后使用 split(',') 分割。例如:

content = f.read()
stock_list = [x.strip() for x in content.split(',') if x.strip()]

Q: 为什么生成的 CSV 文件用 Excel 打开是乱码?
A: 这通常是编码问题。Excel 默认对 CSV 使用 ANSI (GBK) 编码。请确保在 open 函数中指定 encoding='gbk'。如果指定了 utf-8,在 Excel 中导入数据时需要选择 UTF-8 格式。

Q: 这个策略可以直接回测吗?
A: 可以。在回测模式下,handlebar 会随着历史数据逐根 K 线运行,您的 CSV 文件中将会生成大量的历史模拟交易记录。建议在回测前手动删除旧的 CSV 文件,以免数据混淆。