- An helper strategy that runs signals evaluation concurrently

This commit is contained in:
orehunt 2020-03-24 16:33:33 +01:00
parent 030c487d6b
commit e697b5612a
2 changed files with 109 additions and 10 deletions

View File

@ -27,6 +27,7 @@ from freqtrade.rpc import RPCManager, RPCMessageType
from freqtrade.state import State from freqtrade.state import State
from freqtrade.strategy.interface import IStrategy, SellType from freqtrade.strategy.interface import IStrategy, SellType
from freqtrade.wallets import Wallets from freqtrade.wallets import Wallets
from freqtrade.strategy.helper import get_all_signals
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -216,9 +217,15 @@ class FreqtradeBot:
"but checking to sell open trades.") "but checking to sell open trades.")
else: else:
# Create entity and execute trade for each pair from whitelist # Create entity and execute trade for each pair from whitelist
all_signals = get_all_signals(
self.strategy.get_signal, {
pair: (pair, self.strategy.ticker_interval,
self.dataprovider.ohlcv(pair, self.strategy.ticker_interval))
for pair in whitelist
})
for pair in whitelist: for pair in whitelist:
try: try:
trades_created += self.create_trade(pair) trades_created += self.create_trade(pair, all_signals[pair])
except DependencyException as exception: except DependencyException as exception:
logger.warning('Unable to create trade for %s: %s', pair, exception) logger.warning('Unable to create trade for %s: %s', pair, exception)
@ -379,7 +386,7 @@ class FreqtradeBot:
# See also #2575 at github. # See also #2575 at github.
return max(min_stake_amounts) / amount_reserve_percent return max(min_stake_amounts) / amount_reserve_percent
def create_trade(self, pair: str) -> bool: def create_trade(self, pair: str, signals: Tuple[bool, bool] = None) -> bool:
""" """
Check the implemented trading strategy for buy signals. Check the implemented trading strategy for buy signals.
@ -401,6 +408,10 @@ class FreqtradeBot:
return False return False
# running get_signal on historical data fetched # running get_signal on historical data fetched
if signals:
buy = signals[0]
sell = signals[1]
else:
(buy, sell) = self.strategy.get_signal( (buy, sell) = self.strategy.get_signal(
pair, self.strategy.ticker_interval, pair, self.strategy.ticker_interval,
self.dataprovider.ohlcv(pair, self.strategy.ticker_interval)) self.dataprovider.ohlcv(pair, self.strategy.ticker_interval))
@ -598,6 +609,13 @@ class FreqtradeBot:
Tries to execute sell orders for open trades (positions) Tries to execute sell orders for open trades (positions)
""" """
trades_closed = 0 trades_closed = 0
trades_pairlist = [t.pair for t in trades]
trades_signals = get_all_signals(
self.strategy.get_signal, {
pair: (pair, self.strategy.ticker_interval,
self.dataprovider.ohlcv(pair, self.strategy.ticker_interval))
for pair in trades_pairlist
})
for trade in trades: for trade in trades:
try: try:
self.update_trade_state(trade) self.update_trade_state(trade)
@ -607,7 +625,8 @@ class FreqtradeBot:
trades_closed += 1 trades_closed += 1
continue continue
# Check if we can sell our current pair # Check if we can sell our current pair
if trade.open_order_id is None and self.handle_trade(trade): if trade.open_order_id is None and self.handle_trade(trade,
trades_signals[trade.pair]):
trades_closed += 1 trades_closed += 1
except DependencyException as exception: except DependencyException as exception:
@ -658,7 +677,7 @@ class FreqtradeBot:
self._sell_rate_cache[pair] = rate self._sell_rate_cache[pair] = rate
return rate return rate
def handle_trade(self, trade: Trade) -> bool: def handle_trade(self, trade: Trade, signals: Tuple[bool, bool] = None) -> bool:
""" """
Sells the current pair if the threshold is reached and updates the trade record. Sells the current pair if the threshold is reached and updates the trade record.
:return: True if trade has been sold, False otherwise :return: True if trade has been sold, False otherwise
@ -674,6 +693,9 @@ class FreqtradeBot:
if (config_ask_strategy.get('use_sell_signal', True) or if (config_ask_strategy.get('use_sell_signal', True) or
config_ask_strategy.get('ignore_roi_if_buy_signal', False)): config_ask_strategy.get('ignore_roi_if_buy_signal', False)):
if signals:
(buy, sell) = signals[0], signals[1]
else:
(buy, sell) = self.strategy.get_signal( (buy, sell) = self.strategy.get_signal(
trade.pair, self.strategy.ticker_interval, trade.pair, self.strategy.ticker_interval,
self.dataprovider.ohlcv(trade.pair, self.strategy.ticker_interval)) self.dataprovider.ohlcv(trade.pair, self.strategy.ticker_interval))

View File

@ -0,0 +1,77 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
import logging
import warnings
import numpy as np # noqa
from pandas import DataFrame
from typing import Dict, Callable
from functools import partial
from joblib import cpu_count, wrap_non_picklable_objects
from multiprocessing import Pool, Manager, Queue
from freqtrade.strategy.interface import IStrategy
# import talib.abstract as ta
# import freqtrade.vendor.qtpylib.indicators as qtpylib
logger = logging.getLogger(__name__)
def error_callback(e, q: Queue):
print(e)
q.put((None, None))
def get_all_signals(target: Callable, pairs_args: Dict, jobs=(cpu_count() // 2 or 1)) -> Dict:
""" Apply function over a dict where the values are the args of the function, parallelly """
results = {}
queue = Manager().Queue()
err = partial(error_callback, q=queue)
def func_queue(func: Callable, queue: Queue, pair: str, *args) -> DataFrame:
res = func(*args)
queue.put((pair, res))
return res
target = wrap_non_picklable_objects(target)
func_queue = wrap_non_picklable_objects(func_queue)
try:
with Pool(jobs) as p:
p.starmap_async(
func_queue,
[(target, queue, pair, *v) for pair, v in pairs_args.items()],
error_callback=err,
)
for pair in pairs_args:
proc_pair, res = queue.get()
if proc_pair:
results[proc_pair] = res
else:
break
# preserve the dict order
return {pair: results[pair] for pair in pairs_args}
except KeyError:
return {pair: target(*args) for pair, args in pairs_args.items()}
class HelperStrategy(IStrategy):
"""
This is a strategy template to get you started.
"""
time_weighted_roi = False
def __init__(self, config: dict) -> None:
super().__init__(config)
def ohlcvdata_to_dataframe(self, tickerdata: Dict[str, DataFrame]) -> Dict[str, DataFrame]:
"""
Creates a dataframe and populates indicators for given ticker data
Used by optimize operations only, not during dry / live runs.
"""
return get_all_signals(
self.advise_indicators,
{pair: (pair_data, {"pair": pair}) for pair, pair_data in tickerdata.items()},
)