You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

backtrader导入CSV数据触发'str'无to_pydatetime属性报错排查

问题描述

运行backtrader量化程序时抛出如下错误:

_load dt = tstamp.to_pydatetime() AttributeError: 'str' object has no attribute 'to_pydatetime'

报错触发场景:通过bt.feeds接口导入CSV格式行情数据,自定义了继承自bt.feeds.PandasData的PandasDataPlus类扩展cerebro的数据lines,排查后未定位错误根源。
涉及的实现代码如下:

import backtrader as bt
from backtrader.feeds import GenericCSVData
import pandas as pd
import pandas_ta as ta
import matplotlib.pyplot as plt
import datetime as dt
import numpy as np
from datetime import datetime

class PandasDataPlus(bt.feeds.PandasData):
    lines = ('crossover','crossover2','below','below2', )
    params = (('crossover', 7),('crossover2', 8),('below', 9),('below2', 10),)

class Stoch(bt.Strategy):
    def __init__(self):
        self.dataclose = self.data.close
        self.order=None
        self.buyprice = None
        self.buycomm=None

    def next(self):
        print(self.datas[0].lines.getlinealiases())

    def notify_order(self, order):
        if order.status in [order.Submitted,order.Accepted]:
            return
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log(
                    'BUY EXECUTED, Price: %.2f,Cost: %.2f, Comm: %.2f' %
                    (order.executed.price,
                     order.executed.value,
                     order.executed.comm)
                )
                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
            else:
                self.log('SELL EXECUTED, Price:Price: %.2f,Cost: %.2f, Comm: %.2f' %
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm)
                         )
            self.bar_executed = len(self)
        elif order.status in [order.Canceled,order.Margin,order.Rejected]:
            self.log('Order Canceled/Margin/Rejected')
        self.order = None

    def notify_trader(self, order):
        if not trade.isclosed:
            return
        self.log('OPERATION PROFIT, GROSS %.2f , NET %.2f'%
                 (trade.pnl,trade.pnlcomm))
    def log(self,txt,dt=None,doprint=True):
        if doprint:
            dt=dt or self.datas[0].datetime.date(0)
            print('%s,%s,'% (dt.isoformat(), txt))


if __name__ == '__main__':
    cerebro = bt.Cerebro()
    dataframe = pd.read_csv('out.csv')
    df=dataframe
    df['Datetime'] = pd.to_datetime(df['Date'])
    df.set_index('Datetime',inplace=True)
    df['Stoch'] = pd.DataFrame(ta.stoch(df['High'],df['Low'],df['Close']),columns=['STOCHk_86_15_5'])
    value = 89 
    value2 = 20
    df['crossover'] = ta.cross_value(df['Stoch'], value) 
    df['below'] = ta.cross_value(df['Stoch'], 100-value,above=False)
    df['crossover2'] = ta.cross_value(df['Stoch'], value2)
    df['below2'] = ta.cross_value(df['Stoch'], 100-value2,above=False)
    data_TSLA = PandasDataPlus(
        dataname=dataframe,
        fromdate=dt.datetime(2020,1,1),
        todate=dt.datetime(2021,1,1),
        timeframe=bt.TimeFrame.Minutes,
        datetime = 0,
        open = 1,
        high = 2,
        low = 3,
        close = 4,
        volume = 5,
        openinterest = -1,
        crossover = 7,crossover2 = 8,below = 9,below2 = 10
        )
    cerebro.adddata(data_TSLA)
    cerebro.addstrategy(Stoch)
    cerebro.addanalyzer(bt.analyzers.SharpeRatio,_name='sharpratio')
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
    cerebro.broker.setcash(10000.0)
    cerebro.broker.setcommission(commission=0.0006)
    cerebro.addsizer(bt.sizers.PercentSizer,percents=90)
    result=cerebro.run()

    print('Sharp:',result[0].analyzers.sharpratio.get_analysis()['sharperatio'])
    print('Draw:',result[0].analyzers.drawdown.get_analysis()['max']['drawdown'])
    cerebro.plot()

使用的CSV数据为常规分钟级行情数据,包含Date、Open、High、Low、Close、Volume字段。

错误根源
  • 核心错误:时间格式转换、自定义指标列计算都是在df变量上完成的,但传给PandasDataPlus的dataname参数是未做任何处理的原始dataframe。这个原始DataFrame里的Date列还是字符串类型,也没有新增的crossover等自定义列,backtrader按位置读取时间列时拿到的是字符串,无法调用to_pydatetime()方法,直接抛出属性错误。
  • 次要问题:bt.feeds.PandasData默认会将传入DataFrame的索引作为datetime时间列,额外传入datetime=0这类CSV读取专用的列位置参数,会和PandasData的读取规则冲突。
  • 隐藏bug:策略类里写的notify_trader方法名错误,backtrader的交易状态回调方法名是notify_trade,且入参是trade不是order,原写法会导致交易盈亏日志完全不触发。
修复方案
  1. 将PandasDataPlus初始化时的dataname参数从dataframe替换为处理完成的df
  2. 删除PandasDataPlus初始化时传入的datetime=0、open=1这类按列位置映射的参数,PandasData会自动按列名匹配OHLCV等默认字段,自定义字段的列名和定义的lines名称一致即可自动加载
  3. 修正回调方法名和参数,把def notify_trader(self, order):改成def notify_trade(self, trade):

修复后的数据加载部分代码参考:

data_TSLA = PandasDataPlus(
    dataname=df,  # 替换为处理好的带datetime索引的df
    fromdate=dt.datetime(2020,1,1),
    todate=dt.datetime(2021,1,1),
    timeframe=bt.TimeFrame.Minutes,
    openinterest=-1
)

改完后重新运行即可正常加载数据,不会再出现时间字段的属性错误。

内容的提问来源于stack exchange,提问作者Xu Xu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 23:03:23