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
import exchange
import persistence
from persistence import Trade
from analyze import get_buy_signal
from misc import CONF_SCHEMA, get_state, State, update_state
from rpc import telegram
from freqtrade import exchange, persistence, __version__
from freqtrade.analyze import get_buy_signal
from freqtrade.misc import State, update_state, get_state, CONF_SCHEMA
from freqtrade.persistence import Trade
from freqtrade.rpc import telegram
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
__author__ = "gcarq"
__copyright__ = "gcarq 2017"
__license__ = "GPLv3"
__version__ = "0.10.0"
_CONF = {}
@ -97,7 +91,8 @@ def execute_sell(trade: Trade, current_rate: float) -> None:
whitelist = _CONF[trade.exchange.name.lower()]['pair_whitelist']
profit = trade.exec_sell_order(current_rate, balance)
whitelist.append(trade.pair)
if trade.pair not in whitelist:
whitelist.append(trade.pair)
message = '*{}:* Selling [{}]({}) at rate `{:f} (profit: {}%)`'.format(
trade.exchange.name,
trade.pair.replace('_', '/'),
@ -238,7 +233,7 @@ def init(config: dict, db_url: Optional[str] = None) -> 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
:return: None
"""
@ -269,8 +264,17 @@ def app(config: dict) -> None:
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:
_CONF = json.load(file)
validate(_CONF, CONF_SCHEMA)
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.orm.scoping import scoped_session
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.types import Enum
import exchange
from freqtrade import exchange
_CONF = {}

View File

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

View File

@ -1,9 +1,12 @@
# pragma pylint: disable=missing-docstring
import unittest
from unittest.mock import patch
from pandas import DataFrame
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 = {
'success': True,
@ -38,10 +41,10 @@ class TestAnalyze(unittest.TestCase):
def test_4_returns_latest_buy_signal(self):
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)
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)

View File

@ -1,14 +1,17 @@
# pragma pylint: disable=missing-docstring
import unittest
from unittest.mock import patch
import os
import json
import logging
import os
import unittest
from unittest.mock import patch
import arrow
from pandas import DataFrame
from analyze import analyze_ticker
from persistence import Trade
from main import should_sell
from freqtrade.analyze import analyze_ticker
from freqtrade.main import should_sell
from freqtrade.persistence import Trade
def print_results(results):
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")
def test_backtest(self):
trades = []
with patch.dict('main._CONF', self.conf):
with patch.dict('freqtrade.main._CONF', self.conf):
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)
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')):
ticker = analyze_ticker(pair)
# for each buy point

View File

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

View File

@ -1,13 +1,13 @@
import unittest
from unittest.mock import patch
from exchange import Exchange
from persistence import Trade
from freqtrade.exchange import Exchange
from freqtrade.persistence import Trade
class TestTrade(unittest.TestCase):
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(
pair='BTC_ETH',
stake_amount=1.00,

View File

@ -1,15 +1,15 @@
import unittest
from unittest.mock import patch, MagicMock
from datetime import datetime
from unittest.mock import patch, MagicMock
from jsonschema import validate
from telegram import Bot, Update, Message, Chat
import exchange
from main import init, create_trade
from misc import CONF_SCHEMA, update_state, State, get_state
from persistence import Trade
from rpc.telegram import _status, _profit, _forcesell, _performance, _start, _stop
from freqtrade import exchange
from freqtrade.main import init, create_trade
from freqtrade.misc import update_state, State, get_state, CONF_SCHEMA
from freqtrade.persistence import Trade
from freqtrade.rpc.telegram import _status, _profit, _forcesell, _performance, _start, _stop
class MagicBot(MagicMock, Bot):
@ -48,11 +48,11 @@ class TestTelegram(unittest.TestCase):
}
def test_1_status_handle(self):
with patch.dict('main._CONF', self.conf):
with patch('main.get_buy_signal', side_effect=lambda _: True):
with patch.dict('freqtrade.main._CONF', self.conf):
with patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True):
msg_mock = MagicMock()
with patch.multiple('main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock):
with patch.multiple('main.exchange',
with patch.multiple('freqtrade.main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock):
with patch.multiple('freqtrade.main.exchange',
get_ticker=MagicMock(return_value={
'bid': 0.07256061,
'ask': 0.072661,
@ -72,11 +72,11 @@ class TestTelegram(unittest.TestCase):
self.assertIn('[BTC_ETH]', msg_mock.call_args_list[-1][0][0])
def test_2_profit_handle(self):
with patch.dict('main._CONF', self.conf):
with patch('main.get_buy_signal', side_effect=lambda _: True):
with patch.dict('freqtrade.main._CONF', self.conf):
with patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True):
msg_mock = MagicMock()
with patch.multiple('main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock):
with patch.multiple('main.exchange',
with patch.multiple('freqtrade.main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock):
with patch.multiple('freqtrade.main.exchange',
get_ticker=MagicMock(return_value={
'bid': 0.07256061,
'ask': 0.072661,
@ -101,11 +101,11 @@ class TestTelegram(unittest.TestCase):
self.assertIn('(100.00%)', msg_mock.call_args_list[-1][0][0])
def test_3_forcesell_handle(self):
with patch.dict('main._CONF', self.conf):
with patch('main.get_buy_signal', side_effect=lambda _: True):
with patch.dict('freqtrade.main._CONF', self.conf):
with patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True):
msg_mock = MagicMock()
with patch.multiple('main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock):
with patch.multiple('main.exchange',
with patch.multiple('freqtrade.main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock):
with patch.multiple('freqtrade.main.exchange',
get_ticker=MagicMock(return_value={
'bid': 0.07256061,
'ask': 0.072661,
@ -128,11 +128,11 @@ class TestTelegram(unittest.TestCase):
self.assertIn('0.072561', msg_mock.call_args_list[-1][0][0])
def test_4_performance_handle(self):
with patch.dict('main._CONF', self.conf):
with patch('main.get_buy_signal', side_effect=lambda _: True):
with patch.dict('freqtrade.main._CONF', self.conf):
with patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True):
msg_mock = MagicMock()
with patch.multiple('main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock):
with patch.multiple('main.exchange',
with patch.multiple('freqtrade.main.telegram', _CONF=self.conf, init=MagicMock(), send_msg=msg_mock):
with patch.multiple('freqtrade.main.exchange',
get_ticker=MagicMock(return_value={
'bid': 0.07256061,
'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])
def test_5_start_handle(self):
with patch.dict('main._CONF', self.conf):
with patch.dict('freqtrade.main._CONF', self.conf):
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://')
update_state(State.STOPPED)
@ -170,9 +170,9 @@ class TestTelegram(unittest.TestCase):
self.assertEqual(msg_mock.call_count, 0)
def test_6_stop_handle(self):
with patch.dict('main._CONF', self.conf):
with patch.dict('freqtrade.main._CONF', self.conf):
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://')
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
python-telegram-bot==8.0
arrow==0.10.0
@ -6,9 +6,11 @@ requests==2.18.4
urllib3==1.22
wrapt==1.10.11
pandas==0.20.3
matplotlib==2.0.2
scikit-learn==0.19.0
scipy==0.19.1
jsonschema==2.6.0
TA-Lib==0.4.10
# Required for plotting data
#matplotlib==2.0.2
#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',
])