BitShares Forum

Main => Technical Support => Topic started by: litepresence on February 22, 2018, 03:29:25 pm

Title: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: litepresence on February 22, 2018, 03:29:25 pm
v1 : https://pastebin.com/9zAbqx2A

updated Mar6/2018

v2 https://pastebin.com/pBzi8a4N

 - improved cancel all
 - block number / latency
 - account value in BTS, BTC, USD terms



license: WFTPLv2 http://www.wtfpl.net/about/
Code: [Select]
# python3.4
 
' Wrapper for Common PyBitshares DEX Algo Trading API Calls '
 
# data is in easy to quant float / list of floats / dict of floats format
 
    # buy / sell / cancel
    # outstanding orders
    # account balance for pair
    # complete account balance
    # orderbook
    # last_price
    # account value
    # latency
 
# if no price / amount specified executes market order buy/sell
# if no expiration specified default is 3.2 years
# cancels all outstanding orders in market
 
' BTS: litepresence1 '
 
# http://docs.pybitshares.com
from bitshares.market import Market
from bitshares.account import Account
from bitshares import BitShares
from bitshares.blockchain import Blockchain
import time
 
 
ACCOUNT = Account("")
PASS_PHRASE = ""
 
BitCURRENCY = 'OPEN.BTC'
BitASSET = 'BTS'
BitPAIR = BitASSET + ":" + BitCURRENCY
MARKET = Market(BitPAIR, bitshares_instance=BitShares(nodes()))
CHAIN = Blockchain(bitshares_instance=BitShares(
    nodes()), mode='head')
 
SATOSHI = 0.00000001
ANTISAT = 1 / SATOSHI
 
def nodes():  # Public Nodes List
 
    nodes = [
        'wss://b.mrx.im/ws',
        'wss://bitshares.openledger.info/ws',
        'wss://bitshares.dacplay.org:8089/ws',
        'wss://dele-puppy.com/ws',
        'wss://eu.openledger.info/ws',
        'wss://bit.btsabc.org/ws',
        'wss://eu.openledger.info/ws',
        'wss://dexnode.net/ws',
        'wss://ws.gdex.top',
        'wss://kc-us-dex.xeldal.com/ws',
        'wss://bts.ai.la/ws',
        'wss://btsza.co.za:8091/ws',
        'wss://japan.bitshares.apasia.tech/ws',
        'wss://api.bts.blckchnd.com',
        'wss://bitshares-api.wancloud.io/ws',
        'wss://eu.nodes.bitshares.ws',
        'wss://bitshares.crypto.fans/ws',
        'wss://dex.rnglab.org',
        'wss://bitshares.openledger.info/ws',
        'wss://ws.winex.pro',
        'wss://sg.nodes.bitshares.ws',
        'wss://us.nodes.bitshares.ws',
        'wss://bitshares.apasia.tech/ws',
        'wss://openledger.hk/ws',
        'wss://bitshares.dacplay.org/ws',
    ]
    return nodes
 
def dex(  # Public AND Private API Bitshares
        command, amount=ANTISAT, price=None,
        depth=1, expiration=ANTISAT):
 
    MARKET.bitshares.wallet.unlock(PASS_PHRASE)
    ACCOUNT.refresh()
 
    if command == 'buy':
 
        # buy relentlessly until satisfied or currency exhausted
        print(('Bitshares API', command))
        if price is None:
            price = ANTISAT
        print(('buying', amount, 'at', price))
        attempt = 1
        currency = float(ACCOUNT.balance(BitCURRENCY))
        if amount > 0.998 * currency * price:
            amount = 0.998 * currency * price
        if amount > 0:
            while attempt:
                try:
                    details = (MARKET.buy(price, amount, expiration))
                    print (details)
                    attempt = 0
                except:
                    print(("buy attempt %s failed" % attempt))
                    attempt += 1
                    if attempt > 10:
                        print ('buy aborted')
                        return
                    pass
        else:
            print('no currency to buy')
 
    if command == 'sell':
 
        # sell relentlessly until satisfied or assets exhausted
        expiration = 86400 * 7
        print(('Bitshares API', command))
        if price is None:
            price = SATOSHI
        print(('selling', amount, 'at', price))
        attempt = 1
        assets = float(ACCOUNT.balance(BitASSET))
        if amount > 0.998 * assets:
            amount = 0.998 * assets
        if amount > 0:
            while attempt:
                try:
                    details = (MARKET.sell(price, amount, expiration))
                    print (details)
                    attempt = 0
                except:
                    print(("sell attempt %s failed" % attempt))
                    attempt += 1
                    if attempt > 10:
                        print ('sell aborted')
                        return
                    pass
        else:
            print('no assets to sell')
 
    if command == 'cancel':
 
        # cancel all orders in this MARKET relentlessly until satisfied
        print(('Bitshares API', command)) 
        orders = MARKET.accountopenorders()
        print((len(orders), 'open orders to cancel'))
        if len(orders):
            attempt = 1 
            order_list = []     
            for order in orders:
                order_list.append(order['id'])
            while attempt:
                try:
                    details = MARKET.cancel(order_list)
                    print (details)
                    attempt = 0
                except:
                    print((attempt, 'cancel failed', order_list))
                    attempt += 1
                    if attempt > 10:
                        print ('cancel aborted')
                        return
                    pass   
 
    if command == 'orders':
 
        # dictionary of open orders in traditional format:
        # orderNumber, orderType, market, amount, price
        print(('Bitshares API', command))
        orders = []
        for order in MARKET.accountopenorders():
            orderNumber = order['id']
            asset = order['base']['symbol']
            currency = order['quote']['symbol']
            amount = float(order['base'])
            price = float(order['price'])
            orderType = 'buy'
            if asset == BitASSET:
                orderType = 'sell'
                price = 1 / price
            orders.append({'orderNumber': orderNumber,
                           'orderType': orderType,
                           'market': BitPAIR, 'amount': amount,
                           'price': price})
        for o in orders:
            print (o)
        if len(orders) == 0:
            print ('no open orders')
        return orders
 
    if command == 'market_balances':
 
        # dictionary of currency and assets in this MARKET
        print(('Bitshares API', command))
        currency = float(ACCOUNT.balance(BitCURRENCY))
        assets = float(ACCOUNT.balance(BitASSET))
        balances = {'currency': currency, 'assets': assets}
        print (balances)
        return balances
 
    if command == 'complete_balances':
 
        # dictionary of ALL account balances
        print(('Bitshares API', command))
        raw = list(ACCOUNT.balances)
        balances = {}
        for i in range(len(raw)):
            balances[raw[i]['symbol']] = float(raw[i]['amount'])
        print (balances)
        return balances
 
    if command == 'book':
 
        # dictionary of 4 lists containing bid/ask volume/price
        print(('Bitshares API', command))
        raw = MARKET.orderbook(limit=depth)
        bids = raw['bids']
        asks = raw['asks']
        bidp = [float(bids[i]['price']) for i in range(len(bids))]
        bidv = [float(bids[i]['quote']) for i in range(len(bids))]
        askp = [float(asks[i]['price']) for i in range(len(asks))]
        askv = [float(asks[i]['quote']) for i in range(len(asks))]
        book = {'bidp': bidp, 'bidv': bidv, 'askp': askp, 'askv': askv}
        # print(book)
        print(('ask', ('%.8f' % book['askp'][0])))  # lowest ask price
        print(('bid', ('%.8f' % book['bidp'][0])))  # highest bid price
        # print(book['bidv'][0]) #highest bid volume
        # print(book['askv'][0]) #lowest ask volume
        return book
 
    if command == 'last':
 
        # the most recent transation in this MARKET
        print(('Bitshares API', command))
        raw = MARKET.ticker()['latest']
        price = float(raw)
        # print (price)
        return price
 
    if command == 'account_value':
 
        # dictionary account value in BTS BTC and USD
        print(('Bitshares API', command))
        raw = list(ACCOUNT.balances)
        balances = {}
        for i in range(len(raw)):
            balances[raw[i]['symbol']] = float(raw[i]['amount'])
        btc_value = 0
        for asset, amount in list(balances.items()):
            market_pair = 'OPEN.BTC:' + asset
            market = Market(market_pair)
            price = float(market.ticker()['latest'])
            try:
                value = amount / price
            except:
                value = 0
            if value < 0.0001:
                value = 0
            else:
                if asset != 'USD':
                    price = 1 / (price + SATOSHI)
                print((('%.4f' % value), 'OPEN.BTC', ('%.2f' % amount),
                       asset, '@', ('%.8f' % price)))
                btc_value += value
 
        market_pair = 'OPEN.BTC:USD'
        market = Market(market_pair)
        price = float(market.ticker()['latest'])
        usd_value = btc_value * price
        market_pair = 'OPEN.BTC:BTS'
        market = Market(market_pair)
        price = float(market.ticker()['latest'])
        bts_value = btc_value * price
        print((('%.2f' % bts_value), 'BTS',
             ('%.4f' % btc_value), 'OPEN.BTC',
             ('%.2f' % usd_value), 'bitUSD'))
        return bts_value, btc_value, usd_value
 
    if command == 'blocktime':
 
        current_block = CHAIN.get_current_block_num()
        blocktime = CHAIN.block_time(current_block)
        blocktimestamp = CHAIN.block_timestamp(current_block) - 18000
        now = time.time()
        latency = now - blocktimestamp
        print(('block               :', current_block))
        # print(('blocktime           :', blocktime))
        # print(('stamp               :', blocktimestamp))
        # print(('ctime(stamp)        :', time.ctime(blocktimestamp)))
        # print(('now                 :', now))
        print(('dex_rate latency    :', ('%.2f' % latency)))
        return current_block, blocktimestamp, latency
 
 
 
'''
dex('buy')
dex('sell')
dex('orders')
dex('cancel')
dex('market_balances')
dex('complete_balances')
dex('last')
dex('book')
dex('account_value')
dex('blocktime')
'''
Title: Re: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: xeroc on February 22, 2018, 07:19:56 pm
This looks great!

Just a matter of time until soneone sets up a bot that does sophisticated orders like stop-loss etc ...
Title: Re: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: zapata42 on February 23, 2018, 12:09:49 pm
Just a matter of time until soneone sets up a bot that does sophisticated orders like stop-loss etc ...

A quick win could be to add support of Bitshares DEX to this: https://github.com/DeviaVir/zenbot ...
Title: Re: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: Bangzi on February 23, 2018, 03:25:42 pm
Just a matter of time until soneone sets up a bot that does sophisticated orders like stop-loss etc ...

A quick win could be to add support of Bitshares DEX to this: https://github.com/DeviaVir/zenbot ...

Good if can bring more market makers to Bitshares but they need to setup a node for stable connection.
Title: Re: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: litepresence on February 23, 2018, 05:02:32 pm
Code: [Select]
    if command == 'account_value':

        # returns estimated account value in BTS, BTC, USD
        print(('Bitshares API', command))
        raw = list(ACCOUNT.balances)
        balances = {}
        for i in range(len(raw)):
            balances[raw[i]['symbol']] = float(raw[i]['amount'])
        btc_value = 0
        for asset, amount in balances.items():
            market_pair = 'BTC:' + asset
            market = Market(market_pair)
            price = float(market.ticker()['latest'])
            value = amount/price
            if asset != 'USD':
                price = 1/price
            print (('%.4f' % value), 'BTC', ('%.2f' % amount),
                    asset, '@', ('%.8f' % price))
            btc_value+=value

        market_pair = 'BTC:USD'
        market = Market(market_pair)
        price = float(market.ticker()['latest'])
        usd_value = btc_value*price
        market_pair = 'BTC:BTS'
        market = Market(market_pair)
        price = float(market.ticker()['latest'])
        bts_value = btc_value*price
       
        print(  ('%.2f' % bts_value), 'BTS',
                ('%.4f' % btc_value), 'BTC',
                ('%.2f' % usd_value), 'USD')
        return bts_value, btc_value, usd_value
Title: Re: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: xeroc on February 24, 2018, 03:10:47 pm
Assuming you use the BitShares() class, you can throw a list of endpoints at it and it will cycle thru them on connection issues.

If you are using Notify() class, however, I fear that cycling is not available there, due to websocket notification having been lost during reconnections
Title: Re: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: litepresence on February 24, 2018, 03:58:53 pm
that just went over my head

rewind pls


what is Bitshares() vs Notify() and which did I use?
Title: Re: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: xeroc on February 25, 2018, 11:50:12 am
that just went over my head

rewind pls


what is Bitshares() vs Notify() and which did I use?
You probably use BitShares() somewhere if you dont know about Notify() :)

With Market(), you made use of BitShares() instanced internally.
Try something like this:

from bitshares import BitShares
bts = BitShares([list OF API NODES])
m = Market('USD:BTS", bitshares_instance=bts)
[/list]
Title: Re: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: litepresence on February 25, 2018, 03:59:58 pm
Quote
list OF API NODES

is there api way to fetch this list, else what is best source?

also experiencing some issues:

I run this:

Code: [Select]
from bitshares.blockchain import Blockchain

now = time.time()
chain = Blockchain()
current_block = chain.get_current_block_num()
blocktime = chain.block_time(current_block)
blocktimestamp = chain.block_timestamp(current_block)
print ('block           :', current_block)
print ('blocktime       :', blocktime)
print ('stamp           :', blocktimestamp)
print ('ctime(stamp)    :', time.ctime(blocktimestamp))
print ('now             :', now)
print ('ctime(now)      :', time.ctime(now))
drift = now - blocktimestamp
drifthours = drift/3600
print ('drift seconds   :', ('%.2f' % drift))
print ('drift hours     :', ('%.2f' % drifthours))

and this:

   

Code: [Select]
dex('last')
dex('account_value')



I get:


Code: [Select]
block           : 24736857
blocktime       : 2018-02-25 15:48:24
stamp           : 1519591704
ctime(stamp)    : Sun Feb 25 15:48:24 2018
now             : 1519573746.9503589
ctime(now)      : Sun Feb 25 10:49:06 2018
drift seconds   : -17957.05
drift hours     : -4.99
('Bitshares API', 'last', 'BTS:BTC')
{'highestBid': 0.0000188272226 BTC/BTS, 'lowestAsk': 0.0000192294582 BTC/BTS, 'latest': 0.0000192296154 BTC/BTS, 'core_exchange_rate': 1.0000000000 BTS/BTS, 'quoteVolume': 1,604.15817 BTS, 'percentChange': 5.28284118389577, 'baseVolume': 0.03025533 BTC, 'baseSettlement_price': 0.0000224399912 BTC/BTS}
0.00001923
('Bitshares API', 'book', 'BTS:BTC')
{'askv': [119.82921], 'askp': [1.9229451650394755e-05], 'bidv': [0.92791], 'bidp': [1.88272569537994e-05]}

the block number updates but is 10 blocks slow
relative to my open gui wallet that is counting blocks upward and connected

the blocktimestamp updates but is 5 hours too fast
relative to time.time()

the price data / book is not updating and is stale/not correct at the onset
relative to the price data I see in my gui bitshares exchange



????


buy/sell/cancel from dex() are working; live tested without issues
Title: Re: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: litepresence on February 25, 2018, 05:51:12 pm
Quote
list OF API NODES
Quote
is there api way to fetch this list, else what is best source?

I found this and it works in the format you suggested, but does not solve the issues in previous post

Code: [Select]
nodes = ['wss://bitshares.openledger.info/ws',
                'wss://bitshares.dacplay.org:8089/ws',
                'wss://dele-puppy.com/ws']

from docs here:

http://docs.bitshares.org/integration/traders/


is there a dynamically updated list or is that fairly static and reliable?
Title: Re: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: litepresence on February 25, 2018, 06:19:28 pm
I pulled this list off the gui wallet:

Code: [Select]
nodes = [   
            'wss://bit.btsabc.org/ws',
            'wss://eu.openledger.info/ws',
            'wss://dexnode.net/ws',
            'wss://ws.gdex.top',
            'wss://kc-us-dex.xeldal.com/ws',
            'wss://bts.ai.la/ws',
            'wss://btsza.co.za:8091/ws',
            'wss://japan.bitshares.apasia.tech/ws',
            'wss://api.bts.blckchnd.com',
            'wss://bitshares-api.wancloud.io/ws',
            'wss://eu.nodes.bitshares.ws',
            'wss://bitshares.crypto.fans/ws',
            'wss://dex.rnglab.org',
            'wss://bitshares.openledger.info/ws',
            'wss://ws.winex.pro',
            'wss://sg.nodes.bitshares.ws',
            'wss://us.nodes.bitshares.ws',
            'wss://bitshares.apasia.tech/ws',
            'wss://openledger.hk/ws',
            'wss://bitshares.dacplay.org/ws',
            'wss://bitshares.openledger.info/ws',
            'wss://bitshares.dacplay.org:8089/ws',
            'wss://dele-puppy.com/ws',
            'wss://eu.openledger.info/ws',
        ]


surely there's better way than copy paste from gui and strip text for starting with wss ??  some api to call?
Title: Re: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: litepresence on February 25, 2018, 07:25:56 pm
This looks great!

Just a matter of time until soneone sets up a bot that does sophisticated orders like stop-loss etc ...

indeed

https://imgur.com/b694qzs.png
Title: Re: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: litepresence on February 26, 2018, 07:30:10 pm
Quote
the block number updates but is 10 blocks slow
relative to my open gui wallet that is counting blocks upward and connected

the blocktimestamp updates but is 5 hours too fast
relative to time.time()

the price data / book is not updating and is stale/not correct at the onset
relative to the price data I see in my gui bitshares exchange

so issue one was resolved using

Code: [Select]
    CHAIN =  Blockchain(bitshares_instance=BitShares(
        nodes()),mode='head')

mode='head' means latest block even if not irreversible

second issue I created another thread for... it seems to be a bug related to timezone

3 issue still working on
Title: Re: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: litepresence on February 26, 2018, 10:10:41 pm
Quote
3 issue still working on
[/quote

I seem to have resolved this as well.

I was calling on 'BTC' markets I needed to be calling 'OPEN.BTC'

Title: Re: Wrapper for Common PyBitshares DEX Algo Trading API Calls
Post by: litepresence on March 04, 2018, 07:23:19 pm
'''CANCEL ALL VERSION 2'''        h/t @juhni

this version cancels all with a single API call using a list of string ID's

the method is not documented here:

http://docs.pybitshares.com/en/latest/market.html

says:

Quote
cancel(orderNumber, account=None)
Cancels an order you have placed in a given market. Requires only the “orderNumber”. An order number takes the form 1.7.xxx.

Parameters:   orderNumber (str) – The Order Object ide of the form 1.7.xxxx

apparently we can also use a list of str

Code: [Select]
    if command == 'cancel':

        # cancel all orders in this MARKET relentlessly until satisfied
        print(('Bitshares API', command)) 
        orders = MARKET.accountopenorders()
        print((len(orders), 'open orders to cancel'))
        if len(orders):
            attempt = 1   
            order_list = []     
            for order in orders:
                order_list.append(order['id'])
            while attempt:
                try:
                    details = MARKET.cancel(order_list)
                    print (details)
                    attempt = 0
                except:
                    print((attempt, 'cancel failed', order_list))
                    attempt += 1
                    if attempt > 10:
                        print ('cancel aborted')
                        return
                    pass