Merge pull request #92 from gcarq/feature/rework-dry_run-mode

rework dry_run
This commit is contained in:
Michael Egger 2017-11-06 16:54:55 +01:00 committed by GitHub
commit ae0b49f532
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 50 additions and 69 deletions

View File

@ -1,6 +1,7 @@
import enum import enum
import logging import logging
from typing import List, Dict from random import randint
from typing import List, Dict, Any
import arrow import arrow
@ -13,6 +14,9 @@ logger = logging.getLogger(__name__)
_API: Exchange = None _API: Exchange = None
_CONF: dict = {} _CONF: dict = {}
# Holds all open sell orders for dry_run
_DRY_RUN_OPEN_ORDERS: Dict[str, Any] = {}
class Exchanges(enum.Enum): class Exchanges(enum.Enum):
""" """
@ -66,14 +70,36 @@ def validate_pairs(pairs: List[str]) -> None:
def buy(pair: str, rate: float, amount: float) -> str: def buy(pair: str, rate: float, amount: float) -> str:
if _CONF['dry_run']: if _CONF['dry_run']:
return 'dry_run_buy' global _DRY_RUN_OPEN_ORDERS
order_id = 'dry_run_buy_{}'.format(randint(0, 1e6))
_DRY_RUN_OPEN_ORDERS[order_id] = {
'pair': pair,
'rate': rate,
'amount': amount,
'type': 'LIMIT_BUY',
'remaining': 0.0,
'opened': arrow.utcnow().datetime,
'closed': arrow.utcnow().datetime,
}
return order_id
return _API.buy(pair, rate, amount) return _API.buy(pair, rate, amount)
def sell(pair: str, rate: float, amount: float) -> str: def sell(pair: str, rate: float, amount: float) -> str:
if _CONF['dry_run']: if _CONF['dry_run']:
return 'dry_run_sell' global _DRY_RUN_OPEN_ORDERS
order_id = 'dry_run_sell_{}'.format(randint(0, 1e6))
_DRY_RUN_OPEN_ORDERS[order_id] = {
'pair': pair,
'rate': rate,
'amount': amount,
'type': 'LIMIT_SELL',
'remaining': 0.0,
'opened': arrow.utcnow().datetime,
'closed': arrow.utcnow().datetime,
}
return order_id
return _API.sell(pair, rate, amount) return _API.sell(pair, rate, amount)
@ -109,16 +135,11 @@ def cancel_order(order_id: str) -> None:
def get_order(order_id: str) -> Dict: def get_order(order_id: str) -> Dict:
if _CONF['dry_run']: if _CONF['dry_run']:
return { order = _DRY_RUN_OPEN_ORDERS[order_id]
'id': 'dry_run_sell', order.update({
'type': 'LIMIT_SELL', 'id': order_id
'pair': 'mocked', })
'opened': arrow.utcnow().datetime, return order
'rate': 0.07256060,
'amount': 206.43811673387373,
'remaining': 0.0,
'closed': arrow.utcnow().datetime,
}
return _API.get_order(order_id) return _API.get_order(order_id)

View File

@ -100,7 +100,7 @@ def execute_sell(trade: Trade, limit: float) -> None:
trade.close_date = datetime.utcnow() trade.close_date = datetime.utcnow()
fmt_exp_profit = round(trade.calc_profit(limit) * 100, 2) fmt_exp_profit = round(trade.calc_profit(limit) * 100, 2)
message = '*{}:* Selling [{}]({}) with limit `{:f} (profit: ~{}%)`'.format( message = '*{}:* Selling [{}]({}) with limit `{:.8f} (profit: ~{:.2f}%)`'.format(
trade.exchange, trade.exchange,
trade.pair.replace('_', '/'), trade.pair.replace('_', '/'),
exchange.get_pair_detail_url(trade.pair), exchange.get_pair_detail_url(trade.pair),
@ -196,7 +196,7 @@ def create_trade(stake_amount: float) -> Optional[Trade]:
order_id = exchange.buy(pair, buy_limit, amount) order_id = exchange.buy(pair, buy_limit, amount)
# Create trade entity and return # Create trade entity and return
message = '*{}:* Buying [{}]({}) with limit `{:f}`'.format( message = '*{}:* Buying [{}]({}) with limit `{:.8f}`'.format(
exchange.get_name().upper(), exchange.get_name().upper(),
pair.replace('_', '/'), pair.replace('_', '/'),
exchange.get_pair_detail_url(pair), exchange.get_pair_detail_url(pair),
@ -208,12 +208,11 @@ def create_trade(stake_amount: float) -> Optional[Trade]:
return Trade(pair=pair, return Trade(pair=pair,
stake_amount=stake_amount, stake_amount=stake_amount,
amount=amount, amount=amount,
fee=fee * 2, fee=fee*2,
open_rate=buy_limit, open_rate=buy_limit,
open_date=datetime.utcnow(), open_date=datetime.utcnow(),
exchange=exchange.get_name().upper(), exchange=exchange.get_name().upper(),
open_order_id=order_id, open_order_id=order_id)
is_open=True)
def init(config: dict, db_url: Optional[str] = None) -> None: def init(config: dict, db_url: Optional[str] = None) -> None:

View File

@ -141,9 +141,9 @@ def _status(bot: Bot, update: Update) -> None:
*Current Pair:* [{pair}]({market_url}) *Current Pair:* [{pair}]({market_url})
*Open Since:* `{date}` *Open Since:* `{date}`
*Amount:* `{amount}` *Amount:* `{amount}`
*Open Rate:* `{open_rate}` *Open Rate:* `{open_rate:.8f}`
*Close Rate:* `{close_rate}` *Close Rate:* `{close_rate}`
*Current Rate:* `{current_rate}` *Current Rate:* `{current_rate:.8f}`
*Close Profit:* `{close_profit}` *Close Profit:* `{close_profit}`
*Current Profit:* `{current_profit:.2f}%` *Current Profit:* `{current_profit:.2f}%`
*Open Order:* `{open_order}` *Open Order:* `{open_order}`
@ -243,13 +243,12 @@ def _profit(bot: Bot, update: Update) -> None:
bp_pair, bp_rate = best_pair bp_pair, bp_rate = best_pair
markdown_msg = """ markdown_msg = """
*ROI:* `{profit_btc:.6f} ({profit:.2f}%)` *ROI:* `{profit_btc:.8f} ({profit:.2f}%)`
*Trade Count:* `{trade_count}` *Trade Count:* `{trade_count}`
*First Trade opened:* `{first_trade_date}` *First Trade opened:* `{first_trade_date}`
*Latest Trade opened:* `{latest_trade_date}` *Latest Trade opened:* `{latest_trade_date}`
*Avg. Duration:* `{avg_duration}` *Avg. Duration:* `{avg_duration}`
*Best Performing:* `{best_pair}: {best_rate:.2f}%` *Best Performing:* `{best_pair}: {best_rate:.2f}%`
{dry_run_info}
""".format( """.format(
profit_btc=round(sum(profit_amounts), 8), profit_btc=round(sum(profit_amounts), 8),
profit=round(sum(profits) * 100, 2), profit=round(sum(profits) * 100, 2),
@ -259,8 +258,6 @@ def _profit(bot: Bot, update: Update) -> None:
avg_duration=str(timedelta(seconds=sum(durations) / float(len(durations)))).split('.')[0], avg_duration=str(timedelta(seconds=sum(durations) / float(len(durations)))).split('.')[0],
best_pair=bp_pair, best_pair=bp_pair,
best_rate=round(bp_rate * 100, 2), best_rate=round(bp_rate * 100, 2),
dry_run_info='\n*NOTE:* These values are mocked because *dry_run* is enabled!'
if _CONF['dry_run'] else ''
) )
send_msg(markdown_msg, bot=bot) send_msg(markdown_msg, bot=bot)
@ -377,11 +374,7 @@ def _performance(bot: Bot, update: Update) -> None:
profit=round(rate * 100, 2) profit=round(rate * 100, 2)
) for i, (pair, rate) in enumerate(pair_rates)) ) for i, (pair, rate) in enumerate(pair_rates))
message = '<b>Performance:</b>\n{}\n{}'.format( message = '<b>Performance:</b>\n{}'.format(stats)
stats,
'<b>NOTE:</b> These values are mocked because <b>dry_run</b> is enabled.'
if _CONF['dry_run'] else ''
)
logger.debug(message) logger.debug(message)
send_msg(message, parse_mode=ParseMode.HTML) send_msg(message, parse_mode=ParseMode.HTML)

View File

@ -8,6 +8,7 @@ import pytest
from jsonschema import validate from jsonschema import validate
from telegram import Bot, Update, Message, Chat from telegram import Bot, Update, Message, Chat
from freqtrade import exchange
from freqtrade.main import init, create_trade from freqtrade.main import init, create_trade
from freqtrade.misc import update_state, State, get_state, CONF_SCHEMA from freqtrade.misc import update_state, State, get_state, CONF_SCHEMA
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
@ -77,8 +78,7 @@ def test_status_handle(conf, update, mocker):
'bid': 0.07256061, 'bid': 0.07256061,
'ask': 0.072661, 'ask': 0.072661,
'last': 0.07256061 'last': 0.07256061
}), }))
buy=MagicMock(return_value='mocked_order_id'))
init(conf, 'sqlite://') init(conf, 'sqlite://')
# Create some test data # Create some test data
@ -87,26 +87,10 @@ def test_status_handle(conf, update, mocker):
Trade.session.add(trade) Trade.session.add(trade)
Trade.session.flush() Trade.session.flush()
# Trigger status while we don't know the open_rate yet
_status(bot=MagicBot(), update=update)
# Simulate fulfilled LIMIT_BUY order for trade
trade.update({
'id': 'mocked_limit_buy',
'type': 'LIMIT_BUY',
'pair': 'mocked',
'opened': datetime.utcnow(),
'rate': 0.07256060,
'amount': 206.43811673387373,
'remaining': 0.0,
'closed': datetime.utcnow(),
})
Trade.session.flush()
# Trigger status while we have a fulfilled order for the open trade # Trigger status while we have a fulfilled order for the open trade
_status(bot=MagicBot(), update=update) _status(bot=MagicBot(), update=update)
assert msg_mock.call_count == 3 assert msg_mock.call_count == 2
assert '[BTC_ETH]' in msg_mock.call_args_list[-1][0][0] assert '[BTC_ETH]' in msg_mock.call_args_list[-1][0][0]
@ -156,8 +140,7 @@ def test_profit_handle(conf, update, mocker):
'bid': 0.07256061, 'bid': 0.07256061,
'ask': 0.072661, 'ask': 0.072661,
'last': 0.07256061 'last': 0.07256061
}), }))
buy=MagicMock(return_value='mocked_limit_buy'))
init(conf, 'sqlite://') init(conf, 'sqlite://')
# Create some test data # Create some test data
@ -188,14 +171,13 @@ def test_profit_handle(conf, update, mocker):
}) })
trade.close_date = datetime.utcnow() trade.close_date = datetime.utcnow()
trade.open_order_id = None
trade.is_open = False trade.is_open = False
Trade.session.add(trade) Trade.session.add(trade)
Trade.session.flush() Trade.session.flush()
_profit(bot=MagicBot(), update=update) _profit(bot=MagicBot(), update=update)
assert msg_mock.call_count == 2 assert msg_mock.call_count == 2
assert '*ROI:* `1.507013 (10.05%)`' in msg_mock.call_args_list[-1][0][0] assert '*ROI:* `1.50701325 (10.05%)`' in msg_mock.call_args_list[-1][0][0]
assert 'Best Performing:* `BTC_ETH: 10.05%`' in msg_mock.call_args_list[-1][0][0] assert 'Best Performing:* `BTC_ETH: 10.05%`' in msg_mock.call_args_list[-1][0][0]
@ -213,26 +195,13 @@ def test_forcesell_handle(conf, update, mocker):
'bid': 0.07256061, 'bid': 0.07256061,
'ask': 0.072661, 'ask': 0.072661,
'last': 0.07256061 'last': 0.07256061
}), }))
buy=MagicMock(return_value='mocked_order_id'))
init(conf, 'sqlite://') init(conf, 'sqlite://')
# Create some test data # Create some test data
trade = create_trade(15.0) trade = create_trade(15.0)
assert trade assert trade
# Simulate fulfilled LIMIT_BUY order for trade
trade.update({
'id': 'mocked_limit_buy',
'type': 'LIMIT_BUY',
'pair': 'mocked',
'opened': datetime.utcnow(),
'rate': 0.07256060,
'amount': 206.43811673387373,
'remaining': 0.0,
'closed': datetime.utcnow(),
})
Trade.session.add(trade) Trade.session.add(trade)
Trade.session.flush() Trade.session.flush()
@ -241,7 +210,7 @@ def test_forcesell_handle(conf, update, mocker):
assert msg_mock.call_count == 2 assert msg_mock.call_count == 2
assert 'Selling [BTC/ETH]' in msg_mock.call_args_list[-1][0][0] assert 'Selling [BTC/ETH]' in msg_mock.call_args_list[-1][0][0]
assert '0.072561 (profit: ~-0.5%)' in msg_mock.call_args_list[-1][0][0] assert '0.07256061 (profit: ~-0.64%)' in msg_mock.call_args_list[-1][0][0]
def test_performance_handle(conf, update, mocker): def test_performance_handle(conf, update, mocker):
@ -258,8 +227,7 @@ def test_performance_handle(conf, update, mocker):
'bid': 0.07256061, 'bid': 0.07256061,
'ask': 0.072661, 'ask': 0.072661,
'last': 0.07256061 'last': 0.07256061
}), }))
buy=MagicMock(return_value='mocked_order_id'))
init(conf, 'sqlite://') init(conf, 'sqlite://')
# Create some test data # Create some test data