diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 399588f88..6b4e8adaf 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -12,8 +12,7 @@ Few pointers for contributions:
- New features need to contain unit tests, must conform to PEP8 (max-line-length = 100) and should be documented with the introduction PR.
- PR's can be declared as `[WIP]` - which signify Work in Progress Pull Requests (which are not finished).
-If you are unsure, discuss the feature on our [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE)
-or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a PR.
+If you are unsure, discuss the feature on our [discord server](https://discord.gg/MA9v74M), on [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a PR.
## Getting started
diff --git a/README.md b/README.md
index c9f4d0a52..8526b5c91 100644
--- a/README.md
+++ b/README.md
@@ -132,15 +132,13 @@ The project is currently setup in two main branches:
## Support
-### Help / Slack / Discord
+### Help / Discord / Slack
-For any questions not covered by the documentation or for further information about the bot, we encourage you to join our slack channel.
+For any questions not covered by the documentation or for further information about the bot, or to simply engage with like-minded individuals, we encourage you to join our slack channel.
-- [Click here to join Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE).
+Please check out our [discord server](https://discord.gg/MA9v74M).
-Alternatively, check out the newly created [discord server](https://discord.gg/MA9v74M).
-
-*Note*: Since the discord server is relatively new, answers to questions might be slightly delayed as currently the user base quite small.
+You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg).
### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
@@ -171,7 +169,7 @@ to understand the requirements before sending your pull-requests.
Coding is not a necessity to contribute - maybe start with improving our documentation?
Issues labeled [good first issue](https://github.com/freqtrade/freqtrade/labels/good%20first%20issue) can be good first contributions, and will help get you familiar with the codebase.
-**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it.
+**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [discord](https://discord.gg/MA9v74M) or [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it.
**Important:** Always create your PR against the `develop` branch, not `stable`.
diff --git a/docs/backtesting.md b/docs/backtesting.md
index 84911568b..277b11083 100644
--- a/docs/backtesting.md
+++ b/docs/backtesting.md
@@ -162,6 +162,8 @@ A backtesting result will look like that:
|-----------------------+---------------------|
| Backtesting from | 2019-01-01 00:00:00 |
| Backtesting to | 2019-05-01 00:00:00 |
+| Max open trades | 3 |
+| | |
| Total trades | 429 |
| First trade | 2019-01-01 18:30:00 |
| First trade Pair | EOS/USDT |
@@ -233,6 +235,8 @@ It contains some useful key metrics about performance of your strategy on backte
|-----------------------+---------------------|
| Backtesting from | 2019-01-01 00:00:00 |
| Backtesting to | 2019-05-01 00:00:00 |
+| Max open trades | 3 |
+| | |
| Total trades | 429 |
| First trade | 2019-01-01 18:30:00 |
| First trade Pair | EOS/USDT |
@@ -251,16 +255,17 @@ It contains some useful key metrics about performance of your strategy on backte
```
+- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option).
+- `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - to clearly see settings for this.
- `Total trades`: Identical to the total trades of the backtest output table.
- `First trade`: First trade entered.
- `First trade pair`: Which pair was part of the first trade.
-- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option).
- `Total Profit %`: Total profit per stake amount. Aligned to the TOTAL column of the first table.
- `Trades per day`: Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy).
- `Best day` / `Worst day`: Best and worst day based on daily profit.
- `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades.
- `Max Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced).
-- `Drawdown Start` / `Drawdown End`: Start and end datetimes for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command).
+- `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command).
- `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column.
### Assumptions made by backtesting
diff --git a/docs/configuration.md b/docs/configuration.md
index 47362e525..56ba13414 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -87,6 +87,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `exchange.ccxt_sync_config` | Additional CCXT parameters passed to the regular (sync) ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
**Datatype:** Dict
| `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
**Datatype:** Dict
| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded.
*Defaults to `60` minutes.*
**Datatype:** Positive Integer
+| `exchange.skip_pair_validation` | Skip pairlist validation on startup.
*Defaults to `false`
**Datatype:** Boolean
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation.
| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
*Defaults to `true`.*
**Datatype:** Boolean
| `pairlists` | Define one or more pairlists to be used. [More information below](#pairlists-and-pairlist-handlers).
*Defaults to `StaticPairList`.*
**Datatype:** List of Dicts
@@ -313,22 +314,21 @@ Configuration:
}
```
-!!! Note
+!!! Note "Market order support"
Not all exchanges support "market" orders.
The following message will be shown if your exchange does not support market orders:
- `"Exchange does not support market orders."`
+ `"Exchange does not support market orders."` and the bot will refuse to start.
-!!! Note
- Stoploss on exchange interval is not mandatory. Do not change its value if you are
+!!! Warning "Using market orders"
+ Please carefully read the section [Market order pricing](#market-order-pricing) section when using market orders.
+
+!!! Note "Stoploss on exchange"
+ `stoploss_on_exchange_interval` is not mandatory. Do not change its value if you are
unsure of what you are doing. For more information about how stoploss works please
refer to [the stoploss documentation](stoploss.md).
-!!! Note
If `stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new stoploss order.
-!!! Warning "Using market orders"
- Please read the section [Market order pricing](#market-order-pricing) section when using market orders.
-
!!! Warning "Warning: stoploss_on_exchange failures"
If stoploss on exchange creation fails for some reason, then an "emergency sell" is initiated. By default, this will sell the asset using a market order. The order-type for the emergency-sell can be changed by setting the `emergencysell` value in the `order_types` dictionary - however this is not advised.
diff --git a/docs/developer.md b/docs/developer.md
index 8ef816d5d..c253f4460 100644
--- a/docs/developer.md
+++ b/docs/developer.md
@@ -2,7 +2,7 @@
This page is intended for developers of Freqtrade, people who want to contribute to the Freqtrade codebase or documentation, or people who want to understand the source code of the application they're running.
-All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel in [slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) where you can ask questions.
+All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel on [discord](https://discord.gg/MA9v74M) or [slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) where you can ask questions.
## Documentation
diff --git a/docs/faq.md b/docs/faq.md
index a775060de..aa33218fb 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -145,7 +145,7 @@ freqtrade hyperopt --hyperop SampleHyperopt --hyperopt-loss SharpeHyperOptLossDa
### Why does it take a long time to run hyperopt?
-* Discovering a great strategy with Hyperopt takes time. Study www.freqtrade.io, the Freqtrade Documentation page, join the Freqtrade [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) - or the Freqtrade [discord community](https://discord.gg/X89cVG). While you patiently wait for the most advanced, free crypto bot in the world, to hand you a possible golden strategy specially designed just for you.
+* Discovering a great strategy with Hyperopt takes time. Study www.freqtrade.io, the Freqtrade Documentation page, join the Freqtrade [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) - or the Freqtrade [discord community](https://discord.gg/X89cVG). While you patiently wait for the most advanced, free crypto bot in the world, to hand you a possible golden strategy specially designed just for you.
* If you wonder why it can take from 20 minutes to days to do 1000 epochs here are some answers:
diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md
index ae4ec818d..e6a9fc1a8 100644
--- a/docs/includes/pairlists.md
+++ b/docs/includes/pairlists.md
@@ -35,6 +35,11 @@ It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklis
],
```
+By default, only currently enabled pairs are allowed.
+To skip pair validation against active markets, set `"allow_inactive": true` within the `StaticPairList` configuration.
+This can be useful for backtesting expired pairs (like quarterly spot-markets).
+This option must be configured along with `exchange.skip_pair_validation` in the exchange configuration.
+
#### Volume Pair List
`VolumePairList` employs sorting/filtering of pairs by their trading volume. It selects `number_assets` top pairs with sorting based on the `sort_key` (which can only be `quoteVolume`).
diff --git a/docs/index.md b/docs/index.md
index 5608587db..f63aeb6b8 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -59,17 +59,14 @@ Alternatively
## Support
-### Help / Slack / Discord
+### Help / Discord / Slack
-For any questions not covered by the documentation or for further information about the bot, we encourage you to join our passionate Slack community.
+For any questions not covered by the documentation or for further information about the bot, or to simply engage with like-minded individuals, we encourage you to join our slack channel.
-Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) to join the Freqtrade Slack channel.
+Please check out our [discord server](https://discord.gg/MA9v74M).
-Alternatively, check out the newly created [discord server](https://discord.gg/MA9v74M).
-
-!!! Note
- Since the discord server is relatively new, answers to questions might be slightly delayed as currently the user base quite small.
+You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg).
## Ready to try?
-Begin by reading our installation guide [for docker](docker.md) (recommended), or for [installation without docker](installation.md).
+Begin by reading our installation guide [for docker](docker_quickstart.md) (recommended), or for [installation without docker](installation.md).
diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt
index f034b0b36..87bc6dfdd 100644
--- a/docs/requirements-docs.txt
+++ b/docs/requirements-docs.txt
@@ -1,3 +1,3 @@
-mkdocs-material==6.1.4
+mkdocs-material==6.1.6
mdx_truly_sane_lists==1.2
pymdown-extensions==8.0.1
diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md
index 6c7d78864..db007985f 100644
--- a/docs/strategy-customization.md
+++ b/docs/strategy-customization.md
@@ -770,8 +770,6 @@ To get additional Ideas for strategies, head over to our [strategy repository](h
Feel free to use any of them as inspiration for your own strategies.
We're happy to accept Pull Requests containing new Strategies to that repo.
-We also got a *strategy-sharing* channel in our [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) which is a great place to get and/or share ideas.
-
## Next step
Now you have a perfect strategy you probably want to backtest it.
diff --git a/docs/windows_installation.md b/docs/windows_installation.md
index 924459a6d..5341ce96b 100644
--- a/docs/windows_installation.md
+++ b/docs/windows_installation.md
@@ -50,8 +50,8 @@ freqtrade
error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools
```
-Unfortunately, many packages requiring compilation don't provide a pre-build wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use.
+Unfortunately, many packages requiring compilation don't provide a pre-built wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use.
-The easiest way is to download install Microsoft Visual Studio Community [here](https://visualstudio.microsoft.com/downloads/) and make sure to install "Common Tools for Visual C++" to enable building c code on Windows. Unfortunately, this is a heavy download / dependency (~4Gb) so you might want to consider WSL or [docker](docker.md) first.
+The easiest way is to download install Microsoft Visual Studio Community [here](https://visualstudio.microsoft.com/downloads/) and make sure to install "Common Tools for Visual C++" to enable building C code on Windows. Unfortunately, this is a heavy download / dependency (~4Gb) so you might want to consider WSL or [docker](docker.md) first.
---
diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py
index 4769bccde..619a300ae 100644
--- a/freqtrade/commands/cli_options.py
+++ b/freqtrade/commands/cli_options.py
@@ -354,13 +354,11 @@ AVAILABLE_CLI_OPTIONS = {
'--data-format-ohlcv',
help='Storage format for downloaded candle (OHLCV) data. (default: `%(default)s`).',
choices=constants.AVAILABLE_DATAHANDLERS,
- default='json'
),
"dataformat_trades": Arg(
'--data-format-trades',
help='Storage format for downloaded trades data. (default: `%(default)s`).',
choices=constants.AVAILABLE_DATAHANDLERS,
- default='jsongz'
),
"exchange": Arg(
'--exchange',
diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py
index d4612d8e0..ab21bc686 100644
--- a/freqtrade/configuration/config_validation.py
+++ b/freqtrade/configuration/config_validation.py
@@ -137,6 +137,10 @@ def _validate_edge(conf: Dict[str, Any]) -> None:
"Edge and VolumePairList are incompatible, "
"Edge will override whatever pairs VolumePairlist selects."
)
+ if not conf.get('ask_strategy', {}).get('use_sell_signal', True):
+ raise OperationalException(
+ "Edge requires `use_sell_signal` to be True, otherwise no sells will happen."
+ )
def _validate_whitelist(conf: Dict[str, Any]) -> None:
diff --git a/freqtrade/constants.py b/freqtrade/constants.py
index dc5384f6f..3271dda39 100644
--- a/freqtrade/constants.py
+++ b/freqtrade/constants.py
@@ -365,3 +365,6 @@ CANCEL_REASON = {
# List of pairs with their timeframes
PairWithTimeframe = Tuple[str, str]
ListPairsWithTimeframes = List[PairWithTimeframe]
+
+# Type for trades list
+TradeList = List[List]
diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py
index 38fa670e9..d4053abaa 100644
--- a/freqtrade/data/converter.py
+++ b/freqtrade/data/converter.py
@@ -10,7 +10,7 @@ from typing import Any, Dict, List
import pandas as pd
from pandas import DataFrame, to_datetime
-from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS
+from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, TradeList
logger = logging.getLogger(__name__)
@@ -168,7 +168,7 @@ def trades_remove_duplicates(trades: List[List]) -> List[List]:
return [i for i, _ in itertools.groupby(sorted(trades, key=itemgetter(0)))]
-def trades_dict_to_list(trades: List[Dict]) -> List[List]:
+def trades_dict_to_list(trades: List[Dict]) -> TradeList:
"""
Convert fetch_trades result into a List (to be more memory efficient).
:param trades: List of trades, as returned by ccxt.fetch_trades.
@@ -177,16 +177,18 @@ def trades_dict_to_list(trades: List[Dict]) -> List[List]:
return [[t[col] for col in DEFAULT_TRADES_COLUMNS] for t in trades]
-def trades_to_ohlcv(trades: List, timeframe: str) -> DataFrame:
+def trades_to_ohlcv(trades: TradeList, timeframe: str) -> DataFrame:
"""
Converts trades list to OHLCV list
- TODO: This should get a dedicated test
:param trades: List of trades, as returned by ccxt.fetch_trades.
:param timeframe: Timeframe to resample data to
:return: OHLCV Dataframe.
+ :raises: ValueError if no trades are provided
"""
from freqtrade.exchange import timeframe_to_minutes
timeframe_minutes = timeframe_to_minutes(timeframe)
+ if not trades:
+ raise ValueError('Trade-list empty.')
df = pd.DataFrame(trades, columns=DEFAULT_TRADES_COLUMNS)
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms',
utc=True,)
diff --git a/freqtrade/data/history/hdf5datahandler.py b/freqtrade/data/history/hdf5datahandler.py
index f6cf9e0d9..d116637e7 100644
--- a/freqtrade/data/history/hdf5datahandler.py
+++ b/freqtrade/data/history/hdf5datahandler.py
@@ -3,14 +3,15 @@ import re
from pathlib import Path
from typing import List, Optional
+import numpy as np
import pandas as pd
from freqtrade import misc
from freqtrade.configuration import TimeRange
from freqtrade.constants import (DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS,
- ListPairsWithTimeframes)
+ ListPairsWithTimeframes, TradeList)
-from .idatahandler import IDataHandler, TradeList
+from .idatahandler import IDataHandler
logger = logging.getLogger(__name__)
@@ -175,7 +176,8 @@ class HDF5DataHandler(IDataHandler):
if timerange.stoptype == 'date':
where.append(f"timestamp < {timerange.stopts * 1e3}")
- trades = pd.read_hdf(filename, key=key, mode="r", where=where)
+ trades: pd.DataFrame = pd.read_hdf(filename, key=key, mode="r", where=where)
+ trades[['id', 'type']] = trades[['id', 'type']].replace({np.nan: None})
return trades.values.tolist()
def trades_purge(self, pair: str) -> bool:
diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py
index a420b9dcc..3b8b5a2f0 100644
--- a/freqtrade/data/history/history_utils.py
+++ b/freqtrade/data/history/history_utils.py
@@ -214,10 +214,9 @@ def _download_pair_history(datadir: Path,
data_handler.ohlcv_store(pair, timeframe, data=data)
return True
- except Exception as e:
- logger.error(
- f'Failed to download history data for pair: "{pair}", timeframe: {timeframe}. '
- f'Error: {e}'
+ except Exception:
+ logger.exception(
+ f'Failed to download history data for pair: "{pair}", timeframe: {timeframe}.'
)
return False
@@ -304,10 +303,9 @@ def _download_trades_history(exchange: Exchange,
logger.info(f"New Amount of trades: {len(trades)}")
return True
- except Exception as e:
- logger.error(
+ except Exception:
+ logger.exception(
f'Failed to download historic trades for pair: "{pair}". '
- f'Error: {e}'
)
return False
@@ -356,9 +354,12 @@ def convert_trades_to_ohlcv(pairs: List[str], timeframes: List[str],
if erase:
if data_handler_ohlcv.ohlcv_purge(pair, timeframe):
logger.info(f'Deleting existing data for pair {pair}, interval {timeframe}.')
- ohlcv = trades_to_ohlcv(trades, timeframe)
- # Store ohlcv
- data_handler_ohlcv.ohlcv_store(pair, timeframe, data=ohlcv)
+ try:
+ ohlcv = trades_to_ohlcv(trades, timeframe)
+ # Store ohlcv
+ data_handler_ohlcv.ohlcv_store(pair, timeframe, data=ohlcv)
+ except ValueError:
+ logger.exception(f'Could not convert {pair} to OHLCV.')
def get_timerange(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]:
diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py
index a170a9dc5..070d9039d 100644
--- a/freqtrade/data/history/idatahandler.py
+++ b/freqtrade/data/history/idatahandler.py
@@ -13,16 +13,13 @@ from typing import List, Optional, Type
from pandas import DataFrame
from freqtrade.configuration import TimeRange
-from freqtrade.constants import ListPairsWithTimeframes
+from freqtrade.constants import ListPairsWithTimeframes, TradeList
from freqtrade.data.converter import clean_ohlcv_dataframe, trades_remove_duplicates, trim_dataframe
from freqtrade.exchange import timeframe_to_seconds
logger = logging.getLogger(__name__)
-# Type for trades list
-TradeList = List[List]
-
class IDataHandler(ABC):
diff --git a/freqtrade/data/history/jsondatahandler.py b/freqtrade/data/history/jsondatahandler.py
index 6436aa13d..9122170d5 100644
--- a/freqtrade/data/history/jsondatahandler.py
+++ b/freqtrade/data/history/jsondatahandler.py
@@ -8,10 +8,10 @@ from pandas import DataFrame, read_json, to_datetime
from freqtrade import misc
from freqtrade.configuration import TimeRange
-from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, ListPairsWithTimeframes
+from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, ListPairsWithTimeframes, TradeList
from freqtrade.data.converter import trades_dict_to_list
-from .idatahandler import IDataHandler, TradeList
+from .idatahandler import IDataHandler
logger = logging.getLogger(__name__)
diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index e74f5668c..2bbdb0d59 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -124,7 +124,8 @@ class Exchange:
# Check if all pairs are available
self.validate_stakecurrency(config['stake_currency'])
- self.validate_pairs(config['exchange']['pair_whitelist'])
+ if not exchange_config.get('skip_pair_validation'):
+ self.validate_pairs(config['exchange']['pair_whitelist'])
self.validate_ordertypes(config.get('order_types', {}))
self.validate_order_time_in_force(config.get('order_time_in_force', {}))
self.validate_required_startup_candles(config.get('startup_candle_count', 0))
diff --git a/freqtrade/loggers.py b/freqtrade/loggers.py
index 169cd2610..fbb05d879 100644
--- a/freqtrade/loggers.py
+++ b/freqtrade/loggers.py
@@ -37,6 +37,13 @@ def _set_loggers(verbosity: int = 0, api_verbosity: str = 'info') -> None:
)
+def get_existing_handlers(handlertype):
+ """
+ Returns Existing handler or None (if the handler has not yet been added to the root handlers).
+ """
+ return next((h for h in logging.root.handlers if isinstance(h, handlertype)), None)
+
+
def setup_logging_pre() -> None:
"""
Early setup for logging.
@@ -71,18 +78,24 @@ def setup_logging(config: Dict[str, Any]) -> None:
# config['logfilename']), which defaults to '/dev/log', applicable for most
# of the systems.
address = (s[1], int(s[2])) if len(s) > 2 else s[1] if len(s) > 1 else '/dev/log'
- handler = SysLogHandler(address=address)
+ handler_sl = get_existing_handlers(SysLogHandler)
+ if handler_sl:
+ logging.root.removeHandler(handler_sl)
+ handler_sl = SysLogHandler(address=address)
# No datetime field for logging into syslog, to allow syslog
# to perform reduction of repeating messages if this is set in the
# syslog config. The messages should be equal for this.
- handler.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s'))
- logging.root.addHandler(handler)
+ handler_sl.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s'))
+ logging.root.addHandler(handler_sl)
elif s[0] == 'journald':
try:
from systemd.journal import JournaldLogHandler
except ImportError:
raise OperationalException("You need the systemd python package be installed in "
"order to use logging to journald.")
+ handler_jd = get_existing_handlers(JournaldLogHandler)
+ if handler_jd:
+ logging.root.removeHandler(handler_jd)
handler_jd = JournaldLogHandler()
# No datetime field for logging into journald, to allow syslog
# to perform reduction of repeating messages if this is set in the
@@ -90,6 +103,9 @@ def setup_logging(config: Dict[str, Any]) -> None:
handler_jd.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s'))
logging.root.addHandler(handler_jd)
else:
+ handler_rf = get_existing_handlers(RotatingFileHandler)
+ if handler_rf:
+ logging.root.removeHandler(handler_rf)
handler_rf = RotatingFileHandler(logfile,
maxBytes=1024 * 1024 * 10, # 10Mb
backupCount=10)
diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py
index c977a991b..fc04cbd93 100644
--- a/freqtrade/optimize/optimize_reports.py
+++ b/freqtrade/optimize/optimize_reports.py
@@ -396,6 +396,8 @@ def text_table_add_metrics(strat_results: Dict) -> str:
metrics = [
('Backtesting from', strat_results['backtest_start'].strftime(DATETIME_PRINT_FORMAT)),
('Backtesting to', strat_results['backtest_end'].strftime(DATETIME_PRINT_FORMAT)),
+ ('Max open trades', strat_results['max_open_trades']),
+ ('', ''), # Empty line to improve readability
('Total trades', strat_results['total_trades']),
('First trade', min_trade['open_date'].strftime(DATETIME_PRINT_FORMAT)),
('First trade Pair', min_trade['pair']),
diff --git a/freqtrade/pairlist/AgeFilter.py b/freqtrade/pairlist/AgeFilter.py
index 19cf1c090..20635a9ed 100644
--- a/freqtrade/pairlist/AgeFilter.py
+++ b/freqtrade/pairlist/AgeFilter.py
@@ -37,7 +37,7 @@ class AgeFilter(IPairList):
def needstickers(self) -> bool:
"""
Boolean property defining if tickers are necessary.
- If no Pairlist requires tickers, an empty List is passed
+ If no Pairlist requires tickers, an empty Dict is passed
as tickers argument to filter_pairlist
"""
return True
diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py
index 6b5bd11e7..c869e499b 100644
--- a/freqtrade/pairlist/IPairList.py
+++ b/freqtrade/pairlist/IPairList.py
@@ -68,7 +68,7 @@ class IPairList(ABC):
def needstickers(self) -> bool:
"""
Boolean property defining if tickers are necessary.
- If no Pairlist requires tickers, an empty List is passed
+ If no Pairlist requires tickers, an empty Dict is passed
as tickers argument to filter_pairlist
"""
diff --git a/freqtrade/pairlist/PrecisionFilter.py b/freqtrade/pairlist/PrecisionFilter.py
index cf853397b..29e32fd44 100644
--- a/freqtrade/pairlist/PrecisionFilter.py
+++ b/freqtrade/pairlist/PrecisionFilter.py
@@ -32,7 +32,7 @@ class PrecisionFilter(IPairList):
def needstickers(self) -> bool:
"""
Boolean property defining if tickers are necessary.
- If no Pairlist requires tickers, an empty List is passed
+ If no Pairlist requires tickers, an empty Dict is passed
as tickers argument to filter_pairlist
"""
return True
diff --git a/freqtrade/pairlist/PriceFilter.py b/freqtrade/pairlist/PriceFilter.py
index 8cd57ee1d..bef1c0a15 100644
--- a/freqtrade/pairlist/PriceFilter.py
+++ b/freqtrade/pairlist/PriceFilter.py
@@ -35,7 +35,7 @@ class PriceFilter(IPairList):
def needstickers(self) -> bool:
"""
Boolean property defining if tickers are necessary.
- If no Pairlist requires tickers, an empty List is passed
+ If no Pairlist requires tickers, an empty Dict is passed
as tickers argument to filter_pairlist
"""
return True
diff --git a/freqtrade/pairlist/ShuffleFilter.py b/freqtrade/pairlist/ShuffleFilter.py
index eb4f6dcc3..28778db7b 100644
--- a/freqtrade/pairlist/ShuffleFilter.py
+++ b/freqtrade/pairlist/ShuffleFilter.py
@@ -25,7 +25,7 @@ class ShuffleFilter(IPairList):
def needstickers(self) -> bool:
"""
Boolean property defining if tickers are necessary.
- If no Pairlist requires tickers, an empty List is passed
+ If no Pairlist requires tickers, an empty Dict is passed
as tickers argument to filter_pairlist
"""
return False
diff --git a/freqtrade/pairlist/SpreadFilter.py b/freqtrade/pairlist/SpreadFilter.py
index 2527a3131..a636b90bd 100644
--- a/freqtrade/pairlist/SpreadFilter.py
+++ b/freqtrade/pairlist/SpreadFilter.py
@@ -24,7 +24,7 @@ class SpreadFilter(IPairList):
def needstickers(self) -> bool:
"""
Boolean property defining if tickers are necessary.
- If no Pairlist requires tickers, an empty List is passed
+ If no Pairlist requires tickers, an empty Dict is passed
as tickers argument to filter_pairlist
"""
return True
diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py
index aa6268ba3..2879cb364 100644
--- a/freqtrade/pairlist/StaticPairList.py
+++ b/freqtrade/pairlist/StaticPairList.py
@@ -24,11 +24,13 @@ class StaticPairList(IPairList):
raise OperationalException(f"{self.name} can only be used in the first position "
"in the list of Pairlist Handlers.")
+ self._allow_inactive = self._pairlistconfig.get('allow_inactive', False)
+
@property
def needstickers(self) -> bool:
"""
Boolean property defining if tickers are necessary.
- If no Pairlist requires tickers, an empty List is passed
+ If no Pairlist requires tickers, an empty Dict is passed
as tickers argument to filter_pairlist
"""
return False
@@ -47,7 +49,10 @@ class StaticPairList(IPairList):
:param tickers: Tickers (from exchange.get_tickers()).
:return: List of pairs
"""
- return self._whitelist_for_active_markets(self._config['exchange']['pair_whitelist'])
+ if self._allow_inactive:
+ return self._config['exchange']['pair_whitelist']
+ else:
+ return self._whitelist_for_active_markets(self._config['exchange']['pair_whitelist'])
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
"""
diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py
index 44e5c52d7..7d3c2c653 100644
--- a/freqtrade/pairlist/VolumePairList.py
+++ b/freqtrade/pairlist/VolumePairList.py
@@ -49,7 +49,7 @@ class VolumePairList(IPairList):
def needstickers(self) -> bool:
"""
Boolean property defining if tickers are necessary.
- If no Pairlist requires tickers, an empty List is passed
+ If no Pairlist requires tickers, an empty Dict is passed
as tickers argument to filter_pairlist
"""
return True
diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py
index 7f4773d57..384d7c6c2 100644
--- a/freqtrade/rpc/api_server.py
+++ b/freqtrade/rpc/api_server.py
@@ -508,6 +508,8 @@ class ApiServer(RPC):
"""
asset = request.json.get("pair")
price = request.json.get("price", None)
+ price = float(price) if price is not None else price
+
trade = self._rpc_forcebuy(asset, price)
if trade:
return jsonify(trade.to_json())
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index 90564a19d..e608a2274 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -524,7 +524,7 @@ class RPC:
stake_currency = self._freqtrade.config.get('stake_currency')
if not self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency:
raise RPCException(
- f'Wrong pair selected. Please pairs with stake {stake_currency} pairs only')
+ f'Wrong pair selected. Only pairs with stake-currency {stake_currency} allowed.')
# check if valid pair
# check if pair already has an open pair
diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py
index 44590dbbe..b3f9fef07 100644
--- a/freqtrade/templates/sample_strategy.py
+++ b/freqtrade/templates/sample_strategy.py
@@ -184,6 +184,8 @@ class SampleStrategy(IStrategy):
dataframe['fastk'] = stoch_fast['fastk']
# # Stochastic RSI
+ # Please read https://github.com/freqtrade/freqtrade/issues/2961 before using this.
+ # STOCHRSI is NOT aligned with tradingview, which may result in non-expected results.
# stoch_rsi = ta.STOCHRSI(dataframe)
# dataframe['fastd_rsi'] = stoch_rsi['fastd']
# dataframe['fastk_rsi'] = stoch_rsi['fastk']
diff --git a/freqtrade/templates/subtemplates/indicators_full.j2 b/freqtrade/templates/subtemplates/indicators_full.j2
index 60a358bec..57d2ca665 100644
--- a/freqtrade/templates/subtemplates/indicators_full.j2
+++ b/freqtrade/templates/subtemplates/indicators_full.j2
@@ -62,6 +62,8 @@ dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
# # Stochastic RSI
+# Please read https://github.com/freqtrade/freqtrade/issues/2961 before using this.
+# STOCHRSI is NOT aligned with tradingview, which may result in non-expected results.
# stoch_rsi = ta.STOCHRSI(dataframe)
# dataframe['fastd_rsi'] = stoch_rsi['fastd']
# dataframe['fastk_rsi'] = stoch_rsi['fastk']
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 1c96a880a..e681274c8 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -3,7 +3,7 @@
-r requirements-plot.txt
-r requirements-hyperopt.txt
-coveralls==2.1.2
+coveralls==2.2.0
flake8==3.8.4
flake8-type-annotations==0.1.0
flake8-tidy-imports==4.1.0
diff --git a/requirements.txt b/requirements.txt
index 4622afa15..7490688d4 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,14 +1,14 @@
numpy==1.19.4
pandas==1.1.4
-ccxt==1.37.41
-aiohttp==3.7.2
+ccxt==1.38.13
+aiohttp==3.7.3
SQLAlchemy==1.3.20
python-telegram-bot==13.0
arrow==0.17.0
cachetools==4.1.1
-requests==2.24.0
-urllib3==1.25.11
+requests==2.25.0
+urllib3==1.26.2
wrapt==1.12.1
jsonschema==3.2.0
TA-Lib==0.4.19
@@ -22,18 +22,18 @@ blosc==1.9.2
py_find_1st==1.1.4
# Load ticker files 30% faster
-python-rapidjson==0.9.3
+python-rapidjson==0.9.4
# Notify systemd
sdnotify==0.3.2
# Api server
flask==1.1.2
-flask-jwt-extended==3.24.1
+flask-jwt-extended==3.25.0
flask-cors==3.0.9
# Support for colorized terminal output
colorama==0.4.4
# Building config files interactively
-questionary==1.8.0
+questionary==1.8.1
prompt-toolkit==3.0.8
diff --git a/tests/data/test_converter.py b/tests/data/test_converter.py
index fdba7900f..4fdcce4d2 100644
--- a/tests/data/test_converter.py
+++ b/tests/data/test_converter.py
@@ -1,10 +1,13 @@
# pragma pylint: disable=missing-docstring, C0103
import logging
+import pytest
+
from freqtrade.configuration.timerange import TimeRange
from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_format,
ohlcv_fill_up_missing_data, ohlcv_to_dataframe,
- trades_dict_to_list, trades_remove_duplicates, trim_dataframe)
+ trades_dict_to_list, trades_remove_duplicates,
+ trades_to_ohlcv, trim_dataframe)
from freqtrade.data.history import (get_timerange, load_data, load_pair_history,
validate_backtest_data)
from tests.conftest import log_has
@@ -26,6 +29,28 @@ def test_ohlcv_to_dataframe(ohlcv_history_list, caplog):
assert log_has('Converting candle (OHLCV) data to dataframe for pair UNITTEST/BTC.', caplog)
+def test_trades_to_ohlcv(ohlcv_history_list, caplog):
+
+ caplog.set_level(logging.DEBUG)
+ with pytest.raises(ValueError, match="Trade-list empty."):
+ trades_to_ohlcv([], '1m')
+
+ trades = [
+ [1570752011620, "13519807", None, "sell", 0.00141342, 23.0, 0.03250866],
+ [1570752011620, "13519808", None, "sell", 0.00141266, 54.0, 0.07628364],
+ [1570752017964, "13519809", None, "sell", 0.00141266, 8.0, 0.01130128]]
+
+ df = trades_to_ohlcv(trades, '1m')
+ assert not df.empty
+ assert len(df) == 1
+ assert 'open' in df.columns
+ assert 'high' in df.columns
+ assert 'low' in df.columns
+ assert 'close' in df.columns
+ assert df.loc[:, 'high'][0] == 0.00141342
+ assert df.loc[:, 'low'][0] == 0.00141266
+
+
def test_ohlcv_fill_up_missing_data(testdatadir, caplog):
data = load_pair_history(datadir=testdatadir,
timeframe='1m',
diff --git a/tests/data/test_history.py b/tests/data/test_history.py
index bbc6e55b4..99b22adda 100644
--- a/tests/data/test_history.py
+++ b/tests/data/test_history.py
@@ -312,10 +312,7 @@ def test_download_backtesting_data_exception(ohlcv_history, mocker, caplog,
# clean files freshly downloaded
_clean_test_file(file1_1)
_clean_test_file(file1_5)
- assert log_has(
- 'Failed to download history data for pair: "MEME/BTC", timeframe: 1m. '
- 'Error: File Error', caplog
- )
+ assert log_has('Failed to download history data for pair: "MEME/BTC", timeframe: 1m.', caplog)
def test_load_partial_missing(testdatadir, caplog) -> None:
@@ -620,6 +617,12 @@ def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog):
_clean_test_file(file1)
_clean_test_file(file5)
+ assert not log_has('Could not convert NoDatapair to OHLCV.', caplog)
+
+ convert_trades_to_ohlcv(['NoDatapair'], timeframes=['1m', '5m'],
+ datadir=testdatadir, timerange=tr, erase=True)
+ assert log_has('Could not convert NoDatapair to OHLCV.', caplog)
+
def test_datahandler_ohlcv_get_pairs(testdatadir):
pairs = JsonDataHandler.ohlcv_get_pairs(testdatadir, '5m')
@@ -724,6 +727,8 @@ def test_hdf5datahandler_trades_load(testdatadir):
trades2 = dh._trades_load('XRP/ETH', timerange)
assert len(trades) > len(trades2)
+ # Check that ID is None (If it's nan, it's wrong)
+ assert trades2[0][2] is None
# unfiltered load has trades before starttime
assert len([t for t in trades if t[0] < timerange.startts * 1000]) >= 0
diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py
index 23ca53e53..47e0f763d 100644
--- a/tests/rpc/test_rpc.py
+++ b/tests/rpc/test_rpc.py
@@ -868,7 +868,8 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
assert trade.open_rate == 0.0001
# Test buy pair not with stakes
- with pytest.raises(RPCException, match=r'Wrong pair selected. Please pairs with stake.*'):
+ with pytest.raises(RPCException,
+ match=r'Wrong pair selected. Only pairs with stake-currency.*'):
rpc._rpc_forcebuy('LTC/ETH', 0.0001)
pair = 'XRP/BTC'
diff --git a/tests/test_configuration.py b/tests/test_configuration.py
index 7d6c81f74..e6c91a96e 100644
--- a/tests/test_configuration.py
+++ b/tests/test_configuration.py
@@ -663,7 +663,7 @@ def test_set_loggers() -> None:
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
-def test_set_loggers_syslog(mocker):
+def test_set_loggers_syslog():
logger = logging.getLogger()
orig_handlers = logger.handlers
logger.handlers = []
@@ -678,10 +678,38 @@ def test_set_loggers_syslog(mocker):
assert [x for x in logger.handlers if type(x) == logging.handlers.SysLogHandler]
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
assert [x for x in logger.handlers if type(x) == logging.handlers.BufferingHandler]
+ # setting up logging again should NOT cause the loggers to be added a second time.
+ setup_logging(config)
+ assert len(logger.handlers) == 3
# reset handlers to not break pytest
logger.handlers = orig_handlers
+@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
+def test_set_loggers_Filehandler(tmpdir):
+ logger = logging.getLogger()
+ orig_handlers = logger.handlers
+ logger.handlers = []
+ logfile = Path(tmpdir) / 'ft_logfile.log'
+ config = {'verbosity': 2,
+ 'logfile': str(logfile),
+ }
+
+ setup_logging_pre()
+ setup_logging(config)
+ assert len(logger.handlers) == 3
+ assert [x for x in logger.handlers if type(x) == logging.handlers.RotatingFileHandler]
+ assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
+ assert [x for x in logger.handlers if type(x) == logging.handlers.BufferingHandler]
+ # setting up logging again should NOT cause the loggers to be added a second time.
+ setup_logging(config)
+ assert len(logger.handlers) == 3
+ # reset handlers to not break pytest
+ if logfile.exists:
+ logfile.unlink()
+ logger.handlers = orig_handlers
+
+
@pytest.mark.skip(reason="systemd is not installed on every system, so we're not testing this.")
def test_set_loggers_journald(mocker):
logger = logging.getLogger()
@@ -812,6 +840,21 @@ def test_validate_edge(edge_conf):
validate_config_consistency(edge_conf)
+def test_validate_edge2(edge_conf):
+ edge_conf.update({"ask_strategy": {
+ "use_sell_signal": True,
+ }})
+ # Passes test
+ validate_config_consistency(edge_conf)
+
+ edge_conf.update({"ask_strategy": {
+ "use_sell_signal": False,
+ }})
+ with pytest.raises(OperationalException, match="Edge requires `use_sell_signal` to be True, "
+ "otherwise no sells will happen."):
+ validate_config_consistency(edge_conf)
+
+
def test_validate_whitelist(default_conf):
default_conf['runmode'] = RunMode.DRY_RUN
# Test regular case - has whitelist and uses StaticPairlist