Make Pylint Happy chapter 1

This commit is contained in:
Gerald Lonlas 2018-03-02 23:22:00 +08:00
parent d274f13480
commit 390501bac0
13 changed files with 161 additions and 147 deletions

View File

@ -1,11 +1,11 @@
"""
Functions to analyze ticker data with indicators and produce buy and sell signals
"""
import arrow
from datetime import datetime, timedelta
from enum import Enum
from pandas import DataFrame, to_datetime
from typing import Dict, List
import arrow
from pandas import DataFrame, to_datetime
from freqtrade.exchange import get_ticker_history
from freqtrade.logger import Logger
from freqtrade.strategy.strategy import Strategy
@ -188,10 +188,9 @@ class Analyze(object):
)
return False
def tickerdata_to_dataframe(self, tickerdata: Dict[str, List]) -> Dict[str, DataFrame]:
"""
Creates a dataframe and populates indicators for given ticker data
"""
return {pair: self.populate_indicators(self.parse_ticker_dataframe(pair_data))
for pair, pair_data in tickerdata.items()}
for pair, pair_data in tickerdata.items()}

View File

@ -2,16 +2,15 @@
Freqtrade is the main module of this bot. It contains the class Freqtrade()
"""
import logging
import arrow
import copy
import json
import requests
import time
import traceback
from cachetools import cached, TTLCache
from datetime import datetime
from typing import Dict, List, Optional, Any, Callable
from datetime import datetime
import requests
import arrow
from cachetools import cached, TTLCache
from freqtrade.analyze import Analyze
from freqtrade.constants import Constants
from freqtrade.fiat_convert import CryptoToFiatConverter
@ -507,14 +506,14 @@ class FreqtradeBot(object):
"*Current Rate:* `{current_rate:.8f}`\n" \
"*Profit:* `{profit:.2f}%`" \
"".format(
exchange=trade.exchange,
pair=trade.pair,
pair_url=exchange.get_pair_detail_url(trade.pair),
limit=limit,
open_rate=trade.open_rate,
current_rate=current_rate,
amount=round(trade.amount, 8),
profit=round(profit * 100, 2),
exchange=trade.exchange,
pair=trade.pair,
pair_url=exchange.get_pair_detail_url(trade.pair),
limit=limit,
open_rate=trade.open_rate,
current_rate=current_rate,
amount=round(trade.amount, 8),
profit=round(profit * 100, 2),
)
# For regular case, when the configuration exists
@ -528,12 +527,12 @@ class FreqtradeBot(object):
message += '` ({gain}: {profit_percent:.2f}%, {profit_coin:.8f} {coin}`' \
'` / {profit_fiat:.3f} {fiat})`' \
''.format(
gain="profit" if fmt_exp_profit > 0 else "loss",
profit_percent=fmt_exp_profit,
profit_coin=profit_trade,
coin=self.config['stake_currency'],
profit_fiat=profit_fiat,
fiat=self.config['fiat_display_currency'],
gain="profit" if fmt_exp_profit > 0 else "loss",
profit_percent=fmt_exp_profit,
profit_coin=profit_trade,
coin=self.config['stake_currency'],
profit_fiat=profit_fiat,
fiat=self.config['fiat_display_currency'],
)
# Because telegram._forcesell does not have the configuration
# Ignore the FIAT value and does not show the stake_currency as well

View File

@ -97,10 +97,11 @@ def download_pairs(datadir, pairs: List[str], ticker_interval: int) -> bool:
try:
download_backtesting_testdata(datadir, pair=pair, interval=ticker_interval)
except BaseException:
logger.info('Failed to download the pair: "{pair}", Interval: {interval} min'.format(
pair=pair,
interval=ticker_interval,
))
logger.info(
'Failed to download the pair: "%s", Interval: %s min',
pair,
ticker_interval
)
return False
return True
@ -115,10 +116,11 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) ->
"""
path = make_testdata_path(datadir)
logger.info('Download the pair: "{pair}", Interval: {interval} min'.format(
pair=pair,
interval=interval,
))
logger.info(
'Download the pair: "%s", Interval: %s min',
pair,
interval
)
filepair = pair.replace("-", "_")
filename = os.path.join(path, '{pair}-{interval}.json'.format(
@ -129,8 +131,8 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) ->
if os.path.isfile(filename):
with open(filename, "rt") as file:
data = json.load(file)
logger.debug("Current Start: {}".format(data[1]['T']))
logger.debug("Current End: {}".format(data[-1:][0]['T']))
logger.debug("Current Start: %s", data[1]['T'])
logger.debug("Current End: %s", data[-1:][0]['T'])
else:
data = []
logger.debug("Current Start: None")
@ -140,8 +142,8 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) ->
for row in new_data:
if row not in data:
data.append(row)
logger.debug("New Start: {}".format(data[1]['T']))
logger.debug("New End: {}".format(data[-1:][0]['T']))
logger.debug("New Start: %s", data[1]['T'])
logger.debug("New End: %s", data[-1:][0]['T'])
data = sorted(data, key=lambda data: data['T'])
misc.file_dump_json(filename, data)

View File

@ -25,7 +25,7 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.configuration import Configuration
from freqtrade.optimize import load_data
from freqtrade.arguments import Arguments
from freqtrade.optimize.backtesting import Backtesting, setup_configuration
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.logger import Logger
from user_data.hyperopt_conf import hyperopt_optimize_conf
@ -46,7 +46,6 @@ class Hyperopt(Backtesting):
self.logging = Logger(name=__name__, level=config['loglevel'])
self.logger = self.logging.get_logger()
# set TARGET_TRADES to suit your number concurrent trades so its realistic
# to the number of days
self.target_trades = 600
@ -353,6 +352,9 @@ class Hyperopt(Backtesting):
Define the buy strategy parameters to be used by hyperopt
"""
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
"""
Buy strategy Hyperopt will build and use
"""
conditions = []
# GUARDS AND TRENDS
if 'uptrend_long_ema' in params and params['uptrend_long_ema']['enabled']:
@ -513,8 +515,9 @@ class Hyperopt(Backtesting):
self.current_tries = len(self.trials.results)
self.total_tries += self.current_tries
self.logger.info(
'Continuing with trials. Current: {}, Total: {}'
.format(self.current_tries, self.total_tries)
'Continuing with trials. Current: %d, Total: %d',
self.current_tries,
self.total_tries
)
try:
@ -557,7 +560,10 @@ class Hyperopt(Backtesting):
"""
Hyperopt SIGINT handler
"""
self.logger.info('Hyperopt received {}'.format(signal.Signals(sig).name))
self.logger.info(
'Hyperopt received %s',
signal.Signals(sig).name
)
self.save_trials()
self.log_trials_result()
@ -580,9 +586,7 @@ def start(args) -> None:
logger.info('Starting freqtrade in Hyperopt mode')
# Initialize configuration
#config = setup_configuration(args)
# Monkey patch of the configuration with hyperopt_conf.py
# Monkey patch the configuration with hyperopt_conf.py
configuration = Configuration(args)
optimize_config = hyperopt_optimize_conf()
config = configuration._load_backtesting_config(optimize_config)

View File

@ -1,3 +1,7 @@
"""
This module contains the class to persist trades into SQLite
"""
import logging
from datetime import datetime
from decimal import Decimal, getcontext
@ -72,6 +76,9 @@ def clean_dry_run_db() -> None:
class Trade(_DECL_BASE):
"""
Class used to define a trade structure
"""
__tablename__ = 'trades'
id = Column(Integer, primary_key=True)

View File

@ -2,9 +2,9 @@
This module contains class to define a RPC communications
"""
import arrow
from decimal import Decimal
from datetime import datetime, timedelta
import arrow
from pandas import DataFrame
import sqlalchemy as sql
from freqtrade.logger import Logger
@ -18,7 +18,6 @@ class RPC(object):
"""
RPC class can be used to have extra feature, like bot data, and access to DB data
"""
def __init__(self, freqtrade) -> None:
"""
Initializes all enabled rpc modules
@ -65,21 +64,21 @@ class RPC(object):
"*Close Profit:* `{close_profit}`\n" \
"*Current Profit:* `{current_profit:.2f}%`\n" \
"*Open Order:* `{open_order}`"\
.format(
trade_id=trade.id,
pair=trade.pair,
market_url=exchange.get_pair_detail_url(trade.pair),
date=arrow.get(trade.open_date).humanize(),
open_rate=trade.open_rate,
close_rate=trade.close_rate,
current_rate=current_rate,
amount=round(trade.amount, 8),
close_profit=fmt_close_profit,
current_profit=round(current_profit * 100, 2),
open_order='({} rem={:.8f})'.format(
order['type'], order['remaining']
) if order else None,
)
.format(
trade_id=trade.id,
pair=trade.pair,
market_url=exchange.get_pair_detail_url(trade.pair),
date=arrow.get(trade.open_date).humanize(),
open_rate=trade.open_rate,
close_rate=trade.close_rate,
current_rate=current_rate,
amount=round(trade.amount, 8),
close_profit=fmt_close_profit,
current_profit=round(current_profit * 100, 2),
open_order='({} rem={:.8f})'.format(
order['type'], order['remaining']
) if order else None,
)
result.append(message)
return (False, result)
@ -100,7 +99,7 @@ class RPC(object):
shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)),
'{:.2f}%'.format(100 * trade.calc_profit_percent(current_rate))
])
columns = ['ID', 'Pair', 'Since', 'Profit']
df_statuses = DataFrame.from_records(trades_list, columns=columns)
df_statuses = df_statuses.set_index(columns[0])
@ -113,10 +112,10 @@ class RPC(object):
def rpc_daily_profit(self, timescale, stake_currency, fiat_display_currency):
today = datetime.utcnow().date()
profit_days = {}
if not (isinstance(timescale, int) and timescale > 0):
return (True, '*Daily [n]:* `must be an integer greater than 0`')
fiat = self.freqtrade.fiat_converter
for day in range(0, timescale):
profitday = today - timedelta(days=day)
@ -131,7 +130,7 @@ class RPC(object):
'amount': format(curdayprofit, '.8f'),
'trades': len(trades)
}
stats = [
[
key,
@ -147,7 +146,10 @@ class RPC(object):
),
symbol=fiat_display_currency
),
'{value} trade{s}'.format(value=value['trades'], s='' if value['trades'] < 2 else 's'),
'{value} trade{s}'.format(
value=value['trades'],
s='' if value['trades'] < 2 else 's'
),
]
for key, value in profit_days.items()
]
@ -158,21 +160,21 @@ class RPC(object):
:return: cumulative profit statistics.
"""
trades = Trade.query.order_by(Trade.id).all()
profit_all_coin = []
profit_all_percent = []
profit_closed_coin = []
profit_closed_percent = []
durations = []
for trade in trades:
current_rate = None
if not trade.open_rate:
continue
if trade.close_date:
durations.append((trade.close_date - trade.open_date).total_seconds())
if not trade.is_open:
profit_percent = trade.calc_profit_percent()
profit_closed_coin.append(trade.calc_profit())
@ -181,22 +183,25 @@ class RPC(object):
# Get current rate
current_rate = exchange.get_ticker(trade.pair, False)['bid']
profit_percent = trade.calc_profit_percent(rate=current_rate)
profit_all_coin.append(trade.calc_profit(rate=Decimal(trade.close_rate or current_rate)))
profit_all_coin.append(
trade.calc_profit(rate=Decimal(trade.close_rate or current_rate))
)
profit_all_percent.append(profit_percent)
best_pair = Trade.session.query(Trade.pair,
sql.func.sum(Trade.close_profit).label('profit_sum')) \
.filter(Trade.is_open.is_(False)) \
.group_by(Trade.pair) \
.order_by(sql.text('profit_sum DESC')) \
.first()
best_pair = Trade.session.query(
Trade.pair,
sql.func.sum(Trade.close_profit).label('profit_sum')
)\
.filter(Trade.is_open.is_(False))\
.group_by(Trade.pair)\
.order_by(sql.text('profit_sum DESC')).first()
if not best_pair:
return (True, '*Status:* `no closed trade`')
bp_pair, bp_rate = best_pair
# FIX: we want to keep fiatconverter in a state/environment,
# doing this will utilize its caching functionallity, instead we reinitialize it here
fiat = self.freqtrade.fiat_converter
@ -244,7 +249,7 @@ class RPC(object):
]
if not balances:
return (True, '`All balances are zero.`')
output = []
total = 0.0
for currency in balances:
@ -258,12 +263,15 @@ class RPC(object):
currency["Rate"] = exchange.get_ticker('BTC_' + coin, False)['bid']
currency['BTC'] = currency["Rate"] * currency["Balance"]
total = total + currency['BTC']
output.append({'currency': currency['Currency'],
'available': currency['Available'],
'balance': currency['Balance'],
'pending': currency['Pending'],
'est_btc': currency['BTC']
})
output.append(
{
'currency': currency['Currency'],
'available': currency['Available'],
'balance': currency['Balance'],
'pending': currency['Pending'],
'est_btc': currency['BTC']
}
)
fiat = self.freqtrade.fiat_converter
symbol = fiat_display_currency
value = fiat.convert_amount(total, 'BTC', symbol)
@ -275,9 +283,9 @@ class RPC(object):
"""
if self.freqtrade.get_state() == State.RUNNING:
return (True, '*Status:* `already running`')
else:
self.freqtrade.update_state(State.RUNNING)
return (False, '`Starting trader ...`')
self.freqtrade.update_state(State.RUNNING)
return (False, '`Starting trader ...`')
def rpc_stop(self) -> (bool, str):
"""
@ -286,8 +294,8 @@ class RPC(object):
if self.freqtrade.get_state() == State.RUNNING:
self.freqtrade.update_state(State.STOPPED)
return (False, '`Stopping trader ...`')
else:
return (True, '*Status:* `already stopped`')
return (True, '*Status:* `already stopped`')
# FIX: no test for this!!!!
def rpc_forcesell(self, trade_id) -> None:
@ -300,18 +308,18 @@ class RPC(object):
# Check if there is there is an open order
if trade.open_order_id:
order = exchange.get_order(trade.open_order_id)
# Cancel open LIMIT_BUY orders and close trade
if order and not order['closed'] and order['type'] == 'LIMIT_BUY':
exchange.cancel_order(trade.open_order_id)
trade.close(order.get('rate') or trade.open_rate)
# TODO: sell amount which has been bought already
return
# Ignore trades with an attached LIMIT_SELL order
if order and not order['closed'] and order['type'] == 'LIMIT_SELL':
return
# Get current rate and execute sell
current_rate = exchange.get_ticker(trade.pair, False)['bid']
self.freqtrade.execute_sell(trade, current_rate)
@ -319,13 +327,13 @@ class RPC(object):
if self.freqtrade.get_state() != State.RUNNING:
return (True, '`trader is not running`')
if trade_id == 'all':
# Execute sell for all open orders
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
_exec_forcesell(trade)
return (False, '')
# Query for trade
trade = Trade.query.filter(
sql.and_(
@ -336,7 +344,7 @@ class RPC(object):
if not trade:
self.logger.warning('forcesell: Invalid argument received')
return (True, 'Invalid argument.')
_exec_forcesell(trade)
return (False, '')
@ -347,7 +355,7 @@ class RPC(object):
"""
if self.freqtrade.get_state() != State.RUNNING:
return (True, '`trader is not running`')
pair_rates = Trade.session.query(Trade.pair,
sql.func.sum(Trade.close_profit).label('profit_sum'),
sql.func.count(Trade.pair).label('count')) \
@ -358,9 +366,9 @@ class RPC(object):
trades = []
for (pair, rate, count) in pair_rates:
trades.append({'pair': pair, 'profit': round(rate * 100, 2), 'count': count})
return (False, trades)
def rpc_count(self) -> None:
"""
Returns the number of trades running
@ -368,6 +376,6 @@ class RPC(object):
"""
if self.freqtrade.get_state() != State.RUNNING:
return (True, '`trader is not running`')
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
return (False, trades)

View File

@ -10,7 +10,6 @@ class RPCManager(object):
"""
Class to manage RPC objects (Telegram, Slack, ...)
"""
def __init__(self, freqtrade) -> None:
"""
Initializes all enabled rpc modules

View File

@ -1,14 +1,16 @@
# pragma pylint: disable=unused-argument, unused-variable, protected-access, invalid-name
"""
This module manage Telegram communication
"""
from typing import Any, Callable
from freqtrade.rpc.rpc import RPC
from tabulate import tabulate
from telegram import Bot, ParseMode, ReplyKeyboardMarkup, Update
from telegram.error import NetworkError, TelegramError
from telegram.ext import CommandHandler, Updater
from freqtrade.__init__ import __version__
from freqtrade.rpc.rpc import RPC
def authorized_only(command_handler: Callable[[Bot, Update], None]) -> Callable[..., Any]:
@ -17,10 +19,10 @@ def authorized_only(command_handler: Callable[[Bot, Update], None]) -> Callable[
:param command_handler: Telegram CommandHandler
:return: decorated function
"""
#def wrapper(self, bot: Bot, update: Update):
def wrapper(self, *args, **kwargs):
"""
Decorator logic
"""
update = kwargs.get('update') or args[1]
# Reject unauthorized messages
@ -45,6 +47,7 @@ def authorized_only(command_handler: Callable[[Bot, Update], None]) -> Callable[
return wrapper
class Telegram(RPC):
"""
Telegram, this class send messages to Telegram
@ -57,7 +60,7 @@ class Telegram(RPC):
"""
super().__init__(freqtrade)
self._updater = Updater = None
self._updater = None
self._config = freqtrade.config
self._init()
@ -190,10 +193,10 @@ class Telegram(RPC):
],
tablefmt='simple')
message = '<b>Daily Profit over the last {} days</b>:\n<pre>{}</pre>'\
.format(
timescale,
stats
)
.format(
timescale,
stats
)
self.send_msg(message, bot=bot, parse_mode=ParseMode.HTML)
@authorized_only
@ -225,22 +228,22 @@ class Telegram(RPC):
"*Latest Trade opened:* `{latest_trade_date}`\n" \
"*Avg. Duration:* `{avg_duration}`\n" \
"*Best Performing:* `{best_pair}: {best_rate:.2f}%`"\
.format(
coin=self._config['stake_currency'],
fiat=self._config['fiat_display_currency'],
profit_closed_coin=stats['profit_closed_coin'],
profit_closed_percent=stats['profit_closed_percent'],
profit_closed_fiat=stats['profit_closed_fiat'],
profit_all_coin=stats['profit_all_coin'],
profit_all_percent=stats['profit_all_percent'],
profit_all_fiat=stats['profit_all_fiat'],
trade_count=stats['trade_count'],
first_trade_date=stats['first_trade_date'],
latest_trade_date=stats['latest_trade_date'],
avg_duration=stats['avg_duration'],
best_pair=stats['best_pair'],
best_rate=stats['best_rate']
)
.format(
coin=self._config['stake_currency'],
fiat=self._config['fiat_display_currency'],
profit_closed_coin=stats['profit_closed_coin'],
profit_closed_percent=stats['profit_closed_percent'],
profit_closed_fiat=stats['profit_closed_fiat'],
profit_all_coin=stats['profit_all_coin'],
profit_all_percent=stats['profit_all_percent'],
profit_all_fiat=stats['profit_all_fiat'],
trade_count=stats['trade_count'],
first_trade_date=stats['first_trade_date'],
latest_trade_date=stats['latest_trade_date'],
avg_duration=stats['avg_duration'],
best_pair=stats['best_pair'],
best_rate=stats['best_rate']
)
self.send_msg(markdown_msg, bot=bot)
@authorized_only
@ -294,7 +297,6 @@ class Telegram(RPC):
(error, msg) = self.rpc_stop()
self.send_msg(msg, bot=bot)
# FIX: no test for this!!!!
@authorized_only
def _forcesell(self, bot: Bot, update: Update) -> None:
"""
@ -370,10 +372,12 @@ class Telegram(RPC):
"*/status [table]:* `Lists all open trades`\n" \
" *table :* `will display trades in a table`\n" \
"*/profit:* `Lists cumulative profit from all finished trades`\n" \
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, regardless of profit`\n" \
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, " \
"regardless of profit`\n" \
"*/performance:* `Show performance of each finished trade grouped by pair`\n" \
"*/daily <n>:* `Shows profit or loss per day, over the last n days`\n" \
"*/count:* `Show number of trades running compared to allowed number of trades`\n" \
"*/count:* `Show number of trades running compared to allowed number of trades`" \
"\n" \
"*/balance:* `Show account balance per currency`\n" \
"*/help:* `This help message`\n" \
"*/version:* `Show version`"
@ -391,7 +395,8 @@ class Telegram(RPC):
"""
self.send_msg('*Version:* `{}`'.format(__version__), bot=bot)
def send_msg(self, msg: str, bot: Bot = None, parse_mode: ParseMode = ParseMode.MARKDOWN) -> None:
def send_msg(self, msg: str, bot: Bot = None,
parse_mode: ParseMode = ParseMode.MARKDOWN) -> None:
"""
Send given markdown message
:param msg: message

View File

@ -19,7 +19,7 @@ class Strategy(object):
"""
This class contains all the logic to load custom strategy class
"""
def __init__(self, config: dict={}) -> None:
def __init__(self, config: dict = {}) -> None:
"""
Load the custom class from config parameter
:param config:

View File

@ -1,14 +1,8 @@
# pragma pylint: disable=missing-docstring,W0212,C0103
import logging
import os
import pytest
from copy import deepcopy
#from freqtrade.optimize.hyperopt import EXPECTED_MAX_PROFIT, start, \
# log_results, save_trials, read_trials, generate_roi_table
from unittest.mock import MagicMock
from freqtrade.optimize.hyperopt import Hyperopt, start
from freqtrade.optimize.hyperopt import Hyperopt
import freqtrade.tests.conftest as tt # test tools
@ -24,7 +18,7 @@ def create_trials(mocker) -> None:
- we might have a pickle'd file so make sure that we return
false when looking for it
"""
_HYPEROPT.trials_file = os.path.join('freqtrade', 'tests', 'optimize','ut_trials.pickle')
_HYPEROPT.trials_file = os.path.join('freqtrade', 'tests', 'optimize', 'ut_trials.pickle')
mocker.patch('freqtrade.optimize.hyperopt.os.path.exists', return_value=False)
mocker.patch('freqtrade.optimize.hyperopt.os.remove', return_value=True)

View File

@ -1,4 +1,5 @@
# pragma pylint: disable=protected-access, invalid-name
"""
Unit test file for configuration.py
"""
@ -270,4 +271,3 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
'Parameter --export detected: {} ...'.format(config['export']),
caplog.record_tuples
)

View File

@ -8,7 +8,6 @@ from freqtrade.constants import Constants
def test_constant_object() -> None:
"""
Test the Constants object has the mandatory Constants
:return: None
"""
assert hasattr(Constants, 'CONF_SCHEMA')
assert hasattr(Constants, 'DYNAMIC_WHITELIST')
@ -19,11 +18,9 @@ def test_constant_object() -> None:
assert hasattr(Constants, 'DEFAULT_STRATEGY')
def test_conf_schema() -> None:
"""
Test the CONF_SCHEMA is from the right type
:return:
"""
constant = Constants()
assert isinstance(constant.CONF_SCHEMA, dict)

View File

@ -2,7 +2,7 @@
import pandas
from freqtrade.optimize import load_data
from freqtrade.analyze import Analyze, SignalType
from freqtrade.analyze import Analyze
_pairs = ['BTC_ETH']