diff --git a/README.md b/README.md index 6479bf8ea..d6f817627 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The other values should be self-explanatory, if not feel free to raise a github issue. ##### Prerequisites -* python3 +* python3.6 * sqlite ##### Install diff --git a/analyze.py b/analyze.py index 68b7fa1c6..835fe423f 100644 --- a/analyze.py +++ b/analyze.py @@ -11,7 +11,7 @@ logging.basicConfig(level=logging.DEBUG, logger = logging.getLogger(__name__) -def get_ticker_dataframe(pair): +def get_ticker_dataframe(pair: str) -> StockDataFrame: """ Analyses the trend for the given pair :param pair: pair as str in format BTC_ETH or BTC-ETH @@ -51,7 +51,7 @@ def get_ticker_dataframe(pair): return dataframe -def populate_trends(dataframe): +def populate_trends(dataframe: StockDataFrame) -> StockDataFrame: """ Populates the trends for the given dataframe :param dataframe: StockDataFrame @@ -73,7 +73,7 @@ def populate_trends(dataframe): return dataframe -def get_buy_signal(pair): +def get_buy_signal(pair: str) -> bool: """ Calculates a buy signal based on StochRSI indicator :param pair: pair in format BTC_ANT or BTC-ANT @@ -93,7 +93,7 @@ def get_buy_signal(pair): return signal -def plot_dataframe(dataframe, pair): +def plot_dataframe(dataframe: StockDataFrame, pair: str) -> None: """ Plots the given dataframe :param dataframe: StockDataFrame diff --git a/exchange.py b/exchange.py index 5b4761625..d2e76e7a3 100644 --- a/exchange.py +++ b/exchange.py @@ -1,5 +1,7 @@ import enum import logging +from typing import List + from bittrex.bittrex import Bittrex from poloniex import Poloniex from wrapt import synchronized @@ -9,18 +11,6 @@ logger = logging.getLogger(__name__) _exchange_api = None -@synchronized -def get_exchange_api(conf): - """ - Returns the current exchange api or instantiates a new one - :return: exchange.ApiWrapper - """ - global _exchange_api - if not _exchange_api: - _exchange_api = ApiWrapper(conf) - return _exchange_api - - class Exchange(enum.Enum): POLONIEX = 0 BITTREX = 1 @@ -33,7 +23,7 @@ class ApiWrapper(object): * Bittrex * Poloniex (partly) """ - def __init__(self, config): + def __init__(self, config: dict): """ Initializes the ApiWrapper with the given config, it does not validate those values. :param config: dict @@ -54,13 +44,13 @@ class ApiWrapper(object): else: self.api = None - def buy(self, pair, rate, amount): + def buy(self, pair: str, rate: float, amount: float) -> str: """ Places a limit buy order. :param pair: Pair as str, format: BTC_ETH :param rate: Rate limit for order :param amount: The amount to purchase - :return: None + :return: order_id of the placed buy order """ if self.dry_run: pass @@ -73,7 +63,7 @@ class ApiWrapper(object): raise RuntimeError('BITTREX: {}'.format(data['message'])) return data['result']['uuid'] - def sell(self, pair, rate, amount): + def sell(self, pair: str, rate: float, amount: float) -> str: """ Places a limit sell order. :param pair: Pair as str, format: BTC_ETH @@ -92,7 +82,7 @@ class ApiWrapper(object): raise RuntimeError('BITTREX: {}'.format(data['message'])) return data['result']['uuid'] - def get_balance(self, currency): + def get_balance(self, currency: str) -> float: """ Get account balance. :param currency: currency as str, format: BTC @@ -109,7 +99,7 @@ class ApiWrapper(object): raise RuntimeError('BITTREX: {}'.format(data['message'])) return float(data['result']['Balance'] or 0.0) - def get_ticker(self, pair): + def get_ticker(self, pair: str) -> dict: """ Get Ticker for given pair. :param pair: Pair as str, format: BTC_ETC @@ -132,7 +122,7 @@ class ApiWrapper(object): 'last': float(data['result']['Last']), } - def cancel_order(self, order_id): + def cancel_order(self, order_id: str) -> None: """ Cancel order for given order_id :param order_id: id as str @@ -147,7 +137,7 @@ class ApiWrapper(object): if not data['success']: raise RuntimeError('BITTREX: {}'.format(data['message'])) - def get_open_orders(self, pair): + def get_open_orders(self, pair: str) -> List[dict]: """ Get all open orders for given pair. :param pair: Pair as str, format: BTC_ETC @@ -170,7 +160,7 @@ class ApiWrapper(object): 'remaining': entry['QuantityRemaining'], } for entry in data['result']] - def get_pair_detail_url(self, pair): + def get_pair_detail_url(self, pair: str) -> str: """ Returns the market detail url for the given pair :param pair: pair as str, format: BTC_ANT @@ -180,3 +170,15 @@ class ApiWrapper(object): raise NotImplemented('Not implemented') elif self.exchange == Exchange.BITTREX: return 'https://bittrex.com/Market/Index?MarketName={}'.format(pair.replace('_', '-')) + + +@synchronized +def get_exchange_api(conf: dict) -> ApiWrapper: + """ + Returns the current exchange api or instantiates a new one + :return: exchange.ApiWrapper + """ + global _exchange_api + if not _exchange_api: + _exchange_api = ApiWrapper(conf) + return _exchange_api diff --git a/main.py b/main.py index 88e1d6faf..a17895561 100755 --- a/main.py +++ b/main.py @@ -5,11 +5,13 @@ import time import traceback from datetime import datetime from json import JSONDecodeError +from typing import Optional + from requests import ConnectionError from wrapt import synchronized from analyze import get_buy_signal from persistence import Trade, Session -from exchange import get_exchange_api +from exchange import get_exchange_api, Exchange from rpc.telegram import TelegramHandler from utils import get_conf @@ -32,11 +34,11 @@ class TradeThread(threading.Thread): super().__init__() self._should_stop = False - def stop(self): + def stop(self) -> None: """ stops the trader thread """ self._should_stop = True - def run(self): + def run(self) -> None: """ Threaded main function :return: None @@ -60,7 +62,7 @@ class TradeThread(threading.Thread): TelegramHandler.send_msg('*Status:* `Trader has stopped`') @staticmethod - def _process(): + def _process() -> None: """ Queries the persistence layer for open trades and handles them, otherwise a new trade is created. @@ -107,8 +109,9 @@ class TradeThread(threading.Thread): # Initial stopped TradeThread instance _instance = TradeThread() + @synchronized -def get_instance(recreate=False): +def get_instance(recreate: bool=False) -> TradeThread: """ Get the current instance of this thread. This is a singleton. :param recreate: Must be True if you want to start the instance @@ -122,7 +125,7 @@ def get_instance(recreate=False): return _instance -def close_trade_if_fulfilled(trade): +def close_trade_if_fulfilled(trade: Trade) -> bool: """ Checks if the trade is closable, and if so it is being closed. :param trade: Trade @@ -137,7 +140,7 @@ def close_trade_if_fulfilled(trade): return False -def handle_trade(trade): +def handle_trade(trade: Trade) -> None: """ Sells the current pair if the threshold is reached and updates the trade record. :return: None @@ -178,7 +181,7 @@ def handle_trade(trade): logger.exception('Unable to handle open order') -def create_trade(stake_amount: float, exchange): +def create_trade(stake_amount: float, exchange: Exchange) -> Optional[Trade]: """ Checks the implemented trading indicator(s) for a randomly picked pair, if one pair triggers the buy_signal a new trade record gets created diff --git a/persistence.py b/persistence.py index c5a1c593d..f8c954983 100644 --- a/persistence.py +++ b/persistence.py @@ -46,7 +46,7 @@ class Trade(Base): 'closed' if not self.is_open else round((datetime.utcnow() - self.open_date).total_seconds() / 60, 2) ) - def exec_sell_order(self, rate, amount): + def exec_sell_order(self, rate: float, amount: float) -> float: """ Executes a sell for the given trade and updated the entity. :param rate: rate to sell for diff --git a/utils.py b/utils.py index e87119b5b..faff4619a 100644 --- a/utils.py +++ b/utils.py @@ -1,5 +1,6 @@ import json import logging +from typing import List from wrapt import synchronized from bittrex.bittrex import Bittrex @@ -10,7 +11,7 @@ _cur_conf = None @synchronized -def get_conf(filename='config.json'): +def get_conf(filename: str='config.json') -> dict: """ Loads the config into memory and returns the instance of it :return: dict @@ -23,7 +24,7 @@ def get_conf(filename='config.json'): return _cur_conf -def validate_conf(conf): +def validate_conf(conf: dict) -> None: """ Validates if the minimal possible config is provided :param conf: config as dict @@ -86,7 +87,7 @@ def validate_conf(conf): logger.info('Config is valid ...') -def validate_bittrex_pairs(pairs): +def validate_bittrex_pairs(pairs: List[str]) -> None: """ Validates if all given pairs exist on bittrex :param pairs: list of str