From 1b5cb3427e36498efa85e0bf432b3238b34cb78d Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Oct 2020 08:09:55 +0200 Subject: [PATCH 01/35] Fix example R calculation in edge documentation --- docs/edge.md | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/docs/edge.md b/docs/edge.md index 500c3c833..7b6a6d8e8 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -82,20 +82,33 @@ Risk Reward Ratio ($R$) is a formula used to measure the expected gains of a giv $$ R = \frac{\text{potential_profit}}{\text{potential_loss}} $$ ???+ Example "Worked example of $R$ calculation" - Let's say that you think that the price of *stonecoin* today is $10.0. You believe that, because they will start mining stonecoin, it will go up to $15.0 tomorrow. There is the risk that the stone is too hard, and the GPUs can't mine it, so the price might go to $0 tomorrow. You are planning to invest $100.
- Your potential profit is calculated as:
+ Let's say that you think that the price of *stonecoin* today is $10.0. You believe that, because they will start mining stonecoin, it will go up to $15.0 tomorrow. There is the risk that the stone is too hard, and the GPUs can't mine it, so the price might go to $0 tomorrow. You are planning to invest $100. + + Your potential profit is calculated as: + $\begin{aligned} - \text{potential_profit} &= (\text{potential_price} - \text{cost_per_unit}) * \frac{\text{investment}}{\text{cost_per_unit}} \\ - &= (15 - 10) * \frac{100}{15}\\ - &= 33.33 - \end{aligned}$
- Since the price might go to $0, the $100 dolars invested could turn into 0. We can compute the Risk Reward Ratio as follows:
+ \text{potential_profit} &= (\text{potential_price} - \text{entry_price}) * \text{investment} \\ + &= (15 - 10) * 100\\ + &= 500 + \end{aligned}$ + + Since the price might go to $0, the $100 dollars invested could turn into 0. + + We do however use a stoploss of 15% - so in the worst case, we'll sell 15% below entry price (or at 8.5). + + $\begin{aligned} + \text{risk} &= (\text{entry_price} - \text{stoploss}) * \text{investment} \\ + &= (10 - (10 * (1 - 0.15))) * 100\\ + &= 150 + \end{aligned}$ + + We can compute the Risk Reward Ratio as follows:
$\begin{aligned} R &= \frac{\text{potential_profit}}{\text{potential_loss}}\\ - &= \frac{33.33}{100}\\ - &= 0.333... + &= \frac{500}{150}\\ + &= 3.33 \end{aligned}$
- What it effectivelly means is that the strategy have the potential to make $0.33 for each $1 invested. + What it effectively means is that the strategy have the potential to make 3$ for each $1 invested. On a long horizon, that is, on many trades, we can calculate the risk reward by dividing the strategy' average profit on winning trades by the strategy' average loss on losing trades. We can calculate the average profit, $\mu_{win}$, as follows: From 48750b0ef85c3c52cc20c5431bee13c68ab619ba Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Oct 2020 08:23:56 +0200 Subject: [PATCH 02/35] Improve wording in formula --- docs/edge.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/edge.md b/docs/edge.md index 7b6a6d8e8..0891a851e 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -93,11 +93,11 @@ $$ R = \frac{\text{potential_profit}}{\text{potential_loss}} $$ \end{aligned}$ Since the price might go to $0, the $100 dollars invested could turn into 0. - + We do however use a stoploss of 15% - so in the worst case, we'll sell 15% below entry price (or at 8.5). $\begin{aligned} - \text{risk} &= (\text{entry_price} - \text{stoploss}) * \text{investment} \\ + \text{potential_loss} &= (\text{entry_price} - \text{stoploss}) * \text{investment} \\ &= (10 - (10 * (1 - 0.15))) * 100\\ &= 150 \end{aligned}$ From 6bb045f5655959c01455d4a886ac6485f17e354b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Oct 2020 08:30:30 +0200 Subject: [PATCH 03/35] Simplify stoploss calculation --- docs/edge.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/edge.md b/docs/edge.md index 0891a851e..d3a6af8ba 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -94,11 +94,11 @@ $$ R = \frac{\text{potential_profit}}{\text{potential_loss}} $$ Since the price might go to $0, the $100 dollars invested could turn into 0. - We do however use a stoploss of 15% - so in the worst case, we'll sell 15% below entry price (or at 8.5). + We do however use a stoploss of 15% - so in the worst case, we'll sell 15% below entry price (or at 8.5$). $\begin{aligned} \text{potential_loss} &= (\text{entry_price} - \text{stoploss}) * \text{investment} \\ - &= (10 - (10 * (1 - 0.15))) * 100\\ + &= (10 - 8.5) * 100\\ &= 150 \end{aligned}$ @@ -108,7 +108,7 @@ $$ R = \frac{\text{potential_profit}}{\text{potential_loss}} $$ &= \frac{500}{150}\\ &= 3.33 \end{aligned}$
- What it effectively means is that the strategy have the potential to make 3$ for each $1 invested. + What it effectively means is that the strategy have the potential to make 3.33$ for each $1 invested. On a long horizon, that is, on many trades, we can calculate the risk reward by dividing the strategy' average profit on winning trades by the strategy' average loss on losing trades. We can calculate the average profit, $\mu_{win}$, as follows: From 7f0afe12446fa879463b91ad1dda3611caec7948 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Oct 2020 10:24:52 +0200 Subject: [PATCH 04/35] Fix calculation to not show losses > initial investment --- docs/edge.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/edge.md b/docs/edge.md index d3a6af8ba..7442f1927 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -82,14 +82,14 @@ Risk Reward Ratio ($R$) is a formula used to measure the expected gains of a giv $$ R = \frac{\text{potential_profit}}{\text{potential_loss}} $$ ???+ Example "Worked example of $R$ calculation" - Let's say that you think that the price of *stonecoin* today is $10.0. You believe that, because they will start mining stonecoin, it will go up to $15.0 tomorrow. There is the risk that the stone is too hard, and the GPUs can't mine it, so the price might go to $0 tomorrow. You are planning to invest $100. + Let's say that you think that the price of *stonecoin* today is $10.0. You believe that, because they will start mining stonecoin, it will go up to $15.0 tomorrow. There is the risk that the stone is too hard, and the GPUs can't mine it, so the price might go to $0 tomorrow. You are planning to invest $100, which will give you 10 shares (100 / 10). Your potential profit is calculated as: $\begin{aligned} - \text{potential_profit} &= (\text{potential_price} - \text{entry_price}) * \text{investment} \\ - &= (15 - 10) * 100\\ - &= 500 + \text{potential_profit} &= (\text{potential_price} - \text{entry_price}) * \frac{\text{investment}}{\text{entry_price}} \\ + &= (15 - 10) * (100 / 10) \\ + &= 50 \end{aligned}$ Since the price might go to $0, the $100 dollars invested could turn into 0. @@ -97,15 +97,16 @@ $$ R = \frac{\text{potential_profit}}{\text{potential_loss}} $$ We do however use a stoploss of 15% - so in the worst case, we'll sell 15% below entry price (or at 8.5$). $\begin{aligned} - \text{potential_loss} &= (\text{entry_price} - \text{stoploss}) * \text{investment} \\ - &= (10 - 8.5) * 100\\ - &= 150 + \text{potential_loss} &= (\text{entry_price} - \text{stoploss}) * \frac{\text{investment}}{\text{entry_price}} \\ + &= (10 - 8.5) * (100 / 10)\\ + &= 15 \end{aligned}$ - We can compute the Risk Reward Ratio as follows:
+ We can compute the Risk Reward Ratio as follows: + $\begin{aligned} R &= \frac{\text{potential_profit}}{\text{potential_loss}}\\ - &= \frac{500}{150}\\ + &= \frac{50}{15}\\ &= 3.33 \end{aligned}$
What it effectively means is that the strategy have the potential to make 3.33$ for each $1 invested. From d1db847612cbac7618a170e538cb1878b538608e Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Oct 2020 19:27:00 +0200 Subject: [PATCH 05/35] Fix "storing information" documentation closes #3843 --- docs/strategy-customization.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 14d5fcd84..a6cdef864 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -312,12 +312,17 @@ The name of the variable can be chosen at will, but should be prefixed with `cus class Awesomestrategy(IStrategy): # Create custom dictionary cust_info = {} + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # Check if the entry already exists + if not metadata["pair"] in self._cust_info: + # Create empty entry for this pair + self._cust_info[metadata["pair"]] = {} + if "crosstime" in self.cust_info[metadata["pair"]: - self.cust_info[metadata["pair"]["crosstime"] += 1 + self.cust_info[metadata["pair"]]["crosstime"] += 1 else: - self.cust_info[metadata["pair"]["crosstime"] = 1 + self.cust_info[metadata["pair"]]["crosstime"] = 1 ``` !!! Warning From f676156ec79348adfe82727845eb71ea2dd929a9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 9 Oct 2020 06:39:13 +0200 Subject: [PATCH 06/35] Implement division/0 checks for win and loss columns in edge closes #3839 --- freqtrade/edge/edge_positioning.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 6a95ad91f..a40b63d67 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -310,8 +310,10 @@ class Edge: # Calculating number of losing trades, average win and average loss df['nb_loss_trades'] = df['nb_trades'] - df['nb_win_trades'] - df['average_win'] = df['profit_sum'] / df['nb_win_trades'] - df['average_loss'] = df['loss_sum'] / df['nb_loss_trades'] + df['average_win'] = np.where(df['nb_win_trades'] == 0, 0.0, + df['profit_sum'] / df['nb_win_trades']) + df['average_loss'] = np.where(df['nb_loss_trades'] == 0, 0.0, + df['loss_sum'] / df['nb_loss_trades']) # Win rate = number of profitable trades / number of trades df['winrate'] = df['nb_win_trades'] / df['nb_trades'] From 59b00ad6624645776be1f09ee6baf8856feaf933 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 9 Oct 2020 06:47:02 +0200 Subject: [PATCH 07/35] Add test for only-win scenario --- tests/edge/test_edge.py | 58 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index f19590490..a4bfa1085 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -499,3 +499,61 @@ def test_process_expectancy_remove_pumps(mocker, edge_conf, fee,): assert final['TEST/BTC'].stoploss == -0.9 assert final['TEST/BTC'].nb_trades == len(trades_df) - 1 assert round(final['TEST/BTC'].winrate, 10) == 0.0 + + +def test_process_expectancy_only_wins(mocker, edge_conf, fee,): + edge_conf['edge']['min_trade_number'] = 2 + freqtrade = get_patched_freqtradebot(mocker, edge_conf) + + freqtrade.exchange.get_fee = fee + edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) + + trades = [ + {'pair': 'TEST/BTC', + 'stoploss': -0.9, + 'profit_percent': '', + 'profit_abs': '', + 'open_date': np.datetime64('2018-10-03T00:05:00.000000000'), + 'close_date': np.datetime64('2018-10-03T00:10:00.000000000'), + 'open_index': 1, + 'close_index': 1, + 'trade_duration': '', + 'open_rate': 15, + 'close_rate': 17, + 'exit_type': 'sell_signal'}, + {'pair': 'TEST/BTC', + 'stoploss': -0.9, + 'profit_percent': '', + 'profit_abs': '', + 'open_date': np.datetime64('2018-10-03T00:20:00.000000000'), + 'close_date': np.datetime64('2018-10-03T00:25:00.000000000'), + 'open_index': 4, + 'close_index': 4, + 'trade_duration': '', + 'open_rate': 10, + 'close_rate': 20, + 'exit_type': 'sell_signal'}, + {'pair': 'TEST/BTC', + 'stoploss': -0.9, + 'profit_percent': '', + 'profit_abs': '', + 'open_date': np.datetime64('2018-10-03T00:30:00.000000000'), + 'close_date': np.datetime64('2018-10-03T00:40:00.000000000'), + 'open_index': 6, + 'close_index': 7, + 'trade_duration': '', + 'open_rate': 26, + 'close_rate': 134, + 'exit_type': 'sell_signal'} + ] + + trades_df = DataFrame(trades) + trades_df = edge._fill_calculable_fields(trades_df) + final = edge._process_expectancy(trades_df) + + assert 'TEST/BTC' in final + assert final['TEST/BTC'].stoploss == -0.9 + assert final['TEST/BTC'].nb_trades == len(trades_df) + assert round(final['TEST/BTC'].winrate, 10) == 1.0 + assert round(final['TEST/BTC'].risk_reward_ratio, 10) == float('inf') + assert round(final['TEST/BTC'].expectancy, 10) == float('inf') From 53984a059f43f004e2877d9363f0ae849375a2ee Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 9 Oct 2020 09:02:20 +0200 Subject: [PATCH 08/35] Configure mkdocs to allow page includes --- .github/workflows/ci.yml | 5 +++++ docs/developer.md | 2 +- docs/requirements-docs.txt | 1 + mkdocs.yml | 8 ++++---- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5dad1443b..a89254349 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -184,6 +184,11 @@ jobs: run: | ./tests/test_docs.sh + - name: Documentation build + run: | + pip install -r docs/requirements-docs.txt + mkdocs build + - name: Slack Notification uses: homoluctus/slatify@v1.8.0 if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) diff --git a/docs/developer.md b/docs/developer.md index 788e961cd..8ef816d5d 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -96,7 +96,7 @@ Below is an outline of exception inheritance hierarchy: ## Modules -### Dynamic Pairlist +### Pairlists You have a great idea for a new pair selection algorithm you would like to try out? Great. Hopefully you also want to contribute this back upstream. diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 66225d6d4..69ae33649 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,2 +1,3 @@ mkdocs-material==6.0.2 mdx_truly_sane_lists==1.2 +pymdown-extensions==8.0.1 diff --git a/mkdocs.yml b/mkdocs.yml index 26494ae45..8d1ce1cfe 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -55,16 +55,16 @@ markdown_extensions: permalink: true - pymdownx.arithmatex: generic: true - - pymdownx.caret - - pymdownx.critic - pymdownx.details - pymdownx.inlinehilite - pymdownx.magiclink - - pymdownx.mark + - pymdownx.pathconverter - pymdownx.smartsymbols + - pymdownx.snippets: + base_path: docs + check_paths: true - pymdownx.tabbed - pymdownx.superfences - pymdownx.tasklist: custom_checkbox: true - - pymdownx.tilde - mdx_truly_sane_lists From f43bd250a2155ea276f9e30f8a9bf0c0728ca03d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 9 Oct 2020 09:02:44 +0200 Subject: [PATCH 09/35] Extract pairlists from configuration --- docs/configuration.md | 139 +------------------------------------ docs/includes/pairlists.md | 137 ++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 138 deletions(-) create mode 100644 docs/includes/pairlists.md diff --git a/docs/configuration.md b/docs/configuration.md index d6e26f80e..f8e8aabcd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -574,144 +574,7 @@ Assuming both buy and sell are using market orders, a configuration similar to t ``` Obviously, if only one side is using limit orders, different pricing combinations can be used. - -## Pairlists and Pairlist Handlers - -Pairlist Handlers define the list of pairs (pairlist) that the bot should trade. They are configured in the `pairlists` section of the configuration settings. - -In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) Pairlist Handler). - -Additionaly, [`AgeFilter`](#agefilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter) and [`SpreadFilter`](#spreadfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist. - -If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You should always configure either `StaticPairList` or `VolumePairList` as the starting Pairlist Handler. - -Inactive markets are always removed from the resulting pairlist. Explicitly blacklisted pairs (those in the `pair_blacklist` configuration setting) are also always removed from the resulting pairlist. - -### Available Pairlist Handlers - -* [`StaticPairList`](#static-pair-list) (default, if not configured differently) -* [`VolumePairList`](#volume-pair-list) -* [`AgeFilter`](#agefilter) -* [`PrecisionFilter`](#precisionfilter) -* [`PriceFilter`](#pricefilter) -* [`ShuffleFilter`](#shufflefilter) -* [`SpreadFilter`](#spreadfilter) - -!!! Tip "Testing pairlists" - Pairlist configurations can be quite tricky to get right. Best use the [`test-pairlist`](utils.md#test-pairlist) utility subcommand to test your configuration quickly. - -#### Static Pair List - -By default, the `StaticPairList` method is used, which uses a statically defined pair whitelist from the configuration. - -It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`. - -```json -"pairlists": [ - {"method": "StaticPairList"} - ], -``` - -#### 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`). - -When used in the chain of Pairlist Handlers in a non-leading position (after StaticPairList and other Pairlist Filters), `VolumePairList` considers outputs of previous Pairlist Handlers, adding its sorting/selection of the pairs by the trading volume. - -When used on the leading position of the chain of Pairlist Handlers, it does not consider `pair_whitelist` configuration setting, but selects the top assets from all available markets (with matching stake-currency) on the exchange. - -The `refresh_period` setting allows to define the period (in seconds), at which the pairlist will be refreshed. Defaults to 1800s (30 minutes). - -`VolumePairList` is based on the ticker data from exchange, as reported by the ccxt library: - -* The `quoteVolume` is the amount of quote (stake) currency traded (bought or sold) in last 24 hours. - -```json -"pairlists": [{ - "method": "VolumePairList", - "number_assets": 20, - "sort_key": "quoteVolume", - "refresh_period": 1800, -}], -``` - -#### AgeFilter - -Removes pairs that have been listed on the exchange for less than `min_days_listed` days (defaults to `10`). - -When pairs are first listed on an exchange they can suffer huge price drops and volatility -in the first few days while the pair goes through its price-discovery period. Bots can often -be caught out buying before the pair has finished dropping in price. - -This filter allows freqtrade to ignore pairs until they have been listed for at least `min_days_listed` days. - -#### PrecisionFilter - -Filters low-value coins which would not allow setting stoplosses. - -#### PriceFilter - -The `PriceFilter` allows filtering of pairs by price. Currently the following price filters are supported: - -* `min_price` -* `max_price` -* `low_price_ratio` - -The `min_price` setting removes pairs where the price is below the specified price. This is useful if you wish to avoid trading very low-priced pairs. -This option is disabled by default, and will only apply if set to > 0. - -The `max_price` setting removes pairs where the price is above the specified price. This is useful if you wish to trade only low-priced pairs. -This option is disabled by default, and will only apply if set to > 0. - -The `low_price_ratio` setting removes pairs where a raise of 1 price unit (pip) is above the `low_price_ratio` ratio. -This option is disabled by default, and will only apply if set to > 0. - -For `PriceFiler` at least one of its `min_price`, `max_price` or `low_price_ratio` settings must be applied. - -Calculation example: - -Min price precision for SHITCOIN/BTC is 8 decimals. If its price is 0.00000011 - one price step above would be 0.00000012, which is ~9% higher than the previous price value. You may filter out this pair by using PriceFilter with `low_price_ratio` set to 0.09 (9%) or with `min_price` set to 0.00000011, correspondingly. - -!!! Warning "Low priced pairs" - Low priced pairs with high "1 pip movements" are dangerous since they are often illiquid and it may also be impossible to place the desired stoploss, which can often result in high losses since price needs to be rounded to the next tradable price - so instead of having a stoploss of -5%, you could end up with a stoploss of -9% simply due to price rounding. - -#### ShuffleFilter - -Shuffles (randomizes) pairs in the pairlist. It can be used for preventing the bot from trading some of the pairs more frequently then others when you want all pairs be treated with the same priority. - -!!! Tip - You may set the `seed` value for this Pairlist to obtain reproducible results, which can be useful for repeated backtesting sessions. If `seed` is not set, the pairs are shuffled in the non-repeatable random order. - -#### SpreadFilter - -Removes pairs that have a difference between asks and bids above the specified ratio, `max_spread_ratio` (defaults to `0.005`). - -Example: - -If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio is calculated as: `1 - bid/ask ~= 0.037` which is `> 0.005` and this pair will be filtered out. - -### Full example of Pairlist Handlers - -The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting pairs by `quoteVolume` and applies both [`PrecisionFilter`](#precisionfilter) and [`PriceFilter`](#price-filter), filtering all assets where 1 priceunit is > 1%. Then the `SpreadFilter` is applied and pairs are finally shuffled with the random seed set to some predefined value. - -```json -"exchange": { - "pair_whitelist": [], - "pair_blacklist": ["BNB/BTC"] -}, -"pairlists": [ - { - "method": "VolumePairList", - "number_assets": 20, - "sort_key": "quoteVolume", - }, - {"method": "AgeFilter", "min_days_listed": 10}, - {"method": "PrecisionFilter"}, - {"method": "PriceFilter", "low_price_ratio": 0.01}, - {"method": "SpreadFilter", "max_spread_ratio": 0.005}, - {"method": "ShuffleFilter", "seed": 42} - ], -``` +--8<-- "includes/pairlists.md" ## Switch to Dry-run mode diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md new file mode 100644 index 000000000..ae4ec818d --- /dev/null +++ b/docs/includes/pairlists.md @@ -0,0 +1,137 @@ +## Pairlists and Pairlist Handlers + +Pairlist Handlers define the list of pairs (pairlist) that the bot should trade. They are configured in the `pairlists` section of the configuration settings. + +In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) Pairlist Handler). + +Additionally, [`AgeFilter`](#agefilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter) and [`SpreadFilter`](#spreadfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist. + +If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You should always configure either `StaticPairList` or `VolumePairList` as the starting Pairlist Handler. + +Inactive markets are always removed from the resulting pairlist. Explicitly blacklisted pairs (those in the `pair_blacklist` configuration setting) are also always removed from the resulting pairlist. + +### Available Pairlist Handlers + +* [`StaticPairList`](#static-pair-list) (default, if not configured differently) +* [`VolumePairList`](#volume-pair-list) +* [`AgeFilter`](#agefilter) +* [`PrecisionFilter`](#precisionfilter) +* [`PriceFilter`](#pricefilter) +* [`ShuffleFilter`](#shufflefilter) +* [`SpreadFilter`](#spreadfilter) + +!!! Tip "Testing pairlists" + Pairlist configurations can be quite tricky to get right. Best use the [`test-pairlist`](utils.md#test-pairlist) utility sub-command to test your configuration quickly. + +#### Static Pair List + +By default, the `StaticPairList` method is used, which uses a statically defined pair whitelist from the configuration. + +It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`. + +```json +"pairlists": [ + {"method": "StaticPairList"} + ], +``` + +#### 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`). + +When used in the chain of Pairlist Handlers in a non-leading position (after StaticPairList and other Pairlist Filters), `VolumePairList` considers outputs of previous Pairlist Handlers, adding its sorting/selection of the pairs by the trading volume. + +When used on the leading position of the chain of Pairlist Handlers, it does not consider `pair_whitelist` configuration setting, but selects the top assets from all available markets (with matching stake-currency) on the exchange. + +The `refresh_period` setting allows to define the period (in seconds), at which the pairlist will be refreshed. Defaults to 1800s (30 minutes). + +`VolumePairList` is based on the ticker data from exchange, as reported by the ccxt library: + +* The `quoteVolume` is the amount of quote (stake) currency traded (bought or sold) in last 24 hours. + +```json +"pairlists": [{ + "method": "VolumePairList", + "number_assets": 20, + "sort_key": "quoteVolume", + "refresh_period": 1800, +}], +``` + +#### AgeFilter + +Removes pairs that have been listed on the exchange for less than `min_days_listed` days (defaults to `10`). + +When pairs are first listed on an exchange they can suffer huge price drops and volatility +in the first few days while the pair goes through its price-discovery period. Bots can often +be caught out buying before the pair has finished dropping in price. + +This filter allows freqtrade to ignore pairs until they have been listed for at least `min_days_listed` days. + +#### PrecisionFilter + +Filters low-value coins which would not allow setting stoplosses. + +#### PriceFilter + +The `PriceFilter` allows filtering of pairs by price. Currently the following price filters are supported: + +* `min_price` +* `max_price` +* `low_price_ratio` + +The `min_price` setting removes pairs where the price is below the specified price. This is useful if you wish to avoid trading very low-priced pairs. +This option is disabled by default, and will only apply if set to > 0. + +The `max_price` setting removes pairs where the price is above the specified price. This is useful if you wish to trade only low-priced pairs. +This option is disabled by default, and will only apply if set to > 0. + +The `low_price_ratio` setting removes pairs where a raise of 1 price unit (pip) is above the `low_price_ratio` ratio. +This option is disabled by default, and will only apply if set to > 0. + +For `PriceFiler` at least one of its `min_price`, `max_price` or `low_price_ratio` settings must be applied. + +Calculation example: + +Min price precision for SHITCOIN/BTC is 8 decimals. If its price is 0.00000011 - one price step above would be 0.00000012, which is ~9% higher than the previous price value. You may filter out this pair by using PriceFilter with `low_price_ratio` set to 0.09 (9%) or with `min_price` set to 0.00000011, correspondingly. + +!!! Warning "Low priced pairs" + Low priced pairs with high "1 pip movements" are dangerous since they are often illiquid and it may also be impossible to place the desired stoploss, which can often result in high losses since price needs to be rounded to the next tradable price - so instead of having a stoploss of -5%, you could end up with a stoploss of -9% simply due to price rounding. + +#### ShuffleFilter + +Shuffles (randomizes) pairs in the pairlist. It can be used for preventing the bot from trading some of the pairs more frequently then others when you want all pairs be treated with the same priority. + +!!! Tip + You may set the `seed` value for this Pairlist to obtain reproducible results, which can be useful for repeated backtesting sessions. If `seed` is not set, the pairs are shuffled in the non-repeatable random order. + +#### SpreadFilter + +Removes pairs that have a difference between asks and bids above the specified ratio, `max_spread_ratio` (defaults to `0.005`). + +Example: + +If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio is calculated as: `1 - bid/ask ~= 0.037` which is `> 0.005` and this pair will be filtered out. + +### Full example of Pairlist Handlers + +The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting pairs by `quoteVolume` and applies both [`PrecisionFilter`](#precisionfilter) and [`PriceFilter`](#price-filter), filtering all assets where 1 price unit is > 1%. Then the `SpreadFilter` is applied and pairs are finally shuffled with the random seed set to some predefined value. + +```json +"exchange": { + "pair_whitelist": [], + "pair_blacklist": ["BNB/BTC"] +}, +"pairlists": [ + { + "method": "VolumePairList", + "number_assets": 20, + "sort_key": "quoteVolume", + }, + {"method": "AgeFilter", "min_days_listed": 10}, + {"method": "PrecisionFilter"}, + {"method": "PriceFilter", "low_price_ratio": 0.01}, + {"method": "SpreadFilter", "max_spread_ratio": 0.005}, + {"method": "ShuffleFilter", "seed": 42} + ], +``` From cedddd02dac533ab79799c0dea8b0da5536797fe Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 9 Oct 2020 09:08:58 +0200 Subject: [PATCH 10/35] Install mkdocs for ci --- .github/workflows/ci.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a89254349..f259129d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} @@ -125,7 +125,7 @@ jobs: - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} @@ -184,9 +184,15 @@ jobs: run: | ./tests/test_docs.sh + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Documentation build run: | pip install -r docs/requirements-docs.txt + pip install mkdocs mkdocs build - name: Slack Notification @@ -229,7 +235,7 @@ jobs: - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: 3.8 From 23bad8fd9f320d2754c9d71e134c40a771b4e88f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 10 Oct 2020 14:22:29 +0200 Subject: [PATCH 11/35] Rename DefahltHyperoptLoss function to ShortTradeDurHyperOptLoss --- docs/bot-usage.md | 2 +- docs/hyperopt.md | 2 +- freqtrade/commands/cli_options.py | 4 ++-- freqtrade/optimize/default_hyperopt_loss.py | 8 ++++++-- tests/optimize/test_hyperopt.py | 16 ++++++++-------- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index a07a34b94..4d07435c7 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -353,7 +353,7 @@ optional arguments: class (IHyperOptLoss). Different functions can generate completely different results, since the target for optimization is different. Built-in - Hyperopt-loss-functions are: DefaultHyperOptLoss, + Hyperopt-loss-functions are: ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss, SharpeHyperOptLossDaily, SortinoHyperOptLoss, SortinoHyperOptLossDaily. diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 91bc32e48..5f5ffbee0 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -221,7 +221,7 @@ This class should be in its own file within the `user_data/hyperopts/` directory Currently, the following loss functions are builtin: -* `DefaultHyperOptLoss` (default legacy Freqtrade hyperoptimization loss function) - Mostly for short trade duration and avoiding losses. +* `ShortTradeHyperOptLoss` (default legacy Freqtrade hyperoptimization loss function) - Mostly for short trade duration and avoiding losses. * `OnlyProfitHyperOptLoss` (which takes only amount of profit into consideration) * `SharpeHyperOptLoss` (optimizes Sharpe Ratio calculated on trade returns relative to standard deviation) * `SharpeHyperOptLossDaily` (optimizes Sharpe Ratio calculated on **daily** trade returns relative to standard deviation) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index f991f6a4d..8ea945ae7 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -257,8 +257,8 @@ AVAILABLE_CLI_OPTIONS = { help='Specify the class name of the hyperopt loss function class (IHyperOptLoss). ' 'Different functions can generate completely different results, ' 'since the target for optimization is different. Built-in Hyperopt-loss-functions are: ' - 'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss, SharpeHyperOptLossDaily, ' - 'SortinoHyperOptLoss, SortinoHyperOptLossDaily.', + 'ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss, ' + 'SharpeHyperOptLossDaily, SortinoHyperOptLoss, SortinoHyperOptLossDaily.', metavar='NAME', ), "hyperoptexportfilename": Arg( diff --git a/freqtrade/optimize/default_hyperopt_loss.py b/freqtrade/optimize/default_hyperopt_loss.py index 9e780d0ea..9dbdc4403 100644 --- a/freqtrade/optimize/default_hyperopt_loss.py +++ b/freqtrade/optimize/default_hyperopt_loss.py @@ -1,5 +1,5 @@ """ -DefaultHyperOptLoss +ShortTradeDurHyperOptLoss This module defines the default HyperoptLoss class which is being used for Hyperoptimization. """ @@ -26,7 +26,7 @@ EXPECTED_MAX_PROFIT = 3.0 MAX_ACCEPTED_TRADE_DURATION = 300 -class DefaultHyperOptLoss(IHyperOptLoss): +class ShortTradeDurHyperOptLoss(IHyperOptLoss): """ Defines the default loss function for hyperopt """ @@ -50,3 +50,7 @@ class DefaultHyperOptLoss(IHyperOptLoss): duration_loss = 0.4 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1) result = trade_loss + profit_loss + duration_loss return result + + +# Create an alias for This to allow the legacy Method to work as well. +DefaultHyperOptLoss = ShortTradeDurHyperOptLoss diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index f699473f7..41ad6f5de 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -17,7 +17,7 @@ from freqtrade import constants from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt from freqtrade.data.history import load_data from freqtrade.exceptions import DependencyException, OperationalException -from freqtrade.optimize.default_hyperopt_loss import DefaultHyperOptLoss +from freqtrade.optimize.default_hyperopt_loss import ShortTradeDurHyperOptLoss from freqtrade.optimize.hyperopt import Hyperopt from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver, HyperOptResolver from freqtrade.state import RunMode @@ -33,7 +33,7 @@ def hyperopt_conf(default_conf): hyperconf = deepcopy(default_conf) hyperconf.update({ 'hyperopt': 'DefaultHyperOpt', - 'hyperopt_loss': 'DefaultHyperOptLoss', + 'hyperopt_loss': 'ShortTradeDurHyperOptLoss', 'hyperopt_path': str(Path(__file__).parent / 'hyperopts'), 'epochs': 1, 'timerange': None, @@ -239,12 +239,12 @@ def test_hyperoptlossresolver_noname(default_conf): def test_hyperoptlossresolver(mocker, default_conf) -> None: - hl = DefaultHyperOptLoss + hl = ShortTradeDurHyperOptLoss mocker.patch( 'freqtrade.resolvers.hyperopt_resolver.HyperOptLossResolver.load_object', MagicMock(return_value=hl) ) - default_conf.update({'hyperopt_loss': 'DefaultHyperoptLoss'}) + default_conf.update({'hyperopt_loss': 'SharpeHyperOptLossDaily'}) x = HyperOptLossResolver.load_hyperoptloss(default_conf) assert hasattr(x, "hyperopt_loss_function") @@ -287,7 +287,7 @@ def test_start(mocker, hyperopt_conf, caplog) -> None: 'hyperopt', '--config', 'config.json', '--hyperopt', 'DefaultHyperOpt', - '--hyperopt-loss', 'DefaultHyperOptLoss', + '--hyperopt-loss', 'SharpeHyperOptLossDaily', '--epochs', '5' ] pargs = get_args(args) @@ -311,7 +311,7 @@ def test_start_no_data(mocker, hyperopt_conf) -> None: 'hyperopt', '--config', 'config.json', '--hyperopt', 'DefaultHyperOpt', - '--hyperopt-loss', 'DefaultHyperOptLoss', + '--hyperopt-loss', 'SharpeHyperOptLossDaily', '--epochs', '5' ] pargs = get_args(args) @@ -329,7 +329,7 @@ def test_start_filelock(mocker, hyperopt_conf, caplog) -> None: 'hyperopt', '--config', 'config.json', '--hyperopt', 'DefaultHyperOpt', - '--hyperopt-loss', 'DefaultHyperOptLoss', + '--hyperopt-loss', 'SharpeHyperOptLossDaily', '--epochs', '5' ] pargs = get_args(args) @@ -384,7 +384,7 @@ def test_sharpe_loss_prefers_higher_profits(default_conf, hyperopt_results) -> N results_under = hyperopt_results.copy() results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2 - default_conf.update({'hyperopt_loss': 'SharpeHyperOptLoss'}) + default_conf.update({'hyperopt_loss': 'SharpeHyperOptLossDaily'}) hl = HyperOptLossResolver.load_hyperoptloss(default_conf) correct = hl.hyperopt_loss_function(hyperopt_results, len(hyperopt_results), datetime(2019, 1, 1), datetime(2019, 5, 1)) From 3d911557d1aed4301a2d38679b24027e54c9bc5e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 11 Oct 2020 08:37:47 +0200 Subject: [PATCH 12/35] Fix typo in docs --- docs/hyperopt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 5f5ffbee0..fc7a0dd93 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -221,7 +221,7 @@ This class should be in its own file within the `user_data/hyperopts/` directory Currently, the following loss functions are builtin: -* `ShortTradeHyperOptLoss` (default legacy Freqtrade hyperoptimization loss function) - Mostly for short trade duration and avoiding losses. +* `ShortTradeDurHyperOptLoss` (default legacy Freqtrade hyperoptimization loss function) - Mostly for short trade duration and avoiding losses. * `OnlyProfitHyperOptLoss` (which takes only amount of profit into consideration) * `SharpeHyperOptLoss` (optimizes Sharpe Ratio calculated on trade returns relative to standard deviation) * `SharpeHyperOptLossDaily` (optimizes Sharpe Ratio calculated on **daily** trade returns relative to standard deviation) From fa7dc742d0b288c10c44665ec774a63ce5b7ebc3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Oct 2020 06:07:57 +0200 Subject: [PATCH 13/35] Plot-image should have freqtrade as entrypoint --- docker/Dockerfile.plot | 3 --- 1 file changed, 3 deletions(-) diff --git a/docker/Dockerfile.plot b/docker/Dockerfile.plot index 1843efdcb..40bc72bc5 100644 --- a/docker/Dockerfile.plot +++ b/docker/Dockerfile.plot @@ -5,6 +5,3 @@ FROM freqtradeorg/freqtrade:${sourceimage} COPY requirements-plot.txt /freqtrade/ RUN pip install -r requirements-plot.txt --no-cache-dir - -# Empty the ENTRYPOINT to allow all commands -ENTRYPOINT [] From 491af5a0cbe6af346745558735b9e8972bc7a496 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 05:43:19 +0000 Subject: [PATCH 14/35] Bump pandas from 1.1.2 to 1.1.3 Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.1.2 to 1.1.3. - [Release notes](https://github.com/pandas-dev/pandas/releases) - [Changelog](https://github.com/pandas-dev/pandas/blob/master/RELEASE.md) - [Commits](https://github.com/pandas-dev/pandas/compare/v1.1.2...v1.1.3) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 51313c32c..47847c637 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ numpy==1.19.2 -pandas==1.1.2 +pandas==1.1.3 ccxt==1.35.22 SQLAlchemy==1.3.19 From a2bc9d60a08890d5002aa2c5ebcc3760cebc7d42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 05:43:23 +0000 Subject: [PATCH 15/35] Bump arrow from 0.16.0 to 0.17.0 Bumps [arrow](https://github.com/arrow-py/arrow) from 0.16.0 to 0.17.0. - [Release notes](https://github.com/arrow-py/arrow/releases) - [Changelog](https://github.com/arrow-py/arrow/blob/master/CHANGELOG.rst) - [Commits](https://github.com/arrow-py/arrow/compare/0.16.0...0.17.0) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 51313c32c..b0f9a84e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ pandas==1.1.2 ccxt==1.35.22 SQLAlchemy==1.3.19 python-telegram-bot==12.8 -arrow==0.16.0 +arrow==0.17.0 cachetools==4.1.1 requests==2.24.0 urllib3==1.25.10 From a33865e8c289d24b11269e6d88528e0aaaf41175 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 05:43:37 +0000 Subject: [PATCH 16/35] Bump nbconvert from 6.0.6 to 6.0.7 Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 6.0.6 to 6.0.7. - [Release notes](https://github.com/jupyter/nbconvert/releases) - [Commits](https://github.com/jupyter/nbconvert/compare/6.0.6...6.0.7) Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0710882a4..7330d079b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -16,4 +16,4 @@ pytest-random-order==1.0.4 isort==5.5.4 # Convert jupyter notebooks to markdown documents -nbconvert==6.0.6 +nbconvert==6.0.7 From 80569c5f2179d4ba6ac8cf7df3f9fa9561297a65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 05:43:38 +0000 Subject: [PATCH 17/35] Bump mypy from 0.782 to 0.790 Bumps [mypy](https://github.com/python/mypy) from 0.782 to 0.790. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.782...v0.790) Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0710882a4..a622bac5d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ coveralls==2.1.2 flake8==3.8.4 flake8-type-annotations==0.1.0 flake8-tidy-imports==4.1.0 -mypy==0.782 +mypy==0.790 pytest==6.1.1 pytest-asyncio==0.14.0 pytest-cov==2.10.1 From 623cee61e676362dbdd8c2f13c69dcfd7facd7cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 07:31:08 +0000 Subject: [PATCH 18/35] Bump isort from 5.5.4 to 5.6.3 Bumps [isort](https://github.com/pycqa/isort) from 5.5.4 to 5.6.3. - [Release notes](https://github.com/pycqa/isort/releases) - [Changelog](https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md) - [Commits](https://github.com/pycqa/isort/compare/5.5.4...5.6.3) Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 7330d079b..316c8cb8e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -13,7 +13,7 @@ pytest-asyncio==0.14.0 pytest-cov==2.10.1 pytest-mock==3.3.1 pytest-random-order==1.0.4 -isort==5.5.4 +isort==5.6.3 # Convert jupyter notebooks to markdown documents nbconvert==6.0.7 From e39c2f4a9630072546b54015b9bd8ff74297a59b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 07:35:11 +0000 Subject: [PATCH 19/35] Bump ccxt from 1.35.22 to 1.36.2 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.35.22 to 1.36.2. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst) - [Commits](https://github.com/ccxt/ccxt/compare/1.35.22...1.36.2) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8c6635aaa..1d0290b3c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.19.2 pandas==1.1.3 -ccxt==1.35.22 +ccxt==1.36.2 SQLAlchemy==1.3.19 python-telegram-bot==12.8 arrow==0.17.0 From f299c4188b1f1372c23c7e77fe6cad49ffed8c71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 08:13:28 +0000 Subject: [PATCH 20/35] Bump python-telegram-bot from 12.8 to 13.0 Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 12.8 to 13.0. - [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases) - [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst) - [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v12.8...v13.0) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1d0290b3c..8a344a716 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ pandas==1.1.3 ccxt==1.36.2 SQLAlchemy==1.3.19 -python-telegram-bot==12.8 +python-telegram-bot==13.0 arrow==0.17.0 cachetools==4.1.1 requests==2.24.0 From 44e374878c3443745fe17e13c6326fffee7e2f74 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Oct 2020 19:28:14 +0200 Subject: [PATCH 21/35] Fix mypy errors due to new version --- freqtrade/freqtradebot.py | 4 ++-- freqtrade/misc.py | 4 ++-- freqtrade/pairlist/IPairList.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2bdd8da4b..9562519aa 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -58,8 +58,8 @@ class FreqtradeBot: # Cache values for 1800 to avoid frequent polling of the exchange for prices # Caching only applies to RPC methods, so prices for open trades are still # refreshed once every iteration. - self._sell_rate_cache = TTLCache(maxsize=100, ttl=1800) - self._buy_rate_cache = TTLCache(maxsize=100, ttl=1800) + self._sell_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800) + self._buy_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800) self.strategy: IStrategy = StrategyResolver.load_strategy(self.config) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 071693f8d..359d0d0e4 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -56,8 +56,8 @@ def file_dump_json(filename: Path, data: Any, is_zip: bool = False, log: bool = if log: logger.info(f'dumping json to "{filename}"') - with gzip.open(filename, 'w') as fp: - rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE) + with gzip.open(filename, 'w') as fpz: + rapidjson.dump(data, fpz, default=str, number_mode=rapidjson.NM_NATIVE) else: if log: logger.info(f'dumping json to "{filename}"') diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 67a96cc60..6b5bd11e7 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -36,7 +36,7 @@ class IPairList(ABC): self._pairlist_pos = pairlist_pos self.refresh_period = self._pairlistconfig.get('refresh_period', 1800) self._last_refresh = 0 - self._log_cache = TTLCache(maxsize=1024, ttl=self.refresh_period) + self._log_cache: TTLCache = TTLCache(maxsize=1024, ttl=self.refresh_period) @property def name(self) -> str: From a39898a5b3cb3bd55c0567e2dbe0fb18ce88b885 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Oct 2020 19:44:13 +0200 Subject: [PATCH 22/35] Fix mock for telegram update --- tests/conftest.py | 2 +- tests/rpc/test_rpc_telegram.py | 17 +++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2153fd327..f90f0e62b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -297,7 +297,7 @@ def default_conf(testdatadir): @pytest.fixture def update(): _update = Update(0) - _update.message = Message(0, 0, datetime.utcnow(), Chat(0, 0)) + _update.message = Message(0, datetime.utcnow(), Chat(0, 0)) return _update diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index c62282cf0..230df0df9 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -82,7 +82,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None: assert log_has(message_str, caplog) -def test_cleanup(default_conf, mocker) -> None: +def test_cleanup(default_conf, mocker, ) -> None: updater_mock = MagicMock() updater_mock.stop = MagicMock() mocker.patch('freqtrade.rpc.telegram.Updater', updater_mock) @@ -92,13 +92,9 @@ def test_cleanup(default_conf, mocker) -> None: assert telegram._updater.stop.call_count == 1 -def test_authorized_only(default_conf, mocker, caplog) -> None: +def test_authorized_only(default_conf, mocker, caplog, update) -> None: patch_exchange(mocker) - chat = Chat(0, 0) - update = Update(randint(1, 100)) - update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat) - default_conf['telegram']['enabled'] = False bot = FreqtradeBot(default_conf) patch_get_signal(bot, (True, False)) @@ -114,7 +110,7 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: patch_exchange(mocker) chat = Chat(0xdeadbeef, 0) update = Update(randint(1, 100)) - update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat) + update.message = Message(randint(1, 100), datetime.utcnow(), chat) default_conf['telegram']['enabled'] = False bot = FreqtradeBot(default_conf) @@ -127,12 +123,9 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: assert not log_has('Exception occurred within Telegram module', caplog) -def test_authorized_only_exception(default_conf, mocker, caplog) -> None: +def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None: patch_exchange(mocker) - update = Update(randint(1, 100)) - update.message = Message(randint(1, 100), 0, datetime.utcnow(), Chat(0, 0)) - default_conf['telegram']['enabled'] = False bot = FreqtradeBot(default_conf) @@ -146,7 +139,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None: assert log_has('Exception occurred within Telegram module', caplog) -def test_telegram_status(default_conf, update, mocker, fee, ticker,) -> None: +def test_telegram_status(default_conf, update, mocker) -> None: update.message.chat.id = "123" default_conf['telegram']['enabled'] = False default_conf['telegram']['chat_id'] = "123" From 5aa0d3e05c08a8ecfe15428173de3596c17a5e27 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Oct 2020 20:08:40 +0200 Subject: [PATCH 23/35] Add multidict and aiohttp requirements --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 51313c32c..986739b46 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,8 @@ numpy==1.19.2 pandas==1.1.2 ccxt==1.35.22 +multidict==4.7.6 +aiohttp==3.6.3 SQLAlchemy==1.3.19 python-telegram-bot==12.8 arrow==0.16.0 From 43532a2ffa265129498bbb3135ba952a68e7f74b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 12:39:31 +0000 Subject: [PATCH 24/35] Bump colorama from 0.4.3 to 0.4.4 Bumps [colorama](https://github.com/tartley/colorama) from 0.4.3 to 0.4.4. - [Release notes](https://github.com/tartley/colorama/releases) - [Changelog](https://github.com/tartley/colorama/blob/master/CHANGELOG.rst) - [Commits](https://github.com/tartley/colorama/commits) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ef9182f3b..a03b4ba20 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,7 +34,7 @@ flask-jwt-extended==3.24.1 flask-cors==3.0.9 # Support for colorized terminal output -colorama==0.4.3 +colorama==0.4.4 # Building config files interactively questionary==1.6.0 prompt-toolkit==3.0.7 From 5f5fc513fa710ec83423af4f3c250cebf166b259 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 12:39:35 +0000 Subject: [PATCH 25/35] Bump isort from 5.6.3 to 5.6.4 Bumps [isort](https://github.com/pycqa/isort) from 5.6.3 to 5.6.4. - [Release notes](https://github.com/pycqa/isort/releases) - [Changelog](https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md) - [Commits](https://github.com/pycqa/isort/compare/5.6.3...5.6.4) Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 33aae2dfb..916bb2ec2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -13,7 +13,7 @@ pytest-asyncio==0.14.0 pytest-cov==2.10.1 pytest-mock==3.3.1 pytest-random-order==1.0.4 -isort==5.6.3 +isort==5.6.4 # Convert jupyter notebooks to markdown documents nbconvert==6.0.7 From fd9c8df049eda9598b596458e6263af5cdb08720 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 12:39:47 +0000 Subject: [PATCH 26/35] Bump ccxt from 1.36.2 to 1.36.12 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.36.2 to 1.36.12. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst) - [Commits](https://github.com/ccxt/ccxt/compare/1.36.2...1.36.12) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ef9182f3b..17008d48a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.19.2 pandas==1.1.3 -ccxt==1.36.2 +ccxt==1.36.12 multidict==4.7.6 aiohttp==3.6.3 SQLAlchemy==1.3.19 From 7c1402ef1144d76f612d9194cfd0f4960e4390d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 13:06:37 +0000 Subject: [PATCH 27/35] Bump prompt-toolkit from 3.0.7 to 3.0.8 Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.7 to 3.0.8. - [Release notes](https://github.com/prompt-toolkit/python-prompt-toolkit/releases) - [Changelog](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/CHANGELOG) - [Commits](https://github.com/prompt-toolkit/python-prompt-toolkit/commits) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a03b4ba20..9b0b53cae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,4 +37,4 @@ flask-cors==3.0.9 colorama==0.4.4 # Building config files interactively questionary==1.6.0 -prompt-toolkit==3.0.7 +prompt-toolkit==3.0.8 From 6a0ab83684fc1a2234408362b8e22d0237d538e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 13:20:24 +0000 Subject: [PATCH 28/35] Bump sqlalchemy from 1.3.19 to 1.3.20 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.3.19 to 1.3.20. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/master/CHANGES) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 786b1d4a7..72d16465c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ pandas==1.1.3 ccxt==1.36.12 multidict==4.7.6 aiohttp==3.6.3 -SQLAlchemy==1.3.19 +SQLAlchemy==1.3.20 python-telegram-bot==13.0 arrow==0.17.0 cachetools==4.1.1 From 077374ac42eda2fb4166d46288cee439f3acd245 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Oct 2020 20:02:47 +0200 Subject: [PATCH 29/35] Implement generic solution for l2 limited limit --- freqtrade/exchange/binance.py | 13 +------------ freqtrade/exchange/exchange.py | 15 +++++++++++++-- tests/exchange/test_exchange.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index b85802aad..099f282a2 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -20,20 +20,9 @@ class Binance(Exchange): "order_time_in_force": ['gtc', 'fok', 'ioc'], "trades_pagination": "id", "trades_pagination_arg": "fromId", + "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], } - def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict: - """ - get order book level 2 from exchange - - 20180619: binance support limits but only on specific range - """ - limit_range = [5, 10, 20, 50, 100, 500, 1000] - # get next-higher step in the limit_range list - limit = min(list(filter(lambda x: limit <= x, limit_range))) - - return super().fetch_l2_order_book(pair, limit) - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index bbb94e61f..66126785f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -53,7 +53,7 @@ class Exchange: "ohlcv_partial_candle": True, "trades_pagination": "time", # Possible are "time" or "id" "trades_pagination_arg": "since", - + "l2_limit_range": None, } _ft_has: Dict = {} @@ -1069,6 +1069,16 @@ class Exchange: return self.fetch_stoploss_order(order_id, pair) return self.fetch_order(order_id, pair) + @staticmethod + def get_next_limit_in_list(limit: int, limit_range: Optional[List[int]]): + """ + Get next greater limit + """ + if not limit_range: + return limit + + return min(list(filter(lambda x: limit <= x, limit_range))) + @retrier def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict: """ @@ -1077,9 +1087,10 @@ class Exchange: Returns a dict in the format {'asks': [price, volume], 'bids': [price, volume]} """ + limit1 = self.get_next_limit_in_list(limit, self._ft_has['l2_limit_range']) try: - return self._api.fetch_l2_order_book(pair, limit) + return self._api.fetch_l2_order_book(pair, limit1) except ccxt.NotSupported as e: raise OperationalException( f'Exchange {self._api.name} does not support fetching order book.' diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 7be9c77ac..4adc8b2ea 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1438,6 +1438,25 @@ def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog): assert log_has("Async code raised an exception: TypeError", caplog) +def test_get_next_limit_in_list(): + limit_range = [5, 10, 20, 50, 100, 500, 1000] + assert Exchange.get_next_limit_in_list(1, limit_range) == 5 + assert Exchange.get_next_limit_in_list(5, limit_range) == 5 + assert Exchange.get_next_limit_in_list(6, limit_range) == 10 + assert Exchange.get_next_limit_in_list(9, limit_range) == 10 + assert Exchange.get_next_limit_in_list(10, limit_range) == 10 + assert Exchange.get_next_limit_in_list(11, limit_range) == 20 + assert Exchange.get_next_limit_in_list(19, limit_range) == 20 + assert Exchange.get_next_limit_in_list(21, limit_range) == 50 + assert Exchange.get_next_limit_in_list(51, limit_range) == 100 + assert Exchange.get_next_limit_in_list(1000, limit_range) == 1000 + # assert Exchange.get_next_limit_in_list(1001, limit_range) == 1001 + + assert Exchange.get_next_limit_in_list(21, None) == 21 + assert Exchange.get_next_limit_in_list(100, None) == 100 + assert Exchange.get_next_limit_in_list(1000, None) == 1000 + + @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_fetch_l2_order_book(default_conf, mocker, order_book_l2, exchange_name): default_conf['exchange']['name'] = exchange_name @@ -1450,6 +1469,20 @@ def test_fetch_l2_order_book(default_conf, mocker, order_book_l2, exchange_name) assert 'asks' in order_book assert len(order_book['bids']) == 10 assert len(order_book['asks']) == 10 + assert api_mock.fetch_l2_order_book.call_args_list[0][0][0] == 'ETH/BTC' + assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == 10 + + for val in [1, 5, 12, 20, 50, 100]: + api_mock.fetch_l2_order_book.reset_mock() + + order_book = exchange.fetch_l2_order_book(pair='ETH/BTC', limit=val) + assert api_mock.fetch_l2_order_book.call_args_list[0][0][0] == 'ETH/BTC' + # Not all exchanges support all limits for orderbook + if not exchange._ft_has['l2_limit_range'] or val in exchange._ft_has['l2_limit_range']: + assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == val + else: + next_limit = exchange.get_next_limit_in_list(val, exchange._ft_has['l2_limit_range']) + assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == next_limit @pytest.mark.parametrize("exchange_name", EXCHANGES) From 8962b6d5c9ec49462b43d09c9e66b9b88b87284d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Oct 2020 20:09:43 +0200 Subject: [PATCH 30/35] Add Bittrex subclass to correctly handle L2 orderbook --- freqtrade/exchange/__init__.py | 1 + freqtrade/exchange/bittrex.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 freqtrade/exchange/bittrex.py diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index cbcf961bc..5b58d7a95 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -5,6 +5,7 @@ from freqtrade.exchange.exchange import Exchange # isort: on from freqtrade.exchange.bibox import Bibox from freqtrade.exchange.binance import Binance +from freqtrade.exchange.bittrex import Bittrex from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges, get_exchange_bad_reason, is_exchange_bad, is_exchange_known_ccxt, is_exchange_officially_supported, diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py new file mode 100644 index 000000000..4318f9cf0 --- /dev/null +++ b/freqtrade/exchange/bittrex.py @@ -0,0 +1,23 @@ +""" Bittrex exchange subclass """ +import logging +from typing import Dict + +from freqtrade.exchange import Exchange + + +logger = logging.getLogger(__name__) + + +class Bittrex(Exchange): + """ + Bittrex exchange class. Contains adjustments needed for Freqtrade to work + with this exchange. + + Please note that this exchange is not included in the list of exchanges + officially supported by the Freqtrade development team. So some features + may still not work as expected. + """ + + _ft_has: Dict = { + "l2_limit_range": [1, 25, 500], + } From 2ed20eee4e5a5e5c4b66deba5400293744271931 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Oct 2020 20:10:50 +0200 Subject: [PATCH 31/35] Configs should default to dry-run --- config.json.example | 4 ++-- config_full.json.example | 2 +- config_kraken.json.example | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/config.json.example b/config.json.example index ab517b77c..af45dac74 100644 --- a/config.json.example +++ b/config.json.example @@ -5,15 +5,15 @@ "tradable_balance_ratio": 0.99, "fiat_display_currency": "USD", "timeframe": "5m", - "dry_run": false, + "dry_run": true, "cancel_open_orders_on_exit": false, "unfilledtimeout": { "buy": 10, "sell": 30 }, "bid_strategy": { - "ask_last_balance": 0.0, "use_order_book": false, + "ask_last_balance": 0.0, "order_book_top": 1, "check_depth_of_market": { "enabled": false, diff --git a/config_full.json.example b/config_full.json.example index 659580fb1..45c5c695c 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -7,7 +7,7 @@ "amount_reserve_percent": 0.05, "amend_last_stake_amount": false, "last_stake_amount_min_ratio": 0.5, - "dry_run": false, + "dry_run": true, "cancel_open_orders_on_exit": false, "timeframe": "5m", "trailing_stop": false, diff --git a/config_kraken.json.example b/config_kraken.json.example index fd0b2b95d..5f3b57854 100644 --- a/config_kraken.json.example +++ b/config_kraken.json.example @@ -27,12 +27,11 @@ "use_sell_signal": true, "sell_profit_only": false, "ignore_roi_if_buy_signal": false - }, "exchange": { "name": "kraken", - "key": "", - "secret": "", + "key": "your_exchange_key", + "secret": "your_exchange_key", "ccxt_config": {"enableRateLimit": true}, "ccxt_async_config": { "enableRateLimit": true, From 8165cc11df8f3b68806ae2668b377ae4c17d4084 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Oct 2020 20:29:51 +0200 Subject: [PATCH 32/35] Change get_next_limit_in_list to use list comprehension --- freqtrade/exchange/exchange.py | 6 +++--- tests/data/test_dataprovider.py | 2 +- tests/exchange/test_exchange.py | 15 +++++++++++---- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 66126785f..ffe81ab39 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1072,12 +1072,12 @@ class Exchange: @staticmethod def get_next_limit_in_list(limit: int, limit_range: Optional[List[int]]): """ - Get next greater limit + Get next greater value in the list. + Used by fetch_l2_order_book if the api only supports a limited range """ if not limit_range: return limit - - return min(list(filter(lambda x: limit <= x, limit_range))) + return min([x for x in limit_range if limit <= x]) @retrier def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict: diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index c2ecf4b80..a64dce908 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -132,7 +132,7 @@ def test_orderbook(mocker, default_conf, order_book_l2): res = dp.orderbook('ETH/BTC', 5) assert order_book_l2.call_count == 1 assert order_book_l2.call_args_list[0][0][0] == 'ETH/BTC' - assert order_book_l2.call_args_list[0][0][1] == 5 + assert order_book_l2.call_args_list[0][0][1] >= 5 assert type(res) is dict assert 'bids' in res diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 4adc8b2ea..7b86d3866 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -11,7 +11,7 @@ from pandas import DataFrame from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException, OperationalException, TemporaryError) -from freqtrade.exchange import Binance, Exchange, Kraken +from freqtrade.exchange import Binance, Bittrex, Exchange, Kraken from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT, calculate_backoff) from freqtrade.exchange.exchange import (market_is_active, timeframe_to_minutes, timeframe_to_msecs, @@ -148,11 +148,19 @@ def test_exchange_resolver(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - exchange = ExchangeResolver.load_exchange('Bittrex', default_conf) + + exchange = ExchangeResolver.load_exchange('huobi', default_conf) assert isinstance(exchange, Exchange) assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog) caplog.clear() + exchange = ExchangeResolver.load_exchange('Bittrex', default_conf) + assert isinstance(exchange, Exchange) + assert isinstance(exchange, Bittrex) + assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", + caplog) + caplog.clear() + exchange = ExchangeResolver.load_exchange('kraken', default_conf) assert isinstance(exchange, Exchange) assert isinstance(exchange, Kraken) @@ -1470,9 +1478,8 @@ def test_fetch_l2_order_book(default_conf, mocker, order_book_l2, exchange_name) assert len(order_book['bids']) == 10 assert len(order_book['asks']) == 10 assert api_mock.fetch_l2_order_book.call_args_list[0][0][0] == 'ETH/BTC' - assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == 10 - for val in [1, 5, 12, 20, 50, 100]: + for val in [1, 5, 10, 12, 20, 50, 100]: api_mock.fetch_l2_order_book.reset_mock() order_book = exchange.fetch_l2_order_book(pair='ETH/BTC', limit=val) From 07da21e6335bc511e7ba363545b6b47b590721da Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Oct 2020 20:38:02 +0200 Subject: [PATCH 33/35] Fix problem when limit is > max allowed limit --- freqtrade/exchange/exchange.py | 2 +- tests/exchange/test_exchange.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index ffe81ab39..c0d737f26 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1077,7 +1077,7 @@ class Exchange: """ if not limit_range: return limit - return min([x for x in limit_range if limit <= x]) + return min([x for x in limit_range if limit <= x] + [max(limit_range)]) @retrier def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict: diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 7b86d3866..19f2c7239 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1458,7 +1458,9 @@ def test_get_next_limit_in_list(): assert Exchange.get_next_limit_in_list(21, limit_range) == 50 assert Exchange.get_next_limit_in_list(51, limit_range) == 100 assert Exchange.get_next_limit_in_list(1000, limit_range) == 1000 - # assert Exchange.get_next_limit_in_list(1001, limit_range) == 1001 + # Going over the limit ... + assert Exchange.get_next_limit_in_list(1001, limit_range) == 1000 + assert Exchange.get_next_limit_in_list(2000, limit_range) == 1000 assert Exchange.get_next_limit_in_list(21, None) == 21 assert Exchange.get_next_limit_in_list(100, None) == 100 From ec713ff5aee4657a3e968ed515906d5c95ca1bb9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 16 Oct 2020 06:26:57 +0200 Subject: [PATCH 34/35] Convert _rpc_analysed_history_full to static method --- freqtrade/rpc/api_server.py | 2 +- freqtrade/rpc/rpc.py | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index 4e262b1ec..f31d7b0b5 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -563,7 +563,7 @@ class ApiServer(RPC): config.update({ 'strategy': strategy, }) - results = self._rpc_analysed_history_full(config, pair, timeframe, timerange) + results = RPC._rpc_analysed_history_full(config, pair, timeframe, timerange) return jsonify(results) @require_login diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index b89284acf..911b2d731 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -656,8 +656,9 @@ class RPC: raise RPCException('Edge is not enabled.') return self._freqtrade.edge.accepted_pairs() - def _convert_dataframe_to_dict(self, strategy: str, pair: str, timeframe: str, - dataframe: DataFrame, last_analyzed: datetime) -> Dict[str, Any]: + @staticmethod + def _convert_dataframe_to_dict(strategy: str, pair: str, timeframe: str, dataframe: DataFrame, + last_analyzed: datetime) -> Dict[str, Any]: has_content = len(dataframe) != 0 buy_signals = 0 sell_signals = 0 @@ -711,7 +712,8 @@ class RPC: return self._convert_dataframe_to_dict(self._freqtrade.config['strategy'], pair, timeframe, _data, last_analyzed) - def _rpc_analysed_history_full(self, config, pair: str, timeframe: str, + @staticmethod + def _rpc_analysed_history_full(config, pair: str, timeframe: str, timerange: str) -> Dict[str, Any]: timerange_parsed = TimeRange.parse_timerange(timerange) @@ -726,8 +728,8 @@ class RPC: strategy = StrategyResolver.load_strategy(config) df_analyzed = strategy.analyze_ticker(_data[pair], {'pair': pair}) - return self._convert_dataframe_to_dict(strategy.get_strategy_name(), pair, timeframe, - df_analyzed, arrow.Arrow.utcnow().datetime) + return RPC._convert_dataframe_to_dict(strategy.get_strategy_name(), pair, timeframe, + df_analyzed, arrow.Arrow.utcnow().datetime) def _rpc_plot_config(self) -> Dict[str, Any]: From 8cdc795a44306a57bccd4f7caf64c36a5d8d48b3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 16 Oct 2020 07:39:12 +0200 Subject: [PATCH 35/35] Rename persistence.init to init_db --- freqtrade/commands/list_commands.py | 4 ++-- freqtrade/data/btanalysis.py | 5 ++--- freqtrade/freqtradebot.py | 8 ++++---- freqtrade/persistence/__init__.py | 2 +- freqtrade/persistence/models.py | 6 +++--- tests/commands/test_commands.py | 2 +- tests/conftest.py | 8 ++++---- tests/data/test_btanalysis.py | 2 +- tests/test_freqtradebot.py | 5 ++--- tests/test_main.py | 10 +++++----- tests/test_persistence.py | 20 ++++++++++---------- 11 files changed, 35 insertions(+), 37 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index e81ecf871..9e6076dfb 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -205,14 +205,14 @@ def start_show_trades(args: Dict[str, Any]) -> None: """ import json - from freqtrade.persistence import Trade, init + from freqtrade.persistence import Trade, init_db config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) if 'db_url' not in config: raise OperationalException("--db-url is required for this command.") logger.info(f'Using DB: "{config["db_url"]}"') - init(config['db_url'], clean_open_orders=False) + init_db(config['db_url'], clean_open_orders=False) tfilter = [] if config.get('trade_ids'): diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 6af685712..513fba9e7 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -9,10 +9,9 @@ from typing import Any, Dict, Optional, Tuple, Union import numpy as np import pandas as pd -from freqtrade import persistence from freqtrade.constants import LAST_BT_RESULT_FN from freqtrade.misc import json_load -from freqtrade.persistence import Trade +from freqtrade.persistence import Trade, init_db logger = logging.getLogger(__name__) @@ -218,7 +217,7 @@ def load_trades_from_db(db_url: str, strategy: Optional[str] = None) -> pd.DataF Can also serve as protection to load the correct result. :return: Dataframe containing Trades """ - persistence.init(db_url, clean_open_orders=False) + init_db(db_url, clean_open_orders=False) columns = ["pair", "open_date", "close_date", "profit", "profit_percent", "open_rate", "close_rate", "amount", "trade_duration", "sell_reason", diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 9562519aa..cfc68a3ec 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -12,7 +12,7 @@ from typing import Any, Dict, List, Optional import arrow from cachetools import TTLCache -from freqtrade import __version__, constants, persistence +from freqtrade import __version__, constants from freqtrade.configuration import validate_config_consistency from freqtrade.data.converter import order_book_to_dataframe from freqtrade.data.dataprovider import DataProvider @@ -22,7 +22,7 @@ from freqtrade.exceptions import (DependencyException, ExchangeError, Insufficie from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date from freqtrade.misc import safe_value_fallback, safe_value_fallback2 from freqtrade.pairlist.pairlistmanager import PairListManager -from freqtrade.persistence import Order, Trade +from freqtrade.persistence import Order, Trade, cleanup_db, init_db from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.state import State @@ -68,7 +68,7 @@ class FreqtradeBot: self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config) - persistence.init(self.config.get('db_url', None), clean_open_orders=self.config['dry_run']) + init_db(self.config.get('db_url', None), clean_open_orders=self.config['dry_run']) self.wallets = Wallets(self.config, self.exchange) @@ -123,7 +123,7 @@ class FreqtradeBot: self.check_for_open_trades() self.rpc.cleanup() - persistence.cleanup() + cleanup_db() def startup(self) -> None: """ diff --git a/freqtrade/persistence/__init__.py b/freqtrade/persistence/__init__.py index ee2e40267..a3ec13e98 100644 --- a/freqtrade/persistence/__init__.py +++ b/freqtrade/persistence/__init__.py @@ -1,3 +1,3 @@ # flake8: noqa: F401 -from freqtrade.persistence.models import Order, Trade, clean_dry_run_db, cleanup, init +from freqtrade.persistence.models import Order, Trade, clean_dry_run_db, cleanup_db, init_db diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 8455a3b77..e5acbf937 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -29,7 +29,7 @@ _DECL_BASE: Any = declarative_base() _SQL_DOCS_URL = 'http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls' -def init(db_url: str, clean_open_orders: bool = False) -> None: +def init_db(db_url: str, clean_open_orders: bool = False) -> None: """ Initializes this module with the given config, registers all known command handlers @@ -72,7 +72,7 @@ def init(db_url: str, clean_open_orders: bool = False) -> None: clean_dry_run_db() -def cleanup() -> None: +def cleanup_db() -> None: """ Flushes all pending operations to disk. :return: None @@ -399,7 +399,7 @@ class Trade(_DECL_BASE): self.close(order['average']) else: raise ValueError(f'Unknown order type: {order_type}') - cleanup() + cleanup_db() def close(self, rate: float) -> None: """ diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 5b125697c..713386a8e 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -1149,7 +1149,7 @@ def test_start_list_data(testdatadir, capsys): @pytest.mark.usefixtures("init_persistence") def test_show_trades(mocker, fee, capsys, caplog): - mocker.patch("freqtrade.persistence.init") + mocker.patch("freqtrade.persistence.init_db") create_mock_trades(fee) args = [ "show-trades", diff --git a/tests/conftest.py b/tests/conftest.py index f90f0e62b..520b53b31 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,13 +13,13 @@ import numpy as np import pytest from telegram import Chat, Message, Update -from freqtrade import constants, persistence +from freqtrade import constants from freqtrade.commands import Arguments from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.edge import Edge, PairInfo from freqtrade.exchange import Exchange from freqtrade.freqtradebot import FreqtradeBot -from freqtrade.persistence import Trade +from freqtrade.persistence import Trade, init_db from freqtrade.resolvers import ExchangeResolver from freqtrade.worker import Worker from tests.conftest_trades import (mock_trade_1, mock_trade_2, mock_trade_3, mock_trade_4, @@ -131,7 +131,7 @@ def patch_freqtradebot(mocker, config) -> None: :return: None """ mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - persistence.init(config['db_url']) + init_db(config['db_url']) patch_exchange(mocker) mocker.patch('freqtrade.freqtradebot.RPCManager._init', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock()) @@ -219,7 +219,7 @@ def patch_coingekko(mocker) -> None: @pytest.fixture(scope='function') def init_persistence(default_conf): - persistence.init(default_conf['db_url'], default_conf['dry_run']) + init_db(default_conf['db_url'], default_conf['dry_run']) @pytest.fixture(scope="function") diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 7696dd96a..1592fac10 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -114,7 +114,7 @@ def test_load_trades_from_db(default_conf, fee, mocker): create_mock_trades(fee) # remove init so it does not init again - init_mock = mocker.patch('freqtrade.persistence.init', MagicMock()) + init_mock = mocker.patch('freqtrade.data.btanalysis.init_db', MagicMock()) trades = load_trades_from_db(db_url=default_conf['db_url']) assert init_mock.call_count == 1 diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 8af3e12a7..bb7ff26e7 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -15,8 +15,7 @@ from freqtrade.exceptions import (DependencyException, ExchangeError, Insufficie InvalidOrderException, OperationalException, PricingError, TemporaryError) from freqtrade.freqtradebot import FreqtradeBot -from freqtrade.persistence import Trade -from freqtrade.persistence.models import Order +from freqtrade.persistence import Order, Trade from freqtrade.rpc import RPCMessageType from freqtrade.state import RunMode, State from freqtrade.strategy.interface import SellCheckTuple, SellType @@ -66,7 +65,7 @@ def test_process_stopped(mocker, default_conf) -> None: def test_bot_cleanup(mocker, default_conf, caplog) -> None: - mock_cleanup = mocker.patch('freqtrade.persistence.cleanup') + mock_cleanup = mocker.patch('freqtrade.freqtradebot.cleanup_db') coo_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cancel_all_open_orders') freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade.cleanup() diff --git a/tests/test_main.py b/tests/test_main.py index 9106d4c12..f55aea336 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -65,7 +65,7 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None: mocker.patch('freqtrade.worker.Worker._worker', MagicMock(side_effect=Exception)) patched_configuration_load_config_file(mocker, default_conf) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) + mocker.patch('freqtrade.freqtradebot.init_db', MagicMock()) args = ['trade', '-c', 'config.json.example'] @@ -83,7 +83,7 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None: patched_configuration_load_config_file(mocker, default_conf) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.wallets.Wallets.update', MagicMock()) - mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) + mocker.patch('freqtrade.freqtradebot.init_db', MagicMock()) args = ['trade', '-c', 'config.json.example'] @@ -104,7 +104,7 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None: patched_configuration_load_config_file(mocker, default_conf) mocker.patch('freqtrade.wallets.Wallets.update', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) + mocker.patch('freqtrade.freqtradebot.init_db', MagicMock()) args = ['trade', '-c', 'config.json.example'] @@ -155,7 +155,7 @@ def test_main_reload_config(mocker, default_conf, caplog) -> None: reconfigure_mock = mocker.patch('freqtrade.worker.Worker._reconfigure', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) + mocker.patch('freqtrade.freqtradebot.init_db', MagicMock()) args = Arguments(['trade', '-c', 'config.json.example']).get_parsed_arg() worker = Worker(args=args, config=default_conf) @@ -178,7 +178,7 @@ def test_reconfigure(mocker, default_conf) -> None: mocker.patch('freqtrade.wallets.Wallets.update', MagicMock()) patched_configuration_load_config_file(mocker, default_conf) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) + mocker.patch('freqtrade.freqtradebot.init_db', MagicMock()) args = Arguments(['trade', '-c', 'config.json.example']).get_parsed_arg() worker = Worker(args=args, config=default_conf) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index adfa18876..4216565ac 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -8,13 +8,13 @@ from sqlalchemy import create_engine from freqtrade import constants from freqtrade.exceptions import DependencyException, OperationalException -from freqtrade.persistence import Order, Trade, clean_dry_run_db, init +from freqtrade.persistence import Order, Trade, clean_dry_run_db, init_db from tests.conftest import create_mock_trades, log_has, log_has_re def test_init_create_session(default_conf): # Check if init create a session - init(default_conf['db_url'], default_conf['dry_run']) + init_db(default_conf['db_url'], default_conf['dry_run']) assert hasattr(Trade, 'session') assert 'scoped_session' in type(Trade.session).__name__ @@ -24,7 +24,7 @@ def test_init_custom_db_url(default_conf, mocker): default_conf.update({'db_url': 'sqlite:///tmp/freqtrade2_test.sqlite'}) create_engine_mock = mocker.patch('freqtrade.persistence.models.create_engine', MagicMock()) - init(default_conf['db_url'], default_conf['dry_run']) + init_db(default_conf['db_url'], default_conf['dry_run']) assert create_engine_mock.call_count == 1 assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tmp/freqtrade2_test.sqlite' @@ -33,7 +33,7 @@ def test_init_invalid_db_url(default_conf): # Update path to a value other than default, but still in-memory default_conf.update({'db_url': 'unknown:///some.url'}) with pytest.raises(OperationalException, match=r'.*no valid database URL*'): - init(default_conf['db_url'], default_conf['dry_run']) + init_db(default_conf['db_url'], default_conf['dry_run']) def test_init_prod_db(default_conf, mocker): @@ -42,7 +42,7 @@ def test_init_prod_db(default_conf, mocker): create_engine_mock = mocker.patch('freqtrade.persistence.models.create_engine', MagicMock()) - init(default_conf['db_url'], default_conf['dry_run']) + init_db(default_conf['db_url'], default_conf['dry_run']) assert create_engine_mock.call_count == 1 assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tradesv3.sqlite' @@ -53,7 +53,7 @@ def test_init_dryrun_db(default_conf, mocker): create_engine_mock = mocker.patch('freqtrade.persistence.models.create_engine', MagicMock()) - init(default_conf['db_url'], default_conf['dry_run']) + init_db(default_conf['db_url'], default_conf['dry_run']) assert create_engine_mock.call_count == 1 assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tradesv3.dryrun.sqlite' @@ -482,7 +482,7 @@ def test_migrate_old(mocker, default_conf, fee): engine.execute(insert_table_old) engine.execute(insert_table_old2) # Run init to test migration - init(default_conf['db_url'], default_conf['dry_run']) + init_db(default_conf['db_url'], default_conf['dry_run']) assert len(Trade.query.filter(Trade.id == 1).all()) == 1 trade = Trade.query.filter(Trade.id == 1).first() @@ -581,7 +581,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): engine.execute("create table trades_bak1 as select * from trades") # Run init to test migration - init(default_conf['db_url'], default_conf['dry_run']) + init_db(default_conf['db_url'], default_conf['dry_run']) assert len(Trade.query.filter(Trade.id == 1).all()) == 1 trade = Trade.query.filter(Trade.id == 1).first() @@ -661,7 +661,7 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog): engine.execute(insert_table_old) # Run init to test migration - init(default_conf['db_url'], default_conf['dry_run']) + init_db(default_conf['db_url'], default_conf['dry_run']) assert len(Trade.query.filter(Trade.id == 1).all()) == 1 trade = Trade.query.filter(Trade.id == 1).first() @@ -904,7 +904,7 @@ def test_to_json(default_conf, fee): def test_stoploss_reinitialization(default_conf, fee): - init(default_conf['db_url']) + init_db(default_conf['db_url']) trade = Trade( pair='ETH/BTC', stake_amount=0.001,