major refactoring
This commit is contained in:
parent
c9cc9faf31
commit
1e4f104f51
@ -19,12 +19,16 @@
|
||||
"key": "key",
|
||||
"secret": "secret",
|
||||
"pair_whitelist": [
|
||||
"BTC_MLN",
|
||||
"BTC_RLC",
|
||||
"BTC_TKN",
|
||||
"BTC_TRST",
|
||||
"BTC_SWT",
|
||||
"BTC_PIVX",
|
||||
"BTC_MLN",
|
||||
"BTC_XZC",
|
||||
"BTC_TIME",
|
||||
"BTC_NXS",
|
||||
"BTC_GBYTE",
|
||||
"BTC_SNGLS"
|
||||
"BTC_LUN"
|
||||
]
|
||||
},
|
||||
"telegram": {
|
||||
|
39
exchange.py
39
exchange.py
@ -1,24 +1,23 @@
|
||||
import enum
|
||||
import threading
|
||||
|
||||
import logging
|
||||
from bittrex.bittrex import Bittrex
|
||||
from poloniex import Poloniex
|
||||
from wrapt import synchronized
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_lock = threading.Condition()
|
||||
_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
|
||||
_lock.acquire()
|
||||
if not _exchange_api:
|
||||
_exchange_api = ApiWrapper(conf)
|
||||
_lock.release()
|
||||
return _exchange_api
|
||||
|
||||
|
||||
@ -40,6 +39,8 @@ class ApiWrapper(object):
|
||||
:param config: dict
|
||||
"""
|
||||
self.dry_run = config['dry_run']
|
||||
if self.dry_run:
|
||||
logger.info('Instance is running with dry_run enabled')
|
||||
|
||||
use_poloniex = config.get('poloniex', {}).get('enabled', False)
|
||||
use_bittrex = config.get('bittrex', {}).get('enabled', False)
|
||||
@ -65,10 +66,12 @@ class ApiWrapper(object):
|
||||
pass
|
||||
elif self.exchange == Exchange.POLONIEX:
|
||||
self.api.buy(pair, rate, amount)
|
||||
# TODO: return order id
|
||||
elif self.exchange == Exchange.BITTREX:
|
||||
data = self.api.buy_limit(pair.replace('_', '-'), amount, rate)
|
||||
if not data['success']:
|
||||
raise RuntimeError('BITTREX: {}'.format(data['message']))
|
||||
return data['result']['uuid']
|
||||
|
||||
def sell(self, pair, rate, amount):
|
||||
"""
|
||||
@ -82,10 +85,12 @@ class ApiWrapper(object):
|
||||
pass
|
||||
elif self.exchange == Exchange.POLONIEX:
|
||||
self.api.sell(pair, rate, amount)
|
||||
# TODO: return order id
|
||||
elif self.exchange == Exchange.BITTREX:
|
||||
data = self.api.sell_limit(pair.replace('_', '-'), amount, rate)
|
||||
if not data['success']:
|
||||
raise RuntimeError('BITTREX: {}'.format(data['message']))
|
||||
return data['result']['uuid']
|
||||
|
||||
def get_balance(self, currency):
|
||||
"""
|
||||
@ -93,7 +98,9 @@ class ApiWrapper(object):
|
||||
:param currency: currency as str, format: BTC
|
||||
:return: float
|
||||
"""
|
||||
if self.exchange == Exchange.POLONIEX:
|
||||
if self.dry_run:
|
||||
return 999.9
|
||||
elif self.exchange == Exchange.POLONIEX:
|
||||
data = self.api.returnBalances()
|
||||
return float(data[currency])
|
||||
elif self.exchange == Exchange.BITTREX:
|
||||
@ -125,19 +132,37 @@ class ApiWrapper(object):
|
||||
'last': float(data['result']['Last']),
|
||||
}
|
||||
|
||||
def cancel_order(self, order_id):
|
||||
"""
|
||||
Cancel order for given order_id
|
||||
:param order_id: id as str
|
||||
:return: None
|
||||
"""
|
||||
if self.dry_run:
|
||||
pass
|
||||
elif self.exchange == Exchange.POLONIEX:
|
||||
raise NotImplemented('Not implemented')
|
||||
elif self.exchange == Exchange.BITTREX:
|
||||
data = self.api.cancel(order_id)
|
||||
if not data['success']:
|
||||
raise RuntimeError('BITTREX: {}'.format(data['message']))
|
||||
|
||||
def get_open_orders(self, pair):
|
||||
"""
|
||||
Get all open orders for given pair.
|
||||
:param pair: Pair as str, format: BTC_ETC
|
||||
:return: list of dicts
|
||||
"""
|
||||
if self.exchange == Exchange.POLONIEX:
|
||||
if self.dry_run:
|
||||
return []
|
||||
elif self.exchange == Exchange.POLONIEX:
|
||||
raise NotImplemented('Not implemented')
|
||||
elif self.exchange == Exchange.BITTREX:
|
||||
data = self.api.get_open_orders(pair.replace('_', '-'))
|
||||
if not data['success']:
|
||||
raise RuntimeError('BITTREX: {}'.format(data['message']))
|
||||
return [{
|
||||
'id': entry['OrderUuid'],
|
||||
'type': entry['OrderType'],
|
||||
'opened': entry['Opened'],
|
||||
'rate': entry['PricePerUnit'],
|
||||
|
154
main.py
154
main.py
@ -7,6 +7,8 @@ import time
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
|
||||
from wrapt import synchronized
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -19,44 +21,34 @@ from utils import get_conf
|
||||
__author__ = "gcarq"
|
||||
__copyright__ = "gcarq 2017"
|
||||
__license__ = "custom"
|
||||
__version__ = "0.4"
|
||||
__version__ = "0.5.1"
|
||||
|
||||
|
||||
conf = get_conf()
|
||||
api_wrapper = get_exchange_api(conf)
|
||||
|
||||
_lock = threading.Condition()
|
||||
_instance = None
|
||||
_should_stop = False
|
||||
|
||||
@synchronized
|
||||
def get_instance(recreate=False):
|
||||
"""
|
||||
Get the current instance of this thread. This is a singleton.
|
||||
:param recreate: Must be True if you want to start the instance
|
||||
:return: TradeThread instance
|
||||
"""
|
||||
global _instance, _should_stop
|
||||
if recreate and not _instance.is_alive():
|
||||
logger.debug('Creating TradeThread instance')
|
||||
_should_stop = False
|
||||
_instance = TradeThread()
|
||||
return _instance
|
||||
|
||||
|
||||
def stop_instance():
|
||||
global _should_stop
|
||||
_should_stop = True
|
||||
|
||||
|
||||
class TradeThread(threading.Thread):
|
||||
@staticmethod
|
||||
def get_instance(recreate=False):
|
||||
"""
|
||||
Get the current instance of this thread. This is a singleton.
|
||||
:param recreate: Must be True if you want to start the instance
|
||||
:return: TradeThread instance
|
||||
"""
|
||||
global _instance, _should_stop
|
||||
_lock.acquire()
|
||||
if _instance is None or (not _instance.is_alive() and recreate):
|
||||
_should_stop = False
|
||||
_instance = TradeThread()
|
||||
_lock.release()
|
||||
return _instance
|
||||
|
||||
@staticmethod
|
||||
def stop():
|
||||
"""
|
||||
Sets stop signal for the current instance
|
||||
:return: None
|
||||
"""
|
||||
global _should_stop
|
||||
_lock.acquire()
|
||||
_should_stop = True
|
||||
_lock.release()
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Threaded main function
|
||||
@ -64,25 +56,10 @@ class TradeThread(threading.Thread):
|
||||
"""
|
||||
try:
|
||||
TelegramHandler.send_msg('*Status:* `trader started`')
|
||||
logger.info('Trader started')
|
||||
while not _should_stop:
|
||||
try:
|
||||
# Query trades from persistence layer
|
||||
trade = Trade.query.filter(Trade.is_open.is_(True)).first()
|
||||
if not trade:
|
||||
# Create entity and execute trade
|
||||
Session.add(create_trade(float(conf['stake_amount']), api_wrapper.exchange))
|
||||
continue
|
||||
|
||||
# Check if there is already an open order for this pair
|
||||
orders = api_wrapper.get_open_orders(trade.pair)
|
||||
if orders:
|
||||
msg = 'There is already an open order for this trade. (total: {}, remaining: {}, type: {})'\
|
||||
.format(round(orders[0]['amount'], 8), round(orders[0]['remaining'], 8), orders[0]['type'])
|
||||
logger.info(msg)
|
||||
elif close_trade_if_fulfilled(trade):
|
||||
logger.info('No open orders found and close values are set. Marking trade as closed ...')
|
||||
else:
|
||||
handle_trade(trade)
|
||||
self._process()
|
||||
except ValueError:
|
||||
logger.exception('ValueError')
|
||||
finally:
|
||||
@ -92,9 +69,47 @@ class TradeThread(threading.Thread):
|
||||
TelegramHandler.send_msg('*Status:* Got RuntimeError: ```\n{}\n```'.format(traceback.format_exc()))
|
||||
logger.exception('RuntimeError. Stopping trader ...')
|
||||
finally:
|
||||
Session.flush()
|
||||
TelegramHandler.send_msg('*Status:* `Trader has stopped`')
|
||||
|
||||
@staticmethod
|
||||
def _process():
|
||||
"""
|
||||
Queries the persistence layer for new trades and handles them
|
||||
:return: None
|
||||
"""
|
||||
# Query trades from persistence layer
|
||||
trade = Trade.query.filter(Trade.is_open.is_(True)).first()
|
||||
if not trade:
|
||||
# Create entity and execute trade
|
||||
Session.add(create_trade(float(conf['stake_amount']), api_wrapper.exchange))
|
||||
return
|
||||
|
||||
# Check if there is already an open order for this trade
|
||||
orders = api_wrapper.get_open_orders(trade.pair)
|
||||
orders = [o for o in orders if o['id'] == trade.open_order_id]
|
||||
if orders:
|
||||
msg = 'There exists an open order for this trade: (total: {}, remaining: {}, type: {}, id: {})' \
|
||||
.format(round(orders[0]['amount'], 8),
|
||||
round(orders[0]['remaining'], 8),
|
||||
orders[0]['type'],
|
||||
orders[0]['id'])
|
||||
logger.info(msg)
|
||||
return
|
||||
|
||||
# Update state
|
||||
trade.open_order_id = None
|
||||
# Check if this trade can be marked as closed
|
||||
if close_trade_if_fulfilled(trade):
|
||||
logger.info('No open orders found and trade is fulfilled. Marking as closed ...')
|
||||
return
|
||||
|
||||
# Check if we can sell our current pair
|
||||
handle_trade(trade)
|
||||
|
||||
# Initial stopped TradeThread instance
|
||||
_instance = TradeThread()
|
||||
_should_stop = False
|
||||
|
||||
|
||||
def close_trade_if_fulfilled(trade):
|
||||
"""
|
||||
@ -104,7 +119,7 @@ def close_trade_if_fulfilled(trade):
|
||||
"""
|
||||
# If we don't have an open order and the close rate is already set,
|
||||
# we can close this trade.
|
||||
if trade.close_profit and trade.close_date and trade.close_rate:
|
||||
if trade.close_profit and trade.close_date and trade.close_rate and not trade.open_order_id:
|
||||
trade.is_open = False
|
||||
return True
|
||||
return False
|
||||
@ -112,9 +127,8 @@ def close_trade_if_fulfilled(trade):
|
||||
|
||||
def handle_trade(trade):
|
||||
"""
|
||||
Sells the current pair if the threshold is reached
|
||||
and updates the trade record.
|
||||
:return: current instance
|
||||
Sells the current pair if the threshold is reached and updates the trade record.
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
if not trade.is_open:
|
||||
@ -122,7 +136,7 @@ def handle_trade(trade):
|
||||
|
||||
logger.debug('Handling open trade {} ...'.format(trade))
|
||||
# Get current rate
|
||||
current_rate = api_wrapper.get_ticker(trade.pair)['last']
|
||||
current_rate = api_wrapper.get_ticker(trade.pair)['bid']
|
||||
current_profit = 100 * ((current_rate - trade.open_rate) / trade.open_rate)
|
||||
|
||||
# Get available balance
|
||||
@ -136,10 +150,11 @@ def handle_trade(trade):
|
||||
if time_diff > duration and current_rate > (1 + threshold) * trade.open_rate:
|
||||
|
||||
# Execute sell and update trade record
|
||||
api_wrapper.sell(trade.pair, current_rate, balance)
|
||||
order_id = api_wrapper.sell(trade.pair, current_rate, balance)
|
||||
trade.close_rate = current_rate
|
||||
trade.close_profit = current_profit
|
||||
trade.close_date = datetime.utcnow()
|
||||
trade.open_order_id = order_id
|
||||
|
||||
message = '*{}:* Selling {} at rate `{:f} (profit: {}%)`'.format(
|
||||
trade.exchange.name,
|
||||
@ -168,40 +183,35 @@ def create_trade(stake_amount: float, exchange):
|
||||
raise ValueError('BTC amount is not fulfilled')
|
||||
|
||||
# Remove latest trade pair from whitelist
|
||||
latest_trade = Trade.order_by(Trade.id.desc()).first()
|
||||
latest_trade = Trade.query.order_by(Trade.id.desc()).first()
|
||||
if latest_trade and latest_trade.pair in whitelist:
|
||||
whitelist.remove(latest_trade.pair)
|
||||
logger.debug('Ignoring {} in pair whitelist')
|
||||
logger.debug('Ignoring {} in pair whitelist'.format(latest_trade.pair))
|
||||
if not whitelist:
|
||||
raise ValueError('No pair in whitelist')
|
||||
|
||||
# Pick random pair and execute trade
|
||||
idx = random.randint(0, len(whitelist) - 1)
|
||||
pair = whitelist[idx]
|
||||
open_rate = api_wrapper.get_ticker(pair)['last']
|
||||
open_rate = api_wrapper.get_ticker(pair)['ask']
|
||||
amount = stake_amount / open_rate
|
||||
exchange = exchange
|
||||
api_wrapper.buy(pair, open_rate, amount)
|
||||
order_id = api_wrapper.buy(pair, open_rate, amount)
|
||||
|
||||
trade = Trade(
|
||||
pair=pair,
|
||||
btc_amount=stake_amount,
|
||||
open_rate=open_rate,
|
||||
amount=amount,
|
||||
exchange=exchange,
|
||||
)
|
||||
message = '*{}:* Buying {} at rate `{:f}`'.format(
|
||||
trade.exchange.name,
|
||||
trade.pair.replace('_', '/'),
|
||||
trade.open_rate
|
||||
)
|
||||
# Create trade entity and return
|
||||
message = '*{}:* Buying {} at rate `{:f}`'.format(exchange.name, pair.replace('_', '/'), open_rate)
|
||||
logger.info(message)
|
||||
TelegramHandler.send_msg(message)
|
||||
return trade
|
||||
return Trade(pair=pair,
|
||||
btc_amount=stake_amount,
|
||||
open_rate=open_rate,
|
||||
amount=amount,
|
||||
exchange=exchange,
|
||||
open_order_id=order_id)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger.info('Starting marginbot {}'.format(__version__))
|
||||
TelegramHandler.listen()
|
||||
while True:
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.5)
|
||||
|
@ -29,6 +29,7 @@ class Trade(Base):
|
||||
amount = Column(Float, nullable=False)
|
||||
open_date = Column(DateTime, nullable=False, default=datetime.utcnow)
|
||||
close_date = Column(DateTime)
|
||||
open_order_id = Column(String)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Trade(id={}, pair={}, amount={}, open_rate={}, open_since={})'.format(
|
||||
|
@ -4,4 +4,5 @@ SQLAlchemy==1.1.9
|
||||
python-telegram-bot==5.3.1
|
||||
arrow==0.10.0
|
||||
requests==2.14.2
|
||||
urllib3==1.20
|
||||
urllib3==1.20
|
||||
wrapt==1.10.10
|
228
rpc/telegram.py
228
rpc/telegram.py
@ -1,106 +1,68 @@
|
||||
import threading
|
||||
|
||||
import arrow
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import logging
|
||||
import arrow
|
||||
from telegram.error import NetworkError
|
||||
from telegram.ext import CommandHandler, Updater
|
||||
from telegram import ParseMode
|
||||
from telegram import ParseMode, Bot, Update
|
||||
from wrapt import synchronized
|
||||
|
||||
from persistence import Trade
|
||||
from persistence import Trade, Session
|
||||
from exchange import get_exchange_api
|
||||
from utils import get_conf
|
||||
|
||||
# Remove noisy log messages
|
||||
logging.getLogger('requests.packages.urllib3').setLevel(logging.INFO)
|
||||
logging.getLogger('telegram').setLevel(logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_lock = threading.Condition()
|
||||
_updater = None
|
||||
|
||||
conf = get_conf()
|
||||
api_wrapper = get_exchange_api(conf)
|
||||
|
||||
|
||||
def authorized_only(command_handler):
|
||||
"""
|
||||
Decorator to check if the message comes from the correct chat_id
|
||||
:param command_handler: Telegram CommandHandler
|
||||
:return: decorated function
|
||||
"""
|
||||
def wrapper(*args, **kwargs):
|
||||
bot, update = args[0], args[1]
|
||||
if not isinstance(bot, Bot) or not isinstance(update, Update):
|
||||
raise ValueError('Received invalid Arguments: {}'.format(*args))
|
||||
|
||||
chat_id = int(conf['telegram']['chat_id'])
|
||||
if int(update.message.chat_id) == chat_id:
|
||||
logger.info('Executing handler: {} for chat_id: {}'.format(command_handler.__name__, chat_id))
|
||||
return command_handler(*args, **kwargs)
|
||||
else:
|
||||
logger.info('Rejected unauthorized message from: {}'.format(update.message.chat_id))
|
||||
return wrapper
|
||||
|
||||
|
||||
class TelegramHandler(object):
|
||||
@staticmethod
|
||||
def get_updater(conf):
|
||||
"""
|
||||
Returns the current telegram updater instantiates a new one
|
||||
:param conf:
|
||||
:return: telegram.ext.Updater
|
||||
"""
|
||||
global _updater
|
||||
_lock.acquire()
|
||||
if not _updater:
|
||||
_updater = Updater(token=conf['telegram']['token'], workers=0)
|
||||
_lock.release()
|
||||
return _updater
|
||||
|
||||
@staticmethod
|
||||
def listen():
|
||||
"""
|
||||
Registers all known command handlers and starts polling for message updates
|
||||
:return: None
|
||||
"""
|
||||
# Register command handler and start telegram message polling
|
||||
handles = [CommandHandler('status', TelegramHandler._status),
|
||||
CommandHandler('profit', TelegramHandler._profit),
|
||||
CommandHandler('start', TelegramHandler._start),
|
||||
CommandHandler('stop', TelegramHandler._stop)]
|
||||
for handle in handles:
|
||||
TelegramHandler.get_updater(conf).dispatcher.add_handler(handle)
|
||||
TelegramHandler.get_updater(conf).start_polling(clean=True, bootstrap_retries=3)
|
||||
|
||||
@staticmethod
|
||||
def _is_correct_scope(update):
|
||||
"""
|
||||
Checks if it is save to process the given update
|
||||
:param update:
|
||||
:return: True if valid else False
|
||||
"""
|
||||
# Only answer to our chat
|
||||
return int(update.message.chat_id) == int(conf['telegram']['chat_id'])
|
||||
|
||||
@staticmethod
|
||||
def send_msg(markdown_message, bot=None):
|
||||
"""
|
||||
Send given markdown message
|
||||
:param markdown_message: message
|
||||
:param bot: alternative bot
|
||||
:return: None
|
||||
"""
|
||||
if conf['telegram'].get('enabled', False):
|
||||
bot = bot or TelegramHandler.get_updater(conf).bot
|
||||
try:
|
||||
bot.send_message(
|
||||
chat_id=conf['telegram']['chat_id'],
|
||||
text=markdown_message,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception('Exception occurred within telegram api')
|
||||
|
||||
@staticmethod
|
||||
@authorized_only
|
||||
def _status(bot, update):
|
||||
"""
|
||||
Handler for /status
|
||||
Handler for /status.
|
||||
Returns the current TradeThread status
|
||||
:param bot: telegram bot
|
||||
:param update: message update
|
||||
:return: None
|
||||
"""
|
||||
if not TelegramHandler._is_correct_scope(update):
|
||||
return
|
||||
|
||||
# Fetch open trade
|
||||
trade = Trade.query.filter(Trade.is_open.is_(True)).first()
|
||||
|
||||
from main import TradeThread
|
||||
if not TradeThread.get_instance().is_alive():
|
||||
from main import get_instance
|
||||
if not get_instance().is_alive():
|
||||
message = '*Status:* `trader stopped`'
|
||||
elif not trade:
|
||||
message = '*Status:* `no active order`'
|
||||
else:
|
||||
# calculate profit and send message to user
|
||||
current_rate = api_wrapper.get_ticker(trade.pair)['last']
|
||||
current_rate = api_wrapper.get_ticker(trade.pair)['bid']
|
||||
current_profit = 100 * ((current_rate - trade.open_rate) / trade.open_rate)
|
||||
open_orders = api_wrapper.get_open_orders(trade.pair)
|
||||
order = open_orders[0] if open_orders else None
|
||||
@ -123,23 +85,20 @@ class TelegramHandler(object):
|
||||
amount=round(trade.amount, 8),
|
||||
close_profit='{}%'.format(round(trade.close_profit, 2)) if trade.close_profit else None,
|
||||
current_profit=round(current_profit, 2),
|
||||
open_order='{} ({})'.format(
|
||||
order['remaining'],
|
||||
order['type']
|
||||
) if order else None,
|
||||
open_order='{} ({})'.format(order['remaining'], order['type']) if order else None,
|
||||
)
|
||||
TelegramHandler.send_msg(message, bot=bot)
|
||||
|
||||
@staticmethod
|
||||
@authorized_only
|
||||
def _profit(bot, update):
|
||||
"""
|
||||
Handler for /profit
|
||||
Handler for /profit.
|
||||
Returns a cumulative profit statistics.
|
||||
:param bot: telegram bot
|
||||
:param update: message update
|
||||
:return: None
|
||||
"""
|
||||
if not TelegramHandler._is_correct_scope(update):
|
||||
return
|
||||
trades = Trade.query.filter(Trade.is_open.is_(False)).all()
|
||||
trade_count = len(trades)
|
||||
profit_amount = sum((t.close_profit / 100) * t.btc_amount for t in trades)
|
||||
@ -147,9 +106,7 @@ class TelegramHandler(object):
|
||||
avg_stake_amount = sum(t.btc_amount for t in trades) / float(trade_count)
|
||||
durations_hours = [(t.close_date - t.open_date).total_seconds() / 3600.0 for t in trades]
|
||||
avg_duration = sum(durations_hours) / float(len(durations_hours))
|
||||
|
||||
markdown_msg = """
|
||||
*Total Balance:* `{total_amount} BTC`
|
||||
*Total Profit:* `{profit_btc} BTC ({profit}%)`
|
||||
*Trade Count:* `{trade_count}`
|
||||
*First Action:* `{first_trade_date}`
|
||||
@ -157,7 +114,6 @@ class TelegramHandler(object):
|
||||
*Avg. Stake Amount:* `{avg_open_amount} BTC`
|
||||
*Avg. Duration:* `{avg_duration}`
|
||||
""".format(
|
||||
total_amount=round(api_wrapper.get_balance('BTC'), 8),
|
||||
profit_btc=round(profit_amount, 8),
|
||||
profit=round(profit, 2),
|
||||
trade_count=trade_count,
|
||||
@ -169,34 +125,114 @@ class TelegramHandler(object):
|
||||
TelegramHandler.send_msg(markdown_msg, bot=bot)
|
||||
|
||||
@staticmethod
|
||||
@authorized_only
|
||||
def _start(bot, update):
|
||||
"""
|
||||
Handler for /start
|
||||
Handler for /start.
|
||||
Starts TradeThread
|
||||
:param bot: telegram bot
|
||||
:param update: message update
|
||||
:return: None
|
||||
"""
|
||||
if not TelegramHandler._is_correct_scope(update):
|
||||
return
|
||||
from main import TradeThread
|
||||
if TradeThread.get_instance().is_alive():
|
||||
from main import get_instance
|
||||
if get_instance().is_alive():
|
||||
TelegramHandler.send_msg('*Status:* `already running`', bot=bot)
|
||||
return
|
||||
else:
|
||||
TradeThread.get_instance(recreate=True).start()
|
||||
get_instance(recreate=True).start()
|
||||
|
||||
@staticmethod
|
||||
@authorized_only
|
||||
def _stop(bot, update):
|
||||
"""
|
||||
Handler for /stop
|
||||
Handler for /stop.
|
||||
Stops TradeThread
|
||||
:param bot: telegram bot
|
||||
:param update: message update
|
||||
:return: None
|
||||
"""
|
||||
if not TelegramHandler._is_correct_scope(update):
|
||||
return
|
||||
from main import TradeThread
|
||||
if TradeThread.get_instance().is_alive():
|
||||
TradeThread.stop()
|
||||
from main import get_instance, stop_instance
|
||||
if get_instance().is_alive():
|
||||
TelegramHandler.send_msg('`Stopping trader ...`', bot=bot)
|
||||
stop_instance()
|
||||
else:
|
||||
TelegramHandler.send_msg('*Status:* `already stopped`', bot=bot)
|
||||
|
||||
@staticmethod
|
||||
@authorized_only
|
||||
def _cancel(bot, update):
|
||||
"""
|
||||
Handler for /cancel.
|
||||
Cancels the open order for the current Trade.
|
||||
:param bot: telegram bot
|
||||
:param update: message update
|
||||
:return: None
|
||||
"""
|
||||
trade = Trade.query.filter(Trade.is_open.is_(True)).first()
|
||||
if not trade:
|
||||
TelegramHandler.send_msg('`There is no open trade`')
|
||||
return
|
||||
|
||||
order_id = trade.open_order_id
|
||||
if not order_id:
|
||||
TelegramHandler.send_msg('`There is no open order`')
|
||||
return
|
||||
|
||||
api_wrapper.cancel_order(order_id)
|
||||
trade.open_order_id = None
|
||||
trade.close_rate = None
|
||||
trade.close_date = None
|
||||
trade.close_profit = None
|
||||
Session.flush()
|
||||
TelegramHandler.send_msg('*Order cancelled:* `{}`'.format(order_id), bot=bot)
|
||||
logger.info('Order cancelled: (order_id: {})'.format(order_id))
|
||||
|
||||
@staticmethod
|
||||
@synchronized
|
||||
def get_updater(conf):
|
||||
"""
|
||||
Returns the current telegram updater instantiates a new one
|
||||
:param conf:
|
||||
:return: telegram.ext.Updater
|
||||
"""
|
||||
global _updater
|
||||
if not _updater:
|
||||
_updater = Updater(token=conf['telegram']['token'], workers=0)
|
||||
return _updater
|
||||
|
||||
@staticmethod
|
||||
def listen():
|
||||
"""
|
||||
Registers all known command handlers and starts polling for message updates
|
||||
:return: None
|
||||
"""
|
||||
# Register command handler and start telegram message polling
|
||||
handles = [
|
||||
CommandHandler('status', TelegramHandler._status),
|
||||
CommandHandler('profit', TelegramHandler._profit),
|
||||
CommandHandler('start', TelegramHandler._start),
|
||||
CommandHandler('stop', TelegramHandler._stop),
|
||||
CommandHandler('cancel', TelegramHandler._cancel),
|
||||
]
|
||||
for handle in handles:
|
||||
TelegramHandler.get_updater(conf).dispatcher.add_handler(handle)
|
||||
TelegramHandler.get_updater(conf).start_polling(clean=True, bootstrap_retries=3)
|
||||
logger.info('TelegramHandler is listening for following commands: {}'
|
||||
.format([h.command for h in handles]))
|
||||
|
||||
@staticmethod
|
||||
def send_msg(msg, bot=None):
|
||||
"""
|
||||
Send given markdown message
|
||||
:param msg: message
|
||||
:param bot: alternative bot
|
||||
:return: None
|
||||
"""
|
||||
if conf['telegram'].get('enabled', False):
|
||||
bot = bot or TelegramHandler.get_updater(conf).bot
|
||||
try:
|
||||
bot.send_message(conf['telegram']['chat_id'], msg, parse_mode=ParseMode.MARKDOWN)
|
||||
except NetworkError as e:
|
||||
logger.warning('Got Telegram NetworkError: {}! trying one more time'.format(e.message))
|
||||
bot.send_message(conf['telegram']['chat_id'], msg, parse_mode=ParseMode.MARKDOWN)
|
||||
except Exception:
|
||||
logger.exception('Exception occurred within Telegram API')
|
||||
|
19
utils.py
19
utils.py
@ -1,22 +1,25 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
from wrapt import synchronized
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_CUR_CONF = None
|
||||
_cur_conf = None
|
||||
|
||||
|
||||
def get_conf():
|
||||
@synchronized
|
||||
def get_conf(filename='config.json'):
|
||||
"""
|
||||
Loads the config into memory and returns the instance of it
|
||||
:return: dict
|
||||
"""
|
||||
global _CUR_CONF
|
||||
if not _CUR_CONF:
|
||||
with open('config.json') as fp:
|
||||
_CUR_CONF = json.load(fp)
|
||||
validate_conf(_CUR_CONF)
|
||||
return _CUR_CONF
|
||||
global _cur_conf
|
||||
if not _cur_conf:
|
||||
with open(filename) as fp:
|
||||
_cur_conf = json.load(fp)
|
||||
validate_conf(_cur_conf)
|
||||
return _cur_conf
|
||||
|
||||
|
||||
def validate_conf(conf):
|
||||
|
Loading…
Reference in New Issue
Block a user