BackTrader 中文文档(二十五)(1)

简介: BackTrader 中文文档(二十五)


原文:www.backtrader.com/

Bid-Ask 数据到 OHLC

原文:www.backtrader.com/blog/posts/2016-04-14-bidask-data-to-ohlc/bidask-data-to-ohlc/

最近,backtrader 通过实现线覆盖来执行了从 ohlcland 的逃逸,这允许重新定义整个层次结构,例如拥有仅包含 bid、ask 和 datetime 行的数据源。

(这里是原始 Escape from OHLC Land)

这引发了如何可视化此类数据的问题,这在OHLC格式中最有效地完成(无论是bar还是candlestick

所需步骤:

  1. 定义一个可以读取给定bid/ask格式的数据源加载器
  2. 决定将值分配给哪些字段,即:openhighlowclose(也许还有volume
  3. 决定一个重新采样方案

源数据(10 行 bid-ask 数据):

Date,Time,Symbol,Status,Bid,Ask,Bid Vol,Ask Vol
01/03/16,23:43:11,EUR/JPY,D,,130.520,,1000000
01/03/16,23:43:27,EUR/JPY,D,,130.520,,2000000
01/03/16,23:49:19,EUR/JPY,D,,130.510,,500000
01/03/16,23:49:22,EUR/JPY,D,,130.530,,1500000
01/03/16,23:49:25,EUR/JPY,D,,130.540,,750000
01/03/16,23:49:27,EUR/JPY,D,,130.550,,900000
01/03/16,23:51:25,EUR/JPY,D,,130.500,,1200000
01/03/16,23:52:27,EUR/JPY,D,,130.495,,1100000
01/03/16,23:53:25,EUR/JPY,D,,130.480,,600000
01/03/16,23:54:27,EUR/JPY,D,,130.470,,900000

之后:

  • 阅读数据不会是一个主要问题,因为最终结果必须是 OHLC,这是内置数据源在解析后提供的内容。因为这是另一种csv的变体。我们甚至可以重用现有的GenericCSVData数据源。感谢上帝,它是通用的
  • 只有单个价格元素和单个成交量元素的情况下,价格分配是清晰的:将价格分配给四个价格元素,将成交量分配给成交量
  • 当涉及到重新采样时,与上采样到更大时间框架不同,关键将是条数,即:compression
    而内置的重采样器已经可以提供相同的timeframe但是压缩了。

使用GenericCSVData将数据转换为 OHLC 格式:

data = btfeeds.GenericCSVData(
        dataname=args.data,
        dtformat='%d/%m/%y',
        # tmformat='%H%M%S',  # already the default value
        # datetime=0,  # position at default
        time=1,  # position of time
        open=5,  # position of open
        high=5,
        low=5,
        close=5,
        volume=7,
        openinterest=-1,  # -1 for not present
        timeframe=bt.TimeFrame.Ticks)

一些参数甚至不需要更改,即:

  • tmformat:因为数据源中的时间已经与默认格式匹配
  • datetime:因为日期在 csv 流中的第一个位置

其他:

  • time=1:指示时间不在单个字段中与date一起,并指定其位置
  • open=5(对于highlowclose也是一样):流中哪个字段将用作价格的源
  • volume=7:与上述相同
  • openinterest=-1:负值表示此字段不存在

一旦数据准备就绪,就只需对其进行重新采样:

cerebro.resampledata(data,
                         timeframe=bt.TimeFrame.Ticks,
                         compression=args.compression)

我们提供相同的timeframe,数据携带的是TimeFrame.Ticks,以确保数据不被上采样。而compression是从命令行中传递的参数,因此:compression=args.compression

一个示例执行:

$ ./bidask-to-ohlc.py --compression 2
2016-03-01 23:43:27,130.52,130.52,130.52,130.52,3000000.0
2016-03-01 23:49:22,130.51,130.53,130.53,130.53,2000000.0
2016-03-01 23:49:27,130.54,130.55,130.55,130.55,1650000.0
2016-03-01 23:52:27,130.5,130.5,130.5,130.495,2300000.0
2016-03-01 23:54:27,130.48,130.48,130.48,130.47,1500000.0

不出所料,我们已经从Bid/Ask格式转换为OHLC格式,并且由于分配给压缩的2,数据已经从10行减少到5行。

也不应该让人惊讶,backtrader不能创造奇迹,如果compression因子不是原始行数的除数,它将传递rows / compression + 1行新行:

$ ./bidask-to-ohlc.py --compression 3
2016-03-01 23:49:19,130.52,130.52,130.52,130.51,3500000.0
2016-03-01 23:49:27,130.53,130.55,130.55,130.55,3150000.0
2016-03-01 23:53:25,130.5,130.5,130.5,130.48,2900000.0
2016-03-01 23:54:27,130.47,130.47,130.47,130.47,900000.0

在这种情况下,10 / 3 = 3.33333,这就是为什么会传递4行的原因。

当然,现在手中有了 OHLC 数据,结果可以绘制出来。由于数据量少且数据变化小以及 matplotlib 内部处理此情况的方式,图表看起来并不怎么好看。

样例代码(包含在 backtrader 的源代码中)

from __future__ import (absolute_import, division, print_function,)
#                        unicode_literals)
import argparse
import datetime
import backtrader as bt
import backtrader.feeds as btfeeds
class St(bt.Strategy):
    def next(self):
        print(','.join(str(x) for x in [
            self.data.datetime.datetime(),
            self.data.open[0], self.data.high[0],
            self.data.high[0], self.data.close[0],
            self.data.volume[0]]))
def runstrat():
    args = parse_args()
    cerebro = bt.Cerebro()
    data = btfeeds.GenericCSVData(
        dataname=args.data,
        dtformat='%d/%m/%y',
        # tmformat='%H%M%S',  # already the default value
        # datetime=0,  # position at default
        time=1,  # position of time
        open=5,  # position of open
        high=5,
        low=5,
        close=5,
        volume=7,
        openinterest=-1,  # -1 for not present
        timeframe=bt.TimeFrame.Ticks)
    cerebro.resampledata(data,
                         timeframe=bt.TimeFrame.Ticks,
                         compression=args.compression)
    cerebro.addstrategy(St)
    cerebro.run()
    if args.plot:
        cerebro.plot(style='bar')
def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='BidAsk to OHLC')
    parser.add_argument('--data', required=False,
                        default='../../datas/bidask2.csv',
                        help='Data file to be read in')
    parser.add_argument('--compression', required=False, default=2, type=int,
                        help='How much to compress the bars')
    parser.add_argument('--plot', required=False, action='store_true',
                        help='Plot the vars')
    return parser.parse_args()
if __name__ == '__main__':
    runstrat()

逃离 OHLC 之地

原文:www.backtrader.com/blog/posts/2016-03-08-escape-from-ohlc-land/escape-from-ohlc-land/

在 backtrader 的构思和开发过程中应用的一个关键概念是灵活性。Python 的元编程内省能力是保持许多灵活性的基础,同时仍能够交付成果。

一篇旧文章展示了扩展概念。

基础知识:

from backtrader.feeds import GenericCSVData
class GenericCSV_PE(GenericCSVData):
    lines = ('pe',)  # Add 'pe' to already defined lines

完成。backtrader在后台定义了最常见的线:OHLC。

如果我们深入研究GenericCSV_PE的最终方面,继承加上新定义的线的总和将产生以下线:

('close', 'open', 'high', 'low', 'volume', 'openinterest', 'datetime', 'pe',)

这可以随时通过getlinealiases方法检查(适用于DataFeedsIndicatorsStrategiesObservers

机制是灵活的,通过对内部进行一些探索,你实际上可以得到任何东西,但已经证明这还不够。

Ticket #60询问是否支持高频数据,即:Bid/Ask 数据。这意味着以OHLC形式预定义的lines层次结构不够用。BidAsk价格、成交量和交易数量可以适应现有的OHLC字段,但这不会感觉自然。如果只关注BidAsk价格,会有太多未触及的字段。

这需要一个解决方案,已经在发布 1.2.1.88中实施。这个想法可以总结为:

  • 现在不仅可以扩展现有的层次结构,还可以用新的层次结构替换原有的层次结构

只有一个约束条件:

  • 必须存在一个datetime字段(希望其中包含有意义的datetime信息)
    这是因为backtrader需要一些用于同步的东西(多个数据源、多个时间框架、重新采样、重播),就像阿基米德需要杠杆一样。

这是它的工作原理:

from backtrader.feeds import GenericCSVData
class GenericCSV_BidAsk(GenericCSVData):
    linesoverride = True
    lines = ('bid', 'ask', 'datetime')  # Replace hierarchy with this one

完成。

好的,不完全是因为我们正在查看从csv源加载的行。层次结构实际上已经被替换bid, ask datetime定义,这要归功于linesoverride=True设置。

原始的GenericCSVData类解析一个csv文件,并需要提示哪里是对应于linesfields。原始定义如下:

class GenericCSVData(feed.CSVDataBase):
    params = (
        ('nullvalue', float('NaN')),
        ('dtformat', '%Y-%m-%d %H:%M:%S'),
        ('tmformat', '%H:%M:%S'),
        ('datetime', 0),
        ('time', -1),  # -1 means not present
        ('open', 1),
        ('high', 2),
        ('low', 3),
        ('close', 4),
        ('volume', 5),
        ('openinterest', 6),
    )

新的重新定义层次结构类可以轻松完成:

from backtrader.feeds import GenericCSVData
class GenericCSV_BidAsk(GenericCSVData):
    linesoverride = True
    lines = ('bid', 'ask', 'datetime')  # Replace hierarchy with this one
    params = (('bid', 1), ('ask', 2))

表明Bid价格是 csv 流中的字段#1,Ask价格是字段#2。我们保留了基类中的datetime #0 定义不变。

为此制作一个小数据文件有所帮助:

TIMESTAMP,BID,ASK
02/03/2010 16:53:50,0.5346,0.5347
02/03/2010 16:53:51,0.5343,0.5347
02/03/2010 16:53:52,0.5543,0.5545
02/03/2010 16:53:53,0.5342,0.5344
02/03/2010 16:53:54,0.5245,0.5464
02/03/2010 16:53:54,0.5460,0.5470
02/03/2010 16:53:56,0.5824,0.5826
02/03/2010 16:53:57,0.5371,0.5374
02/03/2010 16:53:58,0.5793,0.5794
02/03/2010 16:53:59,0.5684,0.5688

将一个小测试脚本添加到等式中(对于那些直接转到源代码中的示例的人来说,增加一些内容)(见最后的完整代码):

$ ./bidask.py

输出说明一切:

1: 2010-02-03T16:53:50 - Bid 0.5346 - 0.5347 Ask
 2: 2010-02-03T16:53:51 - Bid 0.5343 - 0.5347 Ask
 3: 2010-02-03T16:53:52 - Bid 0.5543 - 0.5545 Ask
 4: 2010-02-03T16:53:53 - Bid 0.5342 - 0.5344 Ask
 5: 2010-02-03T16:53:54 - Bid 0.5245 - 0.5464 Ask
 6: 2010-02-03T16:53:54 - Bid 0.5460 - 0.5470 Ask
 7: 2010-02-03T16:53:56 - Bid 0.5824 - 0.5826 Ask
 8: 2010-02-03T16:53:57 - Bid 0.5371 - 0.5374 Ask
 9: 2010-02-03T16:53:58 - Bid 0.5793 - 0.5794 Ask
10: 2010-02-03T16:53:59 - Bid 0.5684 - 0.5688 Ask

瞧!Bid/Ask价格已经被正确读取、解析和解释,并且策略已能通过self.data访问数据源中的*.bid.ask*行。

重新定义lines层次结构却带来一个广泛的问题,即已预定义的Indicators的用法。

  • 例如:Stochastic是一种依赖于closehighlow价格来计算其输出的指标。
    即使我们把Bid视为close(因为它是第一个),仅有另一个price元素(Ask),而不是另外两个。而概念上,Askhighlow没有任何关系。
    有可能,与这些字段一起工作并在高频交易领域操作(或研究)的人并不关心Stochastic作为首选指标
  • 其他指标如移动平均完全正常。它们对字段的含义或暗示不做任何假设,且乐意接受任何东西。因此,可以这样做:
mysma = backtrader.indicators.SMA(self.data.bid, period=5)` 
  • 并且将提供最后 5 个bid价格的移动平均线

测试脚本已经支持添加SMA。让我们执行:

$ ./bidask.py --sma --period=3

输出:

3: 2010-02-03T16:53:52 - Bid 0.5543 - 0.5545 Ask - SMA: 0.5411
 4: 2010-02-03T16:53:53 - Bid 0.5342 - 0.5344 Ask - SMA: 0.5409
 5: 2010-02-03T16:53:54 - Bid 0.5245 - 0.5464 Ask - SMA: 0.5377
 6: 2010-02-03T16:53:54 - Bid 0.5460 - 0.5470 Ask - SMA: 0.5349
 7: 2010-02-03T16:53:56 - Bid 0.5824 - 0.5826 Ask - SMA: 0.5510
 8: 2010-02-03T16:53:57 - Bid 0.5371 - 0.5374 Ask - SMA: 0.5552
 9: 2010-02-03T16:53:58 - Bid 0.5793 - 0.5794 Ask - SMA: 0.5663
10: 2010-02-03T16:53:59 - Bid 0.5684 - 0.5688 Ask - SMA: 0.5616

注意

绘图仍然依赖于openhighlowclosevolume存在于数据源中。

一些情况可以通过简单地用Line on Close绘图并仅取对象中的第 1 个定义行来直接覆盖。但是必须开发一个合理的模型。针对即将推出的backtrader版本

测试脚本用法:

$ ./bidask.py --help
usage: bidask.py [-h] [--data DATA] [--dtformat DTFORMAT] [--sma]
                 [--period PERIOD]
Bid/Ask Line Hierarchy
optional arguments:
  -h, --help            show this help message and exit
  --data DATA, -d DATA  data to add to the system (default:
                        ../../datas/bidask.csv)
  --dtformat DTFORMAT, -dt DTFORMAT
                        Format of datetime in input (default: %m/%d/%Y
                        %H:%M:%S)
  --sma, -s             Add an SMA to the mix (default: False)
  --period PERIOD, -p PERIOD
                        Period for the sma (default: 5)

而测试脚本本身(包含在backtrader源代码中)

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import argparse
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
class BidAskCSV(btfeeds.GenericCSVData):
    linesoverride = True  # discard usual OHLC structure
    # datetime must be present and last
    lines = ('bid', 'ask', 'datetime')
    # datetime (always 1st) and then the desired order for
    params = (
        # (datetime, 0), # inherited from parent class
        ('bid', 1),  # default field pos 1
        ('ask', 2),  # default field pos 2
    )
class St(bt.Strategy):
    params = (('sma', False), ('period', 3))
    def __init__(self):
        if self.p.sma:
            self.sma = btind.SMA(self.data, period=self.p.period)
    def next(self):
        dtstr = self.data.datetime.datetime().isoformat()
        txt = '%4d: %s - Bid %.4f - %.4f Ask' % (
            (len(self), dtstr, self.data.bid[0], self.data.ask[0]))
        if self.p.sma:
            txt += ' - SMA: %.4f' % self.sma[0]
        print(txt)
def parse_args():
    parser = argparse.ArgumentParser(
        description='Bid/Ask Line Hierarchy',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )
    parser.add_argument('--data', '-d', action='store',
                        required=False, default='../../datas/bidask.csv',
                        help='data to add to the system')
    parser.add_argument('--dtformat', '-dt',
                        required=False, default='%m/%d/%Y %H:%M:%S',
                        help='Format of datetime in input')
    parser.add_argument('--sma', '-s', action='store_true',
                        required=False,
                        help='Add an SMA to the mix')
    parser.add_argument('--period', '-p', action='store',
                        required=False, default=5, type=int,
                        help='Period for the sma')
    return parser.parse_args()
def runstrategy():
    args = parse_args()
    cerebro = bt.Cerebro()  # Create a cerebro
    data = BidAskCSV(dataname=args.data, dtformat=args.dtformat)
    cerebro.adddata(data)  # Add the 1st data to cerebro
    # Add the strategy to cerebro
    cerebro.addstrategy(St, sma=args.sma, period=args.period)
    cerebro.run()
if __name__ == '__main__':
    runstrategy()

发布版本 1.2.1.88

原文:www.backtrader.com/blog/posts/2016-03-07-release-1.2.1.88/release-1.2.1.88/

将次版本号从 1 更改为 2 花费了一些时间,但旧的 DataResampler 和 DataReplayer 的弃用导致了这一变化。

readthedocs上的文档

文档已更新,只引用了现代化的resamplingreplaying方式。操作如下:

...
data = backtrader.feeds.BacktraderCSVData(dataname='mydata.csv')  # daily bars
cerebro.resampledata(data, timeframe=backtrader.TimeFrame.Weeks) # to weeks
...

对于replaying只需将resampledata更改为replaydata。还有其他方法可以做到,但这是最直接的接口,可能是唯一会被任何人使用的接口。

根据Ticket #60,很明显扩展机制允许向数据源(实际上是任何基于lines的对象)添加额外行不足以支持票号中建议的内容。

因此,实现了一个额外的parameterlines对象,允许完全重新定义行层次结构(逃离 OHLC 领域可能是一个合适的电影标题)

已经添加了一个名为data-bid-ask的示例到数据源中。从这个示例中:

class BidAskCSV(btfeeds.GenericCSVData):
    linesoverride = True  # discard usual OHLC structure
    # datetime must be present and last
    lines = ('bid', 'ask', 'datetime')
    # datetime (always 1st) and then the desired order for
    params = (
        ('dtformat', '%m/%d/%Y %H:%M:%S'),
        ('datetime', 0),  # field pos 0
        ('bid', 1),  # default field pos 1
        ('ask', 2),  # defult field pos 2
    )

通过指定linesoverride,常规的lines继承机制被绕过,对象中定义的行将取代任何先前的行。

此版本可从pypi获取,并可通过常规方式安装:

pip install backtrader

或者如果更新:

pip install backtrader --upgrade

2015

数据过滤器

原文:www.backtrader.com/blog/posts/2015-11-21-data-filters/data-filling-filtering/

一段时间前,票证#23 让我考虑在该票证的上下文中进行的讨论的潜在改进。

在票证中,我添加了一个DataFilter类,但这太过复杂。实际上,这让人想起了内置了相同功能的DataResamplerDataReplayer中构建的复杂性。

因此,自几个版本以来,backtrader支持向数据源添加一个filter(如果愿意,可以称之为processor)。重新采样和重播使用该功能进行了内部重新实现,一切似乎变得不那么复杂(尽管仍然是)

过滤器在起作用

鉴于现有的数据源,您可以使用数据源的addfilter方法:

data = MyDataFeed(name=myname)
data.addfilter(filter, *args, **kwargs)

显然,filter必须符合给定的接口,即:

  • 一个接受此签名的可调用对象:
callable(data, *args, **kwargs)` 

或者

  • 一个可以实例化和调用的类
  • 在实例化期间,init方法必须支持以下签名:
def __init__(self, data, *args, **kwargs)` 
  • 这个对象的call和 last 方法如下:
def __call__(self, data)
def last(self, data)` 

可调用/实例将被调用以处理数据源产生的每个数据。

对票证#23 的更好解决方案

那张票想要:

  • 一个基于白天的相对成交量指标
  • 白天数据可能丢失
  • 预/后市场数据可能到达

实施几个过滤器可以缓解回测环境的情况。

过滤预/后市场数据

以下过滤器(已经在backtrader中可用)挺身而出:

class SessionFilter(with_metaclass(metabase.MetaParams, object)):
    '''
    This class can be applied to a data source as a filter and will filter out
    intraday bars which fall outside of the regular session times (ie: pre/post
    market data)
    This is a "non-simple" filter and must manage the stack of the data (passed
    during init and __call__)
    It needs no "last" method because it has nothing to deliver
    '''
    def __init__(self, data):
        pass
    def __call__(self, data):
        '''
        Return Values:
          - False: data stream was not touched
          - True: data stream was manipulated (bar outside of session times and
          - removed)
        '''
        if data.sessionstart <= data.datetime.tm(0) <= data.sessionend:
            # Both ends of the comparison are in the session
            return False  # say the stream is untouched
        # bar outside of the regular session times
        data.backwards()  # remove bar from data stack
        return True  # signal the data was manipulated

该过滤器使用数据中嵌入的会话开始/结束时间来过滤条形图

  • 如果新数据的日期时间在会话时间内,则返回False以指示数据未受影响
  • 如果日期时间超出范围,则数据源将向后发送,有效地擦除最后生成的数据。并返回True以指示数据流已被操作。

注意

调用data.backwards()可能/可能是低级的,过滤器应该具有处理数据流内部的 API

脚本末尾的示例代码可以在有或无过滤的情况下运行。第一次运行是 100%未经过滤且未指定会话时间:

$ ./data-filler.py --writer --wrcsv

查看第 1 天的开始和结束:

===============================================================================
Id,2006-01-02-volume-min-001,len,datetime,open,high,low,close,volume,openinterest,Strategy,len
1,2006-01-02-volume-min-001,1,2006-01-02 09:01:00,3602.0,3603.0,3597.0,3599.0,5699.0,0.0,Strategy,1
2,2006-01-02-volume-min-001,2,2006-01-02 09:02:00,3600.0,3601.0,3598.0,3599.0,894.0,0.0,Strategy,2
...
...
581,2006-01-02-volume-min-001,581,2006-01-02 19:59:00,3619.0,3619.0,3619.0,3619.0,1.0,0.0,Strategy,581
582,2006-01-02-volume-min-001,582,2006-01-02 20:00:00,3618.0,3618.0,3617.0,3618.0,242.0,0.0,Strategy,582
583,2006-01-02-volume-min-001,583,2006-01-02 20:01:00,3618.0,3618.0,3617.0,3617.0,15.0,0.0,Strategy,583
584,2006-01-02-volume-min-001,584,2006-01-02 20:04:00,3617.0,3617.0,3617.0,3617.0,107.0,0.0,Strategy,584
585,2006-01-02-volume-min-001,585,2006-01-03 09:01:00,3623.0,3625.0,3622.0,3624.0,4026.0,0.0,Strategy,585
...

2006 年 1 月 2 日从 09:01:00 到 20:04:00 进行会话运行。

现在使用SessionFilter运行,并告诉脚本使用 09:30 和 17:30 作为会话的开始/结束时间:

$ ./data-filler.py --writer --wrcsv --tstart 09:30 --tend 17:30 --filter
===============================================================================
Id,2006-01-02-volume-min-001,len,datetime,open,high,low,close,volume,openinterest,Strategy,len
1,2006-01-02-volume-min-001,1,2006-01-02 09:30:00,3604.0,3605.0,3603.0,3604.0,546.0,0.0,Strategy,1
2,2006-01-02-volume-min-001,2,2006-01-02 09:31:00,3604.0,3606.0,3604.0,3606.0,438.0,0.0,Strategy,2
...
...
445,2006-01-02-volume-min-001,445,2006-01-02 17:29:00,3621.0,3621.0,3620.0,3620.0,866.0,0.0,Strategy,445
446,2006-01-02-volume-min-001,446,2006-01-02 17:30:00,3620.0,3621.0,3619.0,3621.0,1670.0,0.0,Strategy,446
447,2006-01-02-volume-min-001,447,2006-01-03 09:30:00,3637.0,3638.0,3635.0,3636.0,1458.0,0.0,Strategy,447
...

数据输出现在从 09:30 开始,到 17:30 结束。已经过滤掉预/后市场数据。


BackTrader 中文文档(二十五)(2)https://developer.aliyun.com/article/1505458

相关文章
|
8月前
|
数据可视化
BackTrader 中文文档(二十五)(4)
BackTrader 中文文档(二十五)
65 0
|
8月前
BackTrader 中文文档(二十五)(2)
BackTrader 中文文档(二十五)
43 0
|
8月前
|
C++
BackTrader 中文文档(二十五)(3)
BackTrader 中文文档(二十五)
60 0
|
8月前
|
存储 测试技术 Python
BackTrader 中文文档(二十二)(2)
BackTrader 中文文档(二十二)
65 0
|
8月前
BackTrader 中文文档(二十二)(1)
BackTrader 中文文档(二十二)
40 0
|
8月前
|
测试技术
BackTrader 中文文档(二十二)(4)
BackTrader 中文文档(二十二)
51 0
|
8月前
|
存储 Python
BackTrader 中文文档(二十二)(3)
BackTrader 中文文档(二十二)
57 0
|
8月前
|
编解码 API Windows
BackTrader 中文文档(二十三)(3)
BackTrader 中文文档(二十三)
52 0
|
8月前
BackTrader 中文文档(二十三)(4)
BackTrader 中文文档(二十三)
46 0
|
8月前
|
编解码 索引
BackTrader 中文文档(二十三)(2)
BackTrader 中文文档(二十三)
51 0

热门文章

最新文章

下一篇
开通oss服务