tv2bt: Bitfinex Example, without leverage

This post provides a complete example script for live trading on Bitfinex (without leverage). It uses tv2btso that you can mix and match between using Tradingview and Backtrader! This means that it also follows on from our last post which introduced thetv2btbridge. So if you have not read that yet, you should start there to learn about tv2bt and how to set it up. To whet your appetite and encourage you to click that linktv2btprovides a way to use Tradingview’s alert system with Backtrader to trigger trades on live exchanges. For those looking for leveraged trading, be patient, an example of that will come in the future!

Requirements

The code in this article requires that you have bt-ccxt-store installed. You also must have a quite recent version for it to work correctly. If you have not updated since November the 20th 2019, then go and grab the latest source code from here: https://github.com/Dave-Vallance/bt-ccxt-store

Scope

As mentioned in the introduction, we will be trading without leverage. To speak more simply, that means just buying and selling coins. It is important to note that when we do this, we are not really in a “position” as most people would see it. We are just swapping assets. With currency pairs, you are always simultaneously long and short. For example, if you use USD to buy some Euro’s then you are long EURO and Short USD at the same time. When you sell your Euro’s, the opposite is true. See the following article for a good explanation: https://www.investopedia.com/terms/c/counter-currency.asp Having said all that, in the example code below, Backtrader will still report being in a position when we buy the base currency. In other words, our position size will be greater than zero. When we sell the currency pair, it will go back to zero. However, for the reasons mentioned above, we are technically not in a position and you will not see an open position in Bitfinex. You will just see your wallet balances change.

The Code

'''
Author: www.backtest-rookies.com

MIT License

Copyright (c) 2019 backtest-rookies.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
'''

import backtrader as bt
from datetime import datetime
from tv2bt import TVFeed
from ccxtbt import CCXTStore

apikey = 'INSERT YOUR KEY'
secret = 'INSERT YOUR SECRET'

class TVTest(bt.Strategy):
    '''
    Simple strat to test TV Feed.
    '''

    params = (
        ('perc_size', 0.7), # 10%
        ('fixed_qty', 12)
        )

    def __init__(self):

        self.data_info = dict()

        for i, d in enumerate(self.datas):

            # Tracking the last bar is useful when we have data coming in
            # at different times.
            self.data_info[d._name] = dict()
            self.data_info[d._name]['last bar'] = 0

            # When we are not using leverage, there is no concept of a
            # position. So we create a dict to store holdings of both
            # the base and quote currencies.
            ticker_components = d._name.split('/')
            self.data_info[d._name]['base'] = ticker_components[0]
            self.data_info[d._name]['counter'] = ticker_components[1]

            for comp in ticker_components:
                self.data_info[d._name][comp] = dict()
                self.data_info[d._name][comp]['cash'] = 0
                self.data_info[d._name][comp]['value'] = 0


    def next(self):

        print('='*80)
        print(' '*36,'NEXT')
        print('='*80)


        for i, d in enumerate(self.datas):

            dn = d._name
            dt = d.datetime.datetime()
            bar = len(d)
            pos = self.getposition(d).size
            base = self.data_info[dn]['base']
            counter = self.data_info[dn]['counter']

            print('Position : {}'.format(pos))

            # Check we have a new bar and are not repeating an old one.
            if bar > self.data_info[dn]['last bar']:
                print('DATE   : {}'.format(dt))
                print('ASSET  : {}'.format(dn))
                print('BAR    : {}'.format(bar))
                print('Open   : {}'.format(d.open[0]))
                print('High   : {}'.format(d.high[0]))
                print('Low    : {}'.format(d.low[0]))
                print('Close  : {}'.format(d.close[0]))
                print('Signal : {}'.format(d.signal[0]))
                print('-'*80)

                # Save the last bar processed
                self.data_info[dn]['last bar'] = bar

            # Get our balances only if we want to go buy/sell.
            # Otherwise, the request slows things down.
            if d.signal in [1,-1]:
                ticker_components = dn.split('/')

                for comp in ticker_components:
                    # Get our cash and value to enter a position
                    cash, value = self.broker.get_wallet_balance(comp)
                    print('{} : {} in Cash'.format(comp, cash))
                    print('{} : {} in Value'.format(comp, value))
                    self.data_info[dn][comp]['cash'] = cash
                    self.data_info[dn][comp]['value'] = value

            # Buy the base currency!
            if d.signal == 1:

                qty = (self.data_info[dn][counter]['value'] * self.p.perc_size) / d.close[0]
                print('Action  : Buy | Qty: {}'.format(qty))
                self.buy(d, size=qty)

            # Sell the base currency!
            if d.signal == -1:

                if cash > 0:
                    print('Action  : Sell | Qty: {}'.format(pos))
                    self.sell(d, size=pos)


    def notify_data(self, data, status, *args, **kwargs):
        print('DATA NOTIF: {}: {}'.format(data._getstatusname(status), ','.join(args)))


    def notify_order(self, order):
        dt = order.data.datetime.datetime()
        dn = order.data._name

        print('='*33, 'NOTIFY ORDER', '='*33)

        if order.status == order.Submitted:
            print('Date   : {}'.format(dt))
            print('Ticker : {}'.format(dn))
            print('Notify : Order Submitted')

        if order.status == order.Accepted:
            print('Date   : {}'.format(dt))
            print('Ticker : {}'.format(dn))
            print('Notify : Order Accepted')

        if order.status == order.Completed:
            print('Date   : {}'.format(dt))
            print('Ticker : {}'.format(dn))
            print('Notify : Order Completed')

        if order.status == order.Canceled:
            print('Date   : {}'.format(dt))
            print('Ticker : {}'.format(dn))
            print('Notify : Order Canceled')

        if order.status == order.Rejected:
            print('Date   : {}'.format(dt))
            print('Ticker : {}'.format(dn))
            print('Notify : Order Rejected')

# Example Alerts
# --------------
# 1. DATA/OHLC
#    The ticker should be the same as you use for your broker to
#    make life easier. Don't blindly copy the Tradingview Ticker.
#    The rest of the string below should be copied in as it. Tradingview
#    will replace the values inside {{}} with the actual values.
'''
{'symbol':'[INSERT TICKER]', 'DT':'{{time}}', 'O':{{open}}, 'H':{{high}}, 'L':{{low}}, 'C':{{close}}, 'V':{{volume}}, 'action':0}
'''
# 2. SIGNALS
#    3 Types of signal are currently supported. 'long', 'short' and 'flat'
#    It is expected that you handle them appropriately and according to your
#    taste in backtrader
'''
{'symbol':'[INSERT TICKER]', 'action':1}
'''

print('='*80)
print('Starting Example Strategy')
print('All data feeds must have one bar of data before before you will see any output \n'
    'on the console. Please be patient...')
print('For instructions how to use, see:')
print('='*80)

debug = False

# Create an instance of cerebro
cerebro = bt.Cerebro()

# Get Data
data = TVFeed(dataname='BTC/USD',  debug=debug)

# Add the data feeds
cerebro.adddata(data)
#cerebro.adddata(data2)

# Set Config
config = {'apiKey': apikey,
          'secret': secret,
          'enableRateLimit': True,
          'rateLimit': 3000
        }

# Add our strategy
cerebro.addstrategy(TVTest)


print('Getting Store')
# Create data feeds
store = CCXTStore(exchange='bitfinex', currency='USD', config=config, retries=5, debug=False)

print('Getting Broker')
broker = store.getbroker()
cerebro.setbroker(broker)

# Run the strategy
cerebro.run()

Testing

Before testing anything, you will need to insert your own Bitfinex API key and secret into the script. You can do that near the top of the script.
apikey = 'INSERT YOUR KEY'
secret = 'INSERT YOUR SECRET'
After this, testing the code should be done in a controlled way. As such, it is recommended that you test the script using fire_alerts.pyfrom the tv2btrepository. It will be much quicker than setting up alerts on Tradingview and waiting for them to fire. Once you confirm that it is working, then setup Tradingivew to send alerts to your server. https://github.com/Dave-Vallance/tv2bt When buying coins, the script uses a percentage of your available counter currency (e.g USD) to calculate the qty. We then use that quantity to buy the base currency (e.g BTC). This is important to point out for two reasons
  1. You must have some counter currency available to purchase the base currency. If you do not, you will get an error.
  2. You must provide a fake close price using fire_alerts.pythat is close to the current price. This is so that the right QTY size can be calculated for the amount of cash in your account.
Note: fire_alerts.pyhas recently been updated to support sending OHLCVdata. So be sure to grab an updated version from the repository. Once you fire a signal to purchase or sell, then you should follow it up with a second signal to receive the order notification that it was completed. So if the first alert has an action of 1 (to buy), then follow up with an actionof 0. Alternatively, you can load up Bitfinex in your web browser and monitor the order history.

Successful Buy Order

If all goes well, you can expect to see some output like this: An image showing a successful buy order sent via tv2bt

Successful Sell Order

And like this for a sell order: Showing a sell order submitted via tv2bt That’s it! Happy Trading.

Find This Post Useful?

If this post saved you time and effort, please consider support the site! There are many ways to support us and some won’t even cost you a penny.