Merge branch 'feat/short' into lev-freqtradebot
This commit is contained in:
commit
bcbe8f229c
@ -53,7 +53,7 @@ Please find the complete documentation on our [website](https://www.freqtrade.io
|
||||
- [x] **Dry-run**: Run the bot without paying money.
|
||||
- [x] **Backtesting**: Run a simulation of your buy/sell strategy.
|
||||
- [x] **Strategy Optimization by machine learning**: Use machine learning to optimize your buy/sell strategy parameters with real exchange data.
|
||||
- [x] **Edge position sizing** Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. [Learn more](https://www.freqtrade.io/en/latest/edge/).
|
||||
- [x] **Edge position sizing** Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. [Learn more](https://www.freqtrade.io/en/stable/edge/).
|
||||
- [x] **Whitelist crypto-currencies**: Select which crypto-currency you want to trade or use dynamic whitelists.
|
||||
- [x] **Blacklist crypto-currencies**: Select which crypto-currency you want to avoid.
|
||||
- [x] **Manageable via Telegram**: Manage the bot with Telegram.
|
||||
@ -71,7 +71,7 @@ cd freqtrade
|
||||
./setup.sh --install
|
||||
```
|
||||
|
||||
For any other type of installation please refer to [Installation doc](https://www.freqtrade.io/en/latest/installation/).
|
||||
For any other type of installation please refer to [Installation doc](https://www.freqtrade.io/en/stable/installation/).
|
||||
|
||||
## Basic Usage
|
||||
|
||||
|
@ -28,10 +28,8 @@
|
||||
"name": "binance",
|
||||
"key": "your_exchange_key",
|
||||
"secret": "your_exchange_secret",
|
||||
"ccxt_config": {"enableRateLimit": true},
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 200
|
||||
},
|
||||
"pair_whitelist": [
|
||||
"ALGO/BTC",
|
||||
|
@ -28,11 +28,8 @@
|
||||
"name": "ftx",
|
||||
"key": "your_exchange_key",
|
||||
"secret": "your_exchange_secret",
|
||||
"ccxt_config": {"enableRateLimit": true},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 50
|
||||
},
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
"BTC/USD",
|
||||
"ETH/USD",
|
||||
|
@ -84,12 +84,8 @@
|
||||
"key": "your_exchange_key",
|
||||
"secret": "your_exchange_secret",
|
||||
"password": "",
|
||||
"ccxt_config": {"enableRateLimit": true},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 500,
|
||||
"aiohttp_trust_env": false
|
||||
},
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
"ALGO/BTC",
|
||||
"ATOM/BTC",
|
||||
|
@ -28,10 +28,8 @@
|
||||
"name": "kraken",
|
||||
"key": "your_exchange_key",
|
||||
"secret": "your_exchange_key",
|
||||
"ccxt_config": {"enableRateLimit": true},
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 1000
|
||||
},
|
||||
"pair_whitelist": [
|
||||
"ADA/EUR",
|
||||
|
@ -15,10 +15,10 @@ services:
|
||||
volumes:
|
||||
- "./user_data:/freqtrade/user_data"
|
||||
# Expose api on port 8080 (localhost only)
|
||||
# Please read the https://www.freqtrade.io/en/latest/rest-api/ documentation
|
||||
# Please read the https://www.freqtrade.io/en/stable/rest-api/ documentation
|
||||
# before enabling this.
|
||||
# ports:
|
||||
# - "127.0.0.1:8080:8080"
|
||||
ports:
|
||||
- "127.0.0.1:8080:8080"
|
||||
# Default command used when running `docker compose up`
|
||||
command: >
|
||||
trade
|
||||
|
@ -447,45 +447,6 @@ The possible values are: `gtc` (default), `fok` or `ioc`.
|
||||
This is ongoing work. For now, it is supported only for binance and kucoin.
|
||||
Please don't change the default value unless you know what you are doing and have researched the impact of using different values for your particular exchange.
|
||||
|
||||
### Exchange configuration
|
||||
|
||||
Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports over 100 cryptocurrency
|
||||
exchange markets and trading APIs. The complete up-to-date list can be found in the
|
||||
[CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python).
|
||||
However, the bot was tested by the development team with only Bittrex, Binance and Kraken,
|
||||
so these are the only officially supported exchanges:
|
||||
|
||||
- [Bittrex](https://bittrex.com/): "bittrex"
|
||||
- [Binance](https://www.binance.com/): "binance"
|
||||
- [Kraken](https://kraken.com/): "kraken"
|
||||
|
||||
Feel free to test other exchanges and submit your PR to improve the bot.
|
||||
|
||||
Some exchanges require special configuration, which can be found on the [Exchange-specific Notes](exchanges.md) documentation page.
|
||||
|
||||
#### Sample exchange configuration
|
||||
|
||||
A exchange configuration for "binance" would look as follows:
|
||||
|
||||
```json
|
||||
"exchange": {
|
||||
"name": "binance",
|
||||
"key": "your_exchange_key",
|
||||
"secret": "your_exchange_secret",
|
||||
"ccxt_config": {"enableRateLimit": true},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 200
|
||||
},
|
||||
```
|
||||
|
||||
This configuration enables binance, as well as rate-limiting to avoid bans from the exchange.
|
||||
`"rateLimit": 200` defines a wait-event of 0.2s between each call. This can also be completely disabled by setting `"enableRateLimit"` to false.
|
||||
|
||||
!!! Note
|
||||
Optimal settings for rate-limiting depend on the exchange and the size of the whitelist, so an ideal parameter will vary on many other settings.
|
||||
We try to provide sensible defaults per exchange where possible, if you encounter bans please make sure that `"enableRateLimit"` is enabled and increase the `"rateLimit"` parameter step by step.
|
||||
|
||||
### What values can be used for fiat_display_currency?
|
||||
|
||||
The `fiat_display_currency` configuration parameter sets the base currency to use for the
|
||||
|
@ -70,6 +70,18 @@ docker-compose up -d
|
||||
!!! Warning "Default configuration"
|
||||
While the configuration generated will be mostly functional, you will still need to verify that all options correspond to what you want (like Pricing, pairlist, ...) before starting the bot.
|
||||
|
||||
#### Accessing the UI
|
||||
|
||||
If you've selected to enable FreqUI in the `new-config` step, you will have freqUI available at port `localhost:8080`.
|
||||
|
||||
You can now access the UI by typing localhost:8080 in your browser.
|
||||
|
||||
??? Note "UI Access on a remote servers"
|
||||
If you're running on a VPS, you should consider using either a ssh tunnel, or setup a VPN (openVPN, wireguard) to connect to your bot.
|
||||
This will ensure that freqUI is not directly exposed to the internet, which is not recommended for security reasons (freqUI does not support https out of the box).
|
||||
Setup of these tools is not part of this tutorial, however many good tutorials can be found on the internet.
|
||||
Please also read the [API configuration with docker](rest-api.md#configuration-with-docker) section to learn more about this configuration.
|
||||
|
||||
#### Monitoring the bot
|
||||
|
||||
You can check for running instances with `docker-compose ps`.
|
||||
@ -148,27 +160,9 @@ You'll then also need to modify the `docker-compose.yml` file and uncomment the
|
||||
dockerfile: "./Dockerfile.<yourextension>"
|
||||
```
|
||||
|
||||
You can then run `docker-compose build` to build the docker image, and run it using the commands described above.
|
||||
You can then run `docker-compose build --pull` to build the docker image, and run it using the commands described above.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### Docker on Windows
|
||||
|
||||
* Error: `"Timestamp for this request is outside of the recvWindow."`
|
||||
* The market api requests require a synchronized clock but the time in the docker container shifts a bit over time into the past.
|
||||
To fix this issue temporarily you need to run `wsl --shutdown` and restart docker again (a popup on windows 10 will ask you to do so).
|
||||
A permanent solution is either to host the docker container on a linux host or restart the wsl from time to time with the scheduler.
|
||||
```
|
||||
taskkill /IM "Docker Desktop.exe" /F
|
||||
wsl --shutdown
|
||||
start "" "C:\Program Files\Docker\Docker\Docker Desktop.exe"
|
||||
```
|
||||
|
||||
!!! Warning
|
||||
Due to the above, we do not recommend the usage of docker on windows for production setups, but only for experimentation, datadownload and backtesting.
|
||||
Best use a linux-VPS for running freqtrade reliably.
|
||||
|
||||
## Plotting with docker-compose
|
||||
### Plotting with docker-compose
|
||||
|
||||
Commands `freqtrade plot-profit` and `freqtrade plot-dataframe` ([Documentation](plotting.md)) are available by changing the image to `*_plot` in your docker-compose.yml file.
|
||||
You can then use these commands as follows:
|
||||
@ -179,7 +173,7 @@ docker-compose run --rm freqtrade plot-dataframe --strategy AwesomeStrategy -p B
|
||||
|
||||
The output will be stored in the `user_data/plot` directory, and can be opened with any modern browser.
|
||||
|
||||
## Data analysis using docker compose
|
||||
### Data analysis using docker compose
|
||||
|
||||
Freqtrade provides a docker-compose file which starts up a jupyter lab server.
|
||||
You can run this server using the following command:
|
||||
@ -196,3 +190,22 @@ Since part of this image is built on your machine, it is recommended to rebuild
|
||||
``` bash
|
||||
docker-compose -f docker/docker-compose-jupyter.yml build --no-cache
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Docker on Windows
|
||||
|
||||
* Error: `"Timestamp for this request is outside of the recvWindow."`
|
||||
* The market api requests require a synchronized clock but the time in the docker container shifts a bit over time into the past.
|
||||
To fix this issue temporarily you need to run `wsl --shutdown` and restart docker again (a popup on windows 10 will ask you to do so).
|
||||
A permanent solution is either to host the docker container on a linux host or restart the wsl from time to time with the scheduler.
|
||||
|
||||
``` bash
|
||||
taskkill /IM "Docker Desktop.exe" /F
|
||||
wsl --shutdown
|
||||
start "" "C:\Program Files\Docker\Docker\Docker Desktop.exe"
|
||||
```
|
||||
|
||||
!!! Warning
|
||||
Due to the above, we do not recommend the usage of docker on windows for production setups, but only for experimentation, datadownload and backtesting.
|
||||
Best use a linux-VPS for running freqtrade reliably.
|
||||
|
@ -2,6 +2,56 @@
|
||||
|
||||
This page combines common gotchas and informations which are exchange-specific and most likely don't apply to other exchanges.
|
||||
|
||||
## Exchange configuration
|
||||
|
||||
Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports over 100 cryptocurrency
|
||||
exchange markets and trading APIs. The complete up-to-date list can be found in the
|
||||
[CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python).
|
||||
However, the bot was tested by the development team with only a few exchanges.
|
||||
A current list of these can be found in the "Home" section of this documentation.
|
||||
|
||||
Feel free to test other exchanges and submit your feedback or PR to improve the bot or confirm exchanges that work flawlessly..
|
||||
|
||||
Some exchanges require special configuration, which can be found below.
|
||||
|
||||
### Sample exchange configuration
|
||||
|
||||
A exchange configuration for "binance" would look as follows:
|
||||
|
||||
```json
|
||||
"exchange": {
|
||||
"name": "binance",
|
||||
"key": "your_exchange_key",
|
||||
"secret": "your_exchange_secret",
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
// ...
|
||||
```
|
||||
|
||||
### Setting rate limits
|
||||
|
||||
Usually, rate limits set by CCXT are reliable and work well.
|
||||
In case of problems related to rate-limits (usually DDOS Exceptions in your logs), it's easy to change rateLimit settings to other values.
|
||||
|
||||
```json
|
||||
"exchange": {
|
||||
"name": "kraken",
|
||||
"key": "your_exchange_key",
|
||||
"secret": "your_exchange_secret",
|
||||
"ccxt_config": {"enableRateLimit": true},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 3100
|
||||
},
|
||||
```
|
||||
|
||||
This configuration enables kraken, as well as rate-limiting to avoid bans from the exchange.
|
||||
`"rateLimit": 3100` defines a wait-event of 0.2s between each call. This can also be completely disabled by setting `"enableRateLimit"` to false.
|
||||
|
||||
!!! Note
|
||||
Optimal settings for rate-limiting depend on the exchange and the size of the whitelist, so an ideal parameter will vary on many other settings.
|
||||
We try to provide sensible defaults per exchange where possible, if you encounter bans please make sure that `"enableRateLimit"` is enabled and increase the `"rateLimit"` parameter step by step.
|
||||
|
||||
## Binance
|
||||
|
||||
Binance supports [time_in_force](configuration.md#understand-order_time_in_force).
|
||||
|
16
docs/faq.md
16
docs/faq.md
@ -54,9 +54,11 @@ you can't say much from few trades.
|
||||
|
||||
Yes. You can edit your config and use the `/reload_config` command to reload the configuration. The bot will stop, reload the configuration and strategy and will restart with the new configuration and strategy.
|
||||
|
||||
### I want to improve the bot with a new strategy
|
||||
### I want to use incomplete candles
|
||||
|
||||
That's great. We have a nice backtesting and hyperoptimization setup. See the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-commands).
|
||||
Freqtrade will not provide incomplete candles to strategies. Using incomplete candles will lead to repainting and consequently to strategies with "ghost" buys, which are impossible to both backtest, and verify after they happened.
|
||||
|
||||
You can use "current" market data by using the [dataprovider](strategy-customization.md#orderbookpair-maximum)'s orderbook or ticker methods - which however cannot be used during backtesting.
|
||||
|
||||
### Is there a setting to only SELL the coins being held and not perform anymore BUYS?
|
||||
|
||||
@ -82,11 +84,11 @@ Currently known to happen for US Bittrex users.
|
||||
|
||||
Read [the Bittrex section about restricted markets](exchanges.md#restricted-markets) for more information.
|
||||
|
||||
### I'm getting the "Exchange Bittrex does not support market orders." message and cannot run my strategy
|
||||
### I'm getting the "Exchange XXX does not support market orders." message and cannot run my strategy
|
||||
|
||||
As the message says, Bittrex does not support market orders and you have one of the [order types](configuration.md/#understand-order_types) set to "market". Your strategy was probably written with other exchanges in mind and sets "market" orders for "stoploss" orders, which is correct and preferable for most of the exchanges supporting market orders (but not for Bittrex).
|
||||
As the message says, your exchange does not support market orders and you have one of the [order types](configuration.md/#understand-order_types) set to "market". Your strategy was probably written with other exchanges in mind and sets "market" orders for "stoploss" orders, which is correct and preferable for most of the exchanges supporting market orders (but not for Bittrex and Gate.io).
|
||||
|
||||
To fix it for Bittrex, redefine order types in the strategy to use "limit" instead of "market":
|
||||
To fix this, redefine order types in the strategy to use "limit" instead of "market":
|
||||
|
||||
```
|
||||
order_types = {
|
||||
@ -136,6 +138,8 @@ On Windows, the `--logfile` option is also supported by Freqtrade and you can us
|
||||
> type \path\to\mylogfile.log | findstr "something"
|
||||
```
|
||||
|
||||
## Hyperopt module
|
||||
|
||||
### Why does freqtrade not have GPU support?
|
||||
|
||||
First of all, most indicator libraries don't have GPU support - as such, there would be little benefit for indicator calculations.
|
||||
@ -152,8 +156,6 @@ The benefit of using GPU would therefore be pretty slim - and will not justify t
|
||||
|
||||
There is however nothing preventing you from using GPU-enabled indicators within your strategy if you think you must have this - you will however probably be disappointed by the slim gain that will give you (compared to the complexity).
|
||||
|
||||
## Hyperopt module
|
||||
|
||||
### How many epochs do I need to get a good Hyperopt result?
|
||||
|
||||
Per default Hyperopt called without the `-e`/`--epochs` command line option will only
|
||||
|
@ -60,7 +60,7 @@ optional arguments:
|
||||
Specify what timerange of data to use.
|
||||
--data-format-ohlcv {json,jsongz,hdf5}
|
||||
Storage format for downloaded candle (OHLCV) data.
|
||||
(default: `None`).
|
||||
(default: `json`).
|
||||
--max-open-trades INT
|
||||
Override the value of the `max_open_trades`
|
||||
configuration setting.
|
||||
@ -114,7 +114,8 @@ optional arguments:
|
||||
Hyperopt-loss-functions are:
|
||||
ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss,
|
||||
SharpeHyperOptLoss, SharpeHyperOptLossDaily,
|
||||
SortinoHyperOptLoss, SortinoHyperOptLossDaily
|
||||
SortinoHyperOptLoss, SortinoHyperOptLossDaily,
|
||||
MaxDrawDownHyperOptLoss
|
||||
--disable-param-export
|
||||
Disable automatic hyperopt parameter export.
|
||||
|
||||
@ -512,12 +513,13 @@ This class should be in its own file within the `user_data/hyperopts/` directory
|
||||
|
||||
Currently, the following loss functions are builtin:
|
||||
|
||||
* `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)
|
||||
* `SortinoHyperOptLoss` (optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation)
|
||||
* `SortinoHyperOptLossDaily` (optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation)
|
||||
* `ShortTradeDurHyperOptLoss` - (default legacy Freqtrade hyperoptimization loss function) - Mostly for short trade duration and avoiding losses.
|
||||
* `OnlyProfitHyperOptLoss` - 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.
|
||||
* `SortinoHyperOptLoss` - optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation.
|
||||
* `SortinoHyperOptLossDaily` - optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation.
|
||||
* `MaxDrawDownHyperOptLoss` - Optimizes Maximum drawdown.
|
||||
|
||||
Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation.
|
||||
|
||||
|
@ -113,6 +113,13 @@ git checkout develop
|
||||
|
||||
You may later switch between branches at any time with the `git checkout stable`/`git checkout develop` commands.
|
||||
|
||||
??? Note "Install from pypi"
|
||||
An alternative way to install Freqtrade is from [pypi](https://pypi.org/project/freqtrade/). The downside is that this method requires ta-lib to be correctly installed beforehand, and is therefore currently not the recommended way to install Freqtrade.
|
||||
|
||||
``` bash
|
||||
pip install freqtrade
|
||||
```
|
||||
|
||||
------
|
||||
|
||||
## Script Installation
|
||||
|
@ -1,4 +1,4 @@
|
||||
mkdocs==1.2.2
|
||||
mkdocs-material==7.3.0
|
||||
mkdocs-material==7.3.2
|
||||
mdx_truly_sane_lists==1.2
|
||||
pymdown-extensions==8.2
|
||||
pymdown-extensions==9.0
|
||||
|
@ -78,7 +78,7 @@ If you run your bot using docker, you'll need to have the bot listen to incoming
|
||||
},
|
||||
```
|
||||
|
||||
Uncomment the following from your docker-compose file:
|
||||
Make sure that the following 2 lines are available in your docker-compose file:
|
||||
|
||||
```yml
|
||||
ports:
|
||||
|
@ -29,7 +29,7 @@ dependencies:
|
||||
- colorama
|
||||
- questionary
|
||||
- prompt-toolkit
|
||||
|
||||
- schedule
|
||||
|
||||
# ============================
|
||||
# 2/4 req dev
|
||||
|
@ -73,7 +73,7 @@ ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit",
|
||||
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
|
||||
"trade_source", "timeframe", "plot_auto_open"]
|
||||
|
||||
ARGS_INSTALL_UI = ["erase_ui_only"]
|
||||
ARGS_INSTALL_UI = ["erase_ui_only", 'ui_version']
|
||||
|
||||
ARGS_SHOW_TRADES = ["db_url", "trade_ids", "print_json"]
|
||||
|
||||
|
@ -163,7 +163,8 @@ def ask_user_config() -> Dict[str, Any]:
|
||||
{
|
||||
"type": "text",
|
||||
"name": "api_server_listen_addr",
|
||||
"message": "Insert Api server Listen Address (best left untouched default!)",
|
||||
"message": ("Insert Api server Listen Address (0.0.0.0 for docker, "
|
||||
"otherwise best left untouched)"),
|
||||
"default": "127.0.0.1",
|
||||
"when": lambda x: x['api_server']
|
||||
},
|
||||
|
@ -414,6 +414,12 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
action='store_true',
|
||||
default=False,
|
||||
),
|
||||
"ui_version": Arg(
|
||||
'--ui-version',
|
||||
help=('Specify a specific version of FreqUI to install. '
|
||||
'Not specifying this installs the latest version.'),
|
||||
type=str,
|
||||
),
|
||||
# Templating options
|
||||
"template": Arg(
|
||||
'--template',
|
||||
|
@ -128,7 +128,7 @@ def download_and_install_ui(dest_folder: Path, dl_url: str, version: str):
|
||||
f.write(version)
|
||||
|
||||
|
||||
def get_ui_download_url() -> Tuple[str, str]:
|
||||
def get_ui_download_url(version: Optional[str] = None) -> Tuple[str, str]:
|
||||
base_url = 'https://api.github.com/repos/freqtrade/frequi/'
|
||||
# Get base UI Repo path
|
||||
|
||||
@ -136,8 +136,16 @@ def get_ui_download_url() -> Tuple[str, str]:
|
||||
resp.raise_for_status()
|
||||
r = resp.json()
|
||||
|
||||
latest_version = r[0]['name']
|
||||
assets = r[0].get('assets', [])
|
||||
if version:
|
||||
tmp = [x for x in r if x['name'] == version]
|
||||
if tmp:
|
||||
latest_version = tmp[0]['name']
|
||||
assets = tmp[0].get('assets', [])
|
||||
else:
|
||||
raise ValueError("UI-Version not found.")
|
||||
else:
|
||||
latest_version = r[0]['name']
|
||||
assets = r[0].get('assets', [])
|
||||
dl_url = ''
|
||||
if assets and len(assets) > 0:
|
||||
dl_url = assets[0]['browser_download_url']
|
||||
@ -156,7 +164,7 @@ def start_install_ui(args: Dict[str, Any]) -> None:
|
||||
|
||||
dest_folder = Path(__file__).parents[1] / 'rpc/api_server/ui/installed/'
|
||||
# First make sure the assets are removed.
|
||||
dl_url, latest_version = get_ui_download_url()
|
||||
dl_url, latest_version = get_ui_download_url(args.get('ui_version'))
|
||||
|
||||
curr_version = read_ui_version(dest_folder)
|
||||
if curr_version == latest_version and not args.get('erase_ui_only'):
|
||||
|
@ -24,7 +24,8 @@ ORDERTYPE_POSSIBILITIES = ['limit', 'market']
|
||||
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
|
||||
HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
|
||||
'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily',
|
||||
'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily']
|
||||
'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily',
|
||||
'MaxDrawDownHyperOptLoss']
|
||||
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
|
||||
'AgeFilter', 'OffsetFilter', 'PerformanceFilter',
|
||||
'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter',
|
||||
|
@ -1,6 +1,6 @@
|
||||
""" Bibox exchange subclass """
|
||||
import logging
|
||||
from typing import Dict
|
||||
from typing import Dict, List
|
||||
|
||||
from freqtrade.exchange import Exchange
|
||||
|
||||
@ -24,3 +24,5 @@ class Bibox(Exchange):
|
||||
def _ccxt_config(self) -> Dict:
|
||||
# Parameters to add directly to ccxt sync/async initialization.
|
||||
return {"has": {"fetchCurrencies": False}}
|
||||
|
||||
funding_fee_times: List[int] = [0, 8, 16] # hours of the day
|
||||
|
@ -28,6 +28,8 @@ class Binance(Exchange):
|
||||
"trades_pagination_arg": "fromId",
|
||||
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
|
||||
}
|
||||
funding_fee_times: List[int] = [0, 8, 16] # hours of the day
|
||||
# but the schedule won't check within this timeframe
|
||||
|
||||
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
|
||||
# TradingMode.SPOT always supported and not required in this list
|
||||
|
@ -1,7 +1,8 @@
|
||||
""" Bybit exchange subclass """
|
||||
import logging
|
||||
from typing import Dict
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from freqtrade.enums import Collateral, TradingMode
|
||||
from freqtrade.exchange import Exchange
|
||||
|
||||
|
||||
@ -21,3 +22,11 @@ class Bybit(Exchange):
|
||||
_ft_has: Dict = {
|
||||
"ohlcv_candle_limit": 200,
|
||||
}
|
||||
|
||||
funding_fee_times: List[int] = [0, 8, 16] # hours of the day
|
||||
|
||||
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
|
||||
# TradingMode.SPOT always supported and not required in this list
|
||||
# (TradingMode.FUTURES, Collateral.CROSS), # TODO-lev: Uncomment once supported
|
||||
# (TradingMode.FUTURES, Collateral.ISOLATED) # TODO-lev: Uncomment once supported
|
||||
]
|
||||
|
@ -9,7 +9,7 @@ import logging
|
||||
from copy import deepcopy
|
||||
from datetime import datetime, timezone
|
||||
from math import ceil
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
import arrow
|
||||
import ccxt
|
||||
@ -72,6 +72,10 @@ class Exchange:
|
||||
}
|
||||
_ft_has: Dict = {}
|
||||
|
||||
# funding_fee_times is currently unused, but should ideally be used to properly
|
||||
# schedule refresh times
|
||||
funding_fee_times: List[int] = [] # hours of the day
|
||||
|
||||
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
|
||||
# TradingMode.SPOT always supported and not required in this list
|
||||
]
|
||||
@ -503,7 +507,7 @@ class Exchange:
|
||||
if startup_candles + 5 > candle_limit:
|
||||
raise OperationalException(
|
||||
f"This strategy requires {startup_candles} candles to start. "
|
||||
f"{self.name} only provides {candle_limit} for {timeframe}.")
|
||||
f"{self.name} only provides {candle_limit - 5} for {timeframe}.")
|
||||
|
||||
def validate_trading_mode_and_collateral(
|
||||
self,
|
||||
@ -565,7 +569,7 @@ class Exchange:
|
||||
precision = self.markets[pair]['precision']['price']
|
||||
missing = price % precision
|
||||
if missing != 0:
|
||||
price = price - missing + precision
|
||||
price = round(price - missing + precision, 10)
|
||||
else:
|
||||
symbol_prec = self.markets[pair]['precision']['price']
|
||||
big_price = price * pow(10, symbol_prec)
|
||||
@ -1130,7 +1134,7 @@ class Exchange:
|
||||
ticker_rate = ticker[conf_strategy['price_side']]
|
||||
if ticker['last'] and ticker_rate:
|
||||
if side == 'buy' and ticker_rate > ticker['last']:
|
||||
balance = conf_strategy['ask_last_balance']
|
||||
balance = conf_strategy.get('ask_last_balance', 0.0)
|
||||
ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate)
|
||||
elif side == 'sell' and ticker_rate < ticker['last']:
|
||||
balance = conf_strategy.get('bid_last_balance', 0.0)
|
||||
@ -1600,6 +1604,37 @@ class Exchange:
|
||||
self._async_get_trade_history(pair=pair, since=since,
|
||||
until=until, from_id=from_id))
|
||||
|
||||
@retrier
|
||||
def get_funding_fees_from_exchange(self, pair: str, since: Union[datetime, int]) -> float:
|
||||
"""
|
||||
Returns the sum of all funding fees that were exchanged for a pair within a timeframe
|
||||
:param pair: (e.g. ADA/USDT)
|
||||
:param since: The earliest time of consideration for calculating funding fees,
|
||||
in unix time or as a datetime
|
||||
"""
|
||||
# TODO-lev: Add dry-run handling for this.
|
||||
|
||||
if not self.exchange_has("fetchFundingHistory"):
|
||||
raise OperationalException(
|
||||
f"fetch_funding_history() has not been implemented on ccxt.{self.name}")
|
||||
|
||||
if type(since) is datetime:
|
||||
since = int(since.timestamp()) * 1000 # * 1000 for ms
|
||||
|
||||
try:
|
||||
funding_history = self._api.fetch_funding_history(
|
||||
pair=pair,
|
||||
since=since
|
||||
)
|
||||
return sum(fee['amount'] for fee in funding_history)
|
||||
except ccxt.DDoSProtection as e:
|
||||
raise DDosProtection(e) from e
|
||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||
raise TemporaryError(
|
||||
f'Could not get funding fees due to {e.__class__.__name__}. Message: {e}') from e
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
def fill_leverage_brackets(self):
|
||||
"""
|
||||
# TODO-lev: Should maybe be renamed, leverage_brackets might not be accurate for kraken
|
||||
|
@ -21,6 +21,7 @@ class Ftx(Exchange):
|
||||
"stoploss_on_exchange": True,
|
||||
"ohlcv_candle_limit": 1500,
|
||||
}
|
||||
funding_fee_times: List[int] = list(range(0, 24))
|
||||
|
||||
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
|
||||
# TradingMode.SPOT always supported and not required in this list
|
||||
|
@ -1,7 +1,8 @@
|
||||
""" Gate.io exchange subclass """
|
||||
import logging
|
||||
from typing import Dict
|
||||
from typing import Dict, List
|
||||
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.exchange import Exchange
|
||||
|
||||
|
||||
@ -23,3 +24,12 @@ class Gateio(Exchange):
|
||||
}
|
||||
|
||||
_headers = {'X-Gate-Channel-Id': 'freqtrade'}
|
||||
|
||||
funding_fee_times: List[int] = [0, 8, 16] # hours of the day
|
||||
|
||||
def validate_ordertypes(self, order_types: Dict) -> None:
|
||||
super().validate_ordertypes(order_types)
|
||||
|
||||
if any(v == 'market' for k, v in order_types.items()):
|
||||
raise OperationalException(
|
||||
f'Exchange {self.name} does not support market orders.')
|
||||
|
@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from typing import Dict
|
||||
from typing import Dict, List
|
||||
|
||||
from freqtrade.exchange import Exchange
|
||||
|
||||
@ -21,3 +21,5 @@ class Hitbtc(Exchange):
|
||||
"ohlcv_candle_limit": 1000,
|
||||
"ohlcv_params": {"sort": "DESC"}
|
||||
}
|
||||
|
||||
funding_fee_times: List[int] = [0, 8, 16] # hours of the day
|
||||
|
@ -23,6 +23,7 @@ class Kraken(Exchange):
|
||||
"trades_pagination": "id",
|
||||
"trades_pagination_arg": "since",
|
||||
}
|
||||
funding_fee_times: List[int] = [0, 4, 8, 12, 16, 20] # hours of the day
|
||||
|
||||
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
|
||||
# TradingMode.SPOT always supported and not required in this list
|
||||
|
@ -1,6 +1,6 @@
|
||||
""" Kucoin exchange subclass """
|
||||
import logging
|
||||
from typing import Dict
|
||||
from typing import Dict, List
|
||||
|
||||
from freqtrade.exchange import Exchange
|
||||
|
||||
@ -24,3 +24,5 @@ class Kucoin(Exchange):
|
||||
"order_time_in_force": ['gtc', 'fok', 'ioc'],
|
||||
"time_in_force_parameter": "timeInForce",
|
||||
}
|
||||
|
||||
funding_fee_times: List[int] = [4, 12, 20] # hours of the day
|
||||
|
@ -4,12 +4,13 @@ Freqtrade is the main module of this bot. It contains the class Freqtrade()
|
||||
import copy
|
||||
import logging
|
||||
import traceback
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime, time, timezone
|
||||
from math import isclose
|
||||
from threading import Lock
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import arrow
|
||||
from schedule import Scheduler
|
||||
|
||||
from freqtrade import __version__, constants
|
||||
from freqtrade.configuration import validate_config_consistency
|
||||
@ -108,14 +109,26 @@ class FreqtradeBot(LoggingMixin):
|
||||
self.trading_mode: TradingMode = TradingMode.SPOT
|
||||
self.collateral_type: Optional[Collateral] = None
|
||||
|
||||
trading_mode = self.config.get('trading_mode')
|
||||
collateral_type = self.config.get('collateral_type')
|
||||
if 'trading_mode' in self.config:
|
||||
self.trading_mode = TradingMode(self.config['trading_mode'])
|
||||
|
||||
if trading_mode:
|
||||
self.trading_mode = TradingMode(trading_mode)
|
||||
if 'collateral_type' in self.config:
|
||||
self.collateral_type = Collateral(self.config['collateral_type'])
|
||||
|
||||
if collateral_type:
|
||||
self.collateral_type = Collateral(collateral_type)
|
||||
self._schedule = Scheduler()
|
||||
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
|
||||
def update():
|
||||
self.update_funding_fees()
|
||||
self.wallets.update()
|
||||
|
||||
# TODO: This would be more efficient if scheduled in utc time, and performed at each
|
||||
# TODO: funding interval, specified by funding_fee_times on the exchange classes
|
||||
for time_slot in range(0, 24):
|
||||
for minutes in [0, 15, 30, 45]:
|
||||
t = str(time(time_slot, minutes, 2))
|
||||
self._schedule.every().day.at(t).do(update)
|
||||
|
||||
def notify_status(self, msg: str) -> None:
|
||||
"""
|
||||
@ -196,7 +209,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Then looking for buy opportunities
|
||||
if self.get_free_open_trades():
|
||||
self.enter_positions()
|
||||
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
self._schedule.run_pending()
|
||||
Trade.commit()
|
||||
|
||||
def process_stopped(self) -> None:
|
||||
@ -252,6 +266,15 @@ class FreqtradeBot(LoggingMixin):
|
||||
open_trades = len(Trade.get_open_trades())
|
||||
return max(0, self.config['max_open_trades'] - open_trades)
|
||||
|
||||
def update_funding_fees(self):
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
for trade in Trade.get_open_trades():
|
||||
funding_fees = self.exchange.get_funding_fees_from_exchange(
|
||||
trade.pair,
|
||||
trade.open_date
|
||||
)
|
||||
trade.funding_fees = funding_fees
|
||||
|
||||
def startup_update_open_orders(self):
|
||||
"""
|
||||
Updates open orders based on order list kept in the database.
|
||||
@ -274,6 +297,9 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
logger.warning(f"Error updating Order {order.order_id} due to {e}")
|
||||
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
self._schedule.run_pending()
|
||||
|
||||
def update_closed_trades_without_assigned_fees(self):
|
||||
"""
|
||||
Update closed trades without close fees assigned.
|
||||
@ -679,6 +705,12 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
|
||||
fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
|
||||
open_date = datetime.now(timezone.utc)
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
funding_fees = self.exchange.get_funding_fees_from_exchange(pair, open_date)
|
||||
else:
|
||||
funding_fees = 0.0
|
||||
|
||||
trade = Trade(
|
||||
pair=pair,
|
||||
stake_amount=stake_amount,
|
||||
@ -689,7 +721,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
fee_close=fee,
|
||||
open_rate=enter_limit_filled_price,
|
||||
open_rate_requested=enter_limit_requested,
|
||||
open_date=datetime.utcnow(),
|
||||
open_date=open_date,
|
||||
exchange=self.exchange.id,
|
||||
open_order_id=order_id,
|
||||
strategy=self.strategy.get_strategy_name(),
|
||||
@ -700,6 +732,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
is_short=is_short,
|
||||
interest_rate=interest_rate,
|
||||
isolated_liq=isolated_liq,
|
||||
trading_mode=self.trading_mode,
|
||||
funding_fees=funding_fees
|
||||
)
|
||||
trade.orders.append(order_obj)
|
||||
|
||||
|
41
freqtrade/optimize/hyperopt_loss_max_drawdown.py
Normal file
41
freqtrade/optimize/hyperopt_loss_max_drawdown.py
Normal file
@ -0,0 +1,41 @@
|
||||
"""
|
||||
MaxDrawDownHyperOptLoss
|
||||
|
||||
This module defines the alternative HyperOptLoss class which can be used for
|
||||
Hyperoptimization.
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.data.btanalysis import calculate_max_drawdown
|
||||
from freqtrade.optimize.hyperopt import IHyperOptLoss
|
||||
|
||||
|
||||
class MaxDrawDownHyperOptLoss(IHyperOptLoss):
|
||||
|
||||
"""
|
||||
Defines the loss function for hyperopt.
|
||||
|
||||
This implementation optimizes for max draw down and profit
|
||||
Less max drawdown more profit -> Lower return value
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def hyperopt_loss_function(results: DataFrame, trade_count: int,
|
||||
min_date: datetime, max_date: datetime,
|
||||
*args, **kwargs) -> float:
|
||||
|
||||
"""
|
||||
Objective function.
|
||||
|
||||
Uses profit ratio weighted max_drawdown when drawdown is available.
|
||||
Otherwise directly optimizes profit ratio.
|
||||
"""
|
||||
total_profit = results['profit_abs'].sum()
|
||||
try:
|
||||
max_drawdown = calculate_max_drawdown(results, value_col='profit_abs')
|
||||
except ValueError:
|
||||
# No losing trade, therefore no drawdown.
|
||||
return -total_profit
|
||||
return -total_profit / max_drawdown[0]
|
@ -49,11 +49,20 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
|
||||
strategy = get_column_def(cols, 'strategy', 'null')
|
||||
buy_tag = get_column_def(cols, 'buy_tag', 'null')
|
||||
|
||||
trading_mode = get_column_def(cols, 'trading_mode', 'null')
|
||||
|
||||
# Leverage Properties
|
||||
leverage = get_column_def(cols, 'leverage', '1.0')
|
||||
interest_rate = get_column_def(cols, 'interest_rate', '0.0')
|
||||
isolated_liq = get_column_def(cols, 'isolated_liq', 'null')
|
||||
# sqlite does not support literals for booleans
|
||||
is_short = get_column_def(cols, 'is_short', '0')
|
||||
|
||||
# Margin Properties
|
||||
interest_rate = get_column_def(cols, 'interest_rate', '0.0')
|
||||
|
||||
# Futures properties
|
||||
funding_fees = get_column_def(cols, 'funding_fees', '0.0')
|
||||
|
||||
# If ticker-interval existed use that, else null.
|
||||
if has_column(cols, 'ticker_interval'):
|
||||
timeframe = get_column_def(cols, 'timeframe', 'ticker_interval')
|
||||
@ -91,7 +100,8 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
|
||||
stoploss_order_id, stoploss_last_update,
|
||||
max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_tag,
|
||||
timeframe, open_trade_value, close_profit_abs,
|
||||
leverage, interest_rate, isolated_liq, is_short
|
||||
trading_mode, leverage, isolated_liq, is_short,
|
||||
interest_rate, funding_fees
|
||||
)
|
||||
select id, lower(exchange), pair,
|
||||
is_open, {fee_open} fee_open, {fee_open_cost} fee_open_cost,
|
||||
@ -108,8 +118,9 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
|
||||
{sell_order_status} sell_order_status,
|
||||
{strategy} strategy, {buy_tag} buy_tag, {timeframe} timeframe,
|
||||
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
|
||||
{leverage} leverage, {interest_rate} interest_rate,
|
||||
{isolated_liq} isolated_liq, {is_short} is_short
|
||||
{trading_mode} trading_mode, {leverage} leverage, {isolated_liq} isolated_liq,
|
||||
{is_short} is_short, {interest_rate} interest_rate,
|
||||
{funding_fees} funding_fees
|
||||
from {table_back_name}
|
||||
"""))
|
||||
|
||||
@ -169,7 +180,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
|
||||
table_back_name = get_backup_name(tabs, 'trades_bak')
|
||||
|
||||
# Check for latest column
|
||||
if not has_column(cols, 'is_short'):
|
||||
if not has_column(cols, 'funding_fees'):
|
||||
logger.info(f'Running database migration for trades - backup: {table_back_name}')
|
||||
migrate_trades_table(decl_base, inspector, engine, table_back_name, cols)
|
||||
# Reread columns - the above recreated the table!
|
||||
|
@ -6,7 +6,7 @@ from datetime import datetime, timedelta, timezone
|
||||
from decimal import Decimal
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Integer, String,
|
||||
from sqlalchemy import (Boolean, Column, DateTime, Enum, Float, ForeignKey, Integer, String,
|
||||
create_engine, desc, func, inspect)
|
||||
from sqlalchemy.exc import NoSuchModuleError
|
||||
from sqlalchemy.orm import Query, declarative_base, relationship, scoped_session, sessionmaker
|
||||
@ -14,7 +14,7 @@ from sqlalchemy.pool import StaticPool
|
||||
from sqlalchemy.sql.schema import UniqueConstraint
|
||||
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES
|
||||
from freqtrade.enums import SellType
|
||||
from freqtrade.enums import SellType, TradingMode
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.leverage import interest
|
||||
from freqtrade.misc import safe_value_fallback
|
||||
@ -265,14 +265,19 @@ class LocalTrade():
|
||||
buy_tag: Optional[str] = None
|
||||
timeframe: Optional[int] = None
|
||||
|
||||
trading_mode: TradingMode = TradingMode.SPOT
|
||||
|
||||
# Leverage trading properties
|
||||
is_short: bool = False
|
||||
isolated_liq: Optional[float] = None
|
||||
is_short: bool = False
|
||||
leverage: float = 1.0
|
||||
|
||||
# Margin trading properties
|
||||
interest_rate: float = 0.0
|
||||
|
||||
# Futures properties
|
||||
funding_fees: Optional[float] = None
|
||||
|
||||
@property
|
||||
def has_no_leverage(self) -> bool:
|
||||
"""Returns true if this is a non-leverage, non-short trade"""
|
||||
@ -439,7 +444,8 @@ class LocalTrade():
|
||||
'interest_rate': self.interest_rate,
|
||||
'isolated_liq': self.isolated_liq,
|
||||
'is_short': self.is_short,
|
||||
|
||||
'trading_mode': self.trading_mode,
|
||||
'funding_fees': self.funding_fees,
|
||||
'open_order_id': self.open_order_id,
|
||||
}
|
||||
|
||||
@ -642,7 +648,7 @@ class LocalTrade():
|
||||
|
||||
zero = Decimal(0.0)
|
||||
# If nothing was borrowed
|
||||
if self.has_no_leverage:
|
||||
if self.has_no_leverage or self.trading_mode != TradingMode.MARGIN:
|
||||
return zero
|
||||
|
||||
open_date = self.open_date.replace(tzinfo=None)
|
||||
@ -656,6 +662,17 @@ class LocalTrade():
|
||||
|
||||
return interest(exchange_name=self.exchange, borrowed=borrowed, rate=rate, hours=hours)
|
||||
|
||||
def _calc_base_close(self, amount: Decimal, rate: Optional[float] = None,
|
||||
fee: Optional[float] = None) -> Decimal:
|
||||
|
||||
close_trade = Decimal(amount) * Decimal(rate or self.close_rate) # type: ignore
|
||||
fees = close_trade * Decimal(fee or self.fee_close)
|
||||
|
||||
if self.is_short:
|
||||
return close_trade + fees
|
||||
else:
|
||||
return close_trade - fees
|
||||
|
||||
def calc_close_trade_value(self, rate: Optional[float] = None,
|
||||
fee: Optional[float] = None,
|
||||
interest_rate: Optional[float] = None) -> float:
|
||||
@ -672,20 +689,32 @@ class LocalTrade():
|
||||
if rate is None and not self.close_rate:
|
||||
return 0.0
|
||||
|
||||
interest = self.calculate_interest(interest_rate)
|
||||
if self.is_short:
|
||||
amount = Decimal(self.amount) + Decimal(interest)
|
||||
else:
|
||||
# Currency already owned for longs, no need to purchase
|
||||
amount = Decimal(self.amount)
|
||||
amount = Decimal(self.amount)
|
||||
trading_mode = self.trading_mode or TradingMode.SPOT
|
||||
|
||||
close_trade = Decimal(amount) * Decimal(rate or self.close_rate) # type: ignore
|
||||
fees = close_trade * Decimal(fee or self.fee_close)
|
||||
if trading_mode == TradingMode.SPOT:
|
||||
return float(self._calc_base_close(amount, rate, fee))
|
||||
|
||||
if self.is_short:
|
||||
return float(close_trade + fees)
|
||||
elif (trading_mode == TradingMode.MARGIN):
|
||||
|
||||
total_interest = self.calculate_interest(interest_rate)
|
||||
|
||||
if self.is_short:
|
||||
amount = amount + total_interest
|
||||
return float(self._calc_base_close(amount, rate, fee))
|
||||
else:
|
||||
# Currency already owned for longs, no need to purchase
|
||||
return float(self._calc_base_close(amount, rate, fee) - total_interest)
|
||||
|
||||
elif (trading_mode == TradingMode.FUTURES):
|
||||
funding_fees = self.funding_fees or 0.0
|
||||
if self.is_short:
|
||||
return float(self._calc_base_close(amount, rate, fee)) - funding_fees
|
||||
else:
|
||||
return float(self._calc_base_close(amount, rate, fee)) + funding_fees
|
||||
else:
|
||||
return float(close_trade - fees - interest)
|
||||
raise OperationalException(
|
||||
f"{self.trading_mode.value} trading is not yet available using freqtrade")
|
||||
|
||||
def calc_profit(self, rate: Optional[float] = None,
|
||||
fee: Optional[float] = None,
|
||||
@ -893,6 +922,8 @@ class Trade(_DECL_BASE, LocalTrade):
|
||||
buy_tag = Column(String(100), nullable=True)
|
||||
timeframe = Column(Integer, nullable=True)
|
||||
|
||||
trading_mode = Column(Enum(TradingMode), nullable=True)
|
||||
|
||||
# Leverage trading properties
|
||||
leverage = Column(Float, nullable=True, default=1.0)
|
||||
is_short = Column(Boolean, nullable=False, default=False)
|
||||
@ -901,6 +932,9 @@ class Trade(_DECL_BASE, LocalTrade):
|
||||
# Margin Trading Properties
|
||||
interest_rate = Column(Float, nullable=False, default=0.0)
|
||||
|
||||
# Futures properties
|
||||
funding_fees = Column(Float, nullable=True, default=None)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.recalc_open_trade_value()
|
||||
|
@ -347,3 +347,8 @@ class BacktestResponse(BaseModel):
|
||||
trade_count: Optional[float]
|
||||
# TODO: Properly type backtestresult...
|
||||
backtest_result: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class SysInfo(BaseModel):
|
||||
cpu_pct: List[float]
|
||||
ram_pct: float
|
||||
|
@ -18,7 +18,8 @@ from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, Blac
|
||||
OpenTradeSchema, PairHistory, PerformanceEntry,
|
||||
Ping, PlotConfig, Profit, ResultMsg, ShowConfig,
|
||||
Stats, StatusMsg, StrategyListResponse,
|
||||
StrategyResponse, Version, WhitelistResponse)
|
||||
StrategyResponse, SysInfo, Version,
|
||||
WhitelistResponse)
|
||||
from freqtrade.rpc.api_server.deps import get_config, get_rpc, get_rpc_optional
|
||||
from freqtrade.rpc.rpc import RPCException
|
||||
|
||||
@ -259,3 +260,8 @@ def list_available_pairs(timeframe: Optional[str] = None, stake_currency: Option
|
||||
'pair_interval': pair_interval,
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
@router.get('/sysinfo', response_model=SysInfo, tags=['info'])
|
||||
def sysinfo():
|
||||
return RPC._rpc_sysinfo()
|
||||
|
@ -8,6 +8,7 @@ from math import isnan
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
import arrow
|
||||
import psutil
|
||||
from numpy import NAN, inf, int64, mean
|
||||
from pandas import DataFrame
|
||||
|
||||
@ -871,3 +872,10 @@ class RPC:
|
||||
'subplots' not in self._freqtrade.strategy.plot_config):
|
||||
self._freqtrade.strategy.plot_config['subplots'] = {}
|
||||
return self._freqtrade.strategy.plot_config
|
||||
|
||||
@staticmethod
|
||||
def _rpc_sysinfo() -> Dict[str, Any]:
|
||||
return {
|
||||
"cpu_pct": psutil.cpu_percent(interval=1, percpu=True),
|
||||
"ram_pct": psutil.virtual_memory().percent
|
||||
}
|
||||
|
@ -2,11 +2,8 @@
|
||||
"name": "{{ exchange_name | lower }}",
|
||||
"key": "{{ exchange_key }}",
|
||||
"secret": "{{ exchange_secret }}",
|
||||
"ccxt_config": {"enableRateLimit": true},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 200
|
||||
},
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
],
|
||||
"pair_blacklist": [
|
||||
|
@ -2,10 +2,8 @@
|
||||
"name": "{{ exchange_name | lower }}",
|
||||
"key": "{{ exchange_key }}",
|
||||
"secret": "{{ exchange_secret }}",
|
||||
"ccxt_config": {"enableRateLimit": true},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": true
|
||||
},
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
|
||||
],
|
||||
|
@ -3,14 +3,8 @@
|
||||
"key": "{{ exchange_key }}",
|
||||
"secret": "{{ exchange_secret }}",
|
||||
"password": "{{ exchange_key_password }}",
|
||||
"ccxt_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 200
|
||||
},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 200
|
||||
},
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
],
|
||||
"pair_blacklist": [
|
||||
|
@ -32,8 +32,7 @@ def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: f
|
||||
use_custom_stoploss = True
|
||||
|
||||
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime',
|
||||
current_rate: float, current_profit: float, dataframe: DataFrame,
|
||||
**kwargs) -> float:
|
||||
current_rate: float, current_profit: float, **kwargs) -> float:
|
||||
"""
|
||||
Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
|
||||
e.g. returning -0.05 would create a stoploss 5% below current_rate.
|
||||
@ -44,14 +43,13 @@ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime',
|
||||
When not implemented by a strategy, returns the initial stoploss value
|
||||
Only called when use_custom_stoploss is set to True.
|
||||
|
||||
:param pair: Pair that's about to be sold.
|
||||
:param pair: Pair that's currently analyzed
|
||||
:param trade: trade object.
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||
:param current_profit: Current profit (as ratio), calculated based on current_rate.
|
||||
:param dataframe: Analyzed dataframe for this pair. Can contain future data in backtesting.
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return float: New stoploss value, relative to the currentrate
|
||||
:return float: New stoploss value, relative to the current_rate
|
||||
"""
|
||||
return self.stoploss
|
||||
|
||||
|
@ -4,13 +4,12 @@
|
||||
-r requirements-hyperopt.txt
|
||||
|
||||
coveralls==3.2.0
|
||||
flake8==3.9.2
|
||||
flake8-type-annotations==0.1.0
|
||||
flake8-tidy-imports==4.4.1
|
||||
flake8==4.0.0
|
||||
flake8-tidy-imports==4.5.0
|
||||
mypy==0.910
|
||||
pytest==6.2.5
|
||||
pytest-asyncio==0.15.1
|
||||
pytest-cov==2.12.1
|
||||
pytest-cov==3.0.0
|
||||
pytest-mock==3.6.1
|
||||
pytest-random-order==1.0.4
|
||||
isort==5.9.3
|
||||
@ -21,7 +20,7 @@ time-machine==2.4.0
|
||||
nbconvert==6.2.0
|
||||
|
||||
# mypy types
|
||||
types-cachetools==4.2.0
|
||||
types-filelock==0.1.5
|
||||
types-cachetools==4.2.2
|
||||
types-filelock==3.2.0
|
||||
types-requests==2.25.9
|
||||
types-tabulate==0.8.2
|
||||
|
@ -3,9 +3,9 @@
|
||||
|
||||
# Required for hyperopt
|
||||
scipy==1.7.1
|
||||
scikit-learn==0.24.2
|
||||
scikit-optimize==0.8.1
|
||||
filelock==3.0.12
|
||||
joblib==1.0.1
|
||||
scikit-learn==1.0
|
||||
scikit-optimize==0.9.0
|
||||
filelock==3.3.0
|
||||
joblib==1.1.0
|
||||
psutil==5.8.0
|
||||
progressbar2==3.53.3
|
||||
|
@ -2,25 +2,25 @@ numpy==1.21.2
|
||||
pandas==1.3.3
|
||||
pandas-ta==0.3.14b
|
||||
|
||||
ccxt==1.57.3
|
||||
ccxt==1.57.94
|
||||
# Pin cryptography for now due to rust build errors with piwheels
|
||||
cryptography==3.4.8
|
||||
cryptography==35.0.0
|
||||
aiohttp==3.7.4.post0
|
||||
SQLAlchemy==1.4.25
|
||||
python-telegram-bot==13.7
|
||||
arrow==1.1.1
|
||||
arrow==1.2.0
|
||||
cachetools==4.2.2
|
||||
requests==2.26.0
|
||||
urllib3==1.26.7
|
||||
wrapt==1.12.1
|
||||
jsonschema==3.2.0
|
||||
wrapt==1.13.1
|
||||
jsonschema==4.1.0
|
||||
TA-Lib==0.4.21
|
||||
technical==1.3.0
|
||||
tabulate==0.8.9
|
||||
pycoingecko==2.2.0
|
||||
jinja2==3.0.1
|
||||
jinja2==3.0.2
|
||||
tables==3.6.1
|
||||
blosc==1.10.4
|
||||
blosc==1.10.6
|
||||
|
||||
# find first, C search in arrays
|
||||
py_find_1st==1.1.5
|
||||
@ -34,11 +34,15 @@ sdnotify==0.3.2
|
||||
# API Server
|
||||
fastapi==0.68.1
|
||||
uvicorn==0.15.0
|
||||
pyjwt==2.1.0
|
||||
pyjwt==2.2.0
|
||||
aiofiles==0.7.0
|
||||
psutil==5.8.0
|
||||
|
||||
# Support for colorized terminal output
|
||||
colorama==0.4.4
|
||||
# Building config files interactively
|
||||
questionary==1.10.0
|
||||
prompt-toolkit==3.0.20
|
||||
|
||||
#Futures
|
||||
schedule==1.1.0
|
||||
|
@ -334,6 +334,13 @@ class FtRestClient():
|
||||
"timerange": timerange if timerange else '',
|
||||
})
|
||||
|
||||
def sysinfo(self):
|
||||
"""Provides system information (CPU, RAM usage)
|
||||
|
||||
:return: json object
|
||||
"""
|
||||
return self._get("sysinfo")
|
||||
|
||||
|
||||
def add_arguments():
|
||||
parser = argparse.ArgumentParser()
|
||||
|
9
setup.py
9
setup.py
@ -11,7 +11,7 @@ hyperopt = [
|
||||
'joblib',
|
||||
'progressbar2',
|
||||
'psutil',
|
||||
]
|
||||
]
|
||||
|
||||
develop = [
|
||||
'coveralls',
|
||||
@ -31,7 +31,7 @@ jupyter = [
|
||||
'nbstripout',
|
||||
'ipykernel',
|
||||
'nbconvert',
|
||||
]
|
||||
]
|
||||
|
||||
all_extra = plot + develop + jupyter + hyperopt
|
||||
|
||||
@ -41,7 +41,7 @@ setup(
|
||||
'pytest-asyncio',
|
||||
'pytest-cov',
|
||||
'pytest-mock',
|
||||
],
|
||||
],
|
||||
install_requires=[
|
||||
# from requirements.txt
|
||||
'ccxt>=1.50.48',
|
||||
@ -72,7 +72,8 @@ setup(
|
||||
'fastapi',
|
||||
'uvicorn',
|
||||
'pyjwt',
|
||||
'aiofiles'
|
||||
'aiofiles',
|
||||
'schedule'
|
||||
],
|
||||
extras_require={
|
||||
'dev': all_extra,
|
||||
|
@ -605,16 +605,33 @@ def test_get_ui_download_url(mocker):
|
||||
def test_get_ui_download_url_direct(mocker):
|
||||
response = MagicMock()
|
||||
response.json = MagicMock(
|
||||
side_effect=[[{
|
||||
'assets_url': 'http://whatever.json',
|
||||
'name': '0.0.1',
|
||||
'assets': [{'browser_download_url': 'http://download11.zip'}]}]])
|
||||
return_value=[
|
||||
{
|
||||
'assets_url': 'http://whatever.json',
|
||||
'name': '0.0.2',
|
||||
'assets': [{'browser_download_url': 'http://download22.zip'}]
|
||||
},
|
||||
{
|
||||
'assets_url': 'http://whatever.json',
|
||||
'name': '0.0.1',
|
||||
'assets': [{'browser_download_url': 'http://download1.zip'}]
|
||||
},
|
||||
])
|
||||
get_mock = mocker.patch("freqtrade.commands.deploy_commands.requests.get",
|
||||
return_value=response)
|
||||
x, last_version = get_ui_download_url()
|
||||
assert get_mock.call_count == 1
|
||||
assert last_version == '0.0.2'
|
||||
assert x == 'http://download22.zip'
|
||||
get_mock.reset_mock()
|
||||
response.json.reset_mock()
|
||||
|
||||
x, last_version = get_ui_download_url('0.0.1')
|
||||
assert last_version == '0.0.1'
|
||||
assert x == 'http://download11.zip'
|
||||
assert x == 'http://download1.zip'
|
||||
|
||||
with pytest.raises(ValueError, match="UI-Version not found."):
|
||||
x, last_version = get_ui_download_url('0.0.3')
|
||||
|
||||
|
||||
def test_download_data_keyboardInterrupt(mocker, caplog, markets):
|
||||
|
@ -277,6 +277,7 @@ def test_amount_to_precision(default_conf, mocker, amount, precision_mode, preci
|
||||
(234.43, 4, 0.5, 234.5),
|
||||
(234.53, 4, 0.5, 235.0),
|
||||
(0.891534, 4, 0.0001, 0.8916),
|
||||
(64968.89, 4, 0.01, 64968.89),
|
||||
|
||||
])
|
||||
def test_price_to_precision(default_conf, mocker, price, precision_mode, precision, expected):
|
||||
@ -295,7 +296,7 @@ def test_price_to_precision(default_conf, mocker, price, precision_mode, precisi
|
||||
PropertyMock(return_value=precision_mode))
|
||||
|
||||
pair = 'ETH/BTC'
|
||||
assert pytest.approx(exchange.price_to_precision(pair, price)) == expected
|
||||
assert exchange.price_to_precision(pair, price) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
|
||||
@ -1895,6 +1896,7 @@ def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name):
|
||||
('ask', 20, 19, 10, 0.3, 17), # Between ask and last
|
||||
('ask', 5, 6, 10, 1.0, 5), # last bigger than ask
|
||||
('ask', 5, 6, 10, 0.5, 5), # last bigger than ask
|
||||
('ask', 20, 19, 10, None, 20), # ask_last_balance missing
|
||||
('ask', 10, 20, None, 0.5, 10), # last not available - uses ask
|
||||
('ask', 4, 5, None, 0.5, 4), # last not available - uses ask
|
||||
('ask', 4, 5, None, 1, 4), # last not available - uses ask
|
||||
@ -1905,6 +1907,7 @@ def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name):
|
||||
('bid', 21, 20, 10, 0.7, 13), # Between bid and last
|
||||
('bid', 21, 20, 10, 0.3, 17), # Between bid and last
|
||||
('bid', 6, 5, 10, 1.0, 5), # last bigger than bid
|
||||
('bid', 21, 20, 10, None, 20), # ask_last_balance missing
|
||||
('bid', 6, 5, 10, 0.5, 5), # last bigger than bid
|
||||
('bid', 21, 20, None, 0.5, 20), # last not available - uses bid
|
||||
('bid', 6, 5, None, 0.5, 5), # last not available - uses bid
|
||||
@ -1914,7 +1917,10 @@ def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name):
|
||||
def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid,
|
||||
last, last_ab, expected) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
default_conf['bid_strategy']['ask_last_balance'] = last_ab
|
||||
if last_ab is None:
|
||||
del default_conf['bid_strategy']['ask_last_balance']
|
||||
else:
|
||||
default_conf['bid_strategy']['ask_last_balance'] = last_ab
|
||||
default_conf['bid_strategy']['price_side'] = side
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
@ -1939,6 +1945,7 @@ def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid,
|
||||
('bid', 12.0, 11.2, 10.5, 1.0, 11.2), # Last smaller than bid - uses bid
|
||||
('bid', 12.0, 11.2, 10.5, 0.5, 11.2), # Last smaller than bid - uses bid
|
||||
('bid', 0.003, 0.002, 0.005, 0.0, 0.002),
|
||||
('bid', 0.003, 0.002, 0.005, None, 0.002),
|
||||
('ask', 12.0, 11.0, 12.5, 0.0, 12.0), # full ask side
|
||||
('ask', 12.0, 11.0, 12.5, 1.0, 12.5), # full last side
|
||||
('ask', 12.0, 11.0, 12.5, 0.5, 12.25), # between bid and lat
|
||||
@ -1949,13 +1956,15 @@ def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid,
|
||||
('ask', 10.11, 11.2, 11.0, 0.0, 10.11),
|
||||
('ask', 0.001, 0.002, 11.0, 0.0, 0.001),
|
||||
('ask', 0.006, 1.0, 11.0, 0.0, 0.006),
|
||||
('ask', 0.006, 1.0, 11.0, None, 0.006),
|
||||
])
|
||||
def test_get_sell_rate(default_conf, mocker, caplog, side, bid, ask,
|
||||
last, last_ab, expected) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
default_conf['ask_strategy']['price_side'] = side
|
||||
default_conf['ask_strategy']['bid_last_balance'] = last_ab
|
||||
if last_ab is not None:
|
||||
default_conf['ask_strategy']['bid_last_balance'] = last_ab
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
return_value={'ask': ask, 'bid': bid, 'last': last})
|
||||
pair = "ETH/BTC"
|
||||
@ -3048,6 +3057,74 @@ def test_calculate_backoff(retrycount, max_retries, expected):
|
||||
assert calculate_backoff(retrycount, max_retries) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", ['binance', 'ftx'])
|
||||
def test_get_funding_fees_from_exchange(default_conf, mocker, exchange_name):
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_funding_history = MagicMock(return_value=[
|
||||
{
|
||||
'amount': 0.14542,
|
||||
'code': 'USDT',
|
||||
'datetime': '2021-09-01T08:00:01.000Z',
|
||||
'id': '485478',
|
||||
'info': {'asset': 'USDT',
|
||||
'income': '0.14542',
|
||||
'incomeType': 'FUNDING_FEE',
|
||||
'info': 'FUNDING_FEE',
|
||||
'symbol': 'XRPUSDT',
|
||||
'time': '1630382001000',
|
||||
'tradeId': '',
|
||||
'tranId': '993203'},
|
||||
'symbol': 'XRP/USDT',
|
||||
'timestamp': 1630382001000
|
||||
},
|
||||
{
|
||||
'amount': -0.14642,
|
||||
'code': 'USDT',
|
||||
'datetime': '2021-09-01T16:00:01.000Z',
|
||||
'id': '485479',
|
||||
'info': {'asset': 'USDT',
|
||||
'income': '-0.14642',
|
||||
'incomeType': 'FUNDING_FEE',
|
||||
'info': 'FUNDING_FEE',
|
||||
'symbol': 'XRPUSDT',
|
||||
'time': '1630314001000',
|
||||
'tradeId': '',
|
||||
'tranId': '993204'},
|
||||
'symbol': 'XRP/USDT',
|
||||
'timestamp': 1630314001000
|
||||
}
|
||||
])
|
||||
type(api_mock).has = PropertyMock(return_value={'fetchFundingHistory': True})
|
||||
|
||||
# mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', lambda pair, since: y)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
date_time = datetime.strptime("2021-09-01T00:00:01.000Z", '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
unix_time = int(date_time.timestamp())
|
||||
expected_fees = -0.001 # 0.14542341 + -0.14642341
|
||||
fees_from_datetime = exchange.get_funding_fees_from_exchange(
|
||||
pair='XRP/USDT',
|
||||
since=date_time
|
||||
)
|
||||
fees_from_unix_time = exchange.get_funding_fees_from_exchange(
|
||||
pair='XRP/USDT',
|
||||
since=unix_time
|
||||
)
|
||||
|
||||
assert(isclose(expected_fees, fees_from_datetime))
|
||||
assert(isclose(expected_fees, fees_from_unix_time))
|
||||
|
||||
ccxt_exceptionhandlers(
|
||||
mocker,
|
||||
default_conf,
|
||||
api_mock,
|
||||
exchange_name,
|
||||
"get_funding_fees_from_exchange",
|
||||
"fetch_funding_history",
|
||||
pair="XRP/USDT",
|
||||
since=unix_time
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange', ['binance', 'kraken', 'ftx'])
|
||||
@pytest.mark.parametrize('stake_amount,leverage,min_stake_with_lev', [
|
||||
(9.0, 3.0, 3.0),
|
||||
|
28
tests/exchange/test_gateio.py
Normal file
28
tests/exchange/test_gateio.py
Normal file
@ -0,0 +1,28 @@
|
||||
import pytest
|
||||
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.exchange import Gateio
|
||||
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
|
||||
|
||||
|
||||
def test_validate_order_types_gateio(default_conf, mocker):
|
||||
default_conf['exchange']['name'] = 'gateio'
|
||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt')
|
||||
mocker.patch('freqtrade.exchange.Exchange._load_markets', return_value={})
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||
mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex')
|
||||
exch = ExchangeResolver.load_exchange('gateio', default_conf, True)
|
||||
assert isinstance(exch, Gateio)
|
||||
|
||||
default_conf['order_types'] = {
|
||||
'buy': 'market',
|
||||
'sell': 'limit',
|
||||
'stoploss': 'market',
|
||||
'stoploss_on_exchange': False
|
||||
}
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'Exchange .* does not support market orders.'):
|
||||
ExchangeResolver.load_exchange('gateio', default_conf, True)
|
@ -84,13 +84,14 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) ->
|
||||
"SortinoHyperOptLossDaily",
|
||||
"SharpeHyperOptLoss",
|
||||
"SharpeHyperOptLossDaily",
|
||||
"MaxDrawDownHyperOptLoss",
|
||||
])
|
||||
def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunction) -> None:
|
||||
results_over = hyperopt_results.copy()
|
||||
results_over['profit_abs'] = hyperopt_results['profit_abs'] * 2
|
||||
results_over['profit_abs'] = hyperopt_results['profit_abs'] * 2 + 0.2
|
||||
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
|
||||
results_under = hyperopt_results.copy()
|
||||
results_under['profit_abs'] = hyperopt_results['profit_abs'] / 2
|
||||
results_under['profit_abs'] = hyperopt_results['profit_abs'] / 2 - 0.2
|
||||
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
|
||||
|
||||
default_conf.update({'hyperopt_loss': lossfunction})
|
||||
|
@ -8,7 +8,7 @@ import pytest
|
||||
from numpy import isnan
|
||||
|
||||
from freqtrade.edge import PairInfo
|
||||
from freqtrade.enums import State
|
||||
from freqtrade.enums import State, TradingMode
|
||||
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.persistence.pairlock_middleware import PairLocks
|
||||
@ -112,6 +112,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'interest_rate': 0.0,
|
||||
'isolated_liq': None,
|
||||
'is_short': False,
|
||||
'funding_fees': 0.0,
|
||||
'trading_mode': TradingMode.SPOT
|
||||
}
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
||||
@ -183,6 +185,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'interest_rate': 0.0,
|
||||
'isolated_liq': None,
|
||||
'is_short': False,
|
||||
'funding_fees': 0.0,
|
||||
'trading_mode': TradingMode.SPOT
|
||||
}
|
||||
|
||||
|
||||
|
@ -1282,6 +1282,16 @@ def test_list_available_pairs(botclient):
|
||||
assert len(rc.json()['pair_interval']) == 1
|
||||
|
||||
|
||||
def test_sysinfo(botclient):
|
||||
ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/sysinfo")
|
||||
assert_response(rc)
|
||||
result = rc.json()
|
||||
assert 'cpu_pct' in result
|
||||
assert 'ram_pct' in result
|
||||
|
||||
|
||||
def test_api_backtesting(botclient, mocker, fee, caplog):
|
||||
ftbot, client = botclient
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
|
@ -4,14 +4,14 @@
|
||||
import logging
|
||||
import time
|
||||
from copy import deepcopy
|
||||
from math import floor, isclose
|
||||
from math import isclose
|
||||
from unittest.mock import ANY, MagicMock, PropertyMock
|
||||
|
||||
import arrow
|
||||
import pytest
|
||||
|
||||
from freqtrade.constants import CANCEL_REASON, MATH_CLOSE_PREC, UNLIMITED_STAKE_AMOUNT
|
||||
from freqtrade.enums import RPCMessageType, RunMode, SellType, SignalDirection, State
|
||||
from freqtrade.enums import RPCMessageType, RunMode, SellType, SignalDirection, State, TradingMode
|
||||
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
|
||||
InvalidOrderException, OperationalException, PricingError,
|
||||
TemporaryError)
|
||||
@ -2832,16 +2832,22 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
|
||||
)
|
||||
# Prevented sell ...
|
||||
# TODO-lev: side="buy"
|
||||
freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||
freqtrade.execute_trade_exit(
|
||||
trade=trade,
|
||||
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI)
|
||||
)
|
||||
assert rpc_mock.call_count == 0
|
||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
||||
|
||||
# Repatch with true
|
||||
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
|
||||
# TODO-lev: side="buy"
|
||||
freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||
freqtrade.execute_trade_exit(
|
||||
trade=trade,
|
||||
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI)
|
||||
)
|
||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
||||
|
||||
assert rpc_mock.call_count == 1
|
||||
@ -4627,3 +4633,36 @@ def test_get_valid_price(mocker, default_conf_usdt) -> None:
|
||||
def test_leverage_prep():
|
||||
# TODO-lev
|
||||
return
|
||||
|
||||
|
||||
@pytest.mark.parametrize('trading_mode,calls,t1,t2', [
|
||||
(TradingMode.SPOT, 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
|
||||
(TradingMode.MARGIN, 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
|
||||
(TradingMode.FUTURES, 31, "2021-09-01 00:00:02", "2021-09-01 08:00:01"),
|
||||
(TradingMode.FUTURES, 32, "2021-09-01 00:00:00", "2021-09-01 08:00:01"),
|
||||
(TradingMode.FUTURES, 32, "2021-09-01 00:00:02", "2021-09-01 08:00:02"),
|
||||
(TradingMode.FUTURES, 33, "2021-09-01 00:00:00", "2021-09-01 08:00:02"),
|
||||
(TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:02"),
|
||||
(TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:03"),
|
||||
(TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:04"),
|
||||
(TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:05"),
|
||||
(TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:06"),
|
||||
(TradingMode.FUTURES, 33, "2021-08-31 23:59:59", "2021-09-01 08:00:07"),
|
||||
(TradingMode.FUTURES, 33, "2021-08-31 23:59:58", "2021-09-01 08:00:07"),
|
||||
])
|
||||
def test_update_funding_fees(mocker, default_conf, trading_mode, calls, time_machine,
|
||||
t1, t2):
|
||||
time_machine.move_to(f"{t1} +00:00")
|
||||
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_funding_fees', return_value=True)
|
||||
default_conf['trading_mode'] = trading_mode
|
||||
default_conf['collateral'] = 'isolated'
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
time_machine.move_to(f"{t2} +00:00")
|
||||
# Check schedule jobs in debugging with freqtrade._schedule.jobs
|
||||
freqtrade._schedule.run_pending()
|
||||
|
||||
assert freqtrade.update_funding_fees.call_count == calls
|
||||
|
@ -11,12 +11,16 @@ import pytest
|
||||
from sqlalchemy import create_engine, inspect, text
|
||||
|
||||
from freqtrade import constants
|
||||
from freqtrade.enums import TradingMode
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.persistence import LocalTrade, Order, Trade, clean_dry_run_db, init_db
|
||||
from tests.conftest import (create_mock_trades, create_mock_trades_with_leverage, get_sides,
|
||||
log_has, log_has_re)
|
||||
|
||||
|
||||
spot, margin, futures = TradingMode.SPOT, TradingMode.MARGIN, TradingMode.FUTURES
|
||||
|
||||
|
||||
def test_init_create_session(default_conf):
|
||||
# Check if init create a session
|
||||
init_db(default_conf['db_url'], default_conf['dry_run'])
|
||||
@ -81,7 +85,8 @@ def test_enter_exit_side(fee, is_short):
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
is_short=is_short,
|
||||
leverage=2.0
|
||||
leverage=2.0,
|
||||
trading_mode=margin
|
||||
)
|
||||
assert trade.enter_side == enter_side
|
||||
assert trade.exit_side == exit_side
|
||||
@ -101,7 +106,8 @@ def test_set_stop_loss_isolated_liq(fee):
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
is_short=False,
|
||||
leverage=2.0
|
||||
leverage=2.0,
|
||||
trading_mode=margin
|
||||
)
|
||||
trade.set_isolated_liq(0.09)
|
||||
assert trade.isolated_liq == 0.09
|
||||
@ -168,32 +174,40 @@ def test_set_stop_loss_isolated_liq(fee):
|
||||
assert trade.initial_stop_loss == 0.09
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange,is_short,lev,minutes,rate,interest', [
|
||||
("binance", False, 3, 10, 0.0005, round(0.0008333333333333334, 8)),
|
||||
("binance", True, 3, 10, 0.0005, 0.000625),
|
||||
("binance", False, 3, 295, 0.0005, round(0.004166666666666667, 8)),
|
||||
("binance", True, 3, 295, 0.0005, round(0.0031249999999999997, 8)),
|
||||
("binance", False, 3, 295, 0.00025, round(0.0020833333333333333, 8)),
|
||||
("binance", True, 3, 295, 0.00025, round(0.0015624999999999999, 8)),
|
||||
("binance", False, 5, 295, 0.0005, 0.005),
|
||||
("binance", True, 5, 295, 0.0005, round(0.0031249999999999997, 8)),
|
||||
("binance", False, 1, 295, 0.0005, 0.0),
|
||||
("binance", True, 1, 295, 0.0005, 0.003125),
|
||||
@pytest.mark.parametrize('exchange,is_short,lev,minutes,rate,interest,trading_mode', [
|
||||
("binance", False, 3, 10, 0.0005, round(0.0008333333333333334, 8), margin),
|
||||
("binance", True, 3, 10, 0.0005, 0.000625, margin),
|
||||
("binance", False, 3, 295, 0.0005, round(0.004166666666666667, 8), margin),
|
||||
("binance", True, 3, 295, 0.0005, round(0.0031249999999999997, 8), margin),
|
||||
("binance", False, 3, 295, 0.00025, round(0.0020833333333333333, 8), margin),
|
||||
("binance", True, 3, 295, 0.00025, round(0.0015624999999999999, 8), margin),
|
||||
("binance", False, 5, 295, 0.0005, 0.005, margin),
|
||||
("binance", True, 5, 295, 0.0005, round(0.0031249999999999997, 8), margin),
|
||||
("binance", False, 1, 295, 0.0005, 0.0, spot),
|
||||
("binance", True, 1, 295, 0.0005, 0.003125, margin),
|
||||
|
||||
("kraken", False, 3, 10, 0.0005, 0.040),
|
||||
("kraken", True, 3, 10, 0.0005, 0.030),
|
||||
("kraken", False, 3, 295, 0.0005, 0.06),
|
||||
("kraken", True, 3, 295, 0.0005, 0.045),
|
||||
("kraken", False, 3, 295, 0.00025, 0.03),
|
||||
("kraken", True, 3, 295, 0.00025, 0.0225),
|
||||
("kraken", False, 5, 295, 0.0005, round(0.07200000000000001, 8)),
|
||||
("kraken", True, 5, 295, 0.0005, 0.045),
|
||||
("kraken", False, 1, 295, 0.0005, 0.0),
|
||||
("kraken", True, 1, 295, 0.0005, 0.045),
|
||||
("binance", False, 3, 10, 0.0005, 0.0, futures),
|
||||
("binance", True, 3, 295, 0.0005, 0.0, futures),
|
||||
("binance", False, 5, 295, 0.0005, 0.0, futures),
|
||||
("binance", True, 5, 295, 0.0005, 0.0, futures),
|
||||
("binance", False, 1, 295, 0.0005, 0.0, futures),
|
||||
("binance", True, 1, 295, 0.0005, 0.0, futures),
|
||||
|
||||
("kraken", False, 3, 10, 0.0005, 0.040, margin),
|
||||
("kraken", True, 3, 10, 0.0005, 0.030, margin),
|
||||
("kraken", False, 3, 295, 0.0005, 0.06, margin),
|
||||
("kraken", True, 3, 295, 0.0005, 0.045, margin),
|
||||
("kraken", False, 3, 295, 0.00025, 0.03, margin),
|
||||
("kraken", True, 3, 295, 0.00025, 0.0225, margin),
|
||||
("kraken", False, 5, 295, 0.0005, round(0.07200000000000001, 8), margin),
|
||||
("kraken", True, 5, 295, 0.0005, 0.045, margin),
|
||||
("kraken", False, 1, 295, 0.0005, 0.0, spot),
|
||||
("kraken", True, 1, 295, 0.0005, 0.045, margin),
|
||||
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_interest(market_buy_order_usdt, fee, exchange, is_short, lev, minutes, rate, interest):
|
||||
def test_interest(market_buy_order_usdt, fee, exchange, is_short, lev, minutes, rate, interest,
|
||||
trading_mode):
|
||||
"""
|
||||
10min, 5hr limit trade on Binance/Kraken at 3x,5x leverage
|
||||
fee: 0.25 % quote
|
||||
@ -258,21 +272,22 @@ def test_interest(market_buy_order_usdt, fee, exchange, is_short, lev, minutes,
|
||||
exchange=exchange,
|
||||
leverage=lev,
|
||||
interest_rate=rate,
|
||||
is_short=is_short
|
||||
is_short=is_short,
|
||||
trading_mode=trading_mode
|
||||
)
|
||||
|
||||
assert round(float(trade.calculate_interest()), 8) == interest
|
||||
|
||||
|
||||
@pytest.mark.parametrize('is_short,lev,borrowed', [
|
||||
(False, 1.0, 0.0),
|
||||
(True, 1.0, 30.0),
|
||||
(False, 3.0, 40.0),
|
||||
(True, 3.0, 30.0),
|
||||
@pytest.mark.parametrize('is_short,lev,borrowed,trading_mode', [
|
||||
(False, 1.0, 0.0, spot),
|
||||
(True, 1.0, 30.0, margin),
|
||||
(False, 3.0, 40.0, margin),
|
||||
(True, 3.0, 30.0, margin),
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_borrowed(limit_buy_order_usdt, limit_sell_order_usdt, fee,
|
||||
caplog, is_short, lev, borrowed):
|
||||
caplog, is_short, lev, borrowed, trading_mode):
|
||||
"""
|
||||
10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
||||
fee: 0.25% quote
|
||||
@ -347,18 +362,19 @@ def test_borrowed(limit_buy_order_usdt, limit_sell_order_usdt, fee,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
is_short=is_short,
|
||||
leverage=lev
|
||||
leverage=lev,
|
||||
trading_mode=trading_mode
|
||||
)
|
||||
assert trade.borrowed == borrowed
|
||||
|
||||
|
||||
@pytest.mark.parametrize('is_short,open_rate,close_rate,lev,profit', [
|
||||
(False, 2.0, 2.2, 1.0, round(0.0945137157107232, 8)),
|
||||
(True, 2.2, 2.0, 3.0, round(0.2589996297562085, 8))
|
||||
@pytest.mark.parametrize('is_short,open_rate,close_rate,lev,profit,trading_mode', [
|
||||
(False, 2.0, 2.2, 1.0, round(0.0945137157107232, 8), spot),
|
||||
(True, 2.2, 2.0, 3.0, round(0.2589996297562085, 8), margin),
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_usdt,
|
||||
is_short, open_rate, close_rate, lev, profit):
|
||||
is_short, open_rate, close_rate, lev, profit, trading_mode):
|
||||
"""
|
||||
10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
||||
fee: 0.25% quote
|
||||
@ -445,7 +461,8 @@ def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_
|
||||
exchange='binance',
|
||||
is_short=is_short,
|
||||
interest_rate=0.0005,
|
||||
leverage=lev
|
||||
leverage=lev,
|
||||
trading_mode=trading_mode
|
||||
)
|
||||
assert trade.open_order_id is None
|
||||
assert trade.close_profit is None
|
||||
@ -491,6 +508,7 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
|
||||
fee_close=fee.return_value,
|
||||
open_date=arrow.utcnow().datetime,
|
||||
exchange='binance',
|
||||
trading_mode=margin
|
||||
)
|
||||
|
||||
trade.open_order_id = 'something'
|
||||
@ -518,20 +536,28 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
|
||||
caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange,is_short,lev,open_value,close_value,profit,profit_ratio', [
|
||||
("binance", False, 1, 60.15, 65.835, 5.685, 0.0945137157107232),
|
||||
("binance", True, 1, 59.850, 66.1663784375, -6.316378437500013, -0.1055368159983292),
|
||||
("binance", False, 3, 60.15, 65.83416667, 5.684166670000003, 0.2834995845386534),
|
||||
("binance", True, 3, 59.85, 66.1663784375, -6.316378437500013, -0.3166104479949876),
|
||||
@pytest.mark.parametrize(
|
||||
'exchange,is_short,lev,open_value,close_value,profit,profit_ratio,trading_mode,funding_fees', [
|
||||
("binance", False, 1, 60.15, 65.835, 5.685, 0.0945137157107232, spot, 0.0),
|
||||
("binance", True, 1, 59.850, 66.1663784375, -6.3163784375, -0.105536815998329, margin, 0.0),
|
||||
("binance", False, 3, 60.15, 65.83416667, 5.68416667, 0.2834995845386534, margin, 0.0),
|
||||
("binance", True, 3, 59.85, 66.1663784375, -6.3163784375, -0.3166104479949876, margin, 0.0),
|
||||
|
||||
("kraken", False, 1, 60.15, 65.835, 5.685, 0.0945137157107232),
|
||||
("kraken", True, 1, 59.850, 66.231165, -6.381165, -0.106619298245614),
|
||||
("kraken", False, 3, 60.15, 65.795, 5.645, 0.2815461346633419),
|
||||
("kraken", True, 3, 59.850, 66.231165, -6.381165000000003, -0.319857894736842),
|
||||
])
|
||||
("kraken", False, 1, 60.15, 65.835, 5.685, 0.0945137157107232, spot, 0.0),
|
||||
("kraken", True, 1, 59.850, 66.231165, -6.381165, -0.106619298245614, margin, 0.0),
|
||||
("kraken", False, 3, 60.15, 65.795, 5.645, 0.2815461346633419, margin, 0.0),
|
||||
("kraken", True, 3, 59.850, 66.231165, -6.381165000000003, -0.319857894736842, margin, 0.0),
|
||||
|
||||
("binance", False, 1, 60.15, 66.835, 6.685, 0.11113881961762262, futures, 1.0),
|
||||
("binance", True, 1, 59.85, 67.165, -7.315, -0.12222222222222223, futures, -1.0),
|
||||
("binance", False, 3, 60.15, 64.835, 4.685, 0.23366583541147135, futures, -1.0),
|
||||
("binance", True, 3, 59.85, 65.165, -5.315, -0.26641604010025066, futures, 1.0),
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_open_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, fee, exchange,
|
||||
is_short, lev, open_value, close_value, profit, profit_ratio):
|
||||
def test_calc_open_close_trade_price(
|
||||
limit_buy_order_usdt, limit_sell_order_usdt, fee, exchange, is_short, lev,
|
||||
open_value, close_value, profit, profit_ratio, trading_mode, funding_fees
|
||||
):
|
||||
trade: Trade = Trade(
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
@ -543,7 +569,9 @@ def test_calc_open_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt
|
||||
fee_close=fee.return_value,
|
||||
exchange=exchange,
|
||||
is_short=is_short,
|
||||
leverage=lev
|
||||
leverage=lev,
|
||||
trading_mode=trading_mode,
|
||||
funding_fees=funding_fees
|
||||
)
|
||||
|
||||
trade.open_order_id = f'something-{is_short}-{lev}-{exchange}'
|
||||
@ -572,6 +600,7 @@ def test_trade_close(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
|
||||
interest_rate=0.0005,
|
||||
exchange='binance',
|
||||
trading_mode=margin
|
||||
)
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
@ -600,6 +629,7 @@ def test_calc_close_trade_price_exception(limit_buy_order_usdt, fee):
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
trading_mode=margin
|
||||
)
|
||||
|
||||
trade.open_order_id = 'something'
|
||||
@ -617,6 +647,7 @@ def test_update_open_order(limit_buy_order_usdt):
|
||||
fee_open=0.1,
|
||||
fee_close=0.1,
|
||||
exchange='binance',
|
||||
trading_mode=margin
|
||||
)
|
||||
|
||||
assert trade.open_order_id is None
|
||||
@ -641,6 +672,7 @@ def test_update_invalid_order(limit_buy_order_usdt):
|
||||
fee_open=0.1,
|
||||
fee_close=0.1,
|
||||
exchange='binance',
|
||||
trading_mode=margin
|
||||
)
|
||||
limit_buy_order_usdt['type'] = 'invalid'
|
||||
with pytest.raises(ValueError, match=r'Unknown order type'):
|
||||
@ -648,6 +680,7 @@ def test_update_invalid_order(limit_buy_order_usdt):
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange', ['binance', 'kraken'])
|
||||
@pytest.mark.parametrize('trading_mode', [spot, margin, futures])
|
||||
@pytest.mark.parametrize('lev', [1, 3])
|
||||
@pytest.mark.parametrize('is_short,fee_rate,result', [
|
||||
(False, 0.003, 60.18),
|
||||
@ -666,7 +699,8 @@ def test_calc_open_trade_value(
|
||||
lev,
|
||||
is_short,
|
||||
fee_rate,
|
||||
result
|
||||
result,
|
||||
trading_mode
|
||||
):
|
||||
# 10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
||||
# fee: 0.25 %, 0.3% quote
|
||||
@ -692,7 +726,8 @@ def test_calc_open_trade_value(
|
||||
fee_close=fee_rate,
|
||||
exchange=exchange,
|
||||
leverage=lev,
|
||||
is_short=is_short
|
||||
is_short=is_short,
|
||||
trading_mode=trading_mode
|
||||
)
|
||||
trade.open_order_id = 'open_trade'
|
||||
|
||||
@ -700,26 +735,37 @@ def test_calc_open_trade_value(
|
||||
assert trade._calc_open_trade_value() == result
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange,is_short,lev,open_rate,close_rate,fee_rate,result', [
|
||||
('binance', False, 1, 2.0, 2.5, 0.0025, 74.8125),
|
||||
('binance', False, 1, 2.0, 2.5, 0.003, 74.775),
|
||||
('binance', False, 1, 2.0, 2.2, 0.005, 65.67),
|
||||
('binance', False, 3, 2.0, 2.5, 0.0025, 74.81166667),
|
||||
('binance', False, 3, 2.0, 2.5, 0.003, 74.77416667),
|
||||
('kraken', False, 3, 2.0, 2.5, 0.0025, 74.7725),
|
||||
('kraken', False, 3, 2.0, 2.5, 0.003, 74.735),
|
||||
('kraken', True, 3, 2.2, 2.5, 0.0025, 75.2626875),
|
||||
('kraken', True, 3, 2.2, 2.5, 0.003, 75.300225),
|
||||
('binance', True, 3, 2.2, 2.5, 0.0025, 75.18906641),
|
||||
('binance', True, 3, 2.2, 2.5, 0.003, 75.22656719),
|
||||
('binance', True, 1, 2.2, 2.5, 0.0025, 75.18906641),
|
||||
('binance', True, 1, 2.2, 2.5, 0.003, 75.22656719),
|
||||
('kraken', True, 1, 2.2, 2.5, 0.0025, 75.2626875),
|
||||
('kraken', True, 1, 2.2, 2.5, 0.003, 75.300225),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
'exchange,is_short,lev,open_rate,close_rate,fee_rate,result,trading_mode,funding_fees', [
|
||||
('binance', False, 1, 2.0, 2.5, 0.0025, 74.8125, spot, 0),
|
||||
('binance', False, 1, 2.0, 2.5, 0.003, 74.775, spot, 0),
|
||||
('binance', False, 1, 2.0, 2.2, 0.005, 65.67, margin, 0),
|
||||
('binance', False, 3, 2.0, 2.5, 0.0025, 74.81166667, margin, 0),
|
||||
('binance', False, 3, 2.0, 2.5, 0.003, 74.77416667, margin, 0),
|
||||
('binance', True, 3, 2.2, 2.5, 0.0025, 75.18906641, margin, 0),
|
||||
('binance', True, 3, 2.2, 2.5, 0.003, 75.22656719, margin, 0),
|
||||
('binance', True, 1, 2.2, 2.5, 0.0025, 75.18906641, margin, 0),
|
||||
('binance', True, 1, 2.2, 2.5, 0.003, 75.22656719, margin, 0),
|
||||
|
||||
# Kraken
|
||||
('kraken', False, 3, 2.0, 2.5, 0.0025, 74.7725, margin, 0),
|
||||
('kraken', False, 3, 2.0, 2.5, 0.003, 74.735, margin, 0),
|
||||
('kraken', True, 3, 2.2, 2.5, 0.0025, 75.2626875, margin, 0),
|
||||
('kraken', True, 3, 2.2, 2.5, 0.003, 75.300225, margin, 0),
|
||||
('kraken', True, 1, 2.2, 2.5, 0.0025, 75.2626875, margin, 0),
|
||||
('kraken', True, 1, 2.2, 2.5, 0.003, 75.300225, margin, 0),
|
||||
|
||||
('binance', False, 1, 2.0, 2.5, 0.0025, 75.8125, futures, 1),
|
||||
('binance', False, 3, 2.0, 2.5, 0.0025, 73.8125, futures, -1),
|
||||
('binance', True, 3, 2.0, 2.5, 0.0025, 74.1875, futures, 1),
|
||||
('binance', True, 1, 2.0, 2.5, 0.0025, 76.1875, futures, -1),
|
||||
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, open_rate,
|
||||
exchange, is_short, lev, close_rate, fee_rate, result):
|
||||
def test_calc_close_trade_price(
|
||||
limit_buy_order_usdt, limit_sell_order_usdt, open_rate, exchange, is_short,
|
||||
lev, close_rate, fee_rate, result, trading_mode, funding_fees
|
||||
):
|
||||
trade = Trade(
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
@ -731,47 +777,83 @@ def test_calc_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, ope
|
||||
exchange=exchange,
|
||||
interest_rate=0.0005,
|
||||
is_short=is_short,
|
||||
leverage=lev
|
||||
leverage=lev,
|
||||
trading_mode=trading_mode,
|
||||
funding_fees=funding_fees
|
||||
)
|
||||
trade.open_order_id = 'close_trade'
|
||||
assert round(trade.calc_close_trade_value(rate=close_rate, fee=fee_rate), 8) == result
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange,is_short,lev,close_rate,fee_close,profit,profit_ratio', [
|
||||
('binance', False, 1, 2.1, 0.0025, 2.6925, 0.04476309226932673),
|
||||
('binance', False, 3, 2.1, 0.0025, 2.69166667, 0.13424771421446402),
|
||||
('binance', True, 1, 2.1, 0.0025, -3.308815781249997, -0.05528514254385963),
|
||||
('binance', True, 3, 2.1, 0.0025, -3.308815781249997, -0.1658554276315789),
|
||||
@pytest.mark.parametrize(
|
||||
'exchange,is_short,lev,close_rate,fee_close,profit,profit_ratio,trading_mode,funding_fees', [
|
||||
('binance', False, 1, 2.1, 0.0025, 2.6925, 0.04476309226932673, spot, 0),
|
||||
('binance', False, 3, 2.1, 0.0025, 2.69166667, 0.13424771421446402, margin, 0),
|
||||
('binance', True, 1, 2.1, 0.0025, -3.308815781249997, -0.05528514254385963, margin, 0),
|
||||
('binance', True, 3, 2.1, 0.0025, -3.308815781249997, -0.1658554276315789, margin, 0),
|
||||
|
||||
('binance', False, 1, 1.9, 0.0025, -3.2925, -0.05473815461346632),
|
||||
('binance', False, 3, 1.9, 0.0025, -3.29333333, -0.16425602643391513),
|
||||
('binance', True, 1, 1.9, 0.0025, 2.7063095312499996, 0.045218204365079395),
|
||||
('binance', True, 3, 1.9, 0.0025, 2.7063095312499996, 0.13565461309523819),
|
||||
('binance', False, 1, 1.9, 0.0025, -3.2925, -0.05473815461346632, margin, 0),
|
||||
('binance', False, 3, 1.9, 0.0025, -3.29333333, -0.16425602643391513, margin, 0),
|
||||
('binance', True, 1, 1.9, 0.0025, 2.7063095312499996, 0.045218204365079395, margin, 0),
|
||||
('binance', True, 3, 1.9, 0.0025, 2.7063095312499996, 0.13565461309523819, margin, 0),
|
||||
|
||||
('binance', False, 1, 2.2, 0.0025, 5.685, 0.0945137157107232),
|
||||
('binance', False, 3, 2.2, 0.0025, 5.68416667, 0.2834995845386534),
|
||||
('binance', True, 1, 2.2, 0.0025, -6.316378437499999, -0.1055368159983292),
|
||||
('binance', True, 3, 2.2, 0.0025, -6.316378437499999, -0.3166104479949876),
|
||||
('binance', False, 1, 2.2, 0.0025, 5.685, 0.0945137157107232, margin, 0),
|
||||
('binance', False, 3, 2.2, 0.0025, 5.68416667, 0.2834995845386534, margin, 0),
|
||||
('binance', True, 1, 2.2, 0.0025, -6.316378437499999, -0.1055368159983292, margin, 0),
|
||||
('binance', True, 3, 2.2, 0.0025, -6.316378437499999, -0.3166104479949876, margin, 0),
|
||||
|
||||
('kraken', False, 1, 2.1, 0.0025, 2.6925, 0.04476309226932673),
|
||||
('kraken', False, 3, 2.1, 0.0025, 2.6525, 0.13229426433915248),
|
||||
('kraken', True, 1, 2.1, 0.0025, -3.3706575, -0.05631842105263152),
|
||||
('kraken', True, 3, 2.1, 0.0025, -3.3706575, -0.16895526315789455),
|
||||
# # Kraken
|
||||
('kraken', False, 1, 2.1, 0.0025, 2.6925, 0.04476309226932673, spot, 0),
|
||||
('kraken', False, 3, 2.1, 0.0025, 2.6525, 0.13229426433915248, margin, 0),
|
||||
('kraken', True, 1, 2.1, 0.0025, -3.3706575, -0.05631842105263152, margin, 0),
|
||||
('kraken', True, 3, 2.1, 0.0025, -3.3706575, -0.16895526315789455, margin, 0),
|
||||
|
||||
('kraken', False, 1, 1.9, 0.0025, -3.2925, -0.05473815461346632),
|
||||
('kraken', False, 3, 1.9, 0.0025, -3.3325, -0.16620947630922667),
|
||||
('kraken', True, 1, 1.9, 0.0025, 2.6503575, 0.04428333333333334),
|
||||
('kraken', True, 3, 1.9, 0.0025, 2.6503575, 0.13285000000000002),
|
||||
('kraken', False, 1, 1.9, 0.0025, -3.2925, -0.05473815461346632, margin, 0),
|
||||
('kraken', False, 3, 1.9, 0.0025, -3.3325, -0.16620947630922667, margin, 0),
|
||||
('kraken', True, 1, 1.9, 0.0025, 2.6503575, 0.04428333333333334, margin, 0),
|
||||
('kraken', True, 3, 1.9, 0.0025, 2.6503575, 0.13285000000000002, margin, 0),
|
||||
|
||||
('kraken', False, 1, 2.2, 0.0025, 5.685, 0.0945137157107232),
|
||||
('kraken', False, 3, 2.2, 0.0025, 5.645, 0.2815461346633419),
|
||||
('kraken', True, 1, 2.2, 0.0025, -6.381165, -0.106619298245614),
|
||||
('kraken', True, 3, 2.2, 0.0025, -6.381165, -0.319857894736842),
|
||||
('kraken', False, 1, 2.2, 0.0025, 5.685, 0.0945137157107232, margin, 0),
|
||||
('kraken', False, 3, 2.2, 0.0025, 5.645, 0.2815461346633419, margin, 0),
|
||||
('kraken', True, 1, 2.2, 0.0025, -6.381165, -0.106619298245614, margin, 0),
|
||||
('kraken', True, 3, 2.2, 0.0025, -6.381165, -0.319857894736842, margin, 0),
|
||||
|
||||
('binance', False, 1, 2.1, 0.003, 2.6610000000000014, 0.04423940149625927),
|
||||
('binance', False, 1, 1.9, 0.003, -3.320999999999998, -0.05521197007481293),
|
||||
('binance', False, 1, 2.2, 0.003, 5.652000000000008, 0.09396508728179565),
|
||||
])
|
||||
('binance', False, 1, 2.1, 0.003, 2.6610000000000014, 0.04423940149625927, spot, 0),
|
||||
('binance', False, 1, 1.9, 0.003, -3.320999999999998, -0.05521197007481293, spot, 0),
|
||||
('binance', False, 1, 2.2, 0.003, 5.652000000000008, 0.09396508728179565, spot, 0),
|
||||
|
||||
# # FUTURES, funding_fee=1
|
||||
('binance', False, 1, 2.1, 0.0025, 3.6925, 0.06138819617622615, futures, 1),
|
||||
('binance', False, 3, 2.1, 0.0025, 3.6925, 0.18416458852867845, futures, 1),
|
||||
('binance', True, 1, 2.1, 0.0025, -2.3074999999999974, -0.038554720133667564, futures, 1),
|
||||
('binance', True, 3, 2.1, 0.0025, -2.3074999999999974, -0.11566416040100269, futures, 1),
|
||||
|
||||
('binance', False, 1, 1.9, 0.0025, -2.2925, -0.0381130507065669, futures, 1),
|
||||
('binance', False, 3, 1.9, 0.0025, -2.2925, -0.1143391521197007, futures, 1),
|
||||
('binance', True, 1, 1.9, 0.0025, 3.707500000000003, 0.06194653299916464, futures, 1),
|
||||
('binance', True, 3, 1.9, 0.0025, 3.707500000000003, 0.18583959899749392, futures, 1),
|
||||
|
||||
('binance', False, 1, 2.2, 0.0025, 6.685, 0.11113881961762262, futures, 1),
|
||||
('binance', False, 3, 2.2, 0.0025, 6.685, 0.33341645885286786, futures, 1),
|
||||
('binance', True, 1, 2.2, 0.0025, -5.315000000000005, -0.08880534670008355, futures, 1),
|
||||
('binance', True, 3, 2.2, 0.0025, -5.315000000000005, -0.26641604010025066, futures, 1),
|
||||
|
||||
# FUTURES, funding_fee=-1
|
||||
('binance', False, 1, 2.1, 0.0025, 1.6925000000000026, 0.028137988362427313, futures, -1),
|
||||
('binance', False, 3, 2.1, 0.0025, 1.6925000000000026, 0.08441396508728194, futures, -1),
|
||||
('binance', True, 1, 2.1, 0.0025, -4.307499999999997, -0.07197159565580624, futures, -1),
|
||||
('binance', True, 3, 2.1, 0.0025, -4.307499999999997, -0.21591478696741873, futures, -1),
|
||||
|
||||
('binance', False, 1, 1.9, 0.0025, -4.292499999999997, -0.07136325852036574, futures, -1),
|
||||
('binance', False, 3, 1.9, 0.0025, -4.292499999999997, -0.2140897755610972, futures, -1),
|
||||
('binance', True, 1, 1.9, 0.0025, 1.7075000000000031, 0.02852965747702596, futures, -1),
|
||||
('binance', True, 3, 1.9, 0.0025, 1.7075000000000031, 0.08558897243107788, futures, -1),
|
||||
|
||||
('binance', False, 1, 2.2, 0.0025, 4.684999999999995, 0.07788861180382378, futures, -1),
|
||||
('binance', False, 3, 2.2, 0.0025, 4.684999999999995, 0.23366583541147135, futures, -1),
|
||||
('binance', True, 1, 2.2, 0.0025, -7.315000000000005, -0.12222222222222223, futures, -1),
|
||||
('binance', True, 3, 2.2, 0.0025, -7.315000000000005, -0.3666666666666667, futures, -1),
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_profit(
|
||||
limit_buy_order_usdt,
|
||||
@ -783,7 +865,9 @@ def test_calc_profit(
|
||||
close_rate,
|
||||
fee_close,
|
||||
profit,
|
||||
profit_ratio
|
||||
profit_ratio,
|
||||
trading_mode,
|
||||
funding_fees
|
||||
):
|
||||
"""
|
||||
10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
||||
@ -802,6 +886,7 @@ def test_calc_profit(
|
||||
1x,-1x: 60.0 quote
|
||||
3x,-3x: 20.0 quote
|
||||
hours: 1/6 (10 minutes)
|
||||
funding_fees: 1
|
||||
borrowed
|
||||
1x: 0 quote
|
||||
3x: 40 quote
|
||||
@ -913,6 +998,87 @@ def test_calc_profit(
|
||||
2.1 quote: (62.811 / 60.15) - 1 = 0.04423940149625927
|
||||
1.9 quote: (56.829 / 60.15) - 1 = -0.05521197007481293
|
||||
2.2 quote: (65.802 / 60.15) - 1 = 0.09396508728179565
|
||||
futures (live):
|
||||
funding_fee: 1
|
||||
close_value:
|
||||
equations:
|
||||
1x,3x: (amount * close_rate) - (amount * close_rate * fee) + funding_fees
|
||||
-1x,-3x: (amount * close_rate) + (amount * close_rate * fee) - funding_fees
|
||||
2.1 quote
|
||||
1x,3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) + 1 = 63.8425
|
||||
-1x,-3x: (30.00 * 2.1) + (30.00 * 2.1 * 0.0025) - 1 = 62.1575
|
||||
1.9 quote
|
||||
1x,3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) + 1 = 57.8575
|
||||
-1x,-3x: (30.00 * 1.9) + (30.00 * 1.9 * 0.0025) - 1 = 56.1425
|
||||
2.2 quote:
|
||||
1x,3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) + 1 = 66.835
|
||||
-1x,-3x: (30.00 * 2.20) + (30.00 * 2.20 * 0.0025) - 1 = 65.165
|
||||
total_profit:
|
||||
2.1 quote
|
||||
1x,3x: 63.8425 - 60.15 = 3.6925
|
||||
-1x,-3x: 59.850 - 62.1575 = -2.3074999999999974
|
||||
1.9 quote
|
||||
1x,3x: 57.8575 - 60.15 = -2.2925
|
||||
-1x,-3x: 59.850 - 56.1425 = 3.707500000000003
|
||||
2.2 quote:
|
||||
1x,3x: 66.835 - 60.15 = 6.685
|
||||
-1x,-3x: 59.850 - 65.165 = -5.315000000000005
|
||||
total_profit_ratio:
|
||||
2.1 quote
|
||||
1x: (63.8425 / 60.15) - 1 = 0.06138819617622615
|
||||
3x: ((63.8425 / 60.15) - 1)*3 = 0.18416458852867845
|
||||
-1x: 1 - (62.1575 / 59.850) = -0.038554720133667564
|
||||
-3x: (1 - (62.1575 / 59.850))*3 = -0.11566416040100269
|
||||
1.9 quote
|
||||
1x: (57.8575 / 60.15) - 1 = -0.0381130507065669
|
||||
3x: ((57.8575 / 60.15) - 1)*3 = -0.1143391521197007
|
||||
-1x: 1 - (56.1425 / 59.850) = 0.06194653299916464
|
||||
-3x: (1 - (56.1425 / 59.850))*3 = 0.18583959899749392
|
||||
2.2 quote
|
||||
1x: (66.835 / 60.15) - 1 = 0.11113881961762262
|
||||
3x: ((66.835 / 60.15) - 1)*3 = 0.33341645885286786
|
||||
-1x: 1 - (65.165 / 59.850) = -0.08880534670008355
|
||||
-3x: (1 - (65.165 / 59.850))*3 = -0.26641604010025066
|
||||
funding_fee: -1
|
||||
close_value:
|
||||
equations:
|
||||
(amount * close_rate) - (amount * close_rate * fee) + funding_fees
|
||||
(amount * close_rate) - (amount * close_rate * fee) - funding_fees
|
||||
2.1 quote
|
||||
1x,3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) + (-1) = 61.8425
|
||||
-1x,-3x: (30.00 * 2.1) + (30.00 * 2.1 * 0.0025) - (-1) = 64.1575
|
||||
1.9 quote
|
||||
1x,3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) + (-1) = 55.8575
|
||||
-1x,-3x: (30.00 * 1.9) + (30.00 * 1.9 * 0.0025) - (-1) = 58.1425
|
||||
2.2 quote:
|
||||
1x,3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) + (-1) = 64.835
|
||||
-1x,-3x: (30.00 * 2.20) + (30.00 * 2.20 * 0.0025) - (-1) = 67.165
|
||||
total_profit:
|
||||
2.1 quote
|
||||
1x,3x: 61.8425 - 60.15 = 1.6925000000000026
|
||||
-1x,-3x: 59.850 - 64.1575 = -4.307499999999997
|
||||
1.9 quote
|
||||
1x,3x: 55.8575 - 60.15 = -4.292499999999997
|
||||
-1x,-3x: 59.850 - 58.1425 = 1.7075000000000031
|
||||
2.2 quote:
|
||||
1x,3x: 64.835 - 60.15 = 4.684999999999995
|
||||
-1x,-3x: 59.850 - 67.165 = -7.315000000000005
|
||||
total_profit_ratio:
|
||||
2.1 quote
|
||||
1x: (61.8425 / 60.15) - 1 = 0.028137988362427313
|
||||
3x: ((61.8425 / 60.15) - 1)*3 = 0.08441396508728194
|
||||
-1x: 1 - (64.1575 / 59.850) = -0.07197159565580624
|
||||
-3x: (1 - (64.1575 / 59.850))*3 = -0.21591478696741873
|
||||
1.9 quote
|
||||
1x: (55.8575 / 60.15) - 1 = -0.07136325852036574
|
||||
3x: ((55.8575 / 60.15) - 1)*3 = -0.2140897755610972
|
||||
-1x: 1 - (58.1425 / 59.850) = 0.02852965747702596
|
||||
-3x: (1 - (58.1425 / 59.850))*3 = 0.08558897243107788
|
||||
2.2 quote
|
||||
1x: (64.835 / 60.15) - 1 = 0.07788861180382378
|
||||
3x: ((64.835 / 60.15) - 1)*3 = 0.23366583541147135
|
||||
-1x: 1 - (67.165 / 59.850) = -0.12222222222222223
|
||||
-3x: (1 - (67.165 / 59.850))*3 = -0.3666666666666667
|
||||
"""
|
||||
trade = Trade(
|
||||
pair='ADA/USDT',
|
||||
@ -925,7 +1091,9 @@ def test_calc_profit(
|
||||
is_short=is_short,
|
||||
leverage=lev,
|
||||
fee_open=0.0025,
|
||||
fee_close=fee_close
|
||||
fee_close=fee_close,
|
||||
trading_mode=trading_mode,
|
||||
funding_fees=funding_fees
|
||||
)
|
||||
trade.open_order_id = 'something'
|
||||
|
||||
@ -1440,6 +1608,8 @@ def test_to_json(default_conf, fee):
|
||||
'interest_rate': None,
|
||||
'isolated_liq': None,
|
||||
'is_short': None,
|
||||
'trading_mode': None,
|
||||
'funding_fees': None
|
||||
}
|
||||
|
||||
# Simulate dry_run entries
|
||||
@ -1511,6 +1681,8 @@ def test_to_json(default_conf, fee):
|
||||
'interest_rate': None,
|
||||
'isolated_liq': None,
|
||||
'is_short': None,
|
||||
'trading_mode': None,
|
||||
'funding_fees': None
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user