165 浏览
0

想要插入一个atr的副图指标,现在在按照示例文档尝试插入ma

复制过去的,代码在17-28

回测后api.py报错ValueError: assignment destination is read-only

请问是什么问题?应该怎么修改?感谢!

from tqsdk import TqApi, TqAuth, TqBacktest, TqSim
from datetime import datetime,date
from tqsdk import BacktestFinished
from tqsdk.ta import ATR,MA
import pandas as pd
 acc=TqSim()
try:
    api=TqApi(acc,backtest=TqBacktest(start_dt=datetime(2026,2,26,21,0,0), end_dt=datetime(2026,3,2,15,0,0)),auth=TqAuth("",""),web_gui="http://192.168.0.101:54031")
     klines = api.get_kline_serial("SHFE.ag2604", 60, data_length=200)
     ticks = api.get_tick_serial("SHFE.ag2604")                                     # 导入tick数据
    quote = api.get_quote("SHFE.ag2604")
    position = api.get_position("SHFE.ag2604")
    day_close_hour, day_close_minute = 14, 58
    night_close_hour, night_close_minute = 2, 28
    noon_close_hour, noon_close_minute = 11, 28
    long_open_pos_info = []
     while True:
        ma = MA(klines, 30)  # 使用tqsdk自带指标函数计算均线
     # 示例1: 在附图中画一根绿色的ma指标线
        klines["ma_B2"] = ma.ma
        klines["ma_B2.board"] = "B2"  # 设置附图: 可以设置任意字符串,同一字符串表示同一副图
        klines["ma_B2.color"] = "green"  # 设置为绿色. 以下设置颜色方式都可行: "green", "#00FF00", "rgb(0,255,0)", "rgba(0,125,0,0.5)"
     # 示例2: 在另一个附图画一根比ma小4的宽度为4的紫色指标线
        klines["ma_4"] = ma.ma - 4
        klines["ma_4.board"] = "MA4"  # 设置为另一个附图
        klines["ma_4.color"] = 0xFF9933CC  # 设置为紫色, 或者 "#9933FF"
        klines["ma_4.width"] = 4  # 设置宽度为4,默认为1
          api.wait_update()
                  # ======================================== 开仓 ======================================================
        if api.is_changing(klines.iloc[-1], "datetime"):
            now_time = datetime.strptime(quote.datetime, "%Y-%m-%d %H:%M:%S.%f")
            is_near_close = (now_time.hour == day_close_hour and now_time.minute >= 55) or \
                            (now_time.hour == night_close_hour and now_time.minute >= 25) or \
                            (now_time.hour == noon_close_hour and now_time.minute >= 25)
            if is_near_close:
                continue
             atr = ATR(klines, 14)
            long_current_atr = atr.atr.iloc[-2]
               # ============================== 多单开仓条件 ==============================
            long_prev_k_1 = klines.iloc[-4]  # 第一根阴线
            long_prev_k = klines.iloc[-3]  # 第二根(最后一根)阴线
            long_current = klines.iloc[-2] # 阳线
             # 1. 两根连续阴线,实体≥2跳
            long_prev_k_1_is_down = long_prev_k_1["open"] - long_prev_k_1["close"] >= 2 * quote.price_tick
            long_prev_k_is_down = long_prev_k["open"] - long_prev_k["close"] >= 2 * quote.price_tick
            # 2. 阳线实体在0.8~1.2ATR之间
            long_current_is_up = long_current["close"] - long_current["open"] >= 2 * quote.price_tick
            long_current_entity = long_current["close"] - long_current["open"]
            long_entity_in_range = (long_current_entity >= long_current_atr * 0.8) and (long_current_entity <= long_current_atr * 1.2)
            # 3. 最后一根阴线增量、减仓
            long_prev_vol_change = long_prev_k["volume"] - long_prev_k_1["volume"]
            long_prev_vol_threshold = long_prev_k_1["volume"] * 0.1 if long_prev_k_1["volume"] <=2000 else long_prev_k_1["volume"] *0.05
            long_prev_vol_incr = long_prev_vol_change >= long_prev_vol_threshold
            long_prev_oi_decr = (long_prev_k["close_oi"] - long_prev_k["open_oi"]) < -10
            # 4. 阳线减量、减仓
            long_current_vol_change = long_current["volume"] - long_prev_k["volume"]
            long_current_vol_threshold = long_prev_k["volume"] *0.1 if long_prev_k["volume"] <=2000 else long_prev_k["volume"] *0.05
            long_current_vol_decr = long_current_vol_change <= -long_current_vol_threshold
            long_current_oi_decr = (long_current["close_oi"] - long_current["open_oi"]) < -10
             # 所有条件满足开多单
            if long_prev_k_1_is_down and long_prev_k_is_down and long_current_is_up and long_entity_in_range and long_prev_vol_incr and long_prev_oi_decr and long_current_vol_decr and long_current_oi_decr:
                print("满足开多条件,开多1手")
                long_order_stop_loss = long_current["low"] - quote.price_tick
                order = api.insert_order(symbol="SHFE.ag2604", direction="BUY", offset="OPEN", limit_price=quote.ask_price1, volume=1)
                while order.status != "FINISHED":
                    api.wait_update()
                 # 计算多单止损止盈
                long_open_price = order.trade_price
# 1. 先计算原始的止损价、原始止损距离
                original_stop_loss = long_current["low"] - quote.price_tick
                original_stop_distance = long_open_price - original_stop_loss
# 2. 止损价做最大61跳限制,不影响止盈计算
                long_max_allowed_stop_loss = long_open_price - 61 * quote.price_tick
                long_order_stop_loss = max(original_stop_loss, long_max_allowed_stop_loss)
# 3. 止盈用原始止损距离减1跳,不受最大止损限制
                long_order_take_profit = long_open_price + (original_stop_distance - quote.price_tick)
                 long_open_pos_info.append({
                    "stop_loss": long_order_stop_loss,
                    "take_profit": long_order_take_profit,
                    "volume":1,
                    "open_price": long_open_price
                })
                print(f"已开多单,开仓价{long_open_price:.2f},止损价{long_order_stop_loss:.2f},止盈价{long_order_take_profit:.2f}")
         # =============================================== 平仓:止损+止盈 ======================================================
        if api.is_changing(quote):
            if position.pos_long > 0 and len(long_open_pos_info) > 0:
                long_need_close_volume = 0
                long_remain_pos = []
                for pos in long_open_pos_info:
                    if quote.last_price <= pos["stop_loss"]:
                        print(f"多单触发实时止损,开仓价{pos['open_price']:.2f},止损价{pos['stop_loss']:.2f},平仓{pos['volume']}手,当前价{quote.last_price:.2f}")
                        long_need_close_volume += pos["volume"]
                    elif quote.last_price >= pos["take_profit"]:
                        print(f"多单触发实时止盈,开仓价{pos['open_price']:.2f},止盈价{pos['take_profit']:.2f},平仓{pos['volume']}手,当前价{quote.last_price:.2f}")
                        long_need_close_volume += pos["volume"]
                    else:
                        long_remain_pos.append(pos)
                 # 更新持仓+执行平仓(仅平今仓)
                long_open_pos_info = long_remain_pos
                if long_need_close_volume > 0:
                    long_close_today = min(long_need_close_volume, position.pos_long_today)
                    if long_close_today >0:
                        order = api.insert_order(symbol="SHFE.ag2604", direction="SELL", offset="CLOSETODAY", limit_price=quote.bid_price1, volume=long_close_today)
                        while order.status != "FINISHED":
                            api.wait_update()
                 if len(long_open_pos_info) > 0:
                    print(f"剩余多单{len(long_open_pos_info)}笔,当前价{quote.last_price:.2f},各笔止损价:{[p['stop_loss'] for p in long_open_pos_info]}")
         # 收盘平所有多单(仅平今仓)
        if api.is_changing(quote, "datetime"):
            now_time = datetime.strptime(quote.datetime, "%Y-%m-%d %H:%M:%S.%f")
            if ( (now_time.hour == day_close_hour and now_time.minute >= day_close_minute) or \
                 (now_time.hour == night_close_hour and now_time.minute >= night_close_minute) or \
                 (now_time.hour == noon_close_hour and now_time.minute >= noon_close_minute)) \
                and position.pos_long > 0:
                print("临近收盘,平仓所有多单")
                long_close_today = min(position.pos_long, position.pos_long_today)
                if long_close_today>0:
                    order = api.insert_order(symbol="SHFE.ag2604", direction="SELL", offset="CLOSETODAY", limit_price=quote.bid_price1, volume=long_close_today)
                    while order.status != "FINISHED":
                        api.wait_update()
                long_open_pos_info = []
  except BacktestFinished as e:
    print(acc.tqsdk_stat)
    trades = acc.get_trade()
    trade_list = [dict(trade) for trade in trades.values()]
    pd.DataFrame(trade_list).to_csv("成交记录.csv", index=False, encoding="utf-8-sig")
    print("回测结果已保存到文件")
    # 保留死循环,方便webgui查看结果
    while True:
        api.wait_update()

ringo 已回答的问题 7天 前
0

问题不在 ATR/MA 公式,而在 web_gui 自定义画线和 pandas 3.x 的兼容上。

你加了:

<code>klines["ma_B2"] = ma.ma
klines["ma_B2.board"] = "B2"</code>

之后,TqSdk 内部会在 [api.py (line 3949)](D:/git/tqsdk-python-private/tqsdk/api.py:3949) 里把这些新增列转成 numpy 数组保存。当前环境的 pandas 3.0.1 里,Series.to_numpy() 返回的是只读数组;后面 K 线滚动时,TqSdk 在 [api.py (line 3761)](D:/git/tqsdk-python-private/tqsdk/api.py:3761) / [api.py (line 3920)](D:/git/tqsdk-python-private/tqsdk/api.py:3920) 需要原地移动这些数组,于是报:

<code>ValueError: assignment destination is read-only</code>

临时解决:回退 pandas 到 2.x:

<code>python -m pip install "pandas<3"</code>

SDK 侧更稳的修法是在 _process_serial_extra_array 里复制成可写数组,把:

<code>serial["extra_array"][col] = serial["df"][col].to_numpy()</code>

改成:

<code>arr = serial["df"][col].to_numpy()
if not arr.flags.writeable:
    arr = arr.copy()
serial["extra_array"][col] = arr</code>

ATR 副图可以这样写:

<code>atr = ATR(klines, 14)
klines["atr"] = atr["atr"]
klines["atr.board"] = "ATR"
klines["atr.color"] = "orange"
klines["atr.width"] = 2</code>

结论:用户代码思路没错;这是 pandas 3.xweb_gui 绘图列变成只读数组导致的。优先建议先锁 pandas<3,同时 SDK 里补上 copy 兼容。你贴的 txt 里缩进也有些乱,但这次的 ValueError 不是缩进导致的。

ringo 已回答的问题 7天 前