Пример #1
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()
Пример #2
0
 def sell_market_report(self, sprice: float):
     """ report on a successful market sell
         <sprice>: average sell price """
     profit = 100 * (sprice / self.bprice - 1)
     if profit >= 0:
         CColors.cprint(f'[MARKET SELL PROFIT] +{profit:.2f}%',
                        CColors.OKGREEN)
     else:
         CColors.cprint(f'[MARKET SELL LOSS] {profit:.2f}%', CColors.FAIL)
Пример #3
0
 async def read(self, uri: str):
     """ subscribe to a single stream and yield data on reception """
     url = urllib.parse.urljoin(self.url, uri)
     while True:
         try:
             async with websockets.connect(url, ssl=True) as wsock:
                 while True:
                     yield json.loads(await wsock.recv())
         except websockets.exceptions.WebSocketException as exc:
             CColors.wprint(f'WebSockets error, attempting to reconnect: {exc}')
Пример #4
0
 async def cancel_order(self, client: aiohttp.ClientSession, order_id: int) -> (bool, float):
     """ Cancel a regular order, return success and executed quantity """
     params = {
         'symbol': self.pair['symbol'],
         'orderId': order_id
     }
     try:
         _, resp = await self.req(client, 'DELETE', 'order', params, {})
         CColors.iprint(f'Canceled limit order #{order_id} (status: {resp["status"]})')
         return True, float(resp['executedQty'])
     except BinanceAPI.ApiError as exc:
         CColors.eprint(f'Order cancel failed with {exc.data}')
         return False, 0
Пример #5
0
 async def bailout(self):
     """ bailout and immediately cancel current order and sell coins """
     if not self.allow_bailout:
         return
     async with aiohttp.ClientSession() as client:
         # Cancel limit orders if any
         if self.oid:
             await self.cancel_limit(client, self.oid)
         # Sell everything on market immediately
         CColors.wprint('Selling on market immediately!')
         succ, _, price = await self.api.sell_coin_market(client, self.bqty)
         if succ:
             self.sell_market_report(price)
Пример #6
0
 async def cancel_oco_order(self, client: aiohttp.ClientSession,
                            order_list_id: int) -> (bool, float):
     """ Cancel an OCO order, return success and executed quantity """
     params = {
         'symbol': self.pair['symbol'],
         'orderListId': order_list_id
     }
     try:
         _, resp = await self.req(client, 'DELETE', 'orderList', params, {})
         CColors.iprint(f'Canceled OCO #{order_list_id} (status: {resp["listOrderStatus"]})')
         qty = sum((float(rep['executedQty']) for rep in resp['orderReports']))
         return True, qty
     except BinanceAPI.ApiError as exc:
         CColors.eprint(f'OCO order cancel failed with {exc.data}')
         return False, 0
Пример #7
0
 async def start(self):
     """ start trading, only call this method after locking in a pair """
     CColors.iprint(
         f'Market manager started with pair {self.api.pair["symbol"]}')
     async with aiohttp.ClientSession() as client:
         # buy <bcoin> immediately at market price, get qty. and avg. price
         succ, self.bqty, self.bprice = await self.api.buy_coin_market(
             client, self.qqty)
         if not succ:
             return
         self.tprice = (1 + self.env.profit / 100) * self.bprice
         self.sprice = (1 + self.env.stop / 100) * self.bprice
         # check sell amount eligibility at this point
         await self.check_sell_eligibility(client)
         # sell bought <bcoin> with <profit>% profit
         await self.sell_coins(client)
Пример #8
0
    def analyze_mins(self, mins: int, color: CColors):
        """ analyze symbol per minutes """
        klast = self.klines[-1]
        kprev = self.klines[-1 - mins]
        dcl = (klast.price_cl / kprev.price_cl - 1) * 100
        rvol = self.vol_ratio(mins)

        if mins not in self.nalarm:
            self.nalarm[mins] = 0

        if rvol and dcl > self.limits.price_chg_min and \
                    (dcl > self.limits.price_thr or rvol > self.limits.vol_thr and dcl) > 0:
            mult = self.nalarm[mins] + 1
            CColors.cprint(f'{self.prefix(mult)} up {dcl:.2f}% ' + \
                           f'in {mins}min, vol. chg. {rvol:.2f}%', color)
        else:
            mult = 0
        self.nalarm[mins] = mult
Пример #9
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
Пример #10
0
    async def sell_coin_market(self, client: aiohttp.ClientSession,
                               bqty: float) -> (bool, float, float):
        """ sell <bqty> of <bcoin> for <qcoin> at market price
            return success, amount of <qcoin> purchased and average trade price """
        bcoin = self.pair['baseAsset']
        CColors.cprint(f'[MARKET SELL] Selling {self.bfmt_mkt(bqty)} {bcoin}',
                       CColors.WARNING)

        params = {
            'symbol': self.pair['symbol'],
            'side': 'SELL',
            'type': 'MARKET',
            'quantity': self.bfmt_mkt(bqty), # sell <bqty> of <bcoin>
        }
        try:
            _, resp = await self.req(client, 'POST', 'order', params, {})
            return (True, *self.market_order_status(resp))
        except BinanceAPI.ApiError as exc:
            CColors.eprint(f'Market sell failed with {exc.data}')
            return False, 0, 0
Пример #11
0
    async def buy_coin_market(self, client: aiohttp.ClientSession,
                              qqty: float) -> (bool, float, float):
        """ buy <bcoin> with <qqty> of <qcoin> at market price
            return amount of <bcoin> purchased and average trade price """
        bcoin, qcoin = self.pair['baseAsset'], self.pair['quoteAsset']
        msg = f'[MARKET BUY] Buying {bcoin} with {self.qfmt(qqty)} {qcoin}'
        CColors.cprint(msg, CColors.WARNING)

        params = {
            'symbol': self.pair['symbol'],
            'side': 'BUY',
            'type': 'MARKET',
            'quoteOrderQty': self.qfmt(qqty), # buy with <qqty> of <qcoin>
        }
        try:
            _, resp = await self.req(client, 'POST', 'order', params, {})
            return (True, *self.market_order_status(resp))
        except BinanceAPI.ApiError as exc:
            CColors.eprint(f'Market buy failed with {exc.data}')
            return False, 0, 0
Пример #12
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!')
Пример #13
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
Пример #14
0
async def main():
    """ Entrypoint """
    klen = 241  # 4h remanence
    thresh = 5, 900  # 5% positive price fluctuation, 900% positive volume fluctuation
    min_vol = 0.1  # do not alert under 0.1 executed quote volume (tuned for BTC)
    min_chg = 0.1  # minimum acceptable price change, regardless of volume
    limits = KlineLimits(*thresh, min_vol, min_chg)

    env = Environment('.env')
    api = BinanceAPI(env)
    wapi = BinanceWSAPI(env)

    # fetch symbols to track
    qsymbols = await quote_symbols(api)
    qvalues = qsymbols.values()
    symb_names = [symb['symbol'] for symb in qvalues]
    qlen = len(qvalues)
    CColors.iprint(
        f'DawnSpotter online.\nTracking {qlen} pairs: {", ".join(symb_names)}')

    # prepare the kline data structure
    manager = KlineManager(qvalues, klen, limits)
    # Pull historical data from the API
    maxrun = 1200 // (qlen + 41)
    print(
        f'Pulling historical data from REST API, do not rerun this more than {maxrun}x/min!'
    )
    async with aiohttp.ClientSession() as client:
        coros = (api.last_klines(client, '1m', klen, symbol)
                 for symbol in qvalues)
        preconf = await asyncio.gather(*coros)
    manager.fill(zip(symb_names, preconf))

    # read trade data from WS
    print('Updating data from WebSockets...')
    async for tdata in wapi.klines_bulk(symb_names, '1m'):
        manager.update(tdata['data']['k'])
Пример #15
0
    async def sell_coins(self, client: aiohttp.ClientSession):
        """ coin selling logic """
        bcoin = self.api.pair['baseAsset']
        lprice = 0  # last traded price

        if self.env.sell_type == SellType.LIMIT:
            # put a limit order on the book immediately
            self.oid = await self.sell_coins_limit(client)
            if not self.env.bailout:
                return
        self.allow_bailout = self.env.bailout
        async for tdata in self.wapi.agg_trades_single(
                self.api.pair['symbol']):
            # last traded price is the current market price
            _lprice = float(tdata['p'])
            if _lprice == lprice:
                continue
            lprice = _lprice

            # calculate estimated profit
            eprofit = 100 * (lprice / self.bprice - 1)
            if eprofit >= 0:
                CColors.cprint(f'[+{eprofit:.2f}%] 1 {bcoin} = ' + \
                                f'{self.api.qfmt(lprice)} {self.env.qcoin}', CColors.OKGREEN)
            else:
                CColors.cprint(f'[{eprofit:.2f}%] 1 {bcoin} = ' + \
                                f'{self.api.qfmt(lprice)} {self.env.qcoin}', CColors.FAIL)

            if self.env.sell_type == SellType.LIMIT:
                # limit orders are here just to be able to bailout
                continue

            # sell if forced or profit/loss limits are triggered
            if lprice > self.tprice or lprice < self.sprice:
                self.allow_bailout = False
                await self.sell_coins_market(client)
                return
Пример #16
0
def coin_from_stdin(manager: MarketManager):
    """ fetch a coin name from stdin """
    prompt = CColors.cstr('Enter base coin symbol (coin to buy and sell): ',
                          CColors.WARNING)
    while True:
        try:
            bcoin = input(prompt).upper()
        except Exception:
            break
        if not bcoin:
            continue
        try:
            manager.lock(bcoin)
            return
        except InvalidPair as exc:
            print(str(exc))
        except Exception as exc:
            print(str(exc))
            break
    with manager.cvar:
        manager.cvar.notify()
Пример #17
0
    async def sell_coin_limit(self, client: aiohttp.ClientSession,
                              bqty: float, price: float) -> int:
        """ sell <bqty> of <bcoin>, return order ID (0 = fail) """
        bcoin = self.pair['baseAsset']
        CColors.cprint(f'[LIMIT SELL] Selling {self.bfmt(bqty)} {bcoin} at {self.qfmt(price)}',
                       CColors.WARNING)

        params = {
            'symbol': self.pair['symbol'],
            'side': 'SELL',
            'type': 'LIMIT',
            'timeInForce': 'GTC', # good till cancelled
            'quantity': self.bfmt(bqty),
            'price': self.qfmt(price),
        }
        try:
            _, resp = await self.req(client, 'POST', 'order', params, {})
            CColors.iprint(f'Executed limit sell order (status: {resp["status"]})')
        except BinanceAPI.ApiError as exc:
            CColors.eprint(f'Limit sell failed with {exc.data}')
            return 0
        return resp['orderId']
Пример #18
0
    async def sell_coin_oco(self, client: aiohttp.ClientSession,
                            bqty: float, price: float, sprice: float) -> int:
        """ sell <bqty> of <bcoin> as OCO, return order ID (0 = fail) """
        bcoin = self.pair['baseAsset']
        msg = f'[OCO SELL] Selling {self.bfmt(bqty)} {bcoin} at {self.qfmt(price)}' + \
              f', stop: {self.qfmt(sprice)}'
        CColors.cprint(msg, CColors.WARNING)

        params = {
            'symbol': self.pair['symbol'],
            'side': 'SELL',
            'quantity': self.bfmt(bqty),
            'price': self.qfmt(price),
            'stopPrice': self.qfmt(sprice),
            'stopLimitPrice': self.qfmt(sprice * 0.98),
            'stopLimitTimeInForce': 'GTC' # good till cancelled
        }
        try:
            _, resp = await self.req(client, 'POST', 'order/oco', params, {})
            CColors.iprint(f'Executed OCO order (status: {resp["listOrderStatus"]})')
        except BinanceAPI.ApiError as exc:
            CColors.eprint(f'OCO sell failed with {exc.data}')
            return 0
        return resp['orderListId']
Пример #19
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