rodrigo-brito / backtrader-binance-bot

:money_with_wings: A example of bot using Backtrader to trade Bitcoins in Binance Exchange.
MIT License
262 stars 118 forks source link

MACD comparison between 1m and 5m #3

Closed mr-m0nst3r closed 3 years ago

mr-m0nst3r commented 5 years ago

Hi there, great work.

I want to change the strategy to compare MACD of 1 minute and 5 minute,

How can I achive this?

Should I do:

self.macd1m = bt.indicators.MACD(self.data, peroid=1)
self.macd5m = bt.indicators.MACD(self.data, peroid=5)

Or should I use your MACDHistSMA indicator? but how?

I'm confused. New to backtrader and ccxt and trading indicators. Appreciate any help.

Thank you.

rodrigo-brito commented 5 years ago

Hi @mr-m0nst3r, this period is the number of candles to use in the average. For distinct timelines, you should load different feeds. Check the documentation here.

With resample:

# data0
cerebro.resampledata(data, timeframe=bt.TimeFrame.Minutes, compression=1)
# data1
cerebro.resampledata(data, timeframe=bt.TimeFrame.Minutes, compression=5)

With multiple timeframes, you can use it like this:

self.macd1m = bt.indicators.MACD(self.data0)
self.macd5m = bt.indicators.MACD(self.data1)
mr-m0nst3r commented 5 years ago

@rodrigo-brito You saved my life! Thanks a million!

I the earlier tests, I added 2 feed as data using different timeframe, but got weird values. After trying your suggestion, I deleted adddata, and put 2 data using resampledata from one data feed, using timeframe and compression for different timeframes.

Some little questions are: 1 - The MACD of 1 minute is correct with the Binance APP, but 5minute MACD is not. Posted the script below, am I doing something wrong here? It seems from the output below, that the MACD of 5m is calculated from 2019-06-27 02:14:00, which is different in the app, should be 2019-06-27 02:10:00 or 2019-06-27 02:15:00 I think. 2 - BTW, the MACDs are showing after 167 prenext called. Any meaning of that? 26+5*25 = 156, still 11 more prenext called. I was thinking it should be 26 prenext to generate one macd1m, then another 5 prenext to generate macd5m, but I was wrong and confused. 3 - Looks like fromdate is not working.

Here's the script I run:


from ccxtbt import CCXTStore
import backtrader as bt
from datetime import datetime, timedelta
import time

class TestStrategy(bt.Strategy):
    params = (
        ('maperiod', 15),
        ('printlog', True),
        # Standard MACD Parameters
        ('macd1', 12),
        ('macd2', 26),
        ('macdsig', 9),
        ('atrperiod', 14),  # ATR Period (standard)
        ('atrdist', 3.0),   # ATR distance for stop price
        ('smaperiod', 30),  # SMA Period (pretty standard)
        ('dirperiod', 10),  # Lookback period to consider SMA trend direction
    )

    def log(self, txt, dt=None, doprint=False):
        ''' Logging function fot this strategy'''
        if self.params.printlog or doprint:
            dt = dt or self.datas[0].datetime.date(0)
            print('%s, %s' % (dt.isoformat(), txt))

    def start(self):
        self.counter = 0
        print('START')

    def __init__(self):

        self.macd1m = bt.indicators.MACD(self.datas[0])
        self.macd5m = bt.indicators.MACD(self.datas[1])

    def prenext(self):
        self.counter += 1
        print('prenext len %d - counter %d' % (len(self), self.counter))

    def next(self):      

        # Get cash and balance
        # New broker method that will let you get the cash and balance for
        # any wallet. It also means we can disable the getcash() and getvalue()
        # rest calls before and after next which slows things down.

        # NOTE: If you try to get the wallet balance from a wallet you have
        # never funded, a KeyError will be raised! Change LTC below as approriate
        if self.live_data:
            cash, value = self.broker.get_wallet_balance('USDT')
        else:
            # Avoid checking the balance during a backfill. Otherwise, it will
            # Slow things down.
            cash = 'NA'

        #for data in self.datas:

        print('{} - {} \t| Cash {} | O: {} H: {} L: {} C: {} V:{:.5f} \tMACD(1):{} \tMACD(5):{}'.format(self.datas[0].datetime.datetime(),
            self.datas[0]._name, cash, self.datas[0].open[0], self.datas[0].high[0], self.datas[0].low[0], self.datas[0].close[0], self.datas[0].volume[0],
            self.macd1m[0], self.macd5m[0]))

    def notify_data(self, data, status, *args, **kwargs):
        dn = data._name
        dt = datetime.now()
        msg= 'Data Status: {}'.format(data._getstatusname(status))
        print(dt,dn,msg)
        if data._getstatusname(status) == 'LIVE':
            self.live_data = True
        else:
            self.live_data = False

apikey = 'x'
secret = 'x'

cerebro = bt.Cerebro(quicknotify=True)

# Add the strategy
cerebro.addstrategy(TestStrategy)

# Create our store
config = {'apiKey': apikey,
            'secret': secret,
            'enableRateLimit': True
            }

# IMPORTANT NOTE - Kraken (and some other exchanges) will not return any values
# for get cash or value if You have never held any LTC coins in your account.
# So switch LTC to a coin you have funded previously if you get errors
store = CCXTStore(exchange='binance', currency='USDT', config=config, retries=5, debug=False)

# Get the broker and pass any kwargs if needed.
# ----------------------------------------------
# Broker mappings have been added since some exchanges expect different values
# to the defaults. Case in point, Kraken vs Bitmex. NOTE: Broker mappings are not
# required if the broker uses the same values as the defaults in CCXTBroker.
broker_mapping = {
    'order_types': {
        bt.Order.Market: 'market',
        bt.Order.Limit: 'limit',
        bt.Order.Stop: 'stop-loss', #stop-loss for kraken, stop for bitmex
        bt.Order.StopLimit: 'stop limit'
    },
    'mappings':{
        'closed_order':{
            'key': 'status',
            'value':'closed'
            },
        'canceled_order':{
            'key': 'result',
            'value':1}
            }
    }

#broker = store.getbroker(broker_mapping=broker_mapping)
broker = store.getbroker()
cerebro.setbroker(broker)

# Get our data
# Drop newest will prevent us from loading partial data from incomplete candles
hist_start_date = datetime.utcnow() - timedelta(minutes=150)
data = store.getdata(dataname='ETH/USDT', 
                        name="ETHUSDT",
                        timeframe=bt.TimeFrame.Minutes, 
                        fromdate=hist_start_date,
                        #todate=datetime.utcnow(),
                        compression=1, 
                        #ohlcv_limit=50, 
                        drop_newest=True) #, historical=True)
# data2 = store.getdata(dataname='ETH/USDT', 
#                         name="ETHUSDT",
#                         timeframe=bt.TimeFrame.Minutes, 
#                         fromdate=hist_start_date,
#                         #todate=datetime.utcnow(),
#                         compression=5, 
#                         #ohlcv_limit=50, 
#                         drop_newest=True) #, historical=True)

# Add the feed
# cerebro.adddata(data)

cerebro.resampledata(data, timeframe=bt.TimeFrame.Minutes, compression=1, name="1MIN")
cerebro.resampledata(data, timeframe=bt.TimeFrame.Minutes, compression=5, name="5MIN")

# Run the strategy
cerebro.run()

Here's the output with fromdata:

...
prenext len 19 - counter 19
2019-06-27 09:59:11.744549 1MIN Data Status: LIVE
prenext len 20 - counter 20
...
prenext len 162 - counter 162
prenext len 163 - counter 163
prenext len 164 - counter 164
prenext len 165 - counter 165
prenext len 166 - counter 166
prenext len 167 - counter 167
2019-06-27 02:14:00 - 1MIN  | Cash 97.0 | O: 331.48 H: 332.32 L: 331.09 C: 331.82 V:476.53417   MACD(1):-0.6656385649643539     MACD(5):-2.6617503992747515
2019-06-27 02:15:00 - 1MIN  | Cash 97.0 | O: 332.21 H: 332.71 L: 331.84 C: 332.15 V:254.94723   MACD(1):-0.6459413034261843     MACD(5):-2.6617503992747515
2019-06-27 02:16:00 - 1MIN  | Cash 97.0 | O: 332.15 H: 332.99 L: 331.83 C: 332.42 V:346.18933   MACD(1):-0.6016093600168233     MACD(5):-2.6617503992747515
2019-06-27 02:17:00 - 1MIN  | Cash 97.0 | O: 332.42 H: 332.79 L: 331.81 C: 332.67 V:270.73920   MACD(1):-0.5400773888554227     MACD(5):-2.6617503992747515
2019-06-27 02:18:00 - 1MIN  | Cash 97.0 | O: 332.78 H: 333.05 L: 332.4 C: 332.77 V:247.29118    MACD(1):-0.4777366434855139     MACD(5):-2.6617503992747515
2019-06-27 02:19:00 - 1MIN  | Cash 97.0 | O: 332.89 H: 333.29 L: 332.68 C: 333.17 V:285.10471   MACD(1):-0.3915410550221168     MACD(5):-2.622988045832585
2019-06-27 02:20:00 - 1MIN  | Cash 97.0 | O: 333.29 H: 333.38 L: 332.63 C: 333.0 V:566.22803    MACD(1):-0.33310814605488304    MACD(5):-2.622988045832585
2019-06-27 02:21:00 - 1MIN  | Cash 97.0 | O: 333.07 H: 333.47 L: 332.78 C: 333.25 V:139.05776   MACD(1):-0.26358825274940045    MACD(5):-2.622988045832585
...
rodrigo-brito commented 5 years ago

The implementation seems right. How much MACD value is different from Binance graph?

mr-m0nst3r commented 5 years ago

Hi @rodrigo-brito , From the output above, here's the script's result:

2019-06-27 02:15:00 - 1MIN  MACD(5):-2.6617503992747515
2019-06-27 02:20:00 - 1MIN  MACD(5):-2.622988045832585

And checked the Binance graphic, using UTC timezone and 5m timeframe:

2019-06-27 02:15:00    -0.5649/-1.9667/-1.4018
2019-06-27 02:20:00    -0.3714/-1.8661/-1.4947
according to the graphic settings, the middle one is the macd value:
2019-06-27 02:15:00    -1.9667 (difference with script: -0.695050399274752)
2019-06-27 02:20:00    -1.8661 (difference with script: -0.756888045832585)

And, I also checked the MACD of 1 minute, there's also a slight difference:

time/script/graphic
2019-06-27 02:20:00 / -0.33310814605488304 / -0.3347
2019-06-27 02:21:00 / -0.26358825274940045 / -0.2650

Any idea about how to correct these? The difference between 5m MACD is probably too much.

image image

rodrigo-brito commented 5 years ago

It is very strange. I will check it today in my custom implementation and report to you later.

mr-m0nst3r commented 5 years ago

@rodrigo-brito Hi mate, any updates?

rodrigo-brito commented 5 years ago

I seems a bug in re-sample function, the data is a little delayed. I also notice that standard MACD indicator return a different result from TA-Lib MACD. I will post it in the forum later.

mr-m0nst3r commented 5 years ago

@rodrigo-brito 👍

rodrigo-brito commented 5 years ago

Hi @mr-m0nst3r, I reported this issue in forum: https://community.backtrader.com/topic/1934/standard-macd-vs-talib-macd-different-values Maybe the re-sample data provide some noise for indicators, I do not sure.

mr-m0nst3r commented 5 years ago

@rodrigo-brito Hi mate, I saw your post in the community. Backtrader responded, but seems not helping.