Merge pull request #47 from gcarq/feature/project-structure

Refactor project structure (closes #34)
This commit is contained in:
Janne Sinivirta 2017-09-30 19:19:00 +03:00 committed by GitHub
commit 6389778d49
28 changed files with 142 additions and 81 deletions

3
MANIFEST.in Normal file
View File

@ -0,0 +1,3 @@
include LICENSE
include README.md
include config.json.example

4
bin/freqtrade Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env python
from freqtrade.main import main
main()

3
freqtrade/__init__.py Normal file
View File

@ -0,0 +1,3 @@
__version__ = '0.10.0'
from . import main

View File

@ -8,22 +8,16 @@ from typing import Dict, Optional
from jsonschema import validate from jsonschema import validate
import exchange from freqtrade import exchange, persistence, __version__
import persistence from freqtrade.analyze import get_buy_signal
from persistence import Trade from freqtrade.misc import State, update_state, get_state, CONF_SCHEMA
from analyze import get_buy_signal from freqtrade.persistence import Trade
from misc import CONF_SCHEMA, get_state, State, update_state from freqtrade.rpc import telegram
from rpc import telegram
logging.basicConfig(level=logging.DEBUG, logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
__author__ = "gcarq"
__copyright__ = "gcarq 2017"
__license__ = "GPLv3"
__version__ = "0.10.0"
_CONF = {} _CONF = {}
@ -97,6 +91,7 @@ def execute_sell(trade: Trade, current_rate: float) -> None:
whitelist = _CONF[trade.exchange.name.lower()]['pair_whitelist'] whitelist = _CONF[trade.exchange.name.lower()]['pair_whitelist']
profit = trade.exec_sell_order(current_rate, balance) profit = trade.exec_sell_order(current_rate, balance)
if trade.pair not in whitelist:
whitelist.append(trade.pair) whitelist.append(trade.pair)
message = '*{}:* Selling [{}]({}) at rate `{:f} (profit: {}%)`'.format( message = '*{}:* Selling [{}]({}) at rate `{:f} (profit: {}%)`'.format(
trade.exchange.name, trade.exchange.name,
@ -238,7 +233,7 @@ def init(config: dict, db_url: Optional[str] = None) -> None:
def app(config: dict) -> None: def app(config: dict) -> None:
""" """
Main function which handles the application state Main loop which handles the application state
:param config: config as dict :param config: config as dict
:return: None :return: None
""" """
@ -269,8 +264,17 @@ def app(config: dict) -> None:
telegram.send_msg('*Status:* `Trader has stopped`') telegram.send_msg('*Status:* `Trader has stopped`')
if __name__ == '__main__': def main():
"""
Loads and validates the config and starts the main loop
:return: None
"""
global _CONF
with open('config.json') as file: with open('config.json') as file:
_CONF = json.load(file) _CONF = json.load(file)
validate(_CONF, CONF_SCHEMA) validate(_CONF, CONF_SCHEMA)
app(_CONF) app(_CONF)
if __name__ == '__main__':
main()

View File

@ -5,11 +5,9 @@ from sqlalchemy import Boolean, Column, DateTime, Float, Integer, String, create
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm.scoping import scoped_session from sqlalchemy.orm.scoping import scoped_session
from sqlalchemy.orm.session import sessionmaker from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.types import Enum from sqlalchemy.types import Enum
import exchange from freqtrade import exchange
_CONF = {} _CONF = {}

View File

@ -4,14 +4,13 @@ from typing import Callable, Any
import arrow import arrow
from sqlalchemy import and_, func, text from sqlalchemy import and_, func, text
from telegram import ParseMode, Bot, Update
from telegram.error import NetworkError from telegram.error import NetworkError
from telegram.ext import CommandHandler, Updater from telegram.ext import CommandHandler, Updater
from telegram import ParseMode, Bot, Update
from misc import get_state, State, update_state from freqtrade import exchange
from persistence import Trade from freqtrade.misc import get_state, State, update_state
from freqtrade.persistence import Trade
import exchange
# Remove noisy log messages # Remove noisy log messages
logging.getLogger('requests.packages.urllib3').setLevel(logging.INFO) logging.getLogger('requests.packages.urllib3').setLevel(logging.INFO)

View File

@ -1,9 +1,12 @@
# pragma pylint: disable=missing-docstring # pragma pylint: disable=missing-docstring
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
from pandas import DataFrame
import arrow import arrow
from analyze import parse_ticker_dataframe, populate_buy_trend, populate_indicators, analyze_ticker, get_buy_signal from pandas import DataFrame
from freqtrade.analyze import parse_ticker_dataframe, populate_buy_trend, populate_indicators, \
get_buy_signal
RESULT_BITTREX = { RESULT_BITTREX = {
'success': True, 'success': True,
@ -38,10 +41,10 @@ class TestAnalyze(unittest.TestCase):
def test_4_returns_latest_buy_signal(self): def test_4_returns_latest_buy_signal(self):
buydf = DataFrame([{'buy': 1, 'date': arrow.utcnow()}]) buydf = DataFrame([{'buy': 1, 'date': arrow.utcnow()}])
with patch('analyze.analyze_ticker', return_value=buydf): with patch('freqtrade.analyze.analyze_ticker', return_value=buydf):
self.assertEqual(get_buy_signal('BTC-ETH'), True) self.assertEqual(get_buy_signal('BTC-ETH'), True)
buydf = DataFrame([{'buy': 0, 'date': arrow.utcnow()}]) buydf = DataFrame([{'buy': 0, 'date': arrow.utcnow()}])
with patch('analyze.analyze_ticker', return_value=buydf): with patch('freqtrade.analyze.analyze_ticker', return_value=buydf):
self.assertEqual(get_buy_signal('BTC-ETH'), False) self.assertEqual(get_buy_signal('BTC-ETH'), False)

View File

@ -1,14 +1,17 @@
# pragma pylint: disable=missing-docstring # pragma pylint: disable=missing-docstring
import unittest
from unittest.mock import patch
import os
import json import json
import logging import logging
import os
import unittest
from unittest.mock import patch
import arrow import arrow
from pandas import DataFrame from pandas import DataFrame
from analyze import analyze_ticker
from persistence import Trade from freqtrade.analyze import analyze_ticker
from main import should_sell from freqtrade.main import should_sell
from freqtrade.persistence import Trade
def print_results(results): def print_results(results):
print('Made {} buys. Average profit {:.2f}%. Total profit was {:.3f}. Average duration {:.1f} mins.'.format( print('Made {} buys. Average profit {:.2f}%. Total profit was {:.3f}. Average duration {:.1f} mins.'.format(
@ -37,12 +40,12 @@ class TestMain(unittest.TestCase):
@unittest.skipIf(not os.environ.get('BACKTEST', False), "slow, should be run manually") @unittest.skipIf(not os.environ.get('BACKTEST', False), "slow, should be run manually")
def test_backtest(self): def test_backtest(self):
trades = [] trades = []
with patch.dict('main._CONF', self.conf): with patch.dict('freqtrade.main._CONF', self.conf):
for pair in self.pairs: for pair in self.pairs:
with open('test/testdata/'+pair+'.json') as data_file: with open('testdata/'+pair+'.json') as data_file:
data = json.load(data_file) data = json.load(data_file)
with patch('analyze.get_ticker', return_value=data): with patch('freqtrade.analyze.get_ticker', return_value=data):
with patch('arrow.utcnow', return_value=arrow.get('2017-08-20T14:50:00')): with patch('arrow.utcnow', return_value=arrow.get('2017-08-20T14:50:00')):
ticker = analyze_ticker(pair) ticker = analyze_ticker(pair)
# for each buy point # for each buy point

View File

@ -3,10 +3,11 @@ from unittest.mock import patch, MagicMock
from jsonschema import validate from jsonschema import validate
import exchange from freqtrade import exchange
from main import create_trade, handle_trade, close_trade_if_fulfilled, init, get_target_bid from freqtrade.main import create_trade, handle_trade, close_trade_if_fulfilled, init, \
from misc import CONF_SCHEMA get_target_bid
from persistence import Trade from freqtrade.misc import CONF_SCHEMA
from freqtrade.persistence import Trade
class TestMain(unittest.TestCase): class TestMain(unittest.TestCase):
@ -39,10 +40,10 @@ class TestMain(unittest.TestCase):
} }
def test_1_create_trade(self): def test_1_create_trade(self):
with patch.dict('main._CONF', self.conf): with patch.dict('freqtrade.main._CONF', self.conf):
with patch('main.get_buy_signal', side_effect=lambda _: True) as buy_signal: with patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) as buy_signal:
with patch.multiple('main.telegram', init=MagicMock(), send_msg=MagicMock()): with patch.multiple('freqtrade.main.telegram', init=MagicMock(), send_msg=MagicMock()):
with patch.multiple('main.exchange', with patch.multiple('freqtrade.main.exchange',
get_ticker=MagicMock(return_value={ get_ticker=MagicMock(return_value={
'bid': 0.07256061, 'bid': 0.07256061,
'ask': 0.072661, 'ask': 0.072661,
@ -64,9 +65,9 @@ class TestMain(unittest.TestCase):
buy_signal.assert_called_once_with('BTC_ETH') buy_signal.assert_called_once_with('BTC_ETH')
def test_2_handle_trade(self): def test_2_handle_trade(self):
with patch.dict('main._CONF', self.conf): with patch.dict('freqtrade.main._CONF', self.conf):
with patch.multiple('main.telegram', init=MagicMock(), send_msg=MagicMock()): with patch.multiple('freqtrade.main.telegram', init=MagicMock(), send_msg=MagicMock()):
with patch.multiple('main.exchange', with patch.multiple('freqtrade.main.exchange',
get_ticker=MagicMock(return_value={ get_ticker=MagicMock(return_value={
'bid': 0.17256061, 'bid': 0.17256061,
'ask': 0.172661, 'ask': 0.172661,
@ -82,7 +83,7 @@ class TestMain(unittest.TestCase):
self.assertEqual(trade.open_order_id, 'dry_run') self.assertEqual(trade.open_order_id, 'dry_run')
def test_3_close_trade(self): def test_3_close_trade(self):
with patch.dict('main._CONF', self.conf): with patch.dict('freqtrade.main._CONF', self.conf):
trade = Trade.query.filter(Trade.is_open.is_(True)).first() trade = Trade.query.filter(Trade.is_open.is_(True)).first()
self.assertTrue(trade) self.assertTrue(trade)
@ -94,15 +95,15 @@ class TestMain(unittest.TestCase):
self.assertEqual(trade.is_open, False) self.assertEqual(trade.is_open, False)
def test_balance_fully_ask_side(self): def test_balance_fully_ask_side(self):
with patch.dict('main._CONF', {'bid_strategy': {'ask_last_balance': 0.0}}): with patch.dict('freqtrade.main._CONF', {'bid_strategy': {'ask_last_balance': 0.0}}):
self.assertEqual(get_target_bid({'ask': 20, 'last': 10}), 20) self.assertEqual(get_target_bid({'ask': 20, 'last': 10}), 20)
def test_balance_fully_last_side(self): def test_balance_fully_last_side(self):
with patch.dict('main._CONF', {'bid_strategy': {'ask_last_balance': 1.0}}): with patch.dict('freqtrade.main._CONF', {'bid_strategy': {'ask_last_balance': 1.0}}):
self.assertEqual(get_target_bid({'ask': 20, 'last': 10}), 10) self.assertEqual(get_target_bid({'ask': 20, 'last': 10}), 10)
def test_balance_when_last_bigger_than_ask(self): def test_balance_when_last_bigger_than_ask(self):
with patch.dict('main._CONF', {'bid_strategy': {'ask_last_balance': 1.0}}): with patch.dict('freqtrade.main._CONF', {'bid_strategy': {'ask_last_balance': 1.0}}):
self.assertEqual(get_target_bid({'ask': 5, 'last': 10}), 5) self.assertEqual(get_target_bid({'ask': 5, 'last': 10}), 5)
@classmethod @classmethod

View File

@ -1,13 +1,13 @@
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
from exchange import Exchange from freqtrade.exchange import Exchange
from persistence import Trade from freqtrade.persistence import Trade
class TestTrade(unittest.TestCase): class TestTrade(unittest.TestCase):
def test_1_exec_sell_order(self): def test_1_exec_sell_order(self):
with patch('main.exchange.sell', side_effect='mocked_order_id') as api_mock: with patch('freqtrade.main.exchange.sell', side_effect='mocked_order_id') as api_mock:
trade = Trade( trade = Trade(
pair='BTC_ETH', pair='BTC_ETH',
stake_amount=1.00, stake_amount=1.00,

View File

@ -1,15 +1,15 @@
import unittest import unittest
from unittest.mock import patch, MagicMock
from datetime import datetime from datetime import datetime
from unittest.mock import patch, MagicMock
from jsonschema import validate from jsonschema import validate
from telegram import Bot, Update, Message, Chat from telegram import Bot, Update, Message, Chat
import exchange from freqtrade import exchange
from main import init, create_trade from freqtrade.main import init, create_trade
from misc import CONF_SCHEMA, update_state, State, get_state from freqtrade.misc import update_state, State, get_state, CONF_SCHEMA
from persistence import Trade from freqtrade.persistence import Trade
from rpc.telegram import _status, _profit, _forcesell, _performance, _start, _stop from freqtrade.rpc.telegram import _status, _profit, _forcesell, _performance, _start, _stop
class MagicBot(MagicMock, Bot): class MagicBot(MagicMock, Bot):
@ -48,11 +48,11 @@ class TestTelegram(unittest.TestCase):
} }
def test_1_status_handle(self): def test_1_status_handle(self):
with patch.dict('main._CONF', self.conf): with patch.dict('freqtrade.main._CONF', self.conf):
with patch('main.get_buy_signal', side_effect=lambda _: True): with patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True):
msg_mock = MagicMock() msg_mock = MagicMock()
with patch.multiple('main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock): with patch.multiple('freqtrade.main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock):
with patch.multiple('main.exchange', with patch.multiple('freqtrade.main.exchange',
get_ticker=MagicMock(return_value={ get_ticker=MagicMock(return_value={
'bid': 0.07256061, 'bid': 0.07256061,
'ask': 0.072661, 'ask': 0.072661,
@ -72,11 +72,11 @@ class TestTelegram(unittest.TestCase):
self.assertIn('[BTC_ETH]', msg_mock.call_args_list[-1][0][0]) self.assertIn('[BTC_ETH]', msg_mock.call_args_list[-1][0][0])
def test_2_profit_handle(self): def test_2_profit_handle(self):
with patch.dict('main._CONF', self.conf): with patch.dict('freqtrade.main._CONF', self.conf):
with patch('main.get_buy_signal', side_effect=lambda _: True): with patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True):
msg_mock = MagicMock() msg_mock = MagicMock()
with patch.multiple('main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock): with patch.multiple('freqtrade.main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock):
with patch.multiple('main.exchange', with patch.multiple('freqtrade.main.exchange',
get_ticker=MagicMock(return_value={ get_ticker=MagicMock(return_value={
'bid': 0.07256061, 'bid': 0.07256061,
'ask': 0.072661, 'ask': 0.072661,
@ -101,11 +101,11 @@ class TestTelegram(unittest.TestCase):
self.assertIn('(100.00%)', msg_mock.call_args_list[-1][0][0]) self.assertIn('(100.00%)', msg_mock.call_args_list[-1][0][0])
def test_3_forcesell_handle(self): def test_3_forcesell_handle(self):
with patch.dict('main._CONF', self.conf): with patch.dict('freqtrade.main._CONF', self.conf):
with patch('main.get_buy_signal', side_effect=lambda _: True): with patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True):
msg_mock = MagicMock() msg_mock = MagicMock()
with patch.multiple('main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock): with patch.multiple('freqtrade.main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock):
with patch.multiple('main.exchange', with patch.multiple('freqtrade.main.exchange',
get_ticker=MagicMock(return_value={ get_ticker=MagicMock(return_value={
'bid': 0.07256061, 'bid': 0.07256061,
'ask': 0.072661, 'ask': 0.072661,
@ -128,11 +128,11 @@ class TestTelegram(unittest.TestCase):
self.assertIn('0.072561', msg_mock.call_args_list[-1][0][0]) self.assertIn('0.072561', msg_mock.call_args_list[-1][0][0])
def test_4_performance_handle(self): def test_4_performance_handle(self):
with patch.dict('main._CONF', self.conf): with patch.dict('freqtrade.main._CONF', self.conf):
with patch('main.get_buy_signal', side_effect=lambda _: True): with patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True):
msg_mock = MagicMock() msg_mock = MagicMock()
with patch.multiple('main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock): with patch.multiple('freqtrade.main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock):
with patch.multiple('main.exchange', with patch.multiple('freqtrade.main.exchange',
get_ticker=MagicMock(return_value={ get_ticker=MagicMock(return_value={
'bid': 0.07256061, 'bid': 0.07256061,
'ask': 0.072661, 'ask': 0.072661,
@ -158,9 +158,9 @@ class TestTelegram(unittest.TestCase):
self.assertIn('BTC_ETH 100.00%', msg_mock.call_args_list[-1][0][0]) self.assertIn('BTC_ETH 100.00%', msg_mock.call_args_list[-1][0][0])
def test_5_start_handle(self): def test_5_start_handle(self):
with patch.dict('main._CONF', self.conf): with patch.dict('freqtrade.main._CONF', self.conf):
msg_mock = MagicMock() msg_mock = MagicMock()
with patch.multiple('main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock): with patch.multiple('freqtrade.main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock):
init(self.conf, 'sqlite://') init(self.conf, 'sqlite://')
update_state(State.STOPPED) update_state(State.STOPPED)
@ -170,9 +170,9 @@ class TestTelegram(unittest.TestCase):
self.assertEqual(msg_mock.call_count, 0) self.assertEqual(msg_mock.call_count, 0)
def test_6_stop_handle(self): def test_6_stop_handle(self):
with patch.dict('main._CONF', self.conf): with patch.dict('freqtrade.main._CONF', self.conf):
msg_mock = MagicMock() msg_mock = MagicMock()
with patch.multiple('main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock): with patch.multiple('freqtrade.main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock):
init(self.conf, 'sqlite://') init(self.conf, 'sqlite://')
update_state(State.RUNNING) update_state(State.RUNNING)

View File

@ -1,4 +1,4 @@
-e git+https://github.com/ericsomdahl/python-bittrex.git#egg=python-bittrex -e git+https://github.com/ericsomdahl/python-bittrex.git@d7033d0#egg=python-bittrex
SQLAlchemy==1.1.13 SQLAlchemy==1.1.13
python-telegram-bot==8.0 python-telegram-bot==8.0
arrow==0.10.0 arrow==0.10.0
@ -6,9 +6,11 @@ requests==2.18.4
urllib3==1.22 urllib3==1.22
wrapt==1.10.11 wrapt==1.10.11
pandas==0.20.3 pandas==0.20.3
matplotlib==2.0.2
scikit-learn==0.19.0 scikit-learn==0.19.0
scipy==0.19.1 scipy==0.19.1
jsonschema==2.6.0 jsonschema==2.6.0
TA-Lib==0.4.10 TA-Lib==0.4.10
# Required for plotting data
#matplotlib==2.0.2
#PYQT5==5.9 #PYQT5==5.9

41
setup.py Normal file
View File

@ -0,0 +1,41 @@
from setuptools import setup
from freqtrade import __version__
setup(name='freqtrade',
version=__version__,
description='Simple High Frequency Trading Bot for crypto currencies',
url='https://github.com/gcarq/freqtrade',
author='gcarq and contributors',
author_email='michael.egger@tsn.at',
license='GPLv3',
packages=['freqtrade'],
scripts=['bin/freqtrade'],
setup_requires=['pytest-runner'],
tests_require=['pytest'],
install_requires=[
'python-bittrex==0.1.3',
'SQLAlchemy==1.1.13',
'python-telegram-bot==8.0',
'arrow==0.10.0',
'requests==2.18.4',
'urllib3==1.22',
'wrapt==1.10.11',
'pandas==0.20.3',
'scikit-learn==0.19.0',
'scipy==0.19.1',
'jsonschema==2.6.0',
'TA-Lib==0.4.10',
],
dependency_links=[
"git+https://github.com/ericsomdahl/python-bittrex.git@d7033d0#egg=python-bittrex-0.1.3"
],
include_package_data=True,
zip_safe=False,
classifiers=[
'Programming Language :: Python :: 3.6',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Topic :: Office/Business :: Financial :: Investment',
'Intended Audience :: Science/Research',
])