Skip to content

Commit

Permalink
Add Binance websocket support
Browse files Browse the repository at this point in the history
Squashed as some commits were broken by the rebase:

Add stream manager class

Remove all_tickers

Add method for starting all mark price socket

Add handler for closing websockets on exit

whitespace refactor

Refactor naming and move sleep timer

Update connect_args

Disable check for same thread

Add initial implementation of websockets for getting all mark prices

Co-authored-by: Ryan Sonshine <sonshine@amazon.com>
  • Loading branch information
2 people authored and edeng23 committed Jun 8, 2021
1 parent bd6c083 commit 80ef5fe
Show file tree
Hide file tree
Showing 11 changed files with 182 additions and 101 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,4 @@ repos:
- schedule==1.0.0
- sqlalchemy==1.3.23
- sqlitedict==1.7.0
- twisted==21.2.0
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ ignore-on-opaque-inference=yes
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local
ignored-classes=optparse.Values,thread._local,_thread._local,twisted.internet.reactor

# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
Expand Down
50 changes: 23 additions & 27 deletions binance_trade_bot/auto_trader.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from sqlalchemy.orm import Session

from .binance_api_manager import AllTickers, BinanceAPIManager
from .binance_api_manager import BinanceAPIManager
from .config import Config
from .database import Database
from .logger import Logger
Expand All @@ -20,34 +20,35 @@ def __init__(self, binance_manager: BinanceAPIManager, database: Database, logge
def initialize(self):
self.initialize_trade_thresholds()

def transaction_through_bridge(self, pair: Pair, all_tickers: AllTickers):
def transaction_through_bridge(self, pair: Pair):
"""
Jump from the source coin to the destination coin through bridge coin
"""
can_sell = False
balance = self.manager.get_currency_balance(pair.from_coin.symbol)
from_coin_price = all_tickers.get_price(pair.from_coin + self.config.BRIDGE)
from_coin_price = self.manager.get_ticker_price(pair.from_coin + self.config.BRIDGE)

if balance and balance * from_coin_price > self.manager.get_min_notional(pair.from_coin, self.config.BRIDGE):
if balance and balance * from_coin_price > self.manager.get_min_notional(
pair.from_coin.symbol, self.config.BRIDGE.symbol
):
can_sell = True
else:
self.logger.info("Skipping sell")

if can_sell and self.manager.sell_alt(pair.from_coin, self.config.BRIDGE, all_tickers) is None:
if can_sell and self.manager.sell_alt(pair.from_coin, self.config.BRIDGE) is None:
self.logger.info("Couldn't sell, going back to scouting mode...")
return None

result = self.manager.buy_alt(pair.to_coin, self.config.BRIDGE, all_tickers)

result = self.manager.buy_alt(pair.to_coin, self.config.BRIDGE)
if result is not None:
self.db.set_current_coin(pair.to_coin)
self.update_trade_threshold(pair.to_coin, float(result["price"]), all_tickers)
self.update_trade_threshold(pair.to_coin, float(result["price"]))
return result

self.logger.info("Couldn't buy, going back to scouting mode...")
return None

def update_trade_threshold(self, coin: Coin, coin_price: float, all_tickers: AllTickers):
def update_trade_threshold(self, coin: Coin, coin_price: float):
"""
Update all the coins with the threshold of buying the current held coin
"""
Expand All @@ -59,7 +60,7 @@ def update_trade_threshold(self, coin: Coin, coin_price: float, all_tickers: All
session: Session
with self.db.db_session() as session:
for pair in session.query(Pair).filter(Pair.to_coin == coin):
from_coin_price = all_tickers.get_price(pair.from_coin + self.config.BRIDGE)
from_coin_price = self.manager.get_ticker_price(pair.from_coin + self.config.BRIDGE)

if from_coin_price is None:
self.logger.info(
Expand All @@ -73,23 +74,21 @@ def initialize_trade_thresholds(self):
"""
Initialize the buying threshold of all the coins for trading between them
"""
all_tickers = self.manager.get_all_market_tickers()

session: Session
with self.db.db_session() as session:
for pair in session.query(Pair).filter(Pair.ratio.is_(None)).all():
if not pair.from_coin.enabled or not pair.to_coin.enabled:
continue
self.logger.info(f"Initializing {pair.from_coin} vs {pair.to_coin}")

from_coin_price = all_tickers.get_price(pair.from_coin + self.config.BRIDGE)
from_coin_price = self.manager.get_ticker_price(pair.from_coin + self.config.BRIDGE)
if from_coin_price is None:
self.logger.info(
"Skipping initializing {}, symbol not found".format(pair.from_coin + self.config.BRIDGE)
)
continue

to_coin_price = all_tickers.get_price(pair.to_coin + self.config.BRIDGE)
to_coin_price = self.manager.get_ticker_price(pair.to_coin + self.config.BRIDGE)
if to_coin_price is None:
self.logger.info(
"Skipping initializing {}, symbol not found".format(pair.to_coin + self.config.BRIDGE)
Expand All @@ -104,14 +103,14 @@ def scout(self):
"""
raise NotImplementedError()

def _get_ratios(self, coin: Coin, coin_price: float, all_tickers: AllTickers):
def _get_ratios(self, coin: Coin, coin_price):
"""
Given a coin, get the current price ratio for every other enabled coin
"""
ratio_dict: Dict[Pair, float] = {}

for pair in self.db.get_pairs_from(coin):
optional_coin_price = all_tickers.get_price(pair.to_coin + self.config.BRIDGE)
optional_coin_price = self.manager.get_ticker_price(pair.to_coin + self.config.BRIDGE)

if optional_coin_price is None:
self.logger.info(
Expand All @@ -133,11 +132,11 @@ def _get_ratios(self, coin: Coin, coin_price: float, all_tickers: AllTickers):
) - pair.ratio
return ratio_dict

def _jump_to_best_coin(self, coin: Coin, coin_price: float, all_tickers: AllTickers):
def _jump_to_best_coin(self, coin: Coin, coin_price: float):
"""
Given a coin, search for a coin to jump to
"""
ratio_dict = self._get_ratios(coin, coin_price, all_tickers)
ratio_dict = self._get_ratios(coin, coin_price)

# keep only ratios bigger than zero
ratio_dict = {k: v for k, v in ratio_dict.items() if v > 0}
Expand All @@ -146,36 +145,33 @@ def _jump_to_best_coin(self, coin: Coin, coin_price: float, all_tickers: AllTick
if ratio_dict:
best_pair = max(ratio_dict, key=ratio_dict.get)
self.logger.info(f"Will be jumping from {coin} to {best_pair.to_coin_id}")
self.transaction_through_bridge(best_pair, all_tickers)
self.transaction_through_bridge(best_pair)

def bridge_scout(self):
"""
If we have any bridge coin leftover, buy a coin with it that we won't immediately trade out of
"""
bridge_balance = self.manager.get_currency_balance(self.config.BRIDGE.symbol)
all_tickers = self.manager.get_all_market_tickers()

for coin in self.db.get_coins():
current_coin_price = all_tickers.get_price(coin + self.config.BRIDGE)
current_coin_price = self.manager.get_ticker_price(coin + self.config.BRIDGE)

if current_coin_price is None:
continue

ratio_dict = self._get_ratios(coin, current_coin_price, all_tickers)
ratio_dict = self._get_ratios(coin, current_coin_price)
if not any(v > 0 for v in ratio_dict.values()):
# There will only be one coin where all the ratios are negative. When we find it, buy it if we can
if bridge_balance > self.manager.get_min_notional(coin.symbol, self.config.BRIDGE.symbol):
self.logger.info(f"Will be purchasing {coin} using bridge coin")
self.manager.buy_alt(coin, self.config.BRIDGE, all_tickers)
self.manager.buy_alt(coin, self.config.BRIDGE)
return coin
return None

def update_values(self):
"""
Log current value state of all altcoin balances against BTC and USDT in DB.
"""
all_ticker_values = self.manager.get_all_market_tickers()

now = datetime.now()

session: Session
Expand All @@ -185,8 +181,8 @@ def update_values(self):
balance = self.manager.get_currency_balance(coin.symbol)
if balance == 0:
continue
usd_value = all_ticker_values.get_price(coin + "USDT")
btc_value = all_ticker_values.get_price(coin + "BTC")
usd_value = self.manager.get_ticker_price(coin + "USDT")
btc_value = self.manager.get_ticker_price(coin + "BTC")
cv = CoinValue(coin, balance, usd_value, btc_value, datetime=now)
session.add(cv)
self.db.send_update(cv)
32 changes: 9 additions & 23 deletions binance_trade_bot/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from sqlitedict import SqliteDict

from .binance_api_manager import AllTickers, BinanceAPIManager
from .binance_api_manager import BinanceAPIManager
from .config import Config
from .database import Database
from .logger import Logger
Expand All @@ -14,14 +14,6 @@
cache = SqliteDict("data/backtest_cache.db")


class FakeAllTickers(AllTickers): # pylint: disable=too-few-public-methods
def __init__(self, manager: "MockBinanceManager"): # pylint: disable=super-init-not-called
self.manager = manager

def get_price(self, ticker_symbol):
return self.manager.get_market_ticker_price(ticker_symbol)


class MockBinanceManager(BinanceAPIManager):
def __init__(
self,
Expand All @@ -39,16 +31,10 @@ def __init__(
def increment(self, interval=1):
self.datetime += timedelta(minutes=interval)

def get_all_market_tickers(self):
"""
Get ticker price of all coins
"""
return FakeAllTickers(self)

def get_fee(self, origin_coin: Coin, target_coin: Coin, selling: bool):
return 0.0075

def get_market_ticker_price(self, ticker_symbol: str):
def get_ticker_price(self, ticker_symbol: str):
"""
Get ticker price of a specific coin
"""
Expand Down Expand Up @@ -77,12 +63,12 @@ def get_currency_balance(self, currency_symbol: str):
"""
return self.balances.get(currency_symbol, 0)

def buy_alt(self, origin_coin: Coin, target_coin: Coin, all_tickers: AllTickers):
def buy_alt(self, origin_coin: Coin, target_coin: Coin):
origin_symbol = origin_coin.symbol
target_symbol = target_coin.symbol

target_balance = self.get_currency_balance(target_symbol)
from_coin_price = all_tickers.get_price(origin_symbol + target_symbol)
from_coin_price = self.get_ticker_price(origin_symbol + target_symbol)

order_quantity = self._buy_quantity(origin_symbol, target_symbol, target_balance, from_coin_price)
target_quantity = order_quantity * from_coin_price
Expand All @@ -96,12 +82,12 @@ def buy_alt(self, origin_coin: Coin, target_coin: Coin, all_tickers: AllTickers)
)
return {"price": from_coin_price}

def sell_alt(self, origin_coin: Coin, target_coin: Coin, all_tickers: AllTickers):
def sell_alt(self, origin_coin: Coin, target_coin: Coin):
origin_symbol = origin_coin.symbol
target_symbol = target_coin.symbol

origin_balance = self.get_currency_balance(origin_symbol)
from_coin_price = all_tickers.get_price(origin_symbol + target_symbol)
from_coin_price = self.get_ticker_price(origin_symbol + target_symbol)

order_quantity = self._sell_quantity(origin_symbol, target_symbol, origin_balance)
target_quantity = order_quantity * from_coin_price
Expand All @@ -122,12 +108,12 @@ def collate_coins(self, target_symbol: str):
total += balance
continue
if coin == self.config.BRIDGE.symbol:
price = self.get_market_ticker_price(target_symbol + coin)
price = self.get_ticker_price(target_symbol + coin)
if price is None:
continue
total += balance / price
else:
price = self.get_market_ticker_price(coin + target_symbol)
price = self.get_ticker_price(coin + target_symbol)
if price is None:
continue
total += price * balance
Expand Down Expand Up @@ -175,7 +161,7 @@ def backtest(

starting_coin = db.get_coin(starting_coin or config.SUPPORTED_COIN_LIST[0])
if manager.get_currency_balance(starting_coin.symbol) == 0:
manager.buy_alt(starting_coin, config.BRIDGE, manager.get_all_market_tickers())
manager.buy_alt(starting_coin, config.BRIDGE)
db.set_current_coin(starting_coin)

strategy = get_strategy(config.STRATEGY)
Expand Down
Loading

0 comments on commit 80ef5fe

Please sign in to comment.