Пример #1
0
    async def req(self, client: aiohttp.ClientSession,
                  method: str,
                  uri: str,
                  params: dict,
                  headers: dict,
                  signed: bool = True) -> (dict, dict):
        """ make a Binance API request, return a parsed JSON response """
        headers['Accept'] = 'application/json'

        if signed:
            headers['X-MBX-APIKEY'] = self.key
            params['timestamp'] = tstamp()
            params['signature'] = self.sign(params)

        url = urllib.parse.urljoin(self.url, uri)
        resp = await client.request(method, url, params=params, headers=headers)

        # check for issues
        if resp.status == 418: # IP ban
            raise CException(f"Your IP is banned for {resp.headers['Retry-After']} seconds. Wait!")
        if resp.status == 429: # Rate limiting
            raise CException(f"Your IP is rate limited for ' + \
                             '{resp.headers['Retry-After']} seconds. Wait!")

        jresp = await resp.json()
        if not resp.ok: # other issues
            raise self.ApiError(f'Request for {url} failed\n{resp.status}: {jresp}', jresp)
        return resp.headers, jresp
Пример #2
0
async def quote_qty_from_usd(client: aiohttp.ClientSession, api: BinanceAPI,
                             usd_symbol: dict) -> float:
    """ get quote quantity by its value in USD
        <usd_symbol>: a <quote>/BUSD or <quote>/USDT symbol """
    if api.env.usd_value < 0:
        raise CException('USD quote value cannot be negative')
    if usd_symbol == {}:
        raise CException('No BUSD/USDT trade symbol found, aborting!')
    api.set_pair(usd_symbol)
    return await api.last_price(client)
Пример #3
0
 def __init__(self, symbols: list, nklines: int, limits: KlineLimits):
     if nklines <= 0:
         raise CException(f'Bad kline count: {nklines}')
     self.nklines = nklines
     self.symbols = {}
     for symb in symbols:
         self.symbols[symb['symbol']] = KlineStorage(symb, limits)
Пример #4
0
 def lock(self, bcoin: str):
     """ lock in a trade pair and notify main thread
         InvalidPair => bad pair/try again, Exception => abort
         on success notify a waiting thread to call `start` """
     self.mut1.acquire()
     # reject if locked
     if self.locked:
         self.mut1.release()
         raise CException('Market operation is already running!')
     # retry if bad coin is turned in
     if bcoin not in self.pairs:
         self.mut1.release()
         raise InvalidPair(
             f'Trading pair {bcoin}/{self.env.qcoin} not found')
     self.locked = True
     self.mut1.release()
     # lock in a trading pair
     self.api.set_pair(self.pairs[bcoin])
     self.use_oco = self.env.stop > -100 and self.api.pair['ocoAllowed']
     if self.env.stop > -100 and not self.api.pair['ocoAllowed']:
         CColors.wprint(
             'You set a stop price but this trading pair doesn\'t allow OCO trades!'
         )
     # notify worker
     with self.cvar:
         self.ready = True
         self.cvar.notify()
Пример #5
0
 async def sell_coins_limit(self, client: aiohttp.ClientSession) -> int:
     """ sell coins using a limit or OCO sell, return order ID """
     # try OCO if suitable
     if self.use_oco:
         oid = await self.api.sell_coin_oco(client, self.bqty, self.tprice,
                                            self.sprice)
         if not oid:
             raise CException('OCO sell failed')
         CColors.cprint(f'[OCO SELL] target profit: {self.env.profit:.2f}%, ' + \
                        f'max loss: {-self.env.stop:.2f}%', CColors.OKGREEN)
         return oid
     # try limit sell
     oid = await self.api.sell_coin_limit(client, self.bqty, self.tprice)
     if not oid:
         raise CException('Limit sell failed')
     CColors.cprint(f'[LIMIT SELL] target profit: {self.env.profit:.2f}%',
                    CColors.OKGREEN)
     return oid
Пример #6
0
 async def cancel_limit(self, client: aiohttp.ClientSession, oid: int):
     """ attempts to cancel a limit order and subtract its fills """
     if self.use_oco:
         succ, qty = await self.api.cancel_oco_order(client, oid)
     else:
         succ, qty = await self.api.cancel_order(client, oid)
     if not succ:
         raise CException('Unable to cancel limit/OCO order, ' + \
                          'it is probably executed already!')
     self.bqty -= qty
Пример #7
0
    async def check_sell_eligibility(self, client: aiohttp.ClientSession):
        """ check if you can sell with your strategy """
        avg = await self.api.avg_price(client)
        if self.env.sell_type == SellType.MARKET:
            low, high = self.api.qty_bound(avg, True)
        else:
            # adjust profit/loss targets
            plow, phigh = self.api.price_bound(avg)
            self.tprice = min(self.tprice, phigh)
            low, high = self.api.qty_bound(self.tprice)
            if self.use_oco:
                self.sprice = max(self.sprice, plow)
                low, _ = self.api.qty_bound(self.sprice)

        if not low <= self.bqty <= high:
            raise CException(
                'Sell quantity out of allowed bounds, cannot sell!')
        if not low * 1.1 <= self.bqty <= high * 0.9:
            CColors.wprint('Caution, you are nearing Binance\'s quantity limits, ' + \
                            'high price fluctuations might prohibit your sell!')
Пример #8
0
 def market_order_status(self, resp: dict) -> (float, float):
     """ print market order status and return executed qty and average fill price
         <resp>: response object from `req` """
     bcoin, qcoin = self.pair['baseAsset'], self.pair['quoteAsset']
     bqty, qqty = float(resp["executedQty"]), float(resp["cummulativeQuoteQty"])
     CColors.iprint(f'Executed market order (status: {resp["status"]})')
     if resp['side'] == 'BUY':
         print(f'Bought {self.bfmt(bqty)} {bcoin} with {self.qfmt(qqty)} {qcoin}')
     else:
         print(f'Sold {self.bfmt(bqty)} {bcoin} for {self.qfmt(qqty)} {qcoin}')
     print('Fills:')
     avg_price = 0
     for fill in resp['fills']:
         price, qty = float(fill['price']), float(fill['qty'])
         avg_price += price * qty / bqty
         print(f'  {self.bfmt(qty)} {bcoin} at {self.qfmt(price)} {qcoin} ' + \
                     f'(fee: {fill["commission"]} {fill["commissionAsset"]})')
     print(f'Average fill price: {self.qfmt(avg_price)} {qcoin}')
     if not avg_price:
         raise CException('Average fill price seems to be zero')
     return bqty, avg_price
Пример #9
0
async def buy_from_source(client: aiohttp.ClientSession, api: BinanceAPI,
                          src_symbols: dict, balances: dict, bqty: float):
    """ Buy <val> of quote coin """
    for asset, (free, _) in balances.items():
        if free == 0 or asset not in src_symbols:
            continue
        symbol = src_symbols[asset]
        api.set_pair(symbol)
        avg, last = await asyncio.gather(api.avg_price(client),
                                         api.last_price(client))
        qqty = bqty * last
        if free < qqty:
            continue
        low, high = api.qty_bound(avg, True)
        bqty = max(low, bqty)
        qqty = bqty * last
        if free < qqty or bqty > high:
            continue
        succ, qty, _ = await api.buy_coin_market(client, qqty)
        if not succ:
            continue
        return qty
    raise CException('Failed to buy quote coin')
Пример #10
0
 async def sub_multi(self, streams: list):
     """ subscribe to multiple streams """
     if len(streams) > 1024:
         raise CException('Cannot subscribe to more than 1024 streams')
     async for val in self.read(f'stream?streams={"/".join(streams)}'):
         yield val
Пример #11
0
 async def balances(self, client: aiohttp.ClientSession) -> float:
     """ return coin balances """
     _, resp = await self.req(client, 'GET', 'account', {}, {})
     if 'balances' not in resp:
         raise CException('Failed to query user data, bailing out')
     return resp['balances']
Пример #12
0
async def setup(api: BinanceAPI) -> (dict, float):
    """ main parameter setup
        return exchange info and quote amount to sell """
    env = api.env

    def set_stdin(prompt: str, default):
        if not env.override:
            return default
        ret = input(prompt)
        return ret if ret else default

    prompt = f'Enter quote coin symbol (coin to trade for) [default: {env.qcoin}]: '
    env.qcoin = set_stdin(prompt, env.qcoin).upper()

    async with aiohttp.ClientSession() as client:
        info, lbals = await asyncio.gather(api.exchange_info(client),
                                           api.balances(client))
        symbols, src_symbols, usd_symbol = api.quote_symbols(info)
        bals = filter_balances(lbals, [env.qcoin] + env.src_coins)
        if env.qcoin not in bals:
            raise CException('Quote coin is invalid')
        qbal, qloc = bals[env.qcoin]
        del bals[env.qcoin]
        print(
            f'Your free balance for {env.qcoin} is {ffmt(qbal)} (locked: {ffmt(qloc)})'
        )
        def_qty = env.buy_perc / 100 * qbal
        if env.usd_value:
            # fixed USD quote balance feature
            usd_price = await quote_qty_from_usd(client, api, usd_symbol)
            qqty = env.usd_value / usd_price
            while qqty > qbal:
                diff = 1.02 * (qqty - qbal)
                qbal += await buy_from_source(client, api, src_symbols, bals,
                                              diff)
        else:
            prompt = f'Enter {env.qcoin} amount to sell ' + \
                     f'[default: {ffmt(def_qty)} ({env.buy_perc:.2f}%)]: '
            qqty = float(set_stdin(prompt, def_qty))
            if qqty <= 0:
                raise CException(
                    f'Cannot sell non-positive amount of {env.qcoin}')
            if qqty > qbal:
                raise CException('Insufficient quote balance')

    prompt = f'Enter sell type (LIMIT|MARKET) [default: {env.sell_type.name}]: '
    env.sell_type = SellType(set_stdin(prompt, env.sell_type))

    prompt = f'Enter desired profit in % [default: {env.profit:.2f}]: '
    env.profit = float(set_stdin(prompt, env.profit))

    if env.profit <= 0:
        CColors.wprint(
            'You have set a non-positive profit. Proceeding may net you a loss!'
        )

    prompt = f'Enter stop level in % to manage risk [default: {env.stop:.2f}]: '
    env.stop = float(set_stdin(prompt, env.stop))
    if not -100 <= env.stop < env.profit:
        raise CException('Stop percentage must be lower than profits!')

    print('---- SELECTED OPTIONS ----')
    print(f'Selected quote coin: {env.qcoin}')
    print(f'Selected quote amount to sell: {ffmt(qqty)} {env.qcoin} ' + \
          f'(available: {ffmt(qbal)} {env.qcoin})')
    print(f'Selected sell strategy: {env.sell_type.name}')
    print(f'Selected target profit: {env.profit:.2f}%')
    print(f'Selected stop percentage: {env.stop:.2f}%')
    print('--------------------------')
    return symbols, qqty
Пример #13
0
 async def sell_coins_market(self, client: aiohttp.ClientSession):
     """ sell coins using market sell """
     succ, _, price = await self.api.sell_coin_market(client, self.bqty)
     if not succ:
         raise CException('Market sell failed')
     self.sell_market_report(price)