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