Merge branch 'develop' into datadir
This commit is contained in:
commit
890083ce7f
@ -204,19 +204,39 @@ signal. Given following result from hyperopt:
|
|||||||
```
|
```
|
||||||
Best parameters:
|
Best parameters:
|
||||||
{
|
{
|
||||||
"adx": 1,
|
"adx": {
|
||||||
"adx-value": 15.0,
|
"enabled": true,
|
||||||
"fastd": 1,
|
"value": 15.0
|
||||||
"fastd-value": 40.0,
|
},
|
||||||
"green_candle": 1,
|
"fastd": {
|
||||||
"mfi": 0,
|
"enabled": true,
|
||||||
"over_sar": 0,
|
"value": 40.0
|
||||||
"rsi": 1,
|
},
|
||||||
"rsi-value": 37.0,
|
"green_candle": {
|
||||||
"trigger": 0,
|
"enabled": true
|
||||||
"uptrend_long_ema": 1,
|
},
|
||||||
"uptrend_short_ema": 0,
|
"mfi": {
|
||||||
"uptrend_sma": 0
|
"enabled": false
|
||||||
|
},
|
||||||
|
"over_sar": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"rsi": {
|
||||||
|
"enabled": true,
|
||||||
|
"value": 37.0
|
||||||
|
},
|
||||||
|
"trigger": {
|
||||||
|
"type": "lower_bb"
|
||||||
|
},
|
||||||
|
"uptrend_long_ema": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"uptrend_short_ema": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"uptrend_sma": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Best Result:
|
Best Result:
|
||||||
@ -224,14 +244,14 @@ Best Result:
|
|||||||
```
|
```
|
||||||
|
|
||||||
You should understand this result like:
|
You should understand this result like:
|
||||||
- You should **consider** the guard "adx" (`"adx": 1,` = `adx` is true)
|
- You should **consider** the guard "adx" (`"adx"` is `"enabled": true`)
|
||||||
and the best value is `15.0` (`"adx-value": 15.0,`)
|
and the best value is `15.0` (`"value": 15.0,`)
|
||||||
- You should **consider** the guard "fastd" (`"fastd": 1,` = `fastd`
|
- You should **consider** the guard "fastd" (`"fastd"` is `"enabled":
|
||||||
is true) and the best value is `40.0` (`"fastd-value": 40.0,`)
|
true`) and the best value is `40.0` (`"value": 40.0,`)
|
||||||
- You should **consider** to enable the guard "green_candle"
|
- You should **consider** to enable the guard "green_candle"
|
||||||
(`"green_candle": 1,` = `candle` is true) but this guards as no
|
(`"green_candle"` is `"enabled": true`) but this guards as no
|
||||||
customizable value.
|
customizable value.
|
||||||
- You should **ignore** the guard "mfi" (`"mfi": 0,` = `mfi` is false)
|
- You should **ignore** the guard "mfi" (`"mfi"` is `"enabled": false`)
|
||||||
- and so on...
|
- and so on...
|
||||||
|
|
||||||
|
|
||||||
@ -239,8 +259,8 @@ You have to look from
|
|||||||
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L170-L200)
|
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L170-L200)
|
||||||
what those values match to.
|
what those values match to.
|
||||||
|
|
||||||
So for example you had `adx-value: 15.0` (and `adx: 1` was true) so we
|
So for example you had `adx:` with the `value: 15.0` so we would look
|
||||||
would look at `adx`-block from
|
at `adx`-block from
|
||||||
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L178-L179).
|
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L178-L179).
|
||||||
That translates to the following code block to
|
That translates to the following code block to
|
||||||
[analyze.populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L73)
|
[analyze.populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L73)
|
||||||
|
@ -11,7 +11,7 @@ import talib.abstract as ta
|
|||||||
from pandas import DataFrame, to_datetime
|
from pandas import DataFrame, to_datetime
|
||||||
|
|
||||||
from freqtrade.exchange import get_ticker_history
|
from freqtrade.exchange import get_ticker_history
|
||||||
from freqtrade.vendor.qtpylib.indicators import awesome_oscillator, crossed_above
|
from freqtrade.vendor.qtpylib.indicators import awesome_oscillator, PandasObject as qtpylib
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -40,34 +40,185 @@ def parse_ticker_dataframe(ticker: list) -> DataFrame:
|
|||||||
def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Adds several different TA indicators to the given DataFrame
|
Adds several different TA indicators to the given DataFrame
|
||||||
|
|
||||||
|
Performance Note: For the best performance be frugal on the number of indicators
|
||||||
|
you are using. Let uncomment only the indicator you are using in your strategies
|
||||||
|
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
||||||
"""
|
"""
|
||||||
dataframe['sar'] = ta.SAR(dataframe)
|
|
||||||
|
# Momentum Indicator
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# ADX
|
||||||
dataframe['adx'] = ta.ADX(dataframe)
|
dataframe['adx'] = ta.ADX(dataframe)
|
||||||
stoch = ta.STOCHF(dataframe)
|
|
||||||
dataframe['fastd'] = stoch['fastd']
|
# Awesome oscillator
|
||||||
dataframe['fastk'] = stoch['fastk']
|
|
||||||
dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
|
|
||||||
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
|
||||||
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
|
||||||
dataframe['mfi'] = ta.MFI(dataframe)
|
|
||||||
dataframe['rsi'] = ta.RSI(dataframe)
|
|
||||||
dataframe['cci'] = ta.CCI(dataframe)
|
|
||||||
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
|
|
||||||
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
|
|
||||||
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
|
|
||||||
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
|
|
||||||
dataframe['ao'] = awesome_oscillator(dataframe)
|
dataframe['ao'] = awesome_oscillator(dataframe)
|
||||||
|
"""
|
||||||
|
# Commodity Channel Index: values Oversold:<-100, Overbought:>100
|
||||||
|
dataframe['cci'] = ta.CCI(dataframe)
|
||||||
|
"""
|
||||||
|
# MACD
|
||||||
macd = ta.MACD(dataframe)
|
macd = ta.MACD(dataframe)
|
||||||
dataframe['macd'] = macd['macd']
|
dataframe['macd'] = macd['macd']
|
||||||
dataframe['macdsignal'] = macd['macdsignal']
|
dataframe['macdsignal'] = macd['macdsignal']
|
||||||
dataframe['macdhist'] = macd['macdhist']
|
dataframe['macdhist'] = macd['macdhist']
|
||||||
|
|
||||||
|
# MFI
|
||||||
|
dataframe['mfi'] = ta.MFI(dataframe)
|
||||||
|
|
||||||
|
# Minus Directional Indicator / Movement
|
||||||
|
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
||||||
|
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||||
|
|
||||||
|
# Plus Directional Indicator / Movement
|
||||||
|
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
|
||||||
|
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
||||||
|
"""
|
||||||
|
# ROC
|
||||||
|
dataframe['roc'] = ta.ROC(dataframe)
|
||||||
|
"""
|
||||||
|
# RSI
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe)
|
||||||
|
"""
|
||||||
|
# Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
|
||||||
|
rsi = 0.1 * (dataframe['rsi'] - 50)
|
||||||
|
dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)
|
||||||
|
|
||||||
|
# Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
|
||||||
|
dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
|
||||||
|
|
||||||
|
# Stoch
|
||||||
|
stoch = ta.STOCH(dataframe)
|
||||||
|
dataframe['slowd'] = stoch['slowd']
|
||||||
|
dataframe['slowk'] = stoch['slowk']
|
||||||
|
"""
|
||||||
|
# Stoch fast
|
||||||
|
stoch_fast = ta.STOCHF(dataframe)
|
||||||
|
dataframe['fastd'] = stoch_fast['fastd']
|
||||||
|
dataframe['fastk'] = stoch_fast['fastk']
|
||||||
|
"""
|
||||||
|
# Stoch RSI
|
||||||
|
stoch_rsi = ta.STOCHRSI(dataframe)
|
||||||
|
dataframe['fastd_rsi'] = stoch_rsi['fastd']
|
||||||
|
dataframe['fastk_rsi'] = stoch_rsi['fastk']
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Overlap Studies
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# Previous Bollinger bands
|
||||||
|
# Because ta.BBANDS implementation is broken with small numbers, it actually
|
||||||
|
# returns middle band for all the three bands. Switch to qtpylib.bollinger_bands
|
||||||
|
# and use middle band instead.
|
||||||
|
dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
|
||||||
|
"""
|
||||||
|
# Bollinger bands
|
||||||
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||||
|
dataframe['bb_lowerband'] = bollinger['lower']
|
||||||
|
dataframe['bb_middleband'] = bollinger['mid']
|
||||||
|
dataframe['bb_upperband'] = bollinger['upper']
|
||||||
|
"""
|
||||||
|
|
||||||
|
# EMA - Exponential Moving Average
|
||||||
|
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
|
||||||
|
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
|
||||||
|
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
|
||||||
|
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
|
||||||
|
|
||||||
|
# SAR Parabol
|
||||||
|
dataframe['sar'] = ta.SAR(dataframe)
|
||||||
|
|
||||||
|
# SMA - Simple Moving Average
|
||||||
|
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
||||||
|
|
||||||
|
# TEMA - Triple Exponential Moving Average
|
||||||
|
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
||||||
|
|
||||||
|
# Cycle Indicator
|
||||||
|
# ------------------------------------
|
||||||
|
# Hilbert Transform Indicator - SineWave
|
||||||
hilbert = ta.HT_SINE(dataframe)
|
hilbert = ta.HT_SINE(dataframe)
|
||||||
dataframe['htsine'] = hilbert['sine']
|
dataframe['htsine'] = hilbert['sine']
|
||||||
dataframe['htleadsine'] = hilbert['leadsine']
|
dataframe['htleadsine'] = hilbert['leadsine']
|
||||||
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
|
|
||||||
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
# Pattern Recognition - Bullish candlestick patterns
|
||||||
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
# ------------------------------------
|
||||||
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
"""
|
||||||
|
# Hammer: values [0, 100]
|
||||||
|
dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
|
||||||
|
|
||||||
|
# Inverted Hammer: values [0, 100]
|
||||||
|
dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
|
||||||
|
|
||||||
|
# Dragonfly Doji: values [0, 100]
|
||||||
|
dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
|
||||||
|
|
||||||
|
# Piercing Line: values [0, 100]
|
||||||
|
dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
|
||||||
|
|
||||||
|
# Morningstar: values [0, 100]
|
||||||
|
dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
|
||||||
|
|
||||||
|
# Three White Soldiers: values [0, 100]
|
||||||
|
dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Pattern Recognition - Bearish candlestick patterns
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Hanging Man: values [0, 100]
|
||||||
|
dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
|
||||||
|
|
||||||
|
# Shooting Star: values [0, 100]
|
||||||
|
dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
|
||||||
|
|
||||||
|
# Gravestone Doji: values [0, 100]
|
||||||
|
dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
|
||||||
|
|
||||||
|
# Dark Cloud Cover: values [0, 100]
|
||||||
|
dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
|
||||||
|
|
||||||
|
# Evening Doji Star: values [0, 100]
|
||||||
|
dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
|
||||||
|
|
||||||
|
# Evening Star: values [0, 100]
|
||||||
|
dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Pattern Recognition - Bullish/Bearish candlestick patterns
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Three Line Strike: values [0, -100, 100]
|
||||||
|
dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
|
||||||
|
|
||||||
|
# Spinning Top: values [0, -100, 100]
|
||||||
|
dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
|
||||||
|
|
||||||
|
# Engulfing: values [0, -100, 100]
|
||||||
|
dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
|
||||||
|
|
||||||
|
# Harami: values [0, -100, 100]
|
||||||
|
dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
|
||||||
|
|
||||||
|
# Three Outside Up/Down: values [0, -100, 100]
|
||||||
|
dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
|
||||||
|
|
||||||
|
# Three Inside Up/Down: values [0, -100, 100]
|
||||||
|
dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Chart type
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Heikinashi stategy
|
||||||
|
heikinashi = qtpylib.heikinashi(dataframe)
|
||||||
|
dataframe['ha_open'] = heikinashi['open']
|
||||||
|
dataframe['ha_close'] = heikinashi['close']
|
||||||
|
dataframe['ha_high'] = heikinashi['high']
|
||||||
|
dataframe['ha_low'] = heikinashi['low']
|
||||||
|
"""
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
|
||||||
@ -102,8 +253,8 @@ def populate_sell_trend(dataframe: DataFrame) -> DataFrame:
|
|||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
(crossed_above(dataframe['rsi'], 70)) |
|
(qtpylib.crossed_above(dataframe['rsi'], 70)) |
|
||||||
(crossed_above(dataframe['fastd'], 70))
|
(qtpylib.crossed_above(dataframe['fastd'], 70))
|
||||||
) &
|
) &
|
||||||
(dataframe['adx'] > 10) &
|
(dataframe['adx'] > 10) &
|
||||||
(dataframe['minus_di'] > 0)
|
(dataframe['minus_di'] > 0)
|
||||||
|
@ -57,7 +57,11 @@ class CryptoToFiatConverter():
|
|||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
try:
|
||||||
self._coinmarketcap = Pymarketcap()
|
self._coinmarketcap = Pymarketcap()
|
||||||
|
except BaseException:
|
||||||
|
self._coinmarketcap = None
|
||||||
|
|
||||||
self._pairs = []
|
self._pairs = []
|
||||||
|
|
||||||
def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float:
|
def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float:
|
||||||
@ -147,10 +151,12 @@ class CryptoToFiatConverter():
|
|||||||
# Check if the fiat convertion you want is supported
|
# Check if the fiat convertion you want is supported
|
||||||
if not self._is_supported_fiat(fiat=fiat_symbol):
|
if not self._is_supported_fiat(fiat=fiat_symbol):
|
||||||
raise ValueError('The fiat {} is not supported.'.format(fiat_symbol))
|
raise ValueError('The fiat {} is not supported.'.format(fiat_symbol))
|
||||||
|
try:
|
||||||
return float(
|
return float(
|
||||||
self._coinmarketcap.ticker(
|
self._coinmarketcap.ticker(
|
||||||
currency=crypto_symbol,
|
currency=crypto_symbol,
|
||||||
convert=fiat_symbol
|
convert=fiat_symbol
|
||||||
)['price_' + fiat_symbol.lower()]
|
)['price_' + fiat_symbol.lower()]
|
||||||
)
|
)
|
||||||
|
except BaseException:
|
||||||
|
return 0.0
|
||||||
|
@ -247,12 +247,6 @@ def handle_trade(trade: Trade) -> bool:
|
|||||||
logger.debug('Handling %s ...', trade)
|
logger.debug('Handling %s ...', trade)
|
||||||
current_rate = exchange.get_ticker(trade.pair)['bid']
|
current_rate = exchange.get_ticker(trade.pair)['bid']
|
||||||
|
|
||||||
# Experimental: Check if the trade is profitable before selling it (avoid selling at loss)
|
|
||||||
if _CONF.get('experimental', {}).get('sell_profit_only'):
|
|
||||||
logger.debug('Checking if trade is profitable ...')
|
|
||||||
if trade.calc_profit(rate=current_rate) <= 0:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Check if minimal roi has been reached
|
# Check if minimal roi has been reached
|
||||||
if min_roi_reached(trade, current_rate, datetime.utcnow()):
|
if min_roi_reached(trade, current_rate, datetime.utcnow()):
|
||||||
logger.debug('Executing sell due to ROI ...')
|
logger.debug('Executing sell due to ROI ...')
|
||||||
@ -261,6 +255,11 @@ def handle_trade(trade: Trade) -> bool:
|
|||||||
|
|
||||||
# Experimental: Check if sell signal has been enabled and triggered
|
# Experimental: Check if sell signal has been enabled and triggered
|
||||||
if _CONF.get('experimental', {}).get('use_sell_signal'):
|
if _CONF.get('experimental', {}).get('use_sell_signal'):
|
||||||
|
# Experimental: Check if the trade is profitable before selling it (avoid selling at loss)
|
||||||
|
if _CONF.get('experimental', {}).get('sell_profit_only'):
|
||||||
|
logger.debug('Checking if trade is profitable ...')
|
||||||
|
if trade.calc_profit(rate=current_rate) <= 0:
|
||||||
|
return False
|
||||||
logger.debug('Checking sell_signal ...')
|
logger.debug('Checking sell_signal ...')
|
||||||
if get_signal(trade.pair, SignalType.SELL):
|
if get_signal(trade.pair, SignalType.SELL):
|
||||||
logger.debug('Executing sell due to sell signal ...')
|
logger.debug('Executing sell due to sell signal ...')
|
||||||
@ -399,14 +398,18 @@ def cleanup() -> None:
|
|||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main(sysargv=sys.argv[1:]) -> None:
|
||||||
"""
|
"""
|
||||||
Loads and validates the config and handles the main loop
|
Loads and validates the config and handles the main loop
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
global _CONF
|
global _CONF
|
||||||
args = parse_args(sys.argv[1:])
|
args = parse_args(sysargv,
|
||||||
if not args:
|
'Simple High Frequency Trading Bot for crypto currencies')
|
||||||
|
|
||||||
|
# A subcommand has been issued
|
||||||
|
if hasattr(args, 'func'):
|
||||||
|
args.func(args)
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
# Initialize logger
|
# Initialize logger
|
||||||
|
@ -81,21 +81,12 @@ def throttle(func: Callable[..., Any], min_secs: float, *args, **kwargs) -> Any:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def parse_args(args: List[str]):
|
def parse_args_common(args: List[str], description: str):
|
||||||
"""
|
"""
|
||||||
Parses given arguments and returns an argparse Namespace instance.
|
Parses given common arguments and returns them as a parsed object.
|
||||||
Returns None if a sub command has been selected and executed.
|
|
||||||
"""
|
"""
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description='Simple High Frequency Trading Bot for crypto currencies'
|
description=description
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-c', '--config',
|
|
||||||
help='specify configuration file (default: config.json)',
|
|
||||||
dest='config',
|
|
||||||
default='config.json',
|
|
||||||
type=str,
|
|
||||||
metavar='PATH',
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-v', '--verbose',
|
'-v', '--verbose',
|
||||||
@ -111,14 +102,22 @@ def parse_args(args: List[str]):
|
|||||||
version='%(prog)s {}'.format(__version__),
|
version='%(prog)s {}'.format(__version__),
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--dynamic-whitelist',
|
'-c', '--config',
|
||||||
help='dynamically generate and update whitelist based on 24h BaseVolume (Default 20 currencies)', # noqa
|
help='specify configuration file (default: config.json)',
|
||||||
dest='dynamic_whitelist',
|
dest='config',
|
||||||
const=20,
|
default='config.json',
|
||||||
type=int,
|
type=str,
|
||||||
metavar='INT',
|
metavar='PATH',
|
||||||
nargs='?',
|
|
||||||
)
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args(args: List[str], description: str):
|
||||||
|
"""
|
||||||
|
Parses given arguments and returns an argparse Namespace instance.
|
||||||
|
Returns None if a sub command has been selected and executed.
|
||||||
|
"""
|
||||||
|
parser = parse_args_common(args, description)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--dry-run-db',
|
'--dry-run-db',
|
||||||
help='Force dry run to use a local DB "tradesv3.dry_run.sqlite" instead of memory DB. Work only if dry_run is \
|
help='Force dry run to use a local DB "tradesv3.dry_run.sqlite" instead of memory DB. Work only if dry_run is \
|
||||||
@ -134,16 +133,18 @@ def parse_args(args: List[str]):
|
|||||||
type=str,
|
type=str,
|
||||||
metavar='PATH',
|
metavar='PATH',
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--dynamic-whitelist',
|
||||||
|
help='dynamically generate and update whitelist based on 24h BaseVolume (Default 20 currencies)', # noqa
|
||||||
|
dest='dynamic_whitelist',
|
||||||
|
const=20,
|
||||||
|
type=int,
|
||||||
|
metavar='INT',
|
||||||
|
nargs='?',
|
||||||
|
)
|
||||||
|
|
||||||
build_subcommands(parser)
|
build_subcommands(parser)
|
||||||
parsed_args = parser.parse_args(args)
|
return parser.parse_args(args)
|
||||||
|
|
||||||
# No subcommand as been selected
|
|
||||||
if not hasattr(parsed_args, 'func'):
|
|
||||||
return parsed_args
|
|
||||||
|
|
||||||
parsed_args.func(parsed_args)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def build_subcommands(parser: argparse.ArgumentParser) -> None:
|
def build_subcommands(parser: argparse.ArgumentParser) -> None:
|
||||||
|
@ -8,7 +8,7 @@ from functools import reduce
|
|||||||
from math import exp
|
from math import exp
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from hyperopt import fmin, tpe, hp, Trials, STATUS_OK, STATUS_FAIL
|
from hyperopt import fmin, tpe, hp, Trials, STATUS_OK, STATUS_FAIL, space_eval
|
||||||
from hyperopt.mongoexp import MongoTrials
|
from hyperopt.mongoexp import MongoTrials
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
@ -209,7 +209,7 @@ def buy_strategy_generator(params):
|
|||||||
|
|
||||||
|
|
||||||
def start(args):
|
def start(args):
|
||||||
global TOTAL_TRIES, PROCESSED
|
global TOTAL_TRIES, PROCESSED, SPACE
|
||||||
TOTAL_TRIES = args.epochs
|
TOTAL_TRIES = args.epochs
|
||||||
|
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = Bittrex({'key': '', 'secret': ''})
|
||||||
@ -236,6 +236,11 @@ def start(args):
|
|||||||
trials = Trials()
|
trials = Trials()
|
||||||
|
|
||||||
best = fmin(fn=optimizer, space=SPACE, algo=tpe.suggest, max_evals=TOTAL_TRIES, trials=trials)
|
best = fmin(fn=optimizer, space=SPACE, algo=tpe.suggest, max_evals=TOTAL_TRIES, trials=trials)
|
||||||
|
|
||||||
|
# Improve best parameter logging display
|
||||||
|
if best:
|
||||||
|
best = space_eval(SPACE, best)
|
||||||
|
|
||||||
logger.info('Best parameters:\n%s', json.dumps(best, indent=4))
|
logger.info('Best parameters:\n%s', json.dumps(best, indent=4))
|
||||||
|
|
||||||
results = sorted(trials.results, key=itemgetter('loss'))
|
results = sorted(trials.results, key=itemgetter('loss'))
|
||||||
|
@ -77,3 +77,40 @@ def test_no_log_if_loss_does_not_improve(mocker):
|
|||||||
})
|
})
|
||||||
|
|
||||||
assert not logger.called
|
assert not logger.called
|
||||||
|
|
||||||
|
|
||||||
|
def test_fmin_best_results(mocker, caplog):
|
||||||
|
fmin_result = {
|
||||||
|
"adx": 1,
|
||||||
|
"adx-value": 15.0,
|
||||||
|
"fastd": 1,
|
||||||
|
"fastd-value": 40.0,
|
||||||
|
"green_candle": 1,
|
||||||
|
"mfi": 0,
|
||||||
|
"over_sar": 0,
|
||||||
|
"rsi": 1,
|
||||||
|
"rsi-value": 37.0,
|
||||||
|
"trigger": 2,
|
||||||
|
"uptrend_long_ema": 1,
|
||||||
|
"uptrend_short_ema": 0,
|
||||||
|
"uptrend_sma": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker))
|
||||||
|
mocker.patch('freqtrade.optimize.preprocess')
|
||||||
|
mocker.patch('freqtrade.optimize.load_data')
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result)
|
||||||
|
|
||||||
|
args = mocker.Mock(epochs=1, config='config.json.example')
|
||||||
|
start(args)
|
||||||
|
|
||||||
|
exists = [
|
||||||
|
'Best parameters',
|
||||||
|
'"adx": {\n "enabled": true,\n "value": 15.0\n },',
|
||||||
|
'"green_candle": {\n "enabled": true\n },',
|
||||||
|
'"mfi": {\n "enabled": false\n },',
|
||||||
|
'"trigger": {\n "type": "ao_cross_zero"\n },'
|
||||||
|
]
|
||||||
|
|
||||||
|
for line in exists:
|
||||||
|
assert line in caplog.text
|
||||||
|
@ -167,6 +167,7 @@ def test_profit_handle(
|
|||||||
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
|
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
|
||||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||||
_cache_symbols=MagicMock(return_value={'BTC': 1}))
|
_cache_symbols=MagicMock(return_value={'BTC': 1}))
|
||||||
|
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
_profit(bot=MagicMock(), update=update)
|
_profit(bot=MagicMock(), update=update)
|
||||||
@ -422,6 +423,7 @@ def test_daily_handle(
|
|||||||
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
|
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
|
||||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||||
_cache_symbols=MagicMock(return_value={'BTC': 1}))
|
_cache_symbols=MagicMock(return_value={'BTC': 1}))
|
||||||
|
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
|
@ -72,8 +72,11 @@ def test_fiat_convert_find_price(mocker):
|
|||||||
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
|
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
|
||||||
fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='ABC')
|
fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='ABC')
|
||||||
|
|
||||||
|
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=12345.0)
|
||||||
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 12345.0
|
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 12345.0
|
||||||
assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 12345.0
|
assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 12345.0
|
||||||
|
|
||||||
|
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=13000.2)
|
||||||
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='EUR') == 13000.2
|
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='EUR') == 13000.2
|
||||||
|
|
||||||
|
|
||||||
@ -83,6 +86,7 @@ def test_fiat_convert_get_price(mocker):
|
|||||||
'price_eur': 15000.0
|
'price_eur': 15000.0
|
||||||
})
|
})
|
||||||
mocker.patch('freqtrade.fiat_convert.Pymarketcap.ticker', api_mock)
|
mocker.patch('freqtrade.fiat_convert.Pymarketcap.ticker', api_mock)
|
||||||
|
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=28000.0)
|
||||||
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
|
||||||
@ -109,3 +113,12 @@ def test_fiat_convert_get_price(mocker):
|
|||||||
fiat_convert._pairs[0]._expiration = expiration
|
fiat_convert._pairs[0]._expiration = expiration
|
||||||
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 28000.0
|
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 28000.0
|
||||||
assert fiat_convert._pairs[0]._expiration is not expiration
|
assert fiat_convert._pairs[0]._expiration is not expiration
|
||||||
|
|
||||||
|
|
||||||
|
def test_fiat_convert_without_network(mocker):
|
||||||
|
Pymarketcap = MagicMock(side_effect=ImportError('Oh boy, you have no network!'))
|
||||||
|
mocker.patch('freqtrade.fiat_convert.Pymarketcap', Pymarketcap)
|
||||||
|
|
||||||
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
assert fiat_convert._coinmarketcap is None
|
||||||
|
assert fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='USD') == 0.0
|
||||||
|
@ -15,6 +15,39 @@ from freqtrade.main import create_trade, handle_trade, init, \
|
|||||||
get_target_bid, _process, execute_sell, check_handle_timedout
|
get_target_bid, _process, execute_sell, check_handle_timedout
|
||||||
from freqtrade.misc import get_state, State
|
from freqtrade.misc import get_state, State
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
|
import freqtrade.main as main
|
||||||
|
|
||||||
|
|
||||||
|
# Test that main() can start backtesting or hyperopt.
|
||||||
|
# and also ensure we can pass some specific arguments
|
||||||
|
# argument parsing is done in test_misc.py
|
||||||
|
|
||||||
|
def test_parse_args_backtesting(mocker):
|
||||||
|
backtesting_mock = mocker.patch(
|
||||||
|
'freqtrade.optimize.backtesting.start', MagicMock())
|
||||||
|
with pytest.raises(SystemExit, match=r'0'):
|
||||||
|
main.main(['backtesting'])
|
||||||
|
assert backtesting_mock.call_count == 1
|
||||||
|
call_args = backtesting_mock.call_args[0][0]
|
||||||
|
assert call_args.config == 'config.json'
|
||||||
|
assert call_args.live is False
|
||||||
|
assert call_args.loglevel == 20
|
||||||
|
assert call_args.subparser == 'backtesting'
|
||||||
|
assert call_args.func is not None
|
||||||
|
assert call_args.ticker_interval == 5
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_start_hyperopt(mocker):
|
||||||
|
hyperopt_mock = mocker.patch(
|
||||||
|
'freqtrade.optimize.hyperopt.start', MagicMock())
|
||||||
|
with pytest.raises(SystemExit, match=r'0'):
|
||||||
|
main.main(['hyperopt'])
|
||||||
|
assert hyperopt_mock.call_count == 1
|
||||||
|
call_args = hyperopt_mock.call_args[0][0]
|
||||||
|
assert call_args.config == 'config.json'
|
||||||
|
assert call_args.loglevel == 20
|
||||||
|
assert call_args.subparser == 'hyperopt'
|
||||||
|
assert call_args.func is not None
|
||||||
|
|
||||||
|
|
||||||
def test_process_trade_creation(default_conf, ticker, limit_buy_order, health, mocker):
|
def test_process_trade_creation(default_conf, ticker, limit_buy_order, health, mocker):
|
||||||
@ -331,7 +364,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, health, limit_buy_order
|
|||||||
cancel_order=cancel_order_mock)
|
cancel_order=cancel_order_mock)
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
tradeBuy = Trade(
|
trade_buy = Trade(
|
||||||
pair='BTC_ETH',
|
pair='BTC_ETH',
|
||||||
open_rate=0.00001099,
|
open_rate=0.00001099,
|
||||||
exchange='BITTREX',
|
exchange='BITTREX',
|
||||||
@ -343,12 +376,12 @@ def test_check_handle_timedout_buy(default_conf, ticker, health, limit_buy_order
|
|||||||
is_open=True
|
is_open=True
|
||||||
)
|
)
|
||||||
|
|
||||||
Trade.session.add(tradeBuy)
|
Trade.session.add(trade_buy)
|
||||||
|
|
||||||
# check it does cancel buy orders over the time limit
|
# check it does cancel buy orders over the time limit
|
||||||
check_handle_timedout(600)
|
check_handle_timedout(600)
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
trades = Trade.query.filter(Trade.open_order_id.is_(tradeBuy.open_order_id)).all()
|
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
|
||||||
assert len(trades) == 0
|
assert len(trades) == 0
|
||||||
|
|
||||||
|
|
||||||
@ -363,7 +396,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, health, limit_sell_ord
|
|||||||
cancel_order=cancel_order_mock)
|
cancel_order=cancel_order_mock)
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
tradeSell = Trade(
|
trade_sell = Trade(
|
||||||
pair='BTC_ETH',
|
pair='BTC_ETH',
|
||||||
open_rate=0.00001099,
|
open_rate=0.00001099,
|
||||||
exchange='BITTREX',
|
exchange='BITTREX',
|
||||||
@ -376,12 +409,12 @@ def test_check_handle_timedout_sell(default_conf, ticker, health, limit_sell_ord
|
|||||||
is_open=False
|
is_open=False
|
||||||
)
|
)
|
||||||
|
|
||||||
Trade.session.add(tradeSell)
|
Trade.session.add(trade_sell)
|
||||||
|
|
||||||
# check it does cancel sell orders over the time limit
|
# check it does cancel sell orders over the time limit
|
||||||
check_handle_timedout(600)
|
check_handle_timedout(600)
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
assert tradeSell.is_open is True
|
assert trade_sell.is_open is True
|
||||||
|
|
||||||
|
|
||||||
def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial,
|
def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial,
|
||||||
@ -396,7 +429,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
|
|||||||
cancel_order=cancel_order_mock)
|
cancel_order=cancel_order_mock)
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
tradeBuy = Trade(
|
trade_buy = Trade(
|
||||||
pair='BTC_ETH',
|
pair='BTC_ETH',
|
||||||
open_rate=0.00001099,
|
open_rate=0.00001099,
|
||||||
exchange='BITTREX',
|
exchange='BITTREX',
|
||||||
@ -408,16 +441,16 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
|
|||||||
is_open=True
|
is_open=True
|
||||||
)
|
)
|
||||||
|
|
||||||
Trade.session.add(tradeBuy)
|
Trade.session.add(trade_buy)
|
||||||
|
|
||||||
# check it does cancel buy orders over the time limit
|
# check it does cancel buy orders over the time limit
|
||||||
# note this is for a partially-complete buy order
|
# note this is for a partially-complete buy order
|
||||||
check_handle_timedout(600)
|
check_handle_timedout(600)
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
trades = Trade.query.filter(Trade.open_order_id.is_(tradeBuy.open_order_id)).all()
|
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
|
||||||
assert len(trades) == 1
|
assert len(trades) == 1
|
||||||
assert trades[0].amount == 23.0
|
assert trades[0].amount == 23.0
|
||||||
assert trades[0].stake_amount == tradeBuy.open_rate * trades[0].amount
|
assert trades[0].stake_amount == trade_buy.open_rate * trades[0].amount
|
||||||
|
|
||||||
|
|
||||||
def test_balance_fully_ask_side(mocker):
|
def test_balance_fully_ask_side(mocker):
|
||||||
@ -537,10 +570,13 @@ def test_execute_sell_without_conf(default_conf, ticker, ticker_sell_up, mocker)
|
|||||||
|
|
||||||
|
|
||||||
def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker):
|
def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker):
|
||||||
default_conf['experimental'] = {}
|
default_conf['experimental'] = {
|
||||||
default_conf['experimental']['sell_profit_only'] = True
|
'use_sell_signal': True,
|
||||||
|
'sell_profit_only': True,
|
||||||
|
}
|
||||||
|
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
|
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
|
||||||
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
|
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
@ -561,10 +597,13 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker):
|
|||||||
|
|
||||||
|
|
||||||
def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker):
|
def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker):
|
||||||
default_conf['experimental'] = {}
|
default_conf['experimental'] = {
|
||||||
default_conf['experimental']['sell_profit_only'] = False
|
'use_sell_signal': True,
|
||||||
|
'sell_profit_only': False,
|
||||||
|
}
|
||||||
|
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
|
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
|
||||||
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
|
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
@ -585,10 +624,13 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker):
|
|||||||
|
|
||||||
|
|
||||||
def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker):
|
def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker):
|
||||||
default_conf['experimental'] = {}
|
default_conf['experimental'] = {
|
||||||
default_conf['experimental']['sell_profit_only'] = True
|
'use_sell_signal': True,
|
||||||
|
'sell_profit_only': True,
|
||||||
|
}
|
||||||
|
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
|
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
|
||||||
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
|
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
@ -609,10 +651,13 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker):
|
|||||||
|
|
||||||
|
|
||||||
def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker):
|
def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker):
|
||||||
default_conf['experimental'] = {}
|
default_conf['experimental'] = {
|
||||||
default_conf['experimental']['sell_profit_only'] = False
|
'use_sell_signal': True,
|
||||||
|
'sell_profit_only': False,
|
||||||
|
}
|
||||||
|
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
|
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
|
||||||
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
|
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
# pragma pylint: disable=missing-docstring,C0103
|
# pragma pylint: disable=missing-docstring,C0103
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
import argparse
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from jsonschema import ValidationError
|
from jsonschema import ValidationError
|
||||||
|
|
||||||
from freqtrade.misc import throttle, parse_args, load_config
|
from freqtrade.misc import throttle, parse_args, load_config,\
|
||||||
|
parse_args_common
|
||||||
|
|
||||||
|
|
||||||
def test_throttle():
|
def test_throttle():
|
||||||
@ -38,89 +39,83 @@ def test_throttle_with_assets():
|
|||||||
assert result == -1
|
assert result == -1
|
||||||
|
|
||||||
|
|
||||||
|
# Parse common command-line-arguments
|
||||||
|
# used for all tools
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_args_none():
|
||||||
|
args = parse_args_common([], '')
|
||||||
|
assert isinstance(args, argparse.ArgumentParser)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_defaults():
|
def test_parse_args_defaults():
|
||||||
args = parse_args([])
|
args = parse_args([], '')
|
||||||
assert args is not None
|
|
||||||
assert args.config == 'config.json'
|
assert args.config == 'config.json'
|
||||||
assert args.dynamic_whitelist is None
|
assert args.dynamic_whitelist is None
|
||||||
assert args.loglevel == 20
|
assert args.loglevel == 20
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_invalid():
|
|
||||||
with pytest.raises(SystemExit, match=r'2'):
|
|
||||||
parse_args(['-c'])
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_config():
|
def test_parse_args_config():
|
||||||
args = parse_args(['-c', '/dev/null'])
|
args = parse_args(['-c', '/dev/null'], '')
|
||||||
assert args is not None
|
|
||||||
assert args.config == '/dev/null'
|
assert args.config == '/dev/null'
|
||||||
|
|
||||||
args = parse_args(['--config', '/dev/null'])
|
args = parse_args(['--config', '/dev/null'], '')
|
||||||
assert args is not None
|
|
||||||
assert args.config == '/dev/null'
|
assert args.config == '/dev/null'
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_verbose():
|
def test_parse_args_verbose():
|
||||||
args = parse_args(['-v'])
|
args = parse_args(['-v'], '')
|
||||||
assert args is not None
|
assert args.loglevel == 10
|
||||||
|
|
||||||
|
args = parse_args(['--verbose'], '')
|
||||||
assert args.loglevel == 10
|
assert args.loglevel == 10
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_args_version():
|
||||||
|
with pytest.raises(SystemExit, match=r'0'):
|
||||||
|
parse_args(['--version'], '')
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_args_invalid():
|
||||||
|
with pytest.raises(SystemExit, match=r'2'):
|
||||||
|
parse_args(['-c'], '')
|
||||||
|
|
||||||
|
|
||||||
|
# Parse command-line-arguments
|
||||||
|
# used for main, backtesting and hyperopt
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_dynamic_whitelist():
|
def test_parse_args_dynamic_whitelist():
|
||||||
args = parse_args(['--dynamic-whitelist'])
|
args = parse_args(['--dynamic-whitelist'], '')
|
||||||
assert args is not None
|
|
||||||
assert args.dynamic_whitelist is 20
|
assert args.dynamic_whitelist is 20
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_dynamic_whitelist_10():
|
def test_parse_args_dynamic_whitelist_10():
|
||||||
args = parse_args(['--dynamic-whitelist', '10'])
|
args = parse_args(['--dynamic-whitelist', '10'], '')
|
||||||
assert args is not None
|
|
||||||
assert args.dynamic_whitelist is 10
|
assert args.dynamic_whitelist is 10
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_dynamic_whitelist_invalid_values():
|
def test_parse_args_dynamic_whitelist_invalid_values():
|
||||||
with pytest.raises(SystemExit, match=r'2'):
|
with pytest.raises(SystemExit, match=r'2'):
|
||||||
parse_args(['--dynamic-whitelist', 'abc'])
|
parse_args(['--dynamic-whitelist', 'abc'], '')
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_backtesting(mocker):
|
|
||||||
backtesting_mock = mocker.patch(
|
|
||||||
'freqtrade.optimize.backtesting.start', MagicMock())
|
|
||||||
args = parse_args(['backtesting'])
|
|
||||||
assert args is None
|
|
||||||
assert backtesting_mock.call_count == 1
|
|
||||||
|
|
||||||
call_args = backtesting_mock.call_args[0][0]
|
|
||||||
assert call_args.config == 'config.json'
|
|
||||||
assert call_args.live is False
|
|
||||||
assert call_args.loglevel == 20
|
|
||||||
assert call_args.subparser == 'backtesting'
|
|
||||||
assert call_args.func is not None
|
|
||||||
assert call_args.ticker_interval == 5
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_backtesting_invalid():
|
def test_parse_args_backtesting_invalid():
|
||||||
with pytest.raises(SystemExit, match=r'2'):
|
with pytest.raises(SystemExit, match=r'2'):
|
||||||
parse_args(['backtesting --ticker-interval'])
|
parse_args(['backtesting --ticker-interval'], '')
|
||||||
|
|
||||||
with pytest.raises(SystemExit, match=r'2'):
|
with pytest.raises(SystemExit, match=r'2'):
|
||||||
parse_args(['backtesting --ticker-interval', 'abc'])
|
parse_args(['backtesting --ticker-interval', 'abc'], '')
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_backtesting_custom(mocker):
|
def test_parse_args_backtesting_custom():
|
||||||
backtesting_mock = mocker.patch(
|
args = [
|
||||||
'freqtrade.optimize.backtesting.start', MagicMock())
|
|
||||||
args = parse_args([
|
|
||||||
'-c', 'test_conf.json',
|
'-c', 'test_conf.json',
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--live',
|
'--live',
|
||||||
'--ticker-interval', '1',
|
'--ticker-interval', '1',
|
||||||
'--refresh-pairs-cached'])
|
'--refresh-pairs-cached']
|
||||||
assert args is None
|
call_args = parse_args(args, '')
|
||||||
assert backtesting_mock.call_count == 1
|
|
||||||
|
|
||||||
call_args = backtesting_mock.call_args[0][0]
|
|
||||||
assert call_args.config == 'test_conf.json'
|
assert call_args.config == 'test_conf.json'
|
||||||
assert call_args.live is True
|
assert call_args.live is True
|
||||||
assert call_args.loglevel == 20
|
assert call_args.loglevel == 20
|
||||||
@ -130,28 +125,9 @@ def test_parse_args_backtesting_custom(mocker):
|
|||||||
assert call_args.refresh_pairs is True
|
assert call_args.refresh_pairs is True
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_hyperopt(mocker):
|
|
||||||
hyperopt_mock = mocker.patch(
|
|
||||||
'freqtrade.optimize.hyperopt.start', MagicMock())
|
|
||||||
args = parse_args(['hyperopt'])
|
|
||||||
assert args is None
|
|
||||||
assert hyperopt_mock.call_count == 1
|
|
||||||
|
|
||||||
call_args = hyperopt_mock.call_args[0][0]
|
|
||||||
assert call_args.config == 'config.json'
|
|
||||||
assert call_args.loglevel == 20
|
|
||||||
assert call_args.subparser == 'hyperopt'
|
|
||||||
assert call_args.func is not None
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_hyperopt_custom(mocker):
|
def test_parse_args_hyperopt_custom(mocker):
|
||||||
hyperopt_mock = mocker.patch(
|
args = ['-c', 'test_conf.json', 'hyperopt', '--epochs', '20']
|
||||||
'freqtrade.optimize.hyperopt.start', MagicMock())
|
call_args = parse_args(args, '')
|
||||||
args = parse_args(['-c', 'test_conf.json', 'hyperopt', '--epochs', '20'])
|
|
||||||
assert args is None
|
|
||||||
assert hyperopt_mock.call_count == 1
|
|
||||||
|
|
||||||
call_args = hyperopt_mock.call_args[0][0]
|
|
||||||
assert call_args.config == 'test_conf.json'
|
assert call_args.config == 'test_conf.json'
|
||||||
assert call_args.epochs == 20
|
assert call_args.epochs == 20
|
||||||
assert call_args.loglevel == 20
|
assert call_args.loglevel == 20
|
||||||
|
@ -10,7 +10,7 @@ pandas==0.22.0
|
|||||||
scikit-learn==0.19.1
|
scikit-learn==0.19.1
|
||||||
scipy==1.0.0
|
scipy==1.0.0
|
||||||
jsonschema==2.6.0
|
jsonschema==2.6.0
|
||||||
numpy==1.13.3
|
numpy==1.14.0
|
||||||
TA-Lib==0.4.10
|
TA-Lib==0.4.10
|
||||||
pytest==3.3.2
|
pytest==3.3.2
|
||||||
pytest-mock==1.6.3
|
pytest-mock==1.6.3
|
||||||
|
@ -1,17 +1,33 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
import matplotlib # Install PYQT5 manually if you want to test this helper function
|
import matplotlib # Install PYQT5 manually if you want to test this helper function
|
||||||
matplotlib.use("Qt5Agg")
|
matplotlib.use("Qt5Agg")
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
from freqtrade import exchange, analyze
|
from freqtrade import exchange, analyze
|
||||||
|
from freqtrade.misc import parse_args_common
|
||||||
|
|
||||||
|
|
||||||
def plot_analyzed_dataframe(pair: str) -> None:
|
def plot_parse_args(args ):
|
||||||
|
parser = parse_args_common(args, 'Graph utility')
|
||||||
|
parser.add_argument(
|
||||||
|
'-p', '--pair',
|
||||||
|
help = 'What currency pair',
|
||||||
|
dest = 'pair',
|
||||||
|
default = 'BTC_ETH',
|
||||||
|
type = str,
|
||||||
|
)
|
||||||
|
return parser.parse_args(args)
|
||||||
|
|
||||||
|
|
||||||
|
def plot_analyzed_dataframe(args) -> None:
|
||||||
"""
|
"""
|
||||||
Calls analyze() and plots the returned dataframe
|
Calls analyze() and plots the returned dataframe
|
||||||
:param pair: pair as str
|
:param pair: pair as str
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
pair = args.pair
|
||||||
|
|
||||||
# Init Bittrex to use public API
|
# Init Bittrex to use public API
|
||||||
exchange._API = exchange.Bittrex({'key': '', 'secret': ''})
|
exchange._API = exchange.Bittrex({'key': '', 'secret': ''})
|
||||||
@ -50,4 +66,5 @@ def plot_analyzed_dataframe(pair: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
plot_analyzed_dataframe('BTC_ETH')
|
args = plot_parse_args(sys.argv[1:])
|
||||||
|
plot_analyzed_dataframe(args)
|
||||||
|
Loading…
Reference in New Issue
Block a user