diff --git a/MANIFEST.in b/MANIFEST.in index adbcd2e30..a14965c09 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,5 +2,6 @@ include LICENSE include README.md recursive-include freqtrade *.py recursive-include freqtrade/templates/ *.j2 *.ipynb +include freqtrade/exchange/binance_leverage_tiers.json include freqtrade/rpc/api_server/ui/fallback_file.html include freqtrade/rpc/api_server/ui/favicon.ico diff --git a/README.md b/README.md index efa334a27..02eb47e00 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Telegram is not mandatory. However, this is a great way to control your bot. Mor - `/stopbuy`: Stop entering new trades. - `/status |[table]`: Lists all or specific open trades. - `/profit []`: Lists cumulative profit from all finished trades, over the last n days. -- `/forcesell |all`: Instantly sells the given trade (Ignoring `minimum_roi`). +- `/forceexit |all`: Instantly exits the given trade (Ignoring `minimum_roi`). - `/performance`: Show performance of each finished trade grouped by pair - `/balance`: Show account balance per currency. - `/daily `: Shows profit or loss per day, over the last n days. diff --git a/build_helpers/publish_docker_arm64.sh b/build_helpers/publish_docker_arm64.sh index 1ad8074d4..70f99e54b 100755 --- a/build_helpers/publish_docker_arm64.sh +++ b/build_helpers/publish_docker_arm64.sh @@ -42,7 +42,7 @@ docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_I docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM # Run backtest -docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV2 +docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV3 if [ $? -ne 0 ]; then echo "failed running backtest" diff --git a/build_helpers/publish_docker_multi.sh b/build_helpers/publish_docker_multi.sh index dd6ac841e..fd5f0ef93 100755 --- a/build_helpers/publish_docker_multi.sh +++ b/build_helpers/publish_docker_multi.sh @@ -53,7 +53,7 @@ docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${CACHE_IMAGE docker tag freqtrade:$TAG_PLOT ${CACHE_IMAGE}:$TAG_PLOT # Run backtest -docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV2 +docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV3 if [ $? -ne 0 ]; then echo "failed running backtest" diff --git a/config_examples/config_binance.example.json b/config_examples/config_binance.example.json index c6faf506c..8e622eeae 100644 --- a/config_examples/config_binance.example.json +++ b/config_examples/config_binance.example.json @@ -8,21 +8,23 @@ "dry_run": true, "cancel_open_orders_on_exit": false, "unfilledtimeout": { - "buy": 10, - "sell": 10, + "entry": 10, + "exit": 10, "exit_timeout_count": 0, "unit": "minutes" }, - "bid_strategy": { - "ask_last_balance": 0.0, + "entry_pricing": { + "price_side": "same", "use_order_book": true, "order_book_top": 1, + "price_last_balance": 0.0, "check_depth_of_market": { "enabled": false, "bids_to_ask_delta": 1 } }, - "ask_strategy": { + "exit_pricing": { + "price_side": "same", "use_order_book": true, "order_book_top": 1 }, diff --git a/config_examples/config_bittrex.example.json b/config_examples/config_bittrex.example.json index 9fe99c835..d40ea6c5a 100644 --- a/config_examples/config_bittrex.example.json +++ b/config_examples/config_bittrex.example.json @@ -8,21 +8,23 @@ "dry_run": true, "cancel_open_orders_on_exit": false, "unfilledtimeout": { - "buy": 10, - "sell": 10, + "entry": 10, + "exit": 10, "exit_timeout_count": 0, "unit": "minutes" }, - "bid_strategy": { + "entry_pricing": { + "price_side": "same", "use_order_book": true, - "ask_last_balance": 0.0, "order_book_top": 1, + "price_last_balance": 0.0, "check_depth_of_market": { "enabled": false, "bids_to_ask_delta": 1 } }, - "ask_strategy":{ + "exit_pricing":{ + "price_side": "same", "use_order_book": true, "order_book_top": 1 }, diff --git a/config_examples/config_ftx.example.json b/config_examples/config_ftx.example.json index 4f7c2af54..f86da8ea0 100644 --- a/config_examples/config_ftx.example.json +++ b/config_examples/config_ftx.example.json @@ -8,21 +8,23 @@ "dry_run": true, "cancel_open_orders_on_exit": false, "unfilledtimeout": { - "buy": 10, - "sell": 10, + "entry": 10, + "exit": 10, "exit_timeout_count": 0, "unit": "minutes" }, - "bid_strategy": { - "ask_last_balance": 0.0, + "entry_pricing": { + "price_side": "same", "use_order_book": true, "order_book_top": 1, + "price_last_balance": 0.0, "check_depth_of_market": { "enabled": false, "bids_to_ask_delta": 1 } }, - "ask_strategy": { + "exit_pricing": { + "price_side": "same", "use_order_book": true, "order_book_top": 1 }, diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index 7931476b4..540e83af6 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -20,6 +20,8 @@ "sell_profit_offset": 0.0, "ignore_roi_if_buy_signal": false, "ignore_buying_expired_candle_after": 300, + "trading_mode": "spot", + "margin_mode": "", "minimal_roi": { "40": 0.0, "30": 0.01, @@ -28,40 +30,41 @@ }, "stoploss": -0.10, "unfilledtimeout": { - "buy": 10, - "sell": 10, + "entry": 10, + "exit": 10, "exit_timeout_count": 0, "unit": "minutes" }, - "bid_strategy": { - "price_side": "bid", + "entry_pricing": { + "price_side": "same", "use_order_book": true, - "ask_last_balance": 0.0, "order_book_top": 1, + "price_last_balance": 0.0, "check_depth_of_market": { "enabled": false, "bids_to_ask_delta": 1 } }, - "ask_strategy":{ - "price_side": "ask", + "exit_pricing":{ + "price_side": "same", "use_order_book": true, - "order_book_top": 1 + "order_book_top": 1, + "price_last_balance": 0.0 }, "order_types": { - "buy": "limit", - "sell": "limit", - "emergencysell": "market", - "forcesell": "market", - "forcebuy": "market", + "entry": "limit", + "exit": "limit", + "emergencyexit": "market", + "forceexit": "market", + "forceentry": "market", "stoploss": "market", "stoploss_on_exchange": false, "stoploss_on_exchange_interval": 60, "stoploss_on_exchange_limit_ratio": 0.99 }, "order_time_in_force": { - "buy": "gtc", - "sell": "gtc" + "entry": "gtc", + "exit": "gtc" }, "pairlists": [ {"method": "StaticPairList"}, diff --git a/config_examples/config_kraken.example.json b/config_examples/config_kraken.example.json index 5ac3a9255..69b00719a 100644 --- a/config_examples/config_kraken.example.json +++ b/config_examples/config_kraken.example.json @@ -8,21 +8,23 @@ "dry_run": true, "cancel_open_orders_on_exit": false, "unfilledtimeout": { - "buy": 10, - "sell": 10, + "entry": 10, + "exit": 10, "exit_timeout_count": 0, "unit": "minutes" }, - "bid_strategy": { + "entry_pricing": { + "price_side": "same", "use_order_book": true, - "ask_last_balance": 0.0, "order_book_top": 1, + "price_last_balance": 0.0, "check_depth_of_market": { "enabled": false, "bids_to_ask_delta": 1 } }, - "ask_strategy":{ + "exit_pricing":{ + "price_side": "same", "use_order_book": true, "order_book_top": 1 }, diff --git a/docs/backtesting.md b/docs/backtesting.md index 11608aad9..f42221f8c 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -274,8 +274,8 @@ A backtesting result will look like that: | XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 | | ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 | | TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 | -========================================================= SELL REASON STATS ========================================================== -| Sell Reason | Sells | Wins | Draws | Losses | +========================================================= EXIT REASON STATS ========================================================== +| Exit Reason | Sells | Wins | Draws | Losses | |:-------------------|--------:|------:|-------:|--------:| | trailing_stop_loss | 205 | 150 | 0 | 55 | | stop_loss | 166 | 0 | 0 | 166 | @@ -287,43 +287,43 @@ A backtesting result will look like that: | ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 | | LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 | | TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 | -=============== SUMMARY METRICS =============== -| Metric | Value | -|-----------------------+---------------------| -| Backtesting from | 2019-01-01 00:00:00 | -| Backtesting to | 2019-05-01 00:00:00 | -| Max open trades | 3 | -| | | -| Total/Daily Avg Trades| 429 / 3.575 | -| Starting balance | 0.01000000 BTC | -| Final balance | 0.01762792 BTC | -| Absolute profit | 0.00762792 BTC | -| Total profit % | 76.2% | -| Trades per day | 3.575 | -| Avg. stake amount | 0.001 BTC | -| Total trade volume | 0.429 BTC | -| | | -| Best Pair | LSK/BTC 26.26% | -| Worst Pair | ZEC/BTC -10.18% | -| Best Trade | LSK/BTC 4.25% | -| Worst Trade | ZEC/BTC -10.25% | -| Best day | 0.00076 BTC | -| Worst day | -0.00036 BTC | -| Days win/draw/lose | 12 / 82 / 25 | -| Avg. Duration Winners | 4:23:00 | -| Avg. Duration Loser | 6:55:00 | -| Rejected Buy signals | 3089 | -| Entry/Exit Timeouts | 0 / 0 | -| | | -| Min balance | 0.00945123 BTC | -| Max balance | 0.01846651 BTC | -| Drawdown (Account) | 13.33% | -| Drawdown | 0.0015 BTC | -| Drawdown high | 0.0013 BTC | -| Drawdown low | -0.0002 BTC | -| Drawdown Start | 2019-02-15 14:10:00 | -| Drawdown End | 2019-04-11 18:15:00 | -| Market change | -5.88% | +================ SUMMARY METRICS =============== +| Metric | Value | +|------------------------+---------------------| +| Backtesting from | 2019-01-01 00:00:00 | +| Backtesting to | 2019-05-01 00:00:00 | +| Max open trades | 3 | +| | | +| Total/Daily Avg Trades | 429 / 3.575 | +| Starting balance | 0.01000000 BTC | +| Final balance | 0.01762792 BTC | +| Absolute profit | 0.00762792 BTC | +| Total profit % | 76.2% | +| Trades per day | 3.575 | +| Avg. stake amount | 0.001 BTC | +| Total trade volume | 0.429 BTC | +| | | +| Best Pair | LSK/BTC 26.26% | +| Worst Pair | ZEC/BTC -10.18% | +| Best Trade | LSK/BTC 4.25% | +| Worst Trade | ZEC/BTC -10.25% | +| Best day | 0.00076 BTC | +| Worst day | -0.00036 BTC | +| Days win/draw/lose | 12 / 82 / 25 | +| Avg. Duration Winners | 4:23:00 | +| Avg. Duration Loser | 6:55:00 | +| Rejected Entry signals | 3089 | +| Entry/Exit Timeouts | 0 / 0 | +| | | +| Min balance | 0.00945123 BTC | +| Max balance | 0.01846651 BTC | +| Drawdown (Account) | 13.33% | +| Drawdown | 0.0015 BTC | +| Drawdown high | 0.0013 BTC | +| Drawdown low | -0.0002 BTC | +| Drawdown Start | 2019-02-15 14:10:00 | +| Drawdown End | 2019-04-11 18:15:00 | +| Market change | -5.88% | =============================================== ``` @@ -359,14 +359,14 @@ On the other hand, if you set a too high `minimal_roi` like `"0": 0.55` (55%), there is almost no chance that the bot will ever reach this profit. Hence, keep in mind that your performance is an integral mix of all different elements of the strategy, your configuration, and the crypto-currency pairs you have set up. -### Sell reasons table +### Exit reasons table -The 2nd table contains a recap of sell reasons. +The 2nd table contains a recap of exit reasons. This table can tell you which area needs some additional work (e.g. all or many of the `sell_signal` trades are losses, so you should work on improving the sell signal, or consider disabling it). ### Left open trades table -The 3rd table contains all trades the bot had to `forcesell` at the end of the backtesting period to present you the full picture. +The 3rd table contains all trades the bot had to `forceexit` at the end of the backtesting period to present you the full picture. This is necessary to simulate realistic behavior, since the backtest period has to end at some point, while realistically, you could leave the bot running forever. These trades are also included in the first table, but are also shown separately in this table for clarity. @@ -376,43 +376,49 @@ The last element of the backtest report is the summary metrics table. It contains some useful key metrics about performance of your strategy on backtesting data. ``` -=============== SUMMARY METRICS =============== -| Metric | Value | -|-----------------------+---------------------| -| Backtesting from | 2019-01-01 00:00:00 | -| Backtesting to | 2019-05-01 00:00:00 | -| Max open trades | 3 | -| | | -| Total/Daily Avg Trades| 429 / 3.575 | -| Starting balance | 0.01000000 BTC | -| Final balance | 0.01762792 BTC | -| Absolute profit | 0.00762792 BTC | -| Total profit % | 76.2% | -| Avg. stake amount | 0.001 BTC | -| Total trade volume | 0.429 BTC | -| | | -| Best Pair | LSK/BTC 26.26% | -| Worst Pair | ZEC/BTC -10.18% | -| Best Trade | LSK/BTC 4.25% | -| Worst Trade | ZEC/BTC -10.25% | -| Best day | 0.00076 BTC | -| Worst day | -0.00036 BTC | -| Days win/draw/lose | 12 / 82 / 25 | -| Avg. Duration Winners | 4:23:00 | -| Avg. Duration Loser | 6:55:00 | -| Rejected Buy signals | 3089 | -| Entry/Exit Timeouts | 0 / 0 | -| | | -| Min balance | 0.00945123 BTC | -| Max balance | 0.01846651 BTC | -| Drawdown (Account) | 13.33% | -| Drawdown | 0.0015 BTC | -| Drawdown high | 0.0013 BTC | -| Drawdown low | -0.0002 BTC | -| Drawdown Start | 2019-02-15 14:10:00 | -| Drawdown End | 2019-04-11 18:15:00 | -| Market change | -5.88% | -=============================================== +================ SUMMARY METRICS =============== +| Metric | Value | +|------------------------+---------------------| +| Backtesting from | 2019-01-01 00:00:00 | +| Backtesting to | 2019-05-01 00:00:00 | +| Max open trades | 3 | +| | | +| Total/Daily Avg Trades | 429 / 3.575 | +| Starting balance | 0.01000000 BTC | +| Final balance | 0.01762792 BTC | +| Absolute profit | 0.00762792 BTC | +| Total profit % | 76.2% | +| Avg. stake amount | 0.001 BTC | +| Total trade volume | 0.429 BTC | +| | | +| Long / Short | 352 / 77 | +| Total profit Long % | 1250.58% | +| Total profit Short % | -15.02% | +| Absolute profit Long | 0.00838792 BTC | +| Absolute profit Short | -0.00076 BTC | +| | | +| Best Pair | LSK/BTC 26.26% | +| Worst Pair | ZEC/BTC -10.18% | +| Best Trade | LSK/BTC 4.25% | +| Worst Trade | ZEC/BTC -10.25% | +| Best day | 0.00076 BTC | +| Worst day | -0.00036 BTC | +| Days win/draw/lose | 12 / 82 / 25 | +| Avg. Duration Winners | 4:23:00 | +| Avg. Duration Loser | 6:55:00 | +| Rejected Entry signals | 3089 | +| Entry/Exit Timeouts | 0 / 0 | +| | | +| Min balance | 0.00945123 BTC | +| Max balance | 0.01846651 BTC | +| Drawdown (Account) | 13.33% | +| Drawdown | 0.0015 BTC | +| Drawdown high | 0.0013 BTC | +| Drawdown low | -0.0002 BTC | +| Drawdown Start | 2019-02-15 14:10:00 | +| Drawdown End | 2019-04-11 18:15:00 | +| Market change | -5.88% | +================================================ ``` @@ -430,7 +436,7 @@ It contains some useful key metrics about performance of your strategy on backte - `Best day` / `Worst day`: Best and worst day based on daily profit. - `Days win/draw/lose`: Winning / Losing days (draws are usually days without closed trade). - `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades. -- `Rejected Buy signals`: Buy signals that could not be acted upon due to max_open_trades being reached. +- `Rejected Entry signals`: Trade entry signals that could not be acted upon due to `max_open_trades` being reached. - `Entry/Exit Timeouts`: Entry/exit orders which did not fill (only applicable if custom pricing is used). - `Min balance` / `Max balance`: Lowest and Highest Wallet balance during the backtest period. - `Drawdown (Account)`: Maximum Account Drawdown experienced. Calculated as $(Absolute Drawdown) / (DrawdownHigh + startingBalance)$. @@ -438,6 +444,9 @@ It contains some useful key metrics about performance of your strategy on backte - `Drawdown high` / `Drawdown low`: Profit at the beginning and end of the largest drawdown period. A negative low value means initial capital lost. - `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command). - `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column. +- `Long / Short`: Split long/short values (Only shown when short trades were made). +- `Total profit Long %` / `Absolute profit Long`: Profit long trades only (Only shown when short trades were made). +- `Total profit Short %` / `Absolute profit Short`: Profit short trades only (Only shown when short trades were made). ### Daily / Weekly / Monthly breakdown @@ -524,7 +533,7 @@ freqtrade backtesting --strategy AwesomeStrategy --timeframe 1h --timeframe-deta ``` This will load 1h data as well as 5m data for the timeframe. The strategy will be analyzed with the 1h timeframe - and for every "open trade candle" (candles where a trade is open) the 5m data will be used to simulate intra-candle movements. -All callback functions (`custom_sell()`, `custom_stoploss()`, ... ) will be running for each 5m candle once the trade is opened (so 12 times in the above example of 1h timeframe, and 5m detailed timeframe). +All callback functions (`custom_exit()`, `custom_stoploss()`, ... ) will be running for each 5m candle once the trade is opened (so 12 times in the above example of 1h timeframe, and 5m detailed timeframe). `--timeframe-detail` must be smaller than the original timeframe, otherwise backtesting will fail to start. diff --git a/docs/bot-basics.md b/docs/bot-basics.md index 4b5ba3a5b..e45e3d9ca 100644 --- a/docs/bot-basics.md +++ b/docs/bot-basics.md @@ -29,21 +29,22 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and * Call `bot_loop_start()` strategy callback. * Analyze strategy per pair. * Call `populate_indicators()` - * Call `populate_buy_trend()` - * Call `populate_sell_trend()` + * Call `populate_entry_trend()` + * Call `populate_exit_trend()` * Check timeouts for open orders. - * Calls `check_buy_timeout()` strategy callback for open buy orders. - * Calls `check_sell_timeout()` strategy callback for open sell orders. -* Verifies existing positions and eventually places sell orders. - * Considers stoploss, ROI and sell-signal, `custom_sell()` and `custom_stoploss()`. - * Determine sell-price based on `ask_strategy` configuration setting or by using the `custom_exit_price()` callback. - * Before a sell order is placed, `confirm_trade_exit()` strategy callback is called. + * Calls `check_entry_timeout()` strategy callback for open entry orders. + * Calls `check_exit_timeout()` strategy callback for open exit orders. +* Verifies existing positions and eventually places exit orders. + * Considers stoploss, ROI and exit-signal, `custom_exit()` and `custom_stoploss()`. + * Determine exit-price based on `exit_pricing` configuration setting or by using the `custom_exit_price()` callback. + * Before a exit order is placed, `confirm_trade_exit()` strategy callback is called. * Check position adjustments for open trades if enabled by calling `adjust_trade_position()` and place additional order if required. * Check if trade-slots are still available (if `max_open_trades` is reached). -* Verifies buy signal trying to enter new positions. - * Determine buy-price based on `bid_strategy` configuration setting, or by using the `custom_entry_price()` callback. +* Verifies entry signal trying to enter new positions. + * Determine entry-price based on `entry_pricing` configuration setting, or by using the `custom_entry_price()` callback. + * In Margin and Futures mode, `leverage()` strategy callback is called to determine the desired leverage. * Determine stake size by calling the `custom_stake_amount()` callback. - * Before a buy order is placed, `confirm_trade_entry()` strategy callback is called. + * Before an entry order is placed, `confirm_trade_entry()` strategy callback is called. This loop will be repeated again and again until the bot is stopped. @@ -54,15 +55,17 @@ This loop will be repeated again and again until the bot is stopped. * Load historic data for configured pairlist. * Calls `bot_loop_start()` once. * Calculate indicators (calls `populate_indicators()` once per pair). -* Calculate buy / sell signals (calls `populate_buy_trend()` and `populate_sell_trend()` once per pair). +* Calculate entry / exit signals (calls `populate_entry_trend()` and `populate_exit_trend()` once per pair). * Loops per candle simulating entry and exit points. - * Confirm trade buy / sell (calls `confirm_trade_entry()` and `confirm_trade_exit()` if implemented in the strategy). + * Check for Order timeouts, either via the `unfilledtimeout` configuration, or via `check_entry_timeout()` / `check_exit_timeout()` strategy callbacks. + * Check for trade entry signals (`enter_long` / `enter_short` columns). + * Confirm trade entry / exits (calls `confirm_trade_entry()` and `confirm_trade_exit()` if implemented in the strategy). * Call `custom_entry_price()` (if implemented in the strategy) to determine entry price (Prices are moved to be within the opening candle). + * In Margin and Futures mode, `leverage()` strategy callback is called to determine the desired leverage. * Determine stake size by calling the `custom_stake_amount()` callback. * Check position adjustments for open trades if enabled and call `adjust_trade_position()` to determine if an additional order is requested. - * Call `custom_stoploss()` and `custom_sell()` to find custom exit points. - * For sells based on sell-signal and custom-sell: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle). - * Check for Order timeouts, either via the `unfilledtimeout` configuration, or via `check_buy_timeout()` / `check_sell_timeout()` strategy callbacks. + * Call `custom_stoploss()` and `custom_exit()` to find custom exit points. + * For exits based on exit-signal and custom-exit: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle). * Generate backtest report output !!! Note diff --git a/docs/configuration.md b/docs/configuration.md index 2cb5dfa93..9ed45fff3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -99,27 +99,30 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md#trailing-stop-loss-only-once-the-trade-has-reached-a-certain-offset). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0.0` (no offset).*
**Datatype:** Float | `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean | `fee` | Fee used during backtesting / dry-runs. Should normally not be configured, which has freqtrade fall back to the exchange default fee. Set as ratio (e.g. 0.001 = 0.1%). Fee is applied twice for each trade, once when buying, once when selling.
**Datatype:** Float (as ratio) -| `unfilledtimeout.buy` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Integer -| `unfilledtimeout.sell` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Integer +| `trading_mode` | Specifies if you want to trade regularly, trade with leverage, or trade contracts whose prices are derived from matching cryptocurrency prices. [leverage documentation](leverage.md).
*Defaults to `"spot"`.*
**Datatype:** String +| `margin_mode` | When trading with leverage, this determines if the collateral owned by the trader will be shared or isolated to each trading pair [leverage documentation](leverage.md).
**Datatype:** String +| `liquidation_buffer` | A ratio specifying how large of a safety net to place between the liquidation price and the stoploss to prevent a position from reaching the liquidation price [leverage documentation](leverage.md).
*Defaults to `0.05`.*
**Datatype:** Float +| `unfilledtimeout.entry` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled entry order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Integer +| `unfilledtimeout.exit` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled exit order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Integer | `unfilledtimeout.unit` | Unit to use in unfilledtimeout setting. Note: If you set unfilledtimeout.unit to "seconds", "internals.process_throttle_secs" must be inferior or equal to timeout [Strategy Override](#parameters-in-the-strategy).
*Defaults to `minutes`.*
**Datatype:** String | `unfilledtimeout.exit_timeout_count` | How many times can exit orders time out. Once this number of timeouts is reached, an emergency sell is triggered. 0 to disable and allow unlimited order cancels. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0`.*
**Datatype:** Integer -| `bid_strategy.price_side` | Select the side of the spread the bot should look at to get the buy rate. [More information below](#buy-price-side).
*Defaults to `bid`.*
**Datatype:** String (either `ask` or `bid`). -| `bid_strategy.ask_last_balance` | **Required.** Interpolate the bidding price. More information [below](#buy-price-without-orderbook-enabled). -| `bid_strategy.use_order_book` | Enable buying using the rates in [Order Book Bids](#buy-price-with-orderbook-enabled).
**Datatype:** Boolean -| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book "price_side" to buy. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in [Order Book Bids](#buy-price-with-orderbook-enabled).
*Defaults to `1`.*
**Datatype:** Positive Integer -| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book. [Check market depth](#check-depth-of-market).
*Defaults to `false`.*
**Datatype:** Boolean -| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The difference ratio of buy orders and sell orders found in Order Book. A value below 1 means sell order size is greater, while value greater than 1 means buy order size is higher. [Check market depth](#check-depth-of-market)
*Defaults to `0`.*
**Datatype:** Float (as ratio) -| `ask_strategy.price_side` | Select the side of the spread the bot should look at to get the sell rate. [More information below](#sell-price-side).
*Defaults to `ask`.*
**Datatype:** String (either `ask` or `bid`). -| `ask_strategy.bid_last_balance` | Interpolate the selling price. More information [below](#sell-price-without-orderbook-enabled). -| `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled).
**Datatype:** Boolean -| `ask_strategy.order_book_top` | Bot will use the top N rate in Order Book "price_side" to sell. I.e. a value of 2 will allow the bot to pick the 2nd ask rate in [Order Book Asks](#sell-price-with-orderbook-enabled)
*Defaults to `1`.*
**Datatype:** Positive Integer +| `entry_pricing.price_side` | Select the side of the spread the bot should look at to get the entry rate. [More information below](#buy-price-side).
*Defaults to `same`.*
**Datatype:** String (either `ask`, `bid`, `same` or `other`). +| `entry_pricing.price_last_balance` | **Required.** Interpolate the bidding price. More information [below](#entry-price-without-orderbook-enabled). +| `entry_pricing.use_order_book` | Enable entering using the rates in [Order Book Entry](#entry-price-with-orderbook-enabled).
*Defaults to `True`.*
**Datatype:** Boolean +| `entry_pricing.order_book_top` | Bot will use the top N rate in Order Book "price_side" to enter a trade. I.e. a value of 2 will allow the bot to pick the 2nd entry in [Order Book Entry](#entry-price-with-orderbook-enabled).
*Defaults to `1`.*
**Datatype:** Positive Integer +| `entry_pricing. check_depth_of_market.enabled` | Do not enter if the difference of buy orders and sell orders is met in Order Book. [Check market depth](#check-depth-of-market).
*Defaults to `false`.*
**Datatype:** Boolean +| `entry_pricing. check_depth_of_market.bids_to_ask_delta` | The difference ratio of buy orders and sell orders found in Order Book. A value below 1 means sell order size is greater, while value greater than 1 means buy order size is higher. [Check market depth](#check-depth-of-market)
*Defaults to `0`.*
**Datatype:** Float (as ratio) +| `exit_pricing.price_side` | Select the side of the spread the bot should look at to get the exit rate. [More information below](#exit-price-side).
*Defaults to `same`.*
**Datatype:** String (either `ask`, `bid`, `same` or `other`). +| `exit_pricing.price_last_balance` | Interpolate the exiting price. More information [below](#exit-price-without-orderbook-enabled). +| `exit_pricing.use_order_book` | Enable exiting of open trades using [Order Book Exit](#exit-price-with-orderbook-enabled).
*Defaults to `True`.*
**Datatype:** Boolean +| `exit_pricing.order_book_top` | Bot will use the top N rate in Order Book "price_side" to sell. I.e. a value of 2 will allow the bot to pick the 2nd ask rate in [Order Book Exit](#exit-price-with-orderbook-enabled)
*Defaults to `1`.*
**Datatype:** Positive Integer | `use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `true`.*
**Datatype:** Boolean | `sell_profit_only` | Wait until the bot reaches `sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean | `sell_profit_offset` | Sell-signal is only active above this value. Only active in combination with `sell_profit_only=True`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0.0`.*
**Datatype:** Float (as ratio) | `ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean | `ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used.
**Datatype:** Integer -| `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict -| `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict +| `order_types` | Configure order-types depending on the action (`"entry"`, `"exit"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict +| `order_time_in_force` | Configure time in force for entry and exit orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict | `custom_price_max_distance_ratio` | Configure maximum distance ratio between current and custom entry or exit price.
*Defaults to `0.02` 2%).*
**Datatype:** Positive float | `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
**Datatype:** String | `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.
**Datatype:** Boolean @@ -371,29 +374,28 @@ For example, if your strategy is using a 1h timeframe, and you only want to buy ### Understand order_types -The `order_types` configuration parameter maps actions (`buy`, `sell`, `stoploss`, `emergencysell`, `forcesell`, `forcebuy`) to order-types (`market`, `limit`, ...) as well as configures stoploss to be on the exchange and defines stoploss on exchange update interval in seconds. +The `order_types` configuration parameter maps actions (`entry`, `exit`, `stoploss`, `emergencyexit`, `forceexit`, `forceentry`) to order-types (`market`, `limit`, ...) as well as configures stoploss to be on the exchange and defines stoploss on exchange update interval in seconds. This allows to buy using limit orders, sell using limit-orders, and create stoplosses using market orders. It also allows to set the stoploss "on exchange" which means stoploss order would be placed immediately once the buy order is fulfilled. - + `order_types` set in the configuration file overwrites values set in the strategy as a whole, so you need to configure the whole `order_types` dictionary in one place. -If this is configured, the following 4 values (`buy`, `sell`, `stoploss` and -`stoploss_on_exchange`) need to be present, otherwise, the bot will fail to start. +If this is configured, the following 4 values (`entry`, `exit`, `stoploss` and `stoploss_on_exchange`) need to be present, otherwise, the bot will fail to start. -For information on (`emergencysell`,`forcesell`, `forcebuy`, `stoploss_on_exchange`,`stoploss_on_exchange_interval`,`stoploss_on_exchange_limit_ratio`) please see stop loss documentation [stop loss on exchange](stoploss.md) +For information on (`emergencyexit`,`forceexit`, `forceentry`, `stoploss_on_exchange`,`stoploss_on_exchange_interval`,`stoploss_on_exchange_limit_ratio`) please see stop loss documentation [stop loss on exchange](stoploss.md) Syntax for Strategy: ```python order_types = { - "buy": "limit", - "sell": "limit", - "emergencysell": "market", - "forcebuy": "market", - "forcesell": "market", + "entry": "limit", + "exit": "limit", + "emergencyexit": "market", + "forceentry": "market", + "forceexit": "market", "stoploss": "market", "stoploss_on_exchange": False, "stoploss_on_exchange_interval": 60, @@ -405,11 +407,11 @@ Configuration: ```json "order_types": { - "buy": "limit", - "sell": "limit", - "emergencysell": "market", - "forcebuy": "market", - "forcesell": "market", + "entry": "limit", + "exit": "limit", + "emergencyexit": "market", + "forceentry": "market", + "forceexit": "market", "stoploss": "market", "stoploss_on_exchange": false, "stoploss_on_exchange_interval": 60 @@ -432,7 +434,7 @@ Configuration: If `stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new stoploss order. !!! Warning "Warning: stoploss_on_exchange failures" - If stoploss on exchange creation fails for some reason, then an "emergency sell" is initiated. By default, this will sell the asset using a market order. The order-type for the emergency-sell can be changed by setting the `emergencysell` value in the `order_types` dictionary - however, this is not advised. + If stoploss on exchange creation fails for some reason, then an "emergency exit" is initiated. By default, this will sell the asset using a market order. The order-type for the emergency-sell can be changed by setting the `emergencyexit` value in the `order_types` dictionary - however, this is not advised. ### Understand order_time_in_force @@ -462,8 +464,8 @@ The possible values are: `gtc` (default), `fok` or `ioc`. ``` python "order_time_in_force": { - "buy": "gtc", - "sell": "gtc" + "entry": "gtc", + "exit": "gtc" }, ``` @@ -509,10 +511,10 @@ creating trades on the exchange. ```json "exchange": { - "name": "bittrex", - "key": "key", - "secret": "secret", - ... + "name": "bittrex", + "key": "key", + "secret": "secret", + ... } ``` diff --git a/docs/data-download.md b/docs/data-download.md index dbd7998c3..9bfc1e685 100644 --- a/docs/data-download.md +++ b/docs/data-download.md @@ -29,6 +29,7 @@ usage: freqtrade download-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--erase] [--data-format-ohlcv {json,jsongz,hdf5}] [--data-format-trades {json,jsongz,hdf5}] + [--trading-mode {spot,margin,futures}] optional arguments: -h, --help show this help message and exit @@ -59,6 +60,8 @@ optional arguments: --data-format-trades {json,jsongz,hdf5} Storage format for downloaded trades data. (default: `jsongz`). + --trading-mode {spot,margin,futures} + Select Trading mode Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). @@ -193,11 +196,14 @@ usage: freqtrade convert-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] {json,jsongz,hdf5} --format-to {json,jsongz,hdf5} [--erase] [-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...]] + [--exchange EXCHANGE] + [--trading-mode {spot,margin,futures}] + [--candle-types {spot,,futures,mark,index,premiumIndex,funding_rate} [{spot,,futures,mark,index,premiumIndex,funding_rate} ...]] optional arguments: -h, --help show this help message and exit -p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...] - Show profits for only these pairs. Pairs are space- + Limit command to these pairs. Pairs are space- separated. --format-from {json,jsongz,hdf5} Source format for data conversion. @@ -208,6 +214,12 @@ optional arguments: -t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...], --timeframes {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...] Specify which tickers to download. Space-separated list. Default: `1m 5m`. + --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no + config is provided. + --trading-mode {spot,margin,futures} + Select Trading mode + --candle-types {spot,,futures,mark,index,premiumIndex,funding_rate} [{spot,,futures,mark,index,premiumIndex,funding_rate} ...] + Select candle type to use Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). @@ -224,6 +236,7 @@ Common arguments: Path to directory with historical backtesting data. --userdir PATH, --user-data-dir PATH Path to userdata directory. + ``` ##### Example converting data @@ -347,6 +360,7 @@ usage: freqtrade list-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--exchange EXCHANGE] [--data-format-ohlcv {json,jsongz,hdf5}] [-p PAIRS [PAIRS ...]] + [--trading-mode {spot,margin,futures}] optional arguments: -h, --help show this help message and exit @@ -356,8 +370,10 @@ optional arguments: Storage format for downloaded candle (OHLCV) data. (default: `json`). -p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...] - Show profits for only these pairs. Pairs are space- + Limit command to these pairs. Pairs are space- separated. + --trading-mode {spot,margin,futures} + Select Trading mode Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). diff --git a/docs/deprecated.md b/docs/deprecated.md index 87c8a2b38..b50eab679 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -47,3 +47,17 @@ As this does however increase risk and provides no benefit, it's been removed fo Using separate hyperopt files was deprecated in 2021.4 and was removed in 2021.9. Please switch to the new [Parametrized Strategies](hyperopt.md) to benefit from the new hyperopt interface. + +## Strategy changes between V2 and V3 + +Isolated Futures / short trading was introduced in 2022.4. This required major changes to configuration settings, strategy interfaces, ... + +We have put a great effort into keeping compatibility with existing strategies, so if you just want to continue using freqtrade in spot markets, there are no changes necessary. +While we may drop support for the current interface sometime in the future, we will announce this separately and have an appropriate transition period. + +Please follow the [Strategy migration](strategy_migration.md) guide to migrate your strategy to the new format to start using the new functionalities. + +### webhooks - `buy_tag` has been renamed to `enter_tag` + +This should apply only to your strategy and potentially to webhooks. +We will keep a compatibility layer for 1-2 versions (so both `buy_tag` and `enter_tag` will still work), but support for this in webhooks will disappear after that. diff --git a/docs/exchanges.md b/docs/exchanges.md index c2368170d..b808096d2 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -1,6 +1,6 @@ # Exchange-specific Notes -This page combines common gotchas and informations which are exchange-specific and most likely don't apply to other exchanges. +This page combines common gotchas and Information which are exchange-specific and most likely don't apply to other exchanges. ## Exchange configuration @@ -64,6 +64,25 @@ Binance supports [time_in_force](configuration.md#understand-order_time_in_force For Binance, please add `"BNB/"` to your blacklist to avoid issues. Accounts having BNB accounts use this to pay for fees - if your first trade happens to be on `BNB`, further trades will consume this position and make the initial BNB trade unsellable as the expected amount is not there anymore. +### Binance Futures' order pricing + +When trading on Binance Futures market, orderbook must be used because there is no price ticker data for futures. + +``` jsonc + "entry_pricing": { + "use_order_book": true, + "order_book_top": 1, + "check_depth_of_market": { + "enabled": false, + "bids_to_ask_delta": 1 + } + }, + "exit_pricing": { + "use_order_book": true, + "order_book_top": 1 + }, +``` + ### Binance sites Binance has been split into 2, and users must use the correct ccxt exchange ID for their exchange, otherwise API keys are not recognized. diff --git a/docs/faq.md b/docs/faq.md index 27bc077ec..f1542d08e 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -6,13 +6,15 @@ Freqtrade supports spot trading only. ### Can I open short positions? -No, Freqtrade does not support trading with margin / leverage, and cannot open short positions. +Freqtrade can open short positions in futures markets. +This requires the strategy to be made for this - and `"trading_mode": "futures"` in the configuration. +Please make sure to read the [relevant documentation page](leverage.md) first. -In some cases, your exchange may provide leveraged spot tokens which can be traded with Freqtrade eg. BTCUP/USD, BTCDOWN/USD, ETHBULL/USD, ETHBEAR/USD, etc... +In spot markets, you can in some cases use leveraged spot tokens, which reflect an inverted pair (eg. BTCUP/USD, BTCDOWN/USD, ETHBULL/USD, ETHBEAR/USD,...) which can be traded with Freqtrade. ### Can I trade options or futures? -No, options and futures trading are not supported. +Futures trading is supported for selected exchanges. ## Beginner Tips & Tricks @@ -77,7 +79,7 @@ You can use "current" market data by using the [dataprovider](strategy-customiza ### Is there a setting to only SELL the coins being held and not perform anymore BUYS? -You can use the `/stopbuy` command in Telegram to prevent future buys, followed by `/forcesell all` (sell all open trades). +You can use the `/stopbuy` command in Telegram to prevent future buys, followed by `/forceexit all` (sell all open trades). ### I want to run multiple bots on the same machine @@ -117,10 +119,10 @@ As the message says, your exchange does not support market orders and you have o To fix this, redefine order types in the strategy to use "limit" instead of "market": -``` +``` python order_types = { ... - 'stoploss': 'limit', + "stoploss": "limit", ... } ``` diff --git a/docs/hyperopt.md b/docs/hyperopt.md index c8cb118f7..3f613a208 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -153,8 +153,8 @@ Checklist on all tasks / possibilities in hyperopt Depending on the space you want to optimize, only some of the below are required: -* define parameters with `space='buy'` - for buy signal optimization -* define parameters with `space='sell'` - for sell signal optimization +* define parameters with `space='buy'` - for entry signal optimization +* define parameters with `space='sell'` - for exit signal optimization !!! Note `populate_indicators` needs to create all indicators any of the spaces may use, otherwise hyperopt will not work. @@ -180,7 +180,7 @@ Hyperopt will first load your data into memory and will then run `populate_indic Hyperopt will then spawn into different processes (number of processors, or `-j `), and run backtesting over and over again, changing the parameters that are part of the `--spaces` defined. -For every new set of parameters, freqtrade will run first `populate_buy_trend()` followed by `populate_sell_trend()`, and then run the regular backtesting process to simulate trades. +For every new set of parameters, freqtrade will run first `populate_entry_trend()` followed by `populate_exit_trend()`, and then run the regular backtesting process to simulate trades. After backtesting, the results are passed into the [loss function](#loss-functions), which will evaluate if this result was better or worse than previous results. Based on the loss function result, hyperopt will determine the next set of parameters to try in the next round of backtesting. @@ -190,7 +190,7 @@ Based on the loss function result, hyperopt will determine the next set of param There are two places you need to change in your strategy file to add a new buy hyperopt for testing: * Define the parameters at the class level hyperopt shall be optimizing. -* Within `populate_buy_trend()` - use defined parameter values instead of raw constants. +* Within `populate_entry_trend()` - use defined parameter values instead of raw constants. There you have two different types of indicators: 1. `guards` and 2. `triggers`. @@ -200,25 +200,25 @@ There you have two different types of indicators: 1. `guards` and 2. `triggers`. !!! Hint "Guards and Triggers" Technically, there is no difference between Guards and Triggers. However, this guide will make this distinction to make it clear that signals should not be "sticking". - Sticking signals are signals that are active for multiple candles. This can lead into buying a signal late (right before the signal disappears - which means that the chance of success is a lot lower than right at the beginning). + Sticking signals are signals that are active for multiple candles. This can lead into entering a signal late (right before the signal disappears - which means that the chance of success is a lot lower than right at the beginning). Hyper-optimization will, for each epoch round, pick one trigger and possibly multiple guards. -#### Sell optimization +#### Exit signal optimization -Similar to the buy-signal above, sell-signals can also be optimized. +Similar to the entry-signal above, exit-signals can also be optimized. Place the corresponding settings into the following methods * Define the parameters at the class level hyperopt shall be optimizing, either naming them `sell_*`, or by explicitly defining `space='sell'`. -* Within `populate_sell_trend()` - use defined parameter values instead of raw constants. +* Within `populate_exit_trend()` - use defined parameter values instead of raw constants. The configuration and rules are the same than for buy signals. ## Solving a Mystery -Let's say you are curious: should you use MACD crossings or lower Bollinger Bands to trigger your buys. -And you also wonder should you use RSI or ADX to help with those buy decisions. -If you decide to use RSI or ADX, which values should I use for them? +Let's say you are curious: should you use MACD crossings or lower Bollinger Bands to trigger your long entries. +And you also wonder should you use RSI or ADX to help with those decisions. +If you decide to use RSI or ADX, which values should I use for them? So let's use hyperparameter optimization to solve this mystery. @@ -274,7 +274,7 @@ The last one we call `trigger` and use it to decide which buy trigger we want to So let's write the buy strategy using these values: ```python - def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = [] # GUARDS AND TRENDS if self.buy_adx_enabled.value: @@ -296,12 +296,12 @@ So let's write the buy strategy using these values: if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), - 'buy'] = 1 + 'enter_long'] = 1 return dataframe ``` -Hyperopt will now call `populate_buy_trend()` many times (`epochs`) with different value combinations. +Hyperopt will now call `populate_entry_trend()` many times (`epochs`) with different value combinations. It will use the given historical data and simulate buys based on the buy signals generated with the above function. Based on the results, hyperopt will tell you which parameter combination produced the best results (based on the configured [loss function](#loss-functions)). @@ -364,7 +364,7 @@ class MyAwesomeStrategy(IStrategy): return dataframe - def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = [] conditions.append(qtpylib.crossed_above( dataframe[f'ema_short_{self.buy_ema_short.value}'], dataframe[f'ema_long_{self.buy_ema_long.value}'] @@ -376,10 +376,10 @@ class MyAwesomeStrategy(IStrategy): if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), - 'buy'] = 1 + 'enter_long'] = 1 return dataframe - def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = [] conditions.append(qtpylib.crossed_above( dataframe[f'ema_long_{self.buy_ema_long.value}'], dataframe[f'ema_short_{self.buy_ema_short.value}'] @@ -391,7 +391,7 @@ class MyAwesomeStrategy(IStrategy): if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), - 'sell'] = 1 + 'exit_long'] = 1 return dataframe ``` diff --git a/docs/includes/pricing.md b/docs/includes/pricing.md index 103df6cd3..ee6ec0cce 100644 --- a/docs/includes/pricing.md +++ b/docs/includes/pricing.md @@ -1,6 +1,6 @@ ## Prices used for orders -Prices for regular orders can be controlled via the parameter structures `bid_strategy` for buying and `ask_strategy` for selling. +Prices for regular orders can be controlled via the parameter structures `entry_pricing` for trade entries and `exit_pricing` for trade exits. Prices are always retrieved right before an order is placed, either by querying the exchange tickers or by using the orderbook data. !!! Note @@ -9,20 +9,11 @@ Prices are always retrieved right before an order is placed, either by querying !!! Warning "Using market orders" Please read the section [Market order pricing](#market-order-pricing) section when using market orders. -### Buy price +### Entry price -#### Check depth of market +#### Enter price side -When check depth of market is enabled (`bid_strategy.check_depth_of_market.enabled=True`), the buy signals are filtered based on the orderbook depth (sum of all amounts) for each orderbook side. - -Orderbook `bid` (buy) side depth is then divided by the orderbook `ask` (sell) side depth and the resulting delta is compared to the value of the `bid_strategy.check_depth_of_market.bids_to_ask_delta` parameter. The buy order is only executed if the orderbook delta is greater than or equal to the configured delta value. - -!!! Note - A delta value below 1 means that `ask` (sell) orderbook side depth is greater than the depth of the `bid` (buy) orderbook side, while a value greater than 1 means opposite (depth of the buy side is higher than the depth of the sell side). - -#### Buy price side - -The configuration setting `bid_strategy.price_side` defines the side of the spread the bot looks for when buying. +The configuration setting `entry_pricing.price_side` defines the side of the orderbook the bot looks for when buying. The following displays an orderbook. @@ -38,30 +29,53 @@ The following displays an orderbook. ... ``` -If `bid_strategy.price_side` is set to `"bid"`, then the bot will use 99 as buying price. -In line with that, if `bid_strategy.price_side` is set to `"ask"`, then the bot will use 101 as buying price. +If `entry_pricing.price_side` is set to `"bid"`, then the bot will use 99 as entry price. +In line with that, if `entry_pricing.price_side` is set to `"ask"`, then the bot will use 101 as entry price. -Using `ask` price often guarantees quicker filled orders, but the bot can also end up paying more than what would have been necessary. +Depending on the order direction (_long_/_short_), this will lead to different results. Therefore we recommend to use `"same"` or `"other"` for this configuration instead. +This would result in the following pricing matrix: + +| direction | Order | setting | price | crosses spread | +|------ |--------|-----|-----|-----| +| long | buy | ask | 101 | yes | +| long | buy | bid | 99 | no | +| long | buy | same | 99 | no | +| long | buy | other | 101 | yes | +| short | sell | ask | 101 | no | +| short | sell | bid | 99 | yes | +| short | sell | same | 101 | no | +| short | sell | other | 99 | yes | + +Using the other side of the orderbook often guarantees quicker filled orders, but the bot can also end up paying more than what would have been necessary. Taker fees instead of maker fees will most likely apply even when using limit buy orders. -Also, prices at the "ask" side of the spread are higher than prices at the "bid" side in the orderbook, so the order behaves similar to a market order (however with a maximum price). +Also, prices at the "other" side of the spread are higher than prices at the "bid" side in the orderbook, so the order behaves similar to a market order (however with a maximum price). -#### Buy price with Orderbook enabled +#### Entry price with Orderbook enabled -When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Freqtrade fetches the `bid_strategy.order_book_top` entries from the orderbook and uses the entry specified as `bid_strategy.order_book_top` on the configured side (`bid_strategy.price_side`) of the orderbook. 1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on. +When entering a trade with the orderbook enabled (`entry_pricing.use_order_book=True`), Freqtrade fetches the `entry_pricing.order_book_top` entries from the orderbook and uses the entry specified as `entry_pricing.order_book_top` on the configured side (`entry_pricing.price_side`) of the orderbook. 1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on. -#### Buy price without Orderbook enabled +#### Entry price without Orderbook enabled -The following section uses `side` as the configured `bid_strategy.price_side` (defaults to `"bid"`). +The following section uses `side` as the configured `entry_pricing.price_side` (defaults to `"same"`). -When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price based on `bid_strategy.ask_last_balance`.. +When not using orderbook (`entry_pricing.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price based on `entry_pricing.price_last_balance`. -The `bid_strategy.ask_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price. +The `entry_pricing.price_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price. -### Sell price +#### Check depth of market -#### Sell price side +When check depth of market is enabled (`entry_pricing.check_depth_of_market.enabled=True`), the entry signals are filtered based on the orderbook depth (sum of all amounts) for each orderbook side. -The configuration setting `ask_strategy.price_side` defines the side of the spread the bot looks for when selling. +Orderbook `bid` (buy) side depth is then divided by the orderbook `ask` (sell) side depth and the resulting delta is compared to the value of the `entry_pricing.check_depth_of_market.bids_to_ask_delta` parameter. The entry order is only executed if the orderbook delta is greater than or equal to the configured delta value. + +!!! Note + A delta value below 1 means that `ask` (sell) orderbook side depth is greater than the depth of the `bid` (buy) orderbook side, while a value greater than 1 means opposite (depth of the buy side is higher than the depth of the sell side). + +### Exit price + +#### Exit price side + +The configuration setting `exit_pricing.price_side` defines the side of the spread the bot looks for when exiting a trade. The following displays an orderbook: @@ -77,40 +91,54 @@ The following displays an orderbook: ... ``` -If `ask_strategy.price_side` is set to `"ask"`, then the bot will use 101 as selling price. -In line with that, if `ask_strategy.price_side` is set to `"bid"`, then the bot will use 99 as selling price. +If `exit_pricing.price_side` is set to `"ask"`, then the bot will use 101 as exiting price. +In line with that, if `exit_pricing.price_side` is set to `"bid"`, then the bot will use 99 as exiting price. -#### Sell price with Orderbook enabled +Depending on the order direction (_long_/_short_), this will lead to different results. Therefore we recommend to use `"same"` or `"other"` for this configuration instead. +This would result in the following pricing matrix: -When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Freqtrade fetches the `ask_strategy.order_book_top` entries in the orderbook and uses the entry specified as `ask_strategy.order_book_top` from the configured side (`ask_strategy.price_side`) as selling price. +| Direction | Order | setting | price | crosses spread | +|------ |--------|-----|-----|-----| +| long | sell | ask | 101 | no | +| long | sell | bid | 99 | yes | +| long | sell | same | 101 | no | +| long | sell | other | 99 | yes | +| short | buy | ask | 101 | yes | +| short | buy | bid | 99 | no | +| short | buy | same | 99 | no | +| short | buy | other | 101 | yes | + +#### Exit price with Orderbook enabled + +When exiting with the orderbook enabled (`exit_pricing.use_order_book=True`), Freqtrade fetches the `exit_pricing.order_book_top` entries in the orderbook and uses the entry specified as `exit_pricing.order_book_top` from the configured side (`exit_pricing.price_side`) as trade exit price. 1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on. -#### Sell price without Orderbook enabled +#### Exit price without Orderbook enabled -The following section uses `side` as the configured `ask_strategy.price_side` (defaults to `"ask"`). +The following section uses `side` as the configured `exit_pricing.price_side` (defaults to `"ask"`). -When not using orderbook (`ask_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's above the `last` traded price from the ticker. Otherwise (when the `side` price is below the `last` price), it calculates a rate between `side` and `last` price based on `ask_strategy.bid_last_balance`. +When not using orderbook (`exit_pricing.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's above the `last` traded price from the ticker. Otherwise (when the `side` price is below the `last` price), it calculates a rate between `side` and `last` price based on `exit_pricing.price_last_balance`. -The `ask_strategy.bid_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the last price and values between those interpolate between `side` and last price. +The `exit_pricing.price_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the last price and values between those interpolate between `side` and last price. ### Market order pricing When using market orders, prices should be configured to use the "correct" side of the orderbook to allow realistic pricing detection. -Assuming both buy and sell are using market orders, a configuration similar to the following might be used +Assuming both entry and exits are using market orders, a configuration similar to the following must be used ``` jsonc "order_types": { - "buy": "market", - "sell": "market" + "entry": "market", + "exit": "market" // ... }, - "bid_strategy": { - "price_side": "ask", + "entry_pricing": { + "price_side": "other", // ... }, - "ask_strategy":{ - "price_side": "bid", + "exit_pricing":{ + "price_side": "other", // ... }, ``` diff --git a/docs/leverage.md b/docs/leverage.md new file mode 100644 index 000000000..70f345601 --- /dev/null +++ b/docs/leverage.md @@ -0,0 +1,106 @@ +# Trading with Leverage + +!!! Warning "Beta feature" + This feature is still in it's testing phase. Should you notice something you think is wrong please let us know via Discord or via Github Issue. + +!!! Note "Multiple bots on one account" + You can't run 2 bots on the same account with leverage. For leveraged / margin trading, freqtrade assumes it's the only user of the account, and all liquidation levels are calculated based on this assumption. + +!!! Danger "Trading with leverage is very risky" + Do not trade with a leverage > 1 using a strategy that hasn't shown positive results in a live run using the spot market. Check the stoploss of your strategy. With a leverage of 2, a stoploss of 0.5 (50%) would be too low, and these trades would be liquidated before reaching that stoploss. + We do not assume any responsibility for eventual losses that occur from using this software or this mode. + + Please only use advanced trading modes when you know how freqtrade (and your strategy) works. + Also, never risk more than what you can afford to lose. + +## Understand `trading_mode` + +The possible values are: `spot` (default), `margin`(*Currently unavailable*) or `futures`. + +### Spot + +Regular trading mode (low risk) + +- Long trades only (No short trades). +- No leverage. +- No Liquidation. +- Profits gained/lost are equal to the change in value of the assets (minus trading fees). + +### Leverage trading modes + +With leverage, a trader borrows capital from the exchange. The capital must be re-payed fully to the exchange (potentially with interest), and the trader keeps any profits, or pays any losses, from any trades made using the borrowed capital. + +Because the capital must always be re-payed, exchanges will **liquidate** (forcefully sell the traders assets) a trade made using borrowed capital when the total value of assets in the leverage account drops to a certain point (a point where the total value of losses is less than the value of the collateral that the trader actually owns in the leverage account), in order to ensure that the trader has enough capital to pay the borrowed assets back to the exchange. The exchange will also charge a **liquidation fee**, adding to the traders losses. + +For this reason, **DO NOT TRADE WITH LEVERAGE IF YOU DON'T KNOW EXACTLY WHAT YOUR DOING. LEVERAGE TRADING IS HIGH RISK, AND CAN RESULT IN THE VALUE OF YOUR ASSETS DROPPING TO 0 VERY QUICKLY, WITH NO CHANCE OF INCREASING IN VALUE AGAIN.** + +#### Margin (currently unavailable) + +Trading occurs on the spot market, but the exchange lends currency to you in an amount equal to the chosen leverage. You pay the amount lent to you back to the exchange with interest, and your profits/losses are multiplied by the leverage specified. + +#### Futures + +Perpetual swaps (also known as Perpetual Futures) are contracts traded at a price that is closely tied to the underlying asset they are based off of (ex.). You are not trading the actual asset but instead are trading a derivative contract. Perpetual swap contracts can last indefinitely, in contrast to futures or option contracts. + +In addition to the gains/losses from the change in price of the futures contract, traders also exchange _funding fees_, which are gains/losses worth an amount that is derived from the difference in price between the futures contract and the underlying asset. The difference in price between a futures contract and the underlying asset varies between exchanges. + +To trade in futures markets, you'll have to set `trading_mode` to "futures". +You will also have to pick a "margin mode" (explanation below) - with freqtrade currently only supporting isolated margin. + +``` json +"trading_mode": "futures", +"margin_mode": "isolated" +``` + +### Margin mode + +The possible values are: `isolated`, or `cross`(*currently unavailable*) + +#### Isolated margin mode + +Each market(trading pair), keeps collateral in a separate account + +``` json +"margin_mode": "isolated" +``` + +#### Cross margin mode (currently unavailable) + +One account is used to share collateral between markets (trading pairs). Margin is taken from total account balance to avoid liquidation when needed. + +``` json +"margin_mode": "cross" +``` + +## Understand `liquidation_buffer` + +*Defaults to `0.05`* + +A ratio specifying how large of a safety net to place between the liquidation price and the stoploss to prevent a position from reaching the liquidation price. +This artificial liquidation price is calculated as: + +`freqtrade_liquidation_price = liquidation_price ± (abs(open_rate - liquidation_price) * liquidation_buffer)` + +- `±` = `+` for long trades +- `±` = `-` for short trades + +Possible values are any floats between 0.0 and 0.99 + +**ex:** If a trade is entered at a price of 10 coin/USDT, and the liquidation price of this trade is 8 coin/USDT, then with `liquidation_buffer` set to `0.05` the minimum stoploss for this trade would be $8 + ((10 - 8) * 0.05) = 8 + 0.1 = 8.1$ + +!!! Danger "A `liquidation_buffer` of 0.0, or a low `liquidation_buffer` is likely to result in liquidations, and liquidation fees" + Currently Freqtrade is able to calculate liquidation prices, but does not calculate liquidation fees. Setting your `liquidation_buffer` to 0.0, or using a low `liquidation_buffer` could result in your positions being liquidated. Freqtrade does not track liquidation fees, so liquidations will result in inaccurate profit/loss results for your bot. If you use a low `liquidation_buffer`, it is recommended to use `stoploss_on_exchange` if your exchange supports this. + +### Developer + +#### Margin mode + +For shorts, the currency which pays the interest fee for the `borrowed` currency is purchased at the same time of the closing trade (This means that the amount purchased in short closing trades is greater than the amount sold in short opening trades). + +For longs, the currency which pays the interest fee for the `borrowed` will already be owned by the user and does not need to be purchased. The interest is subtracted from the `close_value` of the trade. + +All Fees are included in `current_profit` calculations during the trade. + +#### Futures mode + +Funding fees are either added or subtracted from the total amount of a trade diff --git a/docs/plotting.md b/docs/plotting.md index 004dd7821..bbe9307c8 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -14,7 +14,7 @@ pip install -U -r requirements-plot.txt The `freqtrade plot-dataframe` subcommand shows an interactive graph with three subplots: -* Main plot with candlestics and indicators following price (sma/ema) +* Main plot with candlesticks and indicators following price (sma/ema) * Volume bars * Additional indicators as specified by `--indicators2` @@ -276,6 +276,10 @@ def plot_config(self): !!! Note "Trade position adjustments" If `position_adjustment_enable` / `adjust_trade_position()` is used, the trade initial buy price is averaged over multiple orders and the trade start price will most likely appear outside the candle range. +!!! Note "Futures / Margin trading" + `plot-dataframe` does not support Futures / short trades, so these trades will simply be missing, and it's unlikely we'll be adding this functionality to this command. + Please use freqUI instead by starting freqtrade in [webserver mode](utils.md#webserver-mode) and use the Chart page to plot your dataframe. + ## Plot profit ![plot-profit](assets/plot-profit.png) diff --git a/docs/rest-api.md b/docs/rest-api.md index 8c2599cbc..25e7ee205 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -145,9 +145,10 @@ python3 scripts/rest_client.py --config rest_config.json [optional par | `locks` | Displays currently locked pairs. | `delete_lock ` | Deletes (disables) the lock by id. | `profit` | Display a summary of your profit/loss from close trades and some stats about your performance. -| `forcesell ` | Instantly sells the given trade (Ignoring `minimum_roi`). -| `forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`). -| `forcebuy [rate]` | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True) +| `forceexit ` | Instantly exits the given trade (Ignoring `minimum_roi`). +| `forceexit all` | Instantly exits all open trades (Ignoring `minimum_roi`). +| `forceenter [rate]` | Instantly enters the given pair. Rate is optional. (`forcebuy_enable` must be set to True) +| `forceenter [rate]` | Instantly longs or shorts the given pair. Rate is optional. (`forcebuy_enable` must be set to True) | `performance` | Show performance of each finished trade grouped by pair. | `balance` | Show account balance per currency. | `daily ` | Shows profit or loss per day, over the last n days (n defaults to 7). @@ -215,6 +216,13 @@ forcebuy :param pair: Pair to buy (ETH/BTC) :param price: Optional - price to buy +forceenter + Force entering a trade + + :param pair: Pair to buy (ETH/BTC) + :param side: 'long' or 'short' + :param price: Optional - price to buy + forcesell Force-sell a trade. @@ -285,6 +293,9 @@ strategy :param strategy: Strategy class name +sysinfo + Provides system information (CPU, RAM usage) + trade Return specific trade diff --git a/docs/sandbox-testing.md b/docs/sandbox-testing.md index 5f572eba8..2c0f306cf 100644 --- a/docs/sandbox-testing.md +++ b/docs/sandbox-testing.md @@ -104,16 +104,16 @@ To mitigate this, you can try to match the first order on the opposite orderbook ``` jsonc "order_types": { - "buy": "limit", - "sell": "limit" + "entry": "limit", + "exit": "limit" // ... }, - "bid_strategy": { - "price_side": "ask", + "entry_pricing": { + "price_side": "other", // ... }, - "ask_strategy":{ - "price_side": "bid", + "exit_pricing":{ + "price_side": "other", // ... }, ``` diff --git a/docs/sql_cheatsheet.md b/docs/sql_cheatsheet.md index caa3f53a6..0e2abc239 100644 --- a/docs/sql_cheatsheet.md +++ b/docs/sql_cheatsheet.md @@ -49,14 +49,14 @@ sqlite3 SELECT * FROM trades; ``` -## Fix trade still open after a manual sell on the exchange +## Fix trade still open after a manual exit on the exchange !!! Warning - Manually selling a pair on the exchange will not be detected by the bot and it will try to sell anyway. Whenever possible, forcesell should be used to accomplish the same thing. + Manually selling a pair on the exchange will not be detected by the bot and it will try to sell anyway. Whenever possible, forceexit should be used to accomplish the same thing. It is strongly advised to backup your database file before making any manual changes. !!! Note - This should not be necessary after /forcesell, as forcesell orders are closed automatically by the bot on the next iteration. + This should not be necessary after /forceexit, as forceexit orders are closed automatically by the bot on the next iteration. ```sql UPDATE trades diff --git a/docs/stoploss.md b/docs/stoploss.md index 62081b540..2d95813ac 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -17,7 +17,7 @@ Those stoploss modes can be *on exchange* or *off exchange*. These modes can be configured with these values: ``` python - 'emergencysell': 'market', + 'emergencyexit': 'market', 'stoploss_on_exchange': False 'stoploss_on_exchange_interval': 60, 'stoploss_on_exchange_limit_ratio': 0.99 @@ -52,30 +52,30 @@ The bot cannot do these every 5 seconds (at each iteration), otherwise it would So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). This same logic will reapply a stoploss order on the exchange should you cancel it accidentally. -### forcesell +### forceexit -`forcesell` is an optional value, which defaults to the same value as `sell` and is used when sending a `/forcesell` command from Telegram or from the Rest API. +`forceexit` is an optional value, which defaults to the same value as `exit` and is used when sending a `/forceexit` command from Telegram or from the Rest API. -### forcebuy +### forceentry -`forcebuy` is an optional value, which defaults to the same value as `buy` and is used when sending a `/forcebuy` command from Telegram or from the Rest API. +`forceentry` is an optional value, which defaults to the same value as `entry` and is used when sending a `/forceentry` command from Telegram or from the Rest API. -### emergencysell +### emergencyexit -`emergencysell` is an optional value, which defaults to `market` and is used when creating stop loss on exchange orders fails. +`emergencyexit` is an optional value, which defaults to `market` and is used when creating stop loss on exchange orders fails. The below is the default which is used if not changed in strategy or configuration file. Example from strategy file: ``` python order_types = { - 'buy': 'limit', - 'sell': 'limit', - 'emergencysell': 'market', - 'stoploss': 'market', - 'stoploss_on_exchange': True, - 'stoploss_on_exchange_interval': 60, - 'stoploss_on_exchange_limit_ratio': 0.99 + "entry": "limit", + "exit": "limit", + "emergencyexit": "market", + "stoploss": "market", + "stoploss_on_exchange": True, + "stoploss_on_exchange_interval": 60, + "stoploss_on_exchange_limit_ratio": 0.99 } ``` diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index b1f154355..374c675a2 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -49,7 +49,7 @@ from freqtrade.exchange import timeframe_to_prev_date class AwesomeStrategy(IStrategy): def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float, - rate: float, time_in_force: str, sell_reason: str, + rate: float, time_in_force: str, exit_reason: str, current_time: 'datetime', **kwargs) -> bool: # Obtain pair dataframe. dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) @@ -77,47 +77,47 @@ class AwesomeStrategy(IStrategy): *** -## Buy Tag +## Enter Tag When your strategy has multiple buy signals, you can name the signal that triggered. -Then you can access you buy signal on `custom_sell` +Then you can access you buy signal on `custom_exit` ```python -def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: +def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( (dataframe['rsi'] < 35) & (dataframe['volume'] > 0) ), - ['buy', 'buy_tag']] = (1, 'buy_signal_rsi') + ['enter_long', 'enter_tag']] = (1, 'buy_signal_rsi') return dataframe -def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, - current_profit: float, **kwargs): +def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, + current_profit: float, **kwargs): dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() - if trade.buy_tag == 'buy_signal_rsi' and last_candle['rsi'] > 80: + if trade.enter_tag == 'buy_signal_rsi' and last_candle['rsi'] > 80: return 'sell_signal_rsi' return None ``` !!! Note - `buy_tag` is limited to 100 characters, remaining data will be truncated. + `enter_tag` is limited to 100 characters, remaining data will be truncated. ## Exit tag Similar to [Buy Tagging](#buy-tag), you can also specify a sell tag. ``` python -def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: +def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( (dataframe['rsi'] > 70) & (dataframe['volume'] > 0) ), - ['sell', 'exit_tag']] = (1, 'exit_rsi') + ['exit_long', 'exit_tag']] = (1, 'exit_rsi') return dataframe ``` @@ -125,7 +125,7 @@ def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame The provided exit-tag is then used as sell-reason - and shown as such in backtest results. !!! Note - `sell_reason` is limited to 100 characters, remaining data will be truncated. + `exit_reason` is limited to 100 characters, remaining data will be truncated. ## Strategy version diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 555352d21..dfd29d544 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -1,6 +1,6 @@ # Strategy Callbacks -While the main strategy functions (`populate_indicators()`, `populate_buy_trend()`, `populate_sell_trend()`) should be used in a vectorized way, and are only called [once during backtesting](bot-basics.md#backtesting-hyperopt-execution-logic), callbacks are called "whenever needed". +While the main strategy functions (`populate_indicators()`, `populate_entry_trend()`, `populate_exit_trend()`) should be used in a vectorized way, and are only called [once during backtesting](bot-basics.md#backtesting-hyperopt-execution-logic), callbacks are called "whenever needed". As such, you should avoid doing heavy calculations in callbacks to avoid delays during operations. Depending on the callback used, they may be called when entering / exiting a trade, or throughout the duration of a trade. @@ -8,14 +8,15 @@ Depending on the callback used, they may be called when entering / exiting a tra Currently available callbacks: * [`bot_loop_start()`](#bot-loop-start) -* [`custom_stake_amount()`](#custom-stake-size) -* [`custom_sell()`](#custom-sell-signal) +* [`custom_stake_amount()`](#stake-size-management) +* [`custom_exit()`](#custom-exit-signal) * [`custom_stoploss()`](#custom-stoploss) * [`custom_entry_price()` and `custom_exit_price()`](#custom-order-price-rules) -* [`check_buy_timeout()` and `check_sell_timeout()](#custom-order-timeout-rules) +* [`check_entry_timeout()` and `check_exit_timeout()`](#custom-order-timeout-rules) * [`confirm_trade_entry()`](#trade-entry-buy-order-confirmation) * [`confirm_trade_exit()`](#trade-exit-sell-order-confirmation) * [`adjust_trade_position()`](#adjust-trade-position) +* [`leverage()`](#leverage-callback) !!! Tip "Callback calling sequence" You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic) @@ -46,7 +47,7 @@ class AwesomeStrategy(IStrategy): ``` -## Custom Stake size +### Stake size management Called before entering a trade, makes it possible to manage your position size when placing a new trade. @@ -54,7 +55,7 @@ Called before entering a trade, makes it possible to manage your position size w class AwesomeStrategy(IStrategy): def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: float, max_stake: float, - entry_tag: Optional[str], **kwargs) -> float: + entry_tag: Optional[str], side: str, **kwargs) -> float: dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) current_candle = dataframe.iloc[-1].squeeze() @@ -79,15 +80,15 @@ Freqtrade will fall back to the `proposed_stake` value should your code raise an !!! Tip Returning `0` or `None` will prevent trades from being placed. -## Custom sell signal +## Custom exit signal Called for open trade every throttling iteration (roughly every 5 seconds) until a trade is closed. Allows to define custom sell signals, indicating that specified position should be sold. This is very useful when we need to customize sell conditions for each individual trade, or if you need trade data to make an exit decision. -For example you could implement a 1:2 risk-reward ROI with `custom_sell()`. +For example you could implement a 1:2 risk-reward ROI with `custom_exit()`. -Using custom_sell() signals in place of stoploss though *is not recommended*. It is a inferior method to using `custom_stoploss()` in this regard - which also allows you to keep the stoploss on exchange. +Using custom_exit() signals in place of stoploss though *is not recommended*. It is a inferior method to using `custom_stoploss()` in this regard - which also allows you to keep the stoploss on exchange. !!! Note Returning a (none-empty) `string` or `True` from this method is equal to setting sell signal on a candle at specified time. This method is not called when sell signal is set already, or if sell signals are disabled (`use_sell_signal=False` or `sell_profit_only=True` while profit is below `sell_profit_offset`). `string` max length is 64 characters. Exceeding this limit will cause the message to be truncated to 64 characters. @@ -96,7 +97,7 @@ An example of how we can use different indicators depending on the current profi ``` python class AwesomeStrategy(IStrategy): - def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, + def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, current_profit: float, **kwargs): dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() @@ -120,7 +121,8 @@ See [Dataframe access](strategy-advanced.md#dataframe-access) for more informati ## Custom stoploss -Called for open trade every throttling iteration (roughly every 5 seconds) until a trade is closed. +Called for open trade every throttling iteration (roughly every 5 seconds) until a trade is closed. + The usage of the custom stoploss method must be enabled by setting `use_custom_stoploss=True` on the strategy object. The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss (before this method is called for the first time for a trade). @@ -158,7 +160,7 @@ class AwesomeStrategy(IStrategy): :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_rate: Rate, calculated based on pricing settings in exit_pricing. :param current_profit: Current profit (as ratio), calculated based on current_rate. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: New stoploss value, relative to the current rate @@ -283,11 +285,11 @@ class AwesomeStrategy(IStrategy): # evaluate highest to lowest, so that highest possible stop is used if current_profit > 0.40: - return stoploss_from_open(0.25, current_profit) + return stoploss_from_open(0.25, current_profit, is_short=trade.is_short) elif current_profit > 0.25: - return stoploss_from_open(0.15, current_profit) + return stoploss_from_open(0.15, current_profit, is_short=trade.is_short) elif current_profit > 0.20: - return stoploss_from_open(0.07, current_profit) + return stoploss_from_open(0.07, current_profit, is_short=trade.is_short) # return maximum stoploss value, keeping current stoploss price unchanged return 1 @@ -406,7 +408,7 @@ However, freqtrade also offers a custom callback for both order types, which all ### Custom order timeout example Called for every open order until that order is either filled or cancelled. -`check_buy_timeout()` is called for trade entries, while `check_sell_timeout()` is called for trade exit orders. +`check_entry_timeout()` is called for trade entries, while `check_exit_timeout()` is called for trade exit orders. A simple example, which applies different unfilled-timeouts depending on the price of the asset can be seen below. It applies a tight timeout for higher priced assets, while allowing more time to fill on cheap coins. @@ -423,12 +425,12 @@ class AwesomeStrategy(IStrategy): # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours. unfilledtimeout = { - 'buy': 60 * 25, - 'sell': 60 * 25 + 'entry': 60 * 25, + 'exit': 60 * 25 } - def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, - current_time: datetime, **kwargs) -> bool: + def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, + current_time: datetime, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): return True elif trade.open_rate > 10 and trade.open_date_utc < current_time - timedelta(minutes=3): @@ -438,7 +440,7 @@ class AwesomeStrategy(IStrategy): return False - def check_sell_timeout(self, pair: str, trade: Trade, order: dict, + def check_exit_timeout(self, pair: str, trade: Trade, order: dict, current_time: datetime, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): return True @@ -464,12 +466,12 @@ class AwesomeStrategy(IStrategy): # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours. unfilledtimeout = { - 'buy': 60 * 25, - 'sell': 60 * 25 + 'entry': 60 * 25, + 'exit': 60 * 25 } - def check_buy_timeout(self, pair: str, trade: Trade, order: dict, - current_time: datetime, **kwargs) -> bool: + def check_entry_timeout(self, pair: str, trade: Trade, order: dict, + current_time: datetime, **kwargs) -> bool: ob = self.dp.orderbook(pair, 1) current_price = ob['bids'][0][0] # Cancel buy order if price is more than 2% above the order. @@ -478,7 +480,7 @@ class AwesomeStrategy(IStrategy): return False - def check_sell_timeout(self, pair: str, trade: Trade, order: dict, + def check_exit_timeout(self, pair: str, trade: Trade, order: dict, current_time: datetime, **kwargs) -> bool: ob = self.dp.orderbook(pair, 1) current_price = ob['asks'][0][0] @@ -506,9 +508,9 @@ class AwesomeStrategy(IStrategy): def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, current_time: datetime, entry_tag: Optional[str], - **kwargs) -> bool: + side: str, **kwargs) -> bool: """ - Called right before placing a buy order. + Called right before placing a entry order. Timing for this function is critical, so avoid doing heavy computations or network requests in this method. @@ -516,12 +518,13 @@ class AwesomeStrategy(IStrategy): When not implemented by a strategy, returns True (always confirming). - :param pair: Pair that's about to be bought. + :param pair: Pair that's about to be bought/shorted. :param order_type: Order type (as configured in order_types). usually limit or market. :param amount: Amount in target (quote) currency that's going to be traded. :param rate: Rate that's going to be used when using limit orders :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param current_time: datetime object, containing the current datetime + :param side: 'long' or 'short' - indicating the direction of the proposed trade :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the buy-order is placed on the exchange. False aborts the process @@ -543,7 +546,7 @@ class AwesomeStrategy(IStrategy): # ... populate_* methods def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, - rate: float, time_in_force: str, sell_reason: str, + rate: float, time_in_force: str, exit_reason: str, current_time: datetime, **kwargs) -> bool: """ Called right before placing a regular sell order. @@ -559,7 +562,7 @@ class AwesomeStrategy(IStrategy): :param amount: Amount in quote currency. :param rate: Rate that's going to be used when using limit orders :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). - :param sell_reason: Sell reason. + :param exit_reason: Exit reason. Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', 'sell_signal', 'force_sell', 'emergency_sell'] :param current_time: datetime object, containing the current datetime @@ -567,7 +570,7 @@ class AwesomeStrategy(IStrategy): :return bool: When True is returned, then the sell-order is placed on the exchange. False aborts the process """ - if sell_reason == 'force_sell' and trade.calc_profit_ratio(rate) < 0: + if exit_reason == 'force_sell' and trade.calc_profit_ratio(rate) < 0: # Reject force-sells with negative profit # This is just a sample, please adjust to your needs # (this does not necessarily make sense, assuming you know when you're force-selling) @@ -591,6 +594,8 @@ Additional orders also result in additional fees and those orders don't count to This callback is **not** called when there is an open order (either buy or sell) waiting for execution, or when you have reached the maximum amount of extra buys that you have set on `max_entry_position_adjustment`. `adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible. +Position adjustments will always be applied in the direction of the trade, so a positive value will always increase your position, no matter if it's a long or short trade. Modifications to leverage are not possible. + !!! Note "About stake size" Using fixed stake size means it will be the amount used for the first order, just like without position adjustment. If you wish to buy additional orders with DCA, then make sure to leave enough funds in the wallet for that. @@ -626,7 +631,7 @@ class DigDeeperStrategy(IStrategy): # This is called when placing the initial order (opening trade) def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: float, max_stake: float, - entry_tag: Optional[str], **kwargs) -> float: + entry_tag: Optional[str], side: str, **kwargs) -> float: # We need to leave most of the funds for possible further DCA orders # This also applies to fixed stakes @@ -660,8 +665,8 @@ class DigDeeperStrategy(IStrategy): if last_candle['close'] < previous_candle['close']: return None - filled_buys = trade.select_filled_orders('buy') - count_of_buys = trade.nr_of_successful_buys + filled_entries = trade.select_filled_orders(trade.enter_side) + count_of_entries = trade.nr_of_successful_entries # Allow up to 3 additional increasingly larger buys (4 in total) # Initial buy is 1x # If that falls to -5% profit, we buy 1.25x more, average profit should increase to roughly -2.2% @@ -672,9 +677,9 @@ class DigDeeperStrategy(IStrategy): # Hope you have a deep wallet! try: # This returns first order stake size - stake_amount = filled_buys[0].cost + stake_amount = filled_entries[0].cost # This then calculates current safety order size - stake_amount = stake_amount * (1 + (count_of_buys * 0.25)) + stake_amount = stake_amount * (1 + (count_of_entries * 0.25)) return stake_amount except Exception as exception: return None @@ -682,3 +687,31 @@ class DigDeeperStrategy(IStrategy): return None ``` + +## Leverage Callback + +When trading in markets that allow leverage, this method must return the desired Leverage (Defaults to 1 -> No leverage). + +Assuming a capital of 500USDT, a trade with leverage=3 would result in a position with 500 x 3 = 1500 USDT. + +Values that are above `max_leverage` will be adjusted to `max_leverage`. +For markets / exchanges that don't support leverage, this method is ignored. + +``` python +class AwesomeStrategy(IStrategy): + def leverage(self, pair: str, current_time: 'datetime', current_rate: float, + proposed_leverage: float, max_leverage: float, side: str, + **kwargs) -> float: + """ + Customize leverage for each new trade. + + :param pair: Pair that's currently analyzed + :param current_time: datetime object, containing the current datetime + :param current_rate: Rate, calculated based on pricing settings in exit_pricing. + :param proposed_leverage: A leverage proposed by the bot. + :param max_leverage: Max leverage allowed on this pair + :param side: 'long' or 'short' - indicating the direction of the proposed trade + :return: A leverage amount, which is between 1.0 and max_leverage. + """ + return 1.0 +``` diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 045a55c5b..c33ec5fb9 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -26,8 +26,8 @@ This will create a new strategy file from a template, which will be located unde A strategy file contains all the information needed to build a good strategy: - Indicators -- Buy strategy rules -- Sell strategy rules +- Entry strategy rules +- Exit strategy rules - Minimal ROI recommended - Stoploss strongly recommended @@ -35,7 +35,7 @@ The bot also include a sample strategy called `SampleStrategy` you can update: ` You can test it with the parameter: `--strategy SampleStrategy` Additionally, there is an attribute called `INTERFACE_VERSION`, which defines the version of the strategy interface the bot should use. -The current version is 2 - which is also the default when it's not set explicitly in the strategy. +The current version is 3 - which is also the default when it's not set explicitly in the strategy. Future versions will require this to be set. @@ -82,7 +82,7 @@ As a dataframe is a table, simple python comparisons like the following will not ``` python if dataframe['rsi'] > 30: - dataframe['buy'] = 1 + dataframe['enter_long'] = 1 ``` The above section will fail with `The truth value of a Series is ambiguous. [...]`. @@ -92,7 +92,7 @@ This must instead be written in a pandas-compatible way, so the operation is per ``` python dataframe.loc[ (dataframe['rsi'] > 30) - , 'buy'] = 1 + , 'enter_long'] = 1 ``` With this section, you have a new column in your dataframe, which has `1` assigned whenever RSI is above 30. @@ -101,7 +101,7 @@ With this section, you have a new column in your dataframe, which has `1` assign Buy and sell strategies need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file. -You should only add the indicators used in either `populate_buy_trend()`, `populate_sell_trend()`, or to populate another indicator, otherwise performance may suffer. +You should only add the indicators used in either `populate_entry_trend()`, `populate_exit_trend()`, or to populate another indicator, otherwise performance may suffer. It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. @@ -199,18 +199,18 @@ If this data is available, indicators will be calculated with this extended time !!! Note If data for the startup period is not available, then the timerange will be adjusted to account for this startup period - so Backtesting would start at 2019-01-01 08:30:00. -### Buy signal rules +### Entry signal rules -Edit the method `populate_buy_trend()` in your strategy file to update your buy strategy. +Edit the method `populate_entry_trend()` in your strategy file to update your entry strategy. It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. -This method will also define a new column, `"buy"`, which needs to contain 1 for buys, and 0 for "no action". +This method will also define a new column, `"enter_long"`, which needs to contain 1 for entries, and 0 for "no action". `enter_long` column is a mandatory column that must be set even if the strategy is shorting only. Sample from `user_data/strategies/sample_strategy.py`: ```python -def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: +def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame populated with indicators @@ -224,27 +224,56 @@ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard (dataframe['volume'] > 0) # Make sure Volume is not 0 ), - 'buy'] = 1 + ['enter_long', 'enter_tag']] = (1, 'rsi_cross') return dataframe ``` +??? Note "Enter short trades" + Short-entries can be created by setting `enter_short` (corresponds to `enter_long` for long trades). + The `enter_tag` column remains identical. + Short-trades need to be supported by your exchange and market configuration! + Please make sure to set [`can_short`]() appropriately on your strategy if you intend to short. + + ```python + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + (qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30 + (dataframe['tema'] <= dataframe['bb_middleband']) & # Guard + (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + ['enter_long', 'enter_tag']] = (1, 'rsi_cross') + + dataframe.loc[ + ( + (qtpylib.crossed_below(dataframe['rsi'], 70)) & # Signal: RSI crosses below 70 + (dataframe['tema'] > dataframe['bb_middleband']) & # Guard + (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + ['enter_short', 'enter_tag']] = (1, 'rsi_cross') + + return dataframe + ``` + !!! Note Buying requires sellers to buy from - therefore volume needs to be > 0 (`dataframe['volume'] > 0`) to make sure that the bot does not buy/sell in no-activity periods. -### Sell signal rules +### Exit signal rules -Edit the method `populate_sell_trend()` into your strategy file to update your sell strategy. +Edit the method `populate_exit_trend()` into your strategy file to update your sell strategy. Please note that the sell-signal is only used if `use_sell_signal` is set to true in the configuration. It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. -This method will also define a new column, `"sell"`, which needs to contain 1 for sells, and 0 for "no action". +This method will also define a new column, `"exit_long"`, which needs to contain 1 for sells, and 0 for "no action". Sample from `user_data/strategies/sample_strategy.py`: ```python -def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: +def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame populated with indicators @@ -258,10 +287,36 @@ def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard (dataframe['volume'] > 0) # Make sure Volume is not 0 ), - 'sell'] = 1 + ['exit_long', 'exit_tag']] = (1, 'rsi_too_high') return dataframe ``` +??? Note "Exit short trades" + Short-exits can be created by setting `exit_short` (corresponds to `exit_long`). + The `exit_tag` column remains identical. + Short-trades need to be supported by your exchange and market configuration! + + ```python + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + (qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70 + (dataframe['tema'] > dataframe['bb_middleband']) & # Guard + (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + ['exit_long', 'exit_tag']] = (1, 'rsi_too_high') + dataframe.loc[ + ( + (qtpylib.crossed_below(dataframe['rsi'], 30)) & # Signal: RSI crosses below 30 + (dataframe['tema'] < dataframe['bb_middleband']) & # Guard + (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + ['exit_short', 'exit_tag']] = (1, 'rsi_too_low') + return dataframe + ``` + ### Minimal ROI This dict defines the minimal Return On Investment (ROI) a trade should reach before selling, independent from the sell signal. @@ -334,9 +389,15 @@ Please note that the same buy/sell signals may work well with one timeframe, but This setting is accessible within the strategy methods as the `self.timeframe` attribute. +### Can short + +To use short signals in futures markets, you will have to let us know to do so by setting `can_short=True`. +Strategies which enable this will fail to load on spot markets. +Disabling of this will have short signals ignored (also in futures markets). + ### Metadata dict -The metadata-dict (available for `populate_buy_trend`, `populate_sell_trend`, `populate_indicators`) contains additional information. +The metadata-dict (available for `populate_entry_trend`, `populate_exit_trend`, `populate_indicators`) contains additional information. Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`. The Metadata-dict should not be modified and does not persist information across multiple calls. @@ -382,6 +443,19 @@ A full sample can be found [in the DataProvider section](#complete-data-provider It is however better to use resampling to longer timeframes whenever possible to avoid hammering the exchange with too many requests and risk being blocked. +??? Note "Alternative candle types" + Informative_pairs can also provide a 3rd tuple element defining the candle type explicitly. + Availability of alternative candle-types will depend on the trading-mode and the exchange. Details about this can be found in the exchange documentation. + + ``` python + def informative_pairs(self): + return [ + ("ETH/USDT", "5m", ""), # Uses default candletype, depends on trading_mode + ("ETH/USDT", "5m", "spot"), # Forces usage of spot candles + ("BTC/TUSD", "15m", "futures"), # Uses futures candles + ("BTC/TUSD", "15m", "mark"), # Uses mark candles + ] + ``` *** ### Informative pairs decorator (`@informative()`) @@ -395,6 +469,8 @@ for more information. ``` python def informative(timeframe: str, asset: str = '', fmt: Optional[Union[str, Callable[[KwArg(str)], str]]] = None, + *, + candle_type: Optional[CandleType] = None, ffill: bool = True) -> Callable[[PopulateIndicators], PopulateIndicators]: """ A decorator for populate_indicators_Nn(self, dataframe, metadata), allowing these functions to @@ -423,6 +499,7 @@ for more information. * {column} - name of dataframe column. * {timeframe} - timeframe of informative dataframe. :param ffill: ffill dataframe after merging informative pair. + :param candle_type: '', mark, index, premiumIndex, or funding_rate """ ``` @@ -451,7 +528,7 @@ for more information. # Define BTC/STAKE informative pair. Available in populate_indicators and other methods as # 'btc_rsi_1h'. Current stake currency should be specified as {stake} format variable - # instead of hardcoding actual stake currency. Available in populate_indicators and other + # instead of hard-coding actual stake currency. Available in populate_indicators and other # methods as 'btc_usdt_rsi_1h' (when stake currency is USDT). @informative('1h', 'BTC/{stake}') def populate_indicators_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -490,7 +567,7 @@ for more information. Use string formatting when accessing informative dataframes of other pairs. This will allow easily changing stake currency in config without having to adjust strategy code. ``` python - def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: stake = self.config['stake_currency'] dataframe.loc[ ( @@ -498,7 +575,7 @@ for more information. & (dataframe['volume'] > 0) ), - ['buy', 'buy_tag']] = (1, 'buy_signal_rsi') + ['enter_long', 'enter_tag']] = (1, 'buy_signal_rsi') return dataframe ``` @@ -510,7 +587,6 @@ for more information. will overwrite previously defined method and not produce any errors due to limitations of Python programming language. In such cases you will find that indicators created in earlier-defined methods are not available in the dataframe. Carefully review method names and make sure they are unique! - ## Additional data (DataProvider) The strategy provides access to the `DataProvider`. This allows you to get additional data to use in your strategy. @@ -706,7 +782,7 @@ class SampleStrategy(IStrategy): return dataframe - def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( @@ -714,7 +790,7 @@ class SampleStrategy(IStrategy): (dataframe['rsi_1d'] < 30) & # Ensure daily RSI is < 30 (dataframe['volume'] > 0) # Ensure this candle had volume (important for backtesting) ), - 'buy'] = 1 + ['enter_long', 'enter_tag']] = (1, 'rsi_cross') ``` @@ -791,7 +867,7 @@ Stoploss values returned from `custom_stoploss` must specify a percentage relati Say the open price was $100, and `current_price` is $121 (`current_profit` will be `0.21`). - If we want a stop price at 7% above the open price we can call `stoploss_from_open(0.07, current_profit)` which will return `0.1157024793`. 11.57% below $121 is $107, which is the same as 7% above $100. + If we want a stop price at 7% above the open price we can call `stoploss_from_open(0.07, current_profit, False)` which will return `0.1157024793`. 11.57% below $121 is $107, which is the same as 7% above $100. ``` python @@ -811,7 +887,7 @@ Stoploss values returned from `custom_stoploss` must specify a percentage relati # once the profit has risen above 10%, keep the stoploss at 7% above the open price if current_profit > 0.10: - return stoploss_from_open(0.07, current_profit) + return stoploss_from_open(0.07, current_profit, is_short=trade.is_short) return 1 @@ -832,7 +908,7 @@ In some situations it may be confusing to deal with stops relative to current ra ??? Example "Returning a stoploss using absolute price from the custom stoploss function" - If we want to trail a stop price at 2xATR below current proce we can call `stoploss_from_absolute(current_rate - (candle['atr'] * 2), current_rate)`. + If we want to trail a stop price at 2xATR below current price we can call `stoploss_from_absolute(current_rate - (candle['atr'] * 2), current_rate, is_short=trade.is_short)`. ``` python @@ -852,7 +928,7 @@ In some situations it may be confusing to deal with stops relative to current ra current_rate: float, current_profit: float, **kwargs) -> float: dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) candle = dataframe.iloc[-1].squeeze() - return stoploss_from_absolute(current_rate - (candle['atr'] * 2), current_rate) + return stoploss_from_absolute(current_rate - (candle['atr'] * 2), current_rate, is_short=trade.is_short) ``` @@ -920,7 +996,7 @@ if self.config['runmode'].value in ('live', 'dry_run'): Sample return value: ETH/BTC had 5 trades, with a total profit of 1.5% (ratio of 0.015). ``` json -{'pair': "ETH/BTC", 'profit': 0.015, 'count': 5} +{"pair": "ETH/BTC", "profit": 0.015, "count": 5} ``` !!! Warning @@ -974,16 +1050,16 @@ if self.config['runmode'].value in ('live', 'dry_run'): ## Print created dataframe -To inspect the created dataframe, you can issue a print-statement in either `populate_buy_trend()` or `populate_sell_trend()`. +To inspect the created dataframe, you can issue a print-statement in either `populate_entry_trend()` or `populate_exit_trend()`. You may also want to print the pair so it's clear what data is currently shown. ``` python -def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: +def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( #>> whatever condition<<< ), - 'buy'] = 1 + ['enter_long', 'enter_tag']] = (1, 'somestring') # Print the Analyzed pair print(f"result for {metadata['pair']}") @@ -1012,7 +1088,12 @@ The following lists some common patterns which should be avoided to prevent frus ### Colliding signals -When buy and sell signals collide (both `'buy'` and `'sell'` are 1), freqtrade will do nothing and ignore the entry (buy) signal. This will avoid trades that buy, and sell immediately. Obviously, this can potentially lead to missed entries. +When conflicting signals collide (e.g. both `'enter_long'` and `'exit_long'` are 1), freqtrade will do nothing and ignore the entry signal. This will avoid trades that buy, and sell immediately. Obviously, this can potentially lead to missed entries. + +The following rules apply, and entry signals will be ignored if more than one of the 3 signals is set: + +- `enter_long` -> `exit_long`, `enter_short` +- `enter_short` -> `exit_short`, `enter_long` ## Further strategy ideas diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index 90d8d8800..f2ee9c4e4 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -73,7 +73,7 @@ df.tail() ```python # Report results -print(f"Generated {df['buy'].sum()} buy signals") +print(f"Generated {df['enter_long'].sum()} entry signals") data = df.set_index('date', drop=False) data.tail() ``` @@ -244,7 +244,7 @@ import plotly.figure_factory as ff hist_data = [trades.profit_ratio] group_labels = ['profit_ratio'] # name of the dataset -fig = ff.create_distplot(hist_data, group_labels,bin_size=0.01) +fig = ff.create_distplot(hist_data, group_labels, bin_size=0.01) fig.show() ``` diff --git a/docs/strategy_migration.md b/docs/strategy_migration.md new file mode 100644 index 000000000..4b014c657 --- /dev/null +++ b/docs/strategy_migration.md @@ -0,0 +1,388 @@ +# Strategy Migration between V2 and V3 + +To support new markets and trade-types (namely short trades / trades with leverage), some things had to change in the interface. +If you intend on using markets other than spot markets, please migrate your strategy to the new format. + +We have put a great effort into keeping compatibility with existing strategies, so if you just want to continue using freqtrade in __spot markets__, there should be no changes necessary for now. + +You can use the quick summary as checklist. Please refer to the detailed sections below for full migration details. + +## Quick summary / migration checklist + +* Strategy methods: + * [`populate_buy_trend()` -> `populate_entry_trend()`](#populate_buy_trend) + * [`populate_sell_trend()` -> `populate_exit_trend()`](#populate_sell_trend) + * [`custom_sell()` -> `custom_exit()`](#custom_sell) + * [`check_buy_timeout()` -> `check_entry_timeout()`](#custom_entry_timeout) + * [`check_sell_timeout()` -> `check_exit_timeout()`](#custom_entry_timeout) + * New `side` argument to callbacks without trade object + * [`custom_stake_amount`](#custom-stake-amount) + * [`confirm_trade_entry`](#confirm_trade_entry) + * [Changed argument name in `confirm_trade_exit`](#confirm_trade_exit) +* Dataframe columns: + * [`buy` -> `enter_long`](#populate_buy_trend) + * [`sell` -> `exit_long`](#populate_sell_trend) + * [`buy_tag` -> `enter_tag` (used for both long and short trades)](#populate_buy_trend) + * [New column `enter_short` and corresponding new column `exit_short`](#populate_sell_trend) +* trade-object now has the following new properties: `is_short`, `enter_side`, `exit_side` and `trade_direction`. +* [Renamed `trade.nr_of_successful_buys` to `trade.nr_of_successful_entries` (mostly relevant for `adjust_trade_position()`)](#adjust-trade-position-changes) +* Introduced new [`leverage` callback](strategy-callbacks.md#leverage-callback). +* Informative pairs can now pass a 3rd element in the Tuple, defining the candle type. +* `@informative` decorator now takes an optional `candle_type` argument. +* [helper methods](#helper-methods) `stoploss_from_open` and `stoploss_from_absolute` now take `is_short` as additional argument. +* `INTERFACE_VERSION` should be set to 3. +* [Strategy/Configuration settings](#strategyconfiguration-settings). + * `order_time_in_force` buy -> entry, sell -> exit. + * `order_types` buy -> entry, sell -> exit. + * `unfilledtimeout` buy -> entry, sell -> exit. + +## Extensive explanation + +### `populate_buy_trend` + +In `populate_buy_trend()` - you will want to change the columns you assign from `'buy`' to `'enter_long`, as well as the method name from `populate_buy_trend` to `populate_entry_trend`. + +```python hl_lines="1 9" +def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + (qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30 + (dataframe['tema'] <= dataframe['bb_middleband']) & # Guard + (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + ['buy', 'buy_tag']] = (1, 'rsi_cross') + + return dataframe +``` + +After: + +```python hl_lines="1 9" +def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + (qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30 + (dataframe['tema'] <= dataframe['bb_middleband']) & # Guard + (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + ['enter_long', 'enter_tag']] = (1, 'rsi_cross') + + return dataframe +``` + +Please refer to the [Strategy documentation](strategy-customization.md#entry-signal-rules) on how to enter and exit short trades. + +### `populate_sell_trend` + +Similar to `populate_buy_trend`, `populate_sell_trend()` will be renamed to `populate_exit_trend()`. +We'll also change the column from `"sell"` to `"exit_long"`. + +``` python hl_lines="1 9" +def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + (qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70 + (dataframe['tema'] > dataframe['bb_middleband']) & # Guard + (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + ['sell', 'exit_tag']] = (1, 'some_exit_tag') + return dataframe +``` + +After + +``` python hl_lines="1 9" +def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + (qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70 + (dataframe['tema'] > dataframe['bb_middleband']) & # Guard + (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + ['exit_long', 'exit_tag']] = (1, 'some_exit_tag') + return dataframe +``` + +Please refer to the [Strategy documentation](strategy-customization.md#exit-signal-rules) on how to enter and exit short trades. + +### `custom_sell` + +``` python hl_lines="2" +class AwesomeStrategy(IStrategy): + def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, + current_profit: float, **kwargs): + dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + last_candle = dataframe.iloc[-1].squeeze() + # ... +``` + +``` python hl_lines="2" +class AwesomeStrategy(IStrategy): + def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, + current_profit: float, **kwargs): + dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + last_candle = dataframe.iloc[-1].squeeze() + # ... +``` + +### `custom_entry_timeout` + +`check_buy_timeout()` has been renamed to `check_entry_timeout()`, and `check_sell_timeout()` has been renamed to `check_exit_timeout()`. + +``` python hl_lines="2 6" +class AwesomeStrategy(IStrategy): + def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, + current_time: datetime, **kwargs) -> bool: + return False + + def check_sell_timeout(self, pair: str, trade: 'Trade', order: dict, + current_time: datetime, **kwargs) -> bool: + return False +``` + +``` python hl_lines="2 6" +class AwesomeStrategy(IStrategy): + def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, + current_time: datetime, **kwargs) -> bool: + return False + + def check_exit_timeout(self, pair: str, trade: 'Trade', order: dict, + current_time: datetime, **kwargs) -> bool: + return False +``` + +### Custom-stake-amount + +New string argument `side` - which can be either `"long"` or `"short"`. + +``` python hl_lines="4" +class AwesomeStrategy(IStrategy): + def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, + proposed_stake: float, min_stake: float, max_stake: float, + entry_tag: Optional[str], **kwargs) -> float: + # ... + return proposed_stake +``` + +``` python hl_lines="4" +class AwesomeStrategy(IStrategy): + def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, + proposed_stake: float, min_stake: float, max_stake: float, + entry_tag: Optional[str], side: str, **kwargs) -> float: + # ... + return proposed_stake +``` + +### `confirm_trade_entry` + +New string argument `side` - which can be either `"long"` or `"short"`. + +``` python hl_lines="4" +class AwesomeStrategy(IStrategy): + def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, + time_in_force: str, current_time: datetime, entry_tag: Optional[str], + **kwargs) -> bool: + return True +``` + +After: + +``` python hl_lines="4" +class AwesomeStrategy(IStrategy): + def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, + time_in_force: str, current_time: datetime, entry_tag: Optional[str], + side: str, **kwargs) -> bool: + return True +``` + +### `confirm_trade_exit` + +Changed argument `sell_reason` to `exit_reason`. +For compatibility, `sell_reason` will still be provided for a limited time. + +``` python hl_lines="3" +class AwesomeStrategy(IStrategy): + def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, + rate: float, time_in_force: str, sell_reason: str, + current_time: datetime, **kwargs) -> bool: + return True +``` + +After: + +``` python hl_lines="3" +class AwesomeStrategy(IStrategy): + def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, + rate: float, time_in_force: str, exit_reason: str, + current_time: datetime, **kwargs) -> bool: + return True +``` + +### Adjust trade position changes + +While adjust-trade-position itself did not change, you should no longer use `trade.nr_of_successful_buys` - and instead use `trade.nr_of_successful_entries`, which will also include short entries. + +### Helper methods + +Added argument "is_short" to `stoploss_from_open` and `stoploss_from_absolute`. +This should be given the value of `trade.is_short`. + +``` python hl_lines="5 7" + def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + current_rate: float, current_profit: float, **kwargs) -> float: + # once the profit has risen above 10%, keep the stoploss at 7% above the open price + if current_profit > 0.10: + return stoploss_from_open(0.07, current_profit) + + return stoploss_from_absolute(current_rate - (candle['atr'] * 2), current_rate) + + return 1 + +``` + +After: + +``` python hl_lines="5 7" + def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + current_rate: float, current_profit: float, **kwargs) -> float: + # once the profit has risen above 10%, keep the stoploss at 7% above the open price + if current_profit > 0.10: + return stoploss_from_open(0.07, current_profit, is_short=trade.is_short) + + return stoploss_from_absolute(current_rate - (candle['atr'] * 2), current_rate, is_short=trade.is_short) + + +``` + +### Strategy/Configuration settings + +#### `order_time_in_force` + +`order_time_in_force` attributes changed from `"buy"` to `"entry"` and `"sell"` to `"exit"`. + +``` python + order_time_in_force: Dict = { + "buy": "gtc", + "sell": "gtc", + } +``` + +After: + +``` python hl_lines="2 3" + order_time_in_force: Dict = { + "entry": "gtc", + "exit": "gtc", + } +``` + +#### `order_types` + +`order_types` have changed all wordings from `buy` to `entry` - and `sell` to `exit`. + +``` python hl_lines="2-6" + order_types = { + "buy": "limit", + "sell": "limit", + "emergencysell": "market", + "forcesell": "market", + "forcebuy": "market", + "stoploss": "market", + "stoploss_on_exchange": false, + "stoploss_on_exchange_interval": 60 + } +``` + +After: + +``` python hl_lines="2-6" + order_types = { + "entry": "limit", + "exit": "limit", + "emergencyexit": "market", + "forceexit": "market", + "forceentry": "market", + "stoploss": "market", + "stoploss_on_exchange": false, + "stoploss_on_exchange_interval": 60 + } +``` + +#### `unfilledtimeout` + +`unfilledtimeout` have changed all wordings from `buy` to `entry` - and `sell` to `exit`. + +``` python hl_lines="2-3" +unfilledtimeout = { + "buy": 10, + "sell": 10, + "exit_timeout_count": 0, + "unit": "minutes" + } +``` + +After: + +``` python hl_lines="2-3" +unfilledtimeout = { + "entry": 10, + "exit": 10, + "exit_timeout_count": 0, + "unit": "minutes" + } +``` + +#### `order pricing` + +Order pricing changed in 2 ways. `bid_strategy` was renamed to `entry_pricing` and `ask_strategy` was renamed to `exit_pricing`. +The attributes `ask_last_balance` -> `price_last_balance` and `bid_last_balance` -> `price_last_balance` were renamed as well. +Also, price-side can now be defined as `ask`, `bid`, `same` or `other`. +Please refer to the [pricing documentation](configuration.md#prices-used-for-orders) for more information. + +``` json hl_lines="2-3 6 12-13 16" +{ + "bid_strategy": { + "price_side": "bid", + "use_order_book": true, + "order_book_top": 1, + "ask_last_balance": 0.0, + "check_depth_of_market": { + "enabled": false, + "bids_to_ask_delta": 1 + } + }, + "ask_strategy":{ + "price_side": "ask", + "use_order_book": true, + "order_book_top": 1, + "bid_last_balance": 0.0 + } +} +``` + +after: + +``` json hl_lines="2-3 6 12-13 16" +{ + "entry_pricing": { + "price_side": "same", + "use_order_book": true, + "order_book_top": 1, + "price_last_balance": 0.0, + "check_depth_of_market": { + "enabled": false, + "bids_to_ask_delta": 1 + } + }, + "exit_pricing":{ + "price_side": "same", + "use_order_book": true, + "order_book_top": 1, + "price_last_balance": 0.0 + } +} +``` diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 232885ed2..3de3c8846 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -171,9 +171,10 @@ official commands. You can ask at any moment for help with `/help`. | `/locks` | Show currently locked pairs. | `/unlock ` | Remove the lock for this pair (or for this lock id). | `/profit []` | Display a summary of your profit/loss from close trades and some stats about your performance, over the last n days (all trades by default) -| `/forcesell ` | Instantly sells the given trade (Ignoring `minimum_roi`). -| `/forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`). -| `/forcebuy [rate]` | Instantly buys the given pair. Rate is optional and only applies to limit orders. (`forcebuy_enable` must be set to True) +| `/forceexit ` | Instantly exits the given trade (Ignoring `minimum_roi`). +| `/forceexit all` | Instantly exits all open trades (Ignoring `minimum_roi`). +| `/forcelong [rate]` | Instantly buys the given pair. Rate is optional and only applies to limit orders. (`forcebuy_enable` must be set to True) +| `/forceshort [rate]` | Instantly shorts the given pair. Rate is optional and only applies to limit orders. This will only work on non-spot markets. (`forcebuy_enable` must be set to True) | `/performance` | Show performance of each finished trade grouped by pair | `/balance` | Show account balance per currency | `/daily ` | Shows profit or loss per day, over the last n days (n defaults to 7) @@ -216,11 +217,14 @@ Once all positions are sold, run `/stop` to completely stop the bot. ### /status For each open trade, the bot will send you the following message. +Enter Tag is configurable via Strategy. > **Trade ID:** `123` `(since 1 days ago)` > **Current Pair:** CVC/BTC -> **Open Since:** `1 days ago` +> **Direction:** Long +> **Leverage:** 1.0 > **Amount:** `26.64180098` +> **Enter Tag:** Awesome Long Signal > **Open Rate:** `0.00007489` > **Current Rate:** `0.00007489` > **Current Profit:** `12.95%` @@ -231,10 +235,10 @@ For each open trade, the bot will send you the following message. Return the status of all open trades in a table format. ``` - ID Pair Since Profit ----- -------- ------- -------- - 67 SC/BTC 1 d 13.33% - 123 CVC/BTC 1 h 12.95% +ID L/S Pair Since Profit +---- -------- ------- -------- + 67 L SC/BTC 1 d 13.33% + 123 S CVC/BTC 1 h 12.95% ``` ### /count @@ -270,14 +274,16 @@ Starting capital is either taken from the `available_capital` setting, or calcul ### /forcesell -> **BITTREX:** Selling BTC/LTC with limit `0.01650000 (profit: ~-4.07%, -0.00008168)` +> **BINANCE:** Selling BTC/LTC with limit `0.01650000 (profit: ~-4.07%, -0.00008168)` -### /forcebuy [rate] +### /forcelong [rate] | /forceshort [rate] -> **BITTREX:** Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`) +`/forcebuy [rate]` is also supported for longs but should be considered deprecated. -Omitting the pair will open a query asking for the pair to buy (based on the current whitelist). -Trades crated through `/forcebuy` will have the buy-tag of `forceentry`. +> **BINANCE:** Long ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`) + +Omitting the pair will open a query asking for the pair to trade (based on the current whitelist). +Trades crated through `/forceentry` will have the buy-tag of `forceentry`. ![Telegram force-buy screenshot](assets/telegram_forcebuy.png) diff --git a/docs/utils.md b/docs/utils.md index a28a0f456..5ef5646c3 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -439,14 +439,15 @@ usage: freqtrade list-markets [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--exchange EXCHANGE] [--print-list] [--print-json] [-1] [--print-csv] [--base BASE_CURRENCY [BASE_CURRENCY ...]] - [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] - [-a] + [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [-a] + [--trading-mode {spot,margin,futures}] usage: freqtrade list-pairs [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--exchange EXCHANGE] [--print-list] [--print-json] [-1] [--print-csv] [--base BASE_CURRENCY [BASE_CURRENCY ...]] [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [-a] + [--trading-mode {spot,margin,futures}] optional arguments: -h, --help show this help message and exit @@ -463,6 +464,8 @@ optional arguments: Specify quote currency(-ies). Space-separated list. -a, --all Print all pairs or market symbols. By default only active ones are shown. + --trading-mode {spot,margin,futures} + Select Trading mode Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). diff --git a/docs/webhook-config.md b/docs/webhook-config.md index c93f9aac8..1266618f6 100644 --- a/docs/webhook-config.md +++ b/docs/webhook-config.md @@ -98,12 +98,14 @@ Different payloads can be configured for different events. Not all fields are ne ### Webhookbuy -The fields in `webhook.webhookbuy` are filled when the bot executes a buy. Parameters are filled using string.format. +The fields in `webhook.webhookbuy` are filled when the bot executes a long/short. Parameters are filled using string.format. Possible parameters are: * `trade_id` * `exchange` * `pair` +* `direction` +* `leverage` * ~~`limit` # Deprecated - should no longer be used.~~ * `open_rate` * `amount` @@ -114,16 +116,18 @@ Possible parameters are: * `fiat_currency` * `order_type` * `current_rate` -* `buy_tag` +* `enter_tag` ### Webhookbuycancel -The fields in `webhook.webhookbuycancel` are filled when the bot cancels a buy order. Parameters are filled using string.format. +The fields in `webhook.webhookbuycancel` are filled when the bot cancels a long/short order. Parameters are filled using string.format. Possible parameters are: * `trade_id` * `exchange` * `pair` +* `direction` +* `leverage` * `limit` * `amount` * `open_date` @@ -133,16 +137,18 @@ Possible parameters are: * `fiat_currency` * `order_type` * `current_rate` -* `buy_tag` +* `enter_tag` ### Webhookbuyfill -The fields in `webhook.webhookbuyfill` are filled when the bot filled a buy order. Parameters are filled using string.format. +The fields in `webhook.webhookbuyfill` are filled when the bot filled a long/short order. Parameters are filled using string.format. Possible parameters are: * `trade_id` * `exchange` * `pair` +* `direction` +* `leverage` * `open_rate` * `amount` * `open_date` @@ -152,16 +158,17 @@ Possible parameters are: * `fiat_currency` * `order_type` * `current_rate` -* `buy_tag` +* `enter_tag` ### Webhooksell - The fields in `webhook.webhooksell` are filled when the bot sells a trade. Parameters are filled using string.format. Possible parameters are: * `trade_id` * `exchange` * `pair` +* `direction` +* `leverage` * `gain` * `limit` * `amount` @@ -184,6 +191,8 @@ Possible parameters are: * `trade_id` * `exchange` * `pair` +* `direction` +* `leverage` * `gain` * `close_rate` * `amount` @@ -207,6 +216,8 @@ Possible parameters are: * `trade_id` * `exchange` * `pair` +* `direction` +* `leverage` * `gain` * `limit` * `amount` diff --git a/environment.yml b/environment.yml index 50af602e5..f2f961894 100644 --- a/environment.yml +++ b/environment.yml @@ -30,6 +30,7 @@ dependencies: - colorama - questionary - prompt-toolkit + - schedule - python-dateutil diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 28f7d7148..7d4624bd1 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -48,7 +48,8 @@ ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"] ARGS_LIST_TIMEFRAMES = ["exchange", "print_one_column"] ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one_column", - "print_csv", "base_currencies", "quote_currencies", "list_pairs_all"] + "print_csv", "base_currencies", "quote_currencies", "list_pairs_all", + "trading_mode"] ARGS_TEST_PAIRLIST = ["verbosity", "config", "quote_currencies", "print_one_column", "list_pairs_print_json", "exchange"] @@ -60,15 +61,17 @@ ARGS_BUILD_CONFIG = ["config"] ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"] ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase"] -ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes"] + +ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes", "exchange", "trading_mode", + "candle_types"] ARGS_CONVERT_TRADES = ["pairs", "timeframes", "exchange", "dataformat_ohlcv", "dataformat_trades"] -ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs"] +ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs", "trading_mode"] ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "new_pairs_days", "include_inactive", "timerange", "download_trades", "exchange", "timeframes", - "erase", "dataformat_ohlcv", "dataformat_trades"] + "erase", "dataformat_ohlcv", "dataformat_trades", "trading_mode"] ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", "db_url", "trade_source", "export", "exportfilename", diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index ca55dbbc4..b401f52c7 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -104,7 +104,7 @@ def ask_user_config() -> Dict[str, Any]: "type": "select", "name": "exchange_name", "message": "Select exchange", - "choices": [ + "choices": lambda x: [ "binance", "binanceus", "bittrex", @@ -114,10 +114,18 @@ def ask_user_config() -> Dict[str, Any]: "kraken", "kucoin", "okx", - Separator(), + Separator("------------------"), "other", ], }, + { + "type": "confirm", + "name": "trading_mode", + "message": "Do you want to trade Perpetual Swaps (perpetual futures)?", + "default": False, + "filter": lambda val: 'futures' if val else 'spot', + "when": lambda x: x["exchange_name"] in ['binance', 'gateio', 'okx'], + }, { "type": "autocomplete", "name": "exchange_name", @@ -194,7 +202,11 @@ def ask_user_config() -> Dict[str, Any]: if not answers: # Interrupted questionary sessions return an empty dict. raise OperationalException("User interrupted interactive questions.") - + answers['margin_mode'] = ( + 'isolated' + if answers.get('trading_mode') == 'futures' + else '' + ) # Force JWT token to be a random string answers['api_server_jwt_key'] = secrets.token_hex() diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index f30c25ba1..61890d650 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -5,6 +5,7 @@ from argparse import SUPPRESS, ArgumentTypeError from freqtrade import __version__, constants from freqtrade.constants import HYPEROPT_LOSS_BUILTIN +from freqtrade.enums import CandleType def check_int_positive(value: str) -> int: @@ -179,7 +180,6 @@ AVAILABLE_CLI_OPTIONS = { '--export', help='Export backtest results (default: trades).', choices=constants.EXPORT_OPTIONS, - ), "exportfilename": Arg( "--export-filename", @@ -356,6 +356,17 @@ AVAILABLE_CLI_OPTIONS = { nargs='+', metavar='BASE_CURRENCY', ), + "trading_mode": Arg( + '--trading-mode', + help='Select Trading mode', + choices=constants.TRADING_MODES, + ), + "candle_types": Arg( + '--candle-types', + help='Select candle type to use', + choices=[c.value for c in CandleType], + nargs='+', + ), # Script options "pairs": Arg( '-p', '--pairs', diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index 5dc5fe7ea..e41512ccc 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -8,7 +8,7 @@ from freqtrade.configuration import TimeRange, setup_utils_configuration from freqtrade.data.converter import convert_ohlcv_format, convert_trades_format from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_ohlcv_data, refresh_backtest_trades_data) -from freqtrade.enums import RunMode +from freqtrade.enums import CandleType, RunMode, TradingMode from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange.exchange import market_is_active @@ -64,6 +64,8 @@ def start_download_data(args: Dict[str, Any]) -> None: try: if config.get('download_trades'): + if config.get('trading_mode') == 'futures': + raise OperationalException("Trade download not supported for futures.") pairs_not_available = refresh_backtest_trades_data( exchange, pairs=expanded_pairs, datadir=config['datadir'], timerange=timerange, new_pairs_days=config['new_pairs_days'], @@ -81,7 +83,9 @@ def start_download_data(args: Dict[str, Any]) -> None: exchange, pairs=expanded_pairs, timeframes=config['timeframes'], datadir=config['datadir'], timerange=timerange, new_pairs_days=config['new_pairs_days'], - erase=bool(config.get('erase')), data_format=config['dataformat_ohlcv']) + erase=bool(config.get('erase')), data_format=config['dataformat_ohlcv'], + trading_mode=config.get('trading_mode', 'spot'), + ) except KeyboardInterrupt: sys.exit("SIGINT received, aborting ...") @@ -133,9 +137,11 @@ def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None: """ config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) if ohlcv: - convert_ohlcv_format(config, - convert_from=args['format_from'], convert_to=args['format_to'], - erase=args['erase']) + candle_types = [CandleType.from_string(ct) for ct in config.get('candle_types', ['spot'])] + for candle_type in candle_types: + convert_ohlcv_format(config, + convert_from=args['format_from'], convert_to=args['format_to'], + erase=args['erase'], candle_type=candle_type) else: convert_trades_format(config, convert_from=args['format_from'], convert_to=args['format_to'], @@ -154,17 +160,26 @@ def start_list_data(args: Dict[str, Any]) -> None: from freqtrade.data.history.idatahandler import get_datahandler dhc = get_datahandler(config['datadir'], config['dataformat_ohlcv']) - paircombs = dhc.ohlcv_get_available_data(config['datadir']) + paircombs = dhc.ohlcv_get_available_data( + config['datadir'], + config.get('trading_mode', TradingMode.SPOT) + ) if args['pairs']: paircombs = [comb for comb in paircombs if comb[0] in args['pairs']] print(f"Found {len(paircombs)} pair / timeframe combinations.") groupedpair = defaultdict(list) - for pair, timeframe in sorted(paircombs, key=lambda x: (x[0], timeframe_to_minutes(x[1]))): - groupedpair[pair].append(timeframe) + for pair, timeframe, candle_type in sorted( + paircombs, + key=lambda x: (x[0], timeframe_to_minutes(x[1]), x[2]) + ): + groupedpair[(pair, candle_type)].append(timeframe) if groupedpair: - print(tabulate([(pair, ', '.join(timeframes)) for pair, timeframes in groupedpair.items()], - headers=("Pair", "Timeframe"), - tablefmt='psql', stralign='right')) + print(tabulate([ + (pair, ', '.join(timeframes), candle_type) + for (pair, candle_type), timeframes in groupedpair.items() + ], + headers=("Pair", "Timeframe", "Type"), + tablefmt='psql', stralign='right')) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 38fb098a0..c4bd0bf4d 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -131,7 +131,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None: try: pairs = exchange.get_markets(base_currencies=base_currencies, quote_currencies=quote_currencies, - pairs_only=pairs_only, + tradable_only=pairs_only, active_only=active_only) # Sort the pairs/markets by symbol pairs = dict(sorted(pairs.items())) @@ -151,15 +151,19 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None: if quote_currencies else "")) headers = ["Id", "Symbol", "Base", "Quote", "Active", - *(['Is pair'] if not pairs_only else [])] + "Spot", "Margin", "Future", "Leverage"] - tabular_data = [] - for _, v in pairs.items(): - tabular_data.append({'Id': v['id'], 'Symbol': v['symbol'], - 'Base': v['base'], 'Quote': v['quote'], - 'Active': market_is_active(v), - **({'Is pair': exchange.market_is_tradable(v)} - if not pairs_only else {})}) + tabular_data = [{ + 'Id': v['id'], + 'Symbol': v['symbol'], + 'Base': v['base'], + 'Quote': v['quote'], + 'Active': market_is_active(v), + 'Spot': 'Spot' if exchange.market_is_spot(v) else '', + 'Margin': 'Margin' if exchange.market_is_margin(v) else '', + 'Future': 'Future' if exchange.market_is_future(v) else '', + 'Leverage': exchange.get_max_leverage(v['symbol'], 20) + } for _, v in pairs.items()] if (args.get('print_one_column', False) or args.get('list_pairs_print_json', False) or diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 85ff4408f..09e7b3335 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -6,7 +6,8 @@ from jsonschema import Draft4Validator, validators from jsonschema.exceptions import ValidationError, best_match from freqtrade import constants -from freqtrade.enums import RunMode +from freqtrade.configuration.deprecated_settings import process_deprecated_setting +from freqtrade.enums import RunMode, TradingMode from freqtrade.exceptions import OperationalException @@ -80,6 +81,7 @@ def validate_config_consistency(conf: Dict[str, Any]) -> None: _validate_protections(conf) _validate_unlimited_amount(conf) _validate_ask_orderbook(conf) + validate_migrated_strategy_settings(conf) # validate configuration before returning logger.info('Validating configuration ...') @@ -101,13 +103,15 @@ def _validate_price_config(conf: Dict[str, Any]) -> None: """ When using market orders, price sides must be using the "other" side of the price """ - if (conf.get('order_types', {}).get('buy') == 'market' - and conf.get('bid_strategy', {}).get('price_side') != 'ask'): - raise OperationalException('Market buy orders require bid_strategy.price_side = "ask".') + # TODO: The below could be an enforced setting when using market orders + if (conf.get('order_types', {}).get('entry') == 'market' + and conf.get('entry_pricing', {}).get('price_side') not in ('ask', 'other')): + raise OperationalException( + 'Market entry orders require entry_pricing.price_side = "other".') - if (conf.get('order_types', {}).get('sell') == 'market' - and conf.get('ask_strategy', {}).get('price_side') != 'bid'): - raise OperationalException('Market sell orders require ask_strategy.price_side = "bid".') + if (conf.get('order_types', {}).get('exit') == 'market' + and conf.get('exit_pricing', {}).get('price_side') not in ('bid', 'other')): + raise OperationalException('Market exit orders require exit_pricing.price_side = "other".') def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None: @@ -190,13 +194,13 @@ def _validate_protections(conf: Dict[str, Any]) -> None: def _validate_ask_orderbook(conf: Dict[str, Any]) -> None: - ask_strategy = conf.get('ask_strategy', {}) + ask_strategy = conf.get('exit_pricing', {}) ob_min = ask_strategy.get('order_book_min') ob_max = ask_strategy.get('order_book_max') if ob_min is not None and ob_max is not None and ask_strategy.get('use_order_book'): if ob_min != ob_max: raise OperationalException( - "Using order_book_max != order_book_min in ask_strategy is no longer supported." + "Using order_book_max != order_book_min in exit_pricing is no longer supported." "Please pick one value and use `order_book_top` in the future." ) else: @@ -205,5 +209,106 @@ def _validate_ask_orderbook(conf: Dict[str, Any]) -> None: logger.warning( "DEPRECATED: " "Please use `order_book_top` instead of `order_book_min` and `order_book_max` " - "for your `ask_strategy` configuration." + "for your `exit_pricing` configuration." ) + + +def validate_migrated_strategy_settings(conf: Dict[str, Any]) -> None: + + _validate_time_in_force(conf) + _validate_order_types(conf) + _validate_unfilledtimeout(conf) + _validate_pricing_rules(conf) + + +def _validate_time_in_force(conf: Dict[str, Any]) -> None: + + time_in_force = conf.get('order_time_in_force', {}) + if 'buy' in time_in_force or 'sell' in time_in_force: + if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT: + raise OperationalException( + "Please migrate your time_in_force settings to use 'entry' and 'exit'.") + else: + logger.warning( + "DEPRECATED: Using 'buy' and 'sell' for time_in_force is deprecated." + "Please migrate your time_in_force settings to use 'entry' and 'exit'." + ) + process_deprecated_setting( + conf, 'order_time_in_force', 'buy', 'order_time_in_force', 'entry') + + process_deprecated_setting( + conf, 'order_time_in_force', 'sell', 'order_time_in_force', 'exit') + + +def _validate_order_types(conf: Dict[str, Any]) -> None: + + order_types = conf.get('order_types', {}) + if any(x in order_types for x in ['buy', 'sell', 'emergencysell', 'forcebuy', 'forcesell']): + if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT: + raise OperationalException( + "Please migrate your order_types settings to use the new wording.") + else: + logger.warning( + "DEPRECATED: Using 'buy' and 'sell' for order_types is deprecated." + "Please migrate your order_types settings to use 'entry' and 'exit' wording." + ) + for o, n in [ + ('buy', 'entry'), + ('sell', 'exit'), + ('emergencysell', 'emergencyexit'), + ('forcesell', 'forceexit'), + ('forcebuy', 'forceentry'), + ]: + + process_deprecated_setting(conf, 'order_types', o, 'order_types', n) + + +def _validate_unfilledtimeout(conf: Dict[str, Any]) -> None: + unfilledtimeout = conf.get('unfilledtimeout', {}) + if any(x in unfilledtimeout for x in ['buy', 'sell']): + if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT: + raise OperationalException( + "Please migrate your unfilledtimeout settings to use the new wording.") + else: + + logger.warning( + "DEPRECATED: Using 'buy' and 'sell' for unfilledtimeout is deprecated." + "Please migrate your unfilledtimeout settings to use 'entry' and 'exit' wording." + ) + for o, n in [ + ('buy', 'entry'), + ('sell', 'exit'), + ]: + + process_deprecated_setting(conf, 'unfilledtimeout', o, 'unfilledtimeout', n) + + +def _validate_pricing_rules(conf: Dict[str, Any]) -> None: + + if conf.get('ask_strategy') or conf.get('bid_strategy'): + if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT: + raise OperationalException( + "Please migrate your pricing settings to use the new wording.") + else: + + logger.warning( + "DEPRECATED: Using 'ask_strategy' and 'bid_strategy' is deprecated." + "Please migrate your settings to use 'entry_pricing' and 'exit_pricing'." + ) + conf['entry_pricing'] = {} + for obj in list(conf.get('bid_strategy', {}).keys()): + if obj == 'ask_last_balance': + process_deprecated_setting(conf, 'bid_strategy', obj, + 'entry_pricing', 'price_last_balance') + else: + process_deprecated_setting(conf, 'bid_strategy', obj, 'entry_pricing', obj) + del conf['bid_strategy'] + + conf['exit_pricing'] = {} + for obj in list(conf.get('ask_strategy', {}).keys()): + if obj == 'bid_last_balance': + process_deprecated_setting(conf, 'ask_strategy', obj, + 'exit_pricing', 'price_last_balance') + else: + process_deprecated_setting(conf, 'ask_strategy', obj, 'exit_pricing', obj) + del conf['ask_strategy'] diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 1ba17a04d..aa8f51a1d 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -13,7 +13,7 @@ from freqtrade.configuration.deprecated_settings import process_temporary_deprec from freqtrade.configuration.directory_operations import create_datadir, create_userdata_dir from freqtrade.configuration.environment_vars import enironment_vars_to_dict from freqtrade.configuration.load_config import load_config_file, load_file -from freqtrade.enums import NON_UTIL_MODES, TRADING_MODES, RunMode +from freqtrade.enums import NON_UTIL_MODES, TRADING_MODES, CandleType, RunMode, TradingMode from freqtrade.exceptions import OperationalException from freqtrade.loggers import setup_logging from freqtrade.misc import deep_merge_dicts, parse_db_uri_for_logging @@ -81,8 +81,6 @@ class Configuration: # Normalize config if 'internals' not in config: config['internals'] = {} - if 'ask_strategy' not in config: - config['ask_strategy'] = {} if 'pairlists' not in config: config['pairlists'] = [] @@ -433,6 +431,12 @@ class Configuration: def _process_data_options(self, config: Dict[str, Any]) -> None: self._args_to_config(config, argname='new_pairs_days', logstring='Detected --new-pairs-days: {}') + self._args_to_config(config, argname='trading_mode', + logstring='Detected --trading-mode: {}') + config['candle_type_def'] = CandleType.get_default(config.get('trading_mode', 'spot')) + config['trading_mode'] = TradingMode(config.get('trading_mode', 'spot')) + self._args_to_config(config, argname='candle_types', + logstring='Detected --candle-types: {}') def _process_runmode(self, config: Dict[str, Any]) -> None: diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index cafa8957b..aa65e713a 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -64,6 +64,7 @@ def process_deprecated_setting(config: Dict[str, Any], section_new_config = config.get(section_new, {}) if section_new else config section_new_config[name_new] = section_old_config[name_old] + del section_old_config[name_old] def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 200917d43..a06e2771f 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -5,6 +5,8 @@ bot constants """ from typing import List, Tuple +from freqtrade.enums import CandleType + DEFAULT_CONFIG = 'config.json' DEFAULT_EXCHANGE = 'bittrex' @@ -17,9 +19,9 @@ DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite' DEFAULT_DB_DRYRUN_URL = 'sqlite:///tradesv3.dryrun.sqlite' UNLIMITED_STAKE_AMOUNT = 'unlimited' DEFAULT_AMOUNT_RESERVE_PERCENT = 0.05 -REQUIRED_ORDERTIF = ['buy', 'sell'] -REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] -ORDERBOOK_SIDES = ['ask', 'bid'] +REQUIRED_ORDERTIF = ['entry', 'exit'] +REQUIRED_ORDERTYPES = ['entry', 'exit', 'stoploss', 'stoploss_on_exchange'] +PRICING_SIDES = ['ask', 'bid', 'same', 'other'] ORDERTYPE_POSSIBILITIES = ['limit', 'market'] ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', @@ -43,6 +45,8 @@ DEFAULT_DATAFRAME_COLUMNS = ['date', 'open', 'high', 'low', 'close', 'volume'] # Don't modify sequence of DEFAULT_TRADES_COLUMNS # it has wide consequences for stored trades files DEFAULT_TRADES_COLUMNS = ['timestamp', 'id', 'type', 'side', 'price', 'amount', 'cost'] +TRADING_MODES = ['spot', 'margin', 'futures'] +MARGIN_MODES = ['cross', 'isolated', ''] LAST_BT_RESULT_FN = '.last_result.json' FTHYPT_FILEVERSION = 'fthypt_fileversion' @@ -150,6 +154,9 @@ CONF_SCHEMA = { 'sell_profit_offset': {'type': 'number'}, 'ignore_roi_if_buy_signal': {'type': 'boolean'}, 'ignore_buying_expired_candle_after': {'type': 'number'}, + 'trading_mode': {'type': 'string', 'enum': TRADING_MODES}, + 'margin_mode': {'type': 'string', 'enum': MARGIN_MODES}, + 'liquidation_buffer': {'type': 'number', 'minimum': 0.0, 'maximum': 0.99}, 'backtest_breakdown': { 'type': 'array', 'items': {'type': 'string', 'enum': BACKTEST_BREAKDOWNS} @@ -158,22 +165,22 @@ CONF_SCHEMA = { 'unfilledtimeout': { 'type': 'object', 'properties': { - 'buy': {'type': 'number', 'minimum': 1}, - 'sell': {'type': 'number', 'minimum': 1}, + 'entry': {'type': 'number', 'minimum': 1}, + 'exit': {'type': 'number', 'minimum': 1}, 'exit_timeout_count': {'type': 'number', 'minimum': 0, 'default': 0}, 'unit': {'type': 'string', 'enum': TIMEOUT_UNITS, 'default': 'minutes'} } }, - 'bid_strategy': { + 'entry_pricing': { 'type': 'object', 'properties': { - 'ask_last_balance': { + 'price_last_balance': { 'type': 'number', 'minimum': 0, 'maximum': 1, 'exclusiveMaximum': False, }, - 'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'bid'}, + 'price_side': {'type': 'string', 'enum': PRICING_SIDES, 'default': 'same'}, 'use_order_book': {'type': 'boolean'}, 'order_book_top': {'type': 'integer', 'minimum': 1, 'maximum': 50, }, 'check_depth_of_market': { @@ -186,11 +193,11 @@ CONF_SCHEMA = { }, 'required': ['price_side'] }, - 'ask_strategy': { + 'exit_pricing': { 'type': 'object', 'properties': { - 'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'ask'}, - 'bid_last_balance': { + 'price_side': {'type': 'string', 'enum': PRICING_SIDES, 'default': 'same'}, + 'price_last_balance': { 'type': 'number', 'minimum': 0, 'maximum': 1, @@ -207,11 +214,11 @@ CONF_SCHEMA = { 'order_types': { 'type': 'object', 'properties': { - 'buy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, - 'sell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, - 'forcesell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, - 'forcebuy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, - 'emergencysell': { + 'entry': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, + 'exit': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, + 'forceexit': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, + 'forceentry': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, + 'emergencyexit': { 'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES, 'default': 'market'}, @@ -221,15 +228,15 @@ CONF_SCHEMA = { 'stoploss_on_exchange_limit_ratio': {'type': 'number', 'minimum': 0.0, 'maximum': 1.0} }, - 'required': ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] + 'required': ['entry', 'exit', 'stoploss', 'stoploss_on_exchange'] }, 'order_time_in_force': { 'type': 'object', 'properties': { - 'buy': {'type': 'string', 'enum': ORDERTIF_POSSIBILITIES}, - 'sell': {'type': 'string', 'enum': ORDERTIF_POSSIBILITIES} + 'entry': {'type': 'string', 'enum': ORDERTIF_POSSIBILITIES}, + 'exit': {'type': 'string', 'enum': ORDERTIF_POSSIBILITIES} }, - 'required': ['buy', 'sell'] + 'required': REQUIRED_ORDERTIF }, 'exchange': {'$ref': '#/definitions/exchange'}, 'edge': {'$ref': '#/definitions/edge'}, @@ -438,8 +445,8 @@ SCHEMA_TRADE_REQUIRED = [ 'last_stake_amount_min_ratio', 'dry_run', 'dry_run_wallet', - 'ask_strategy', - 'bid_strategy', + 'exit_pricing', + 'entry_pricing', 'stoploss', 'minimal_roi', 'internals', @@ -475,7 +482,7 @@ CANCEL_REASON = { } # List of pairs with their timeframes -PairWithTimeframe = Tuple[str, str] +PairWithTimeframe = Tuple[str, str, CandleType] ListPairsWithTimeframes = List[PairWithTimeframe] # Type for trades list diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 16926923b..4df8b2838 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -24,7 +24,9 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date', 'fee_open', 'fee_close', 'trade_duration', 'profit_ratio', 'profit_abs', 'sell_reason', 'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs', - 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag'] + 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'enter_tag', + 'is_short' + ] def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str: @@ -250,6 +252,13 @@ def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = Non utc=True, infer_datetime_format=True ) + # Compatibility support for pre short Columns + if 'is_short' not in df.columns: + df['is_short'] = 0 + if 'enter_tag' not in df.columns: + df['enter_tag'] = df['buy_tag'] + df = df.drop(['buy_tag'], axis=1) + else: # old format - only with lists. raise OperationalException( diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py index d592b4990..84c57be41 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -11,6 +11,7 @@ import pandas as pd from pandas import DataFrame, to_datetime from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, TradeList +from freqtrade.enums import CandleType logger = logging.getLogger(__name__) @@ -261,13 +262,20 @@ def convert_trades_format(config: Dict[str, Any], convert_from: str, convert_to: src.trades_purge(pair=pair) -def convert_ohlcv_format(config: Dict[str, Any], convert_from: str, convert_to: str, erase: bool): +def convert_ohlcv_format( + config: Dict[str, Any], + convert_from: str, + convert_to: str, + erase: bool, + candle_type: CandleType +): """ Convert OHLCV from one format to another :param config: Config dictionary :param convert_from: Source format :param convert_to: Target format :param erase: Erase source data (does not apply if source and target format are identical) + :param candle_type: Any of the enum CandleType (must match trading mode!) """ from freqtrade.data.history.idatahandler import get_datahandler src = get_datahandler(config['datadir'], convert_from) @@ -279,8 +287,11 @@ def convert_ohlcv_format(config: Dict[str, Any], convert_from: str, convert_to: config['pairs'] = [] # Check timeframes or fall back to timeframe. for timeframe in timeframes: - config['pairs'].extend(src.ohlcv_get_pairs(config['datadir'], - timeframe)) + config['pairs'].extend(src.ohlcv_get_pairs( + config['datadir'], + timeframe, + candle_type=candle_type + )) logger.info(f"Converting candle (OHLCV) data for {config['pairs']}") for timeframe in timeframes: @@ -289,10 +300,16 @@ def convert_ohlcv_format(config: Dict[str, Any], convert_from: str, convert_to: timerange=None, fill_missing=False, drop_incomplete=False, - startup_candles=0) - logger.info(f"Converting {len(data)} candles for {pair}") + startup_candles=0, + candle_type=candle_type) + logger.info(f"Converting {len(data)} {candle_type} candles for {pair}") if len(data) > 0: - trg.ohlcv_store(pair=pair, timeframe=timeframe, data=data) + trg.ohlcv_store( + pair=pair, + timeframe=timeframe, + data=data, + candle_type=candle_type + ) if erase and convert_from != convert_to: logger.info(f"Deleting source data for {pair} / {timeframe}") - src.ohlcv_purge(pair=pair, timeframe=timeframe) + src.ohlcv_purge(pair=pair, timeframe=timeframe, candle_type=candle_type) diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index b197c159f..b9b118c00 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -13,7 +13,7 @@ from pandas import DataFrame from freqtrade.configuration import TimeRange from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe from freqtrade.data.history import load_pair_history -from freqtrade.enums import RunMode +from freqtrade.enums import CandleType, RunMode from freqtrade.exceptions import ExchangeError, OperationalException from freqtrade.exchange import Exchange, timeframe_to_seconds @@ -41,7 +41,13 @@ class DataProvider: """ self.__slice_index = limit_index - def _set_cached_df(self, pair: str, timeframe: str, dataframe: DataFrame) -> None: + def _set_cached_df( + self, + pair: str, + timeframe: str, + dataframe: DataFrame, + candle_type: CandleType + ) -> None: """ Store cached Dataframe. Using private method as this should never be used by a user @@ -49,8 +55,10 @@ class DataProvider: :param pair: pair to get the data for :param timeframe: Timeframe to get data for :param dataframe: analyzed dataframe + :param candle_type: Any of the enum CandleType (must match trading mode!) """ - self.__cached_pairs[(pair, timeframe)] = (dataframe, datetime.now(timezone.utc)) + self.__cached_pairs[(pair, timeframe, candle_type)] = ( + dataframe, datetime.now(timezone.utc)) def add_pairlisthandler(self, pairlists) -> None: """ @@ -58,13 +66,21 @@ class DataProvider: """ self._pairlists = pairlists - def historic_ohlcv(self, pair: str, timeframe: str = None) -> DataFrame: + def historic_ohlcv( + self, + pair: str, + timeframe: str = None, + candle_type: str = '' + ) -> DataFrame: """ Get stored historical candle (OHLCV) data :param pair: pair to get the data for :param timeframe: timeframe to get data for + :param candle_type: '', mark, index, premiumIndex, or funding_rate """ - saved_pair = (pair, str(timeframe)) + _candle_type = CandleType.from_string( + candle_type) if candle_type != '' else self._config['candle_type_def'] + saved_pair = (pair, str(timeframe), _candle_type) if saved_pair not in self.__cached_pairs_backtesting: timerange = TimeRange.parse_timerange(None if self._config.get( 'timerange') is None else str(self._config.get('timerange'))) @@ -77,26 +93,36 @@ class DataProvider: timeframe=timeframe or self._config['timeframe'], datadir=self._config['datadir'], timerange=timerange, - data_format=self._config.get('dataformat_ohlcv', 'json') + data_format=self._config.get('dataformat_ohlcv', 'json'), + candle_type=_candle_type, + ) return self.__cached_pairs_backtesting[saved_pair].copy() - def get_pair_dataframe(self, pair: str, timeframe: str = None) -> DataFrame: + def get_pair_dataframe( + self, + pair: str, + timeframe: str = None, + candle_type: str = '' + ) -> DataFrame: """ Return pair candle (OHLCV) data, either live or cached historical -- depending on the runmode. + Only combinations in the pairlist or which have been specified as informative pairs + will be available. :param pair: pair to get the data for :param timeframe: timeframe to get data for :return: Dataframe for this pair + :param candle_type: '', mark, index, premiumIndex, or funding_rate """ if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE): # Get live OHLCV data. - data = self.ohlcv(pair=pair, timeframe=timeframe) + data = self.ohlcv(pair=pair, timeframe=timeframe, candle_type=candle_type) else: # Get historical OHLCV data (cached on disk). - data = self.historic_ohlcv(pair=pair, timeframe=timeframe) + data = self.historic_ohlcv(pair=pair, timeframe=timeframe, candle_type=candle_type) if len(data) == 0: - logger.warning(f"No data found for ({pair}, {timeframe}).") + logger.warning(f"No data found for ({pair}, {timeframe}, {candle_type}).") return data def get_analyzed_dataframe(self, pair: str, timeframe: str) -> Tuple[DataFrame, datetime]: @@ -109,7 +135,7 @@ class DataProvider: combination. Returns empty dataframe and Epoch 0 (1970-01-01) if no dataframe was cached. """ - pair_key = (pair, timeframe) + pair_key = (pair, timeframe, self._config.get('candle_type_def', CandleType.SPOT)) if pair_key in self.__cached_pairs: if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE): df, date = self.__cached_pairs[pair_key] @@ -177,20 +203,31 @@ class DataProvider: raise OperationalException(NO_EXCHANGE_EXCEPTION) return list(self._exchange._klines.keys()) - def ohlcv(self, pair: str, timeframe: str = None, copy: bool = True) -> DataFrame: + def ohlcv( + self, + pair: str, + timeframe: str = None, + copy: bool = True, + candle_type: str = '' + ) -> DataFrame: """ Get candle (OHLCV) data for the given pair as DataFrame Please use the `available_pairs` method to verify which pairs are currently cached. :param pair: pair to get the data for :param timeframe: Timeframe to get data for + :param candle_type: '', mark, index, premiumIndex, or funding_rate :param copy: copy dataframe before returning if True. Use False only for read-only operations (where the dataframe is not modified) """ if self._exchange is None: raise OperationalException(NO_EXCHANGE_EXCEPTION) if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE): - return self._exchange.klines((pair, timeframe or self._config['timeframe']), - copy=copy) + _candle_type = CandleType.from_string( + candle_type) if candle_type != '' else self._config['candle_type_def'] + return self._exchange.klines( + (pair, timeframe or self._config['timeframe'], _candle_type), + copy=copy + ) else: return DataFrame() diff --git a/freqtrade/data/history/hdf5datahandler.py b/freqtrade/data/history/hdf5datahandler.py index 49fac99ea..23120a4ba 100644 --- a/freqtrade/data/history/hdf5datahandler.py +++ b/freqtrade/data/history/hdf5datahandler.py @@ -9,6 +9,7 @@ import pandas as pd from freqtrade.configuration import TimeRange from freqtrade.constants import (DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, ListPairsWithTimeframes, TradeList) +from freqtrade.enums import CandleType, TradingMode from .idatahandler import IDataHandler @@ -21,44 +22,63 @@ class HDF5DataHandler(IDataHandler): _columns = DEFAULT_DATAFRAME_COLUMNS @classmethod - def ohlcv_get_available_data(cls, datadir: Path) -> ListPairsWithTimeframes: + def ohlcv_get_available_data( + cls, datadir: Path, trading_mode: TradingMode) -> ListPairsWithTimeframes: """ Returns a list of all pairs with ohlcv data available in this datadir :param datadir: Directory to search for ohlcv files + :param trading_mode: trading-mode to be used :return: List of Tuples of (pair, timeframe) """ - _tmp = [re.search(r'^([a-zA-Z_]+)\-(\d+\S+)(?=.h5)', p.name) - for p in datadir.glob("*.h5")] - return [(match[1].replace('_', '/'), match[2]) for match in _tmp - if match and len(match.groups()) > 1] + if trading_mode == TradingMode.FUTURES: + datadir = datadir.joinpath('futures') + _tmp = [ + re.search( + cls._OHLCV_REGEX, p.name + ) for p in datadir.glob("*.h5") + ] + return [ + ( + cls.rebuild_pair_from_filename(match[1]), + match[2], + CandleType.from_string(match[3]) + ) for match in _tmp if match and len(match.groups()) > 1] @classmethod - def ohlcv_get_pairs(cls, datadir: Path, timeframe: str) -> List[str]: + def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> List[str]: """ Returns a list of all pairs with ohlcv data available in this datadir for the specified timeframe :param datadir: Directory to search for ohlcv files :param timeframe: Timeframe to search pairs for + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: List of Pairs """ + candle = "" + if candle_type != CandleType.SPOT: + datadir = datadir.joinpath('futures') + candle = f"-{candle_type}" - _tmp = [re.search(r'^(\S+)(?=\-' + timeframe + '.h5)', p.name) - for p in datadir.glob(f"*{timeframe}.h5")] + _tmp = [re.search(r'^(\S+)(?=\-' + timeframe + candle + '.h5)', p.name) + for p in datadir.glob(f"*{timeframe}{candle}.h5")] # Check if regex found something and only return these results - return [match[0].replace('_', '/') for match in _tmp if match] + return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match] - def ohlcv_store(self, pair: str, timeframe: str, data: pd.DataFrame) -> None: + def ohlcv_store( + self, pair: str, timeframe: str, data: pd.DataFrame, candle_type: CandleType) -> None: """ Store data in hdf5 file. :param pair: Pair - used to generate filename :param timeframe: Timeframe - used to generate filename :param data: Dataframe containing OHLCV data + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: None """ key = self._pair_ohlcv_key(pair, timeframe) _data = data.copy() - filename = self._pair_data_filename(self._datadir, pair, timeframe) + filename = self._pair_data_filename(self._datadir, pair, timeframe, candle_type) + self.create_dir_if_needed(filename) _data.loc[:, self._columns].to_hdf( filename, key, mode='a', complevel=9, complib='blosc', @@ -66,7 +86,8 @@ class HDF5DataHandler(IDataHandler): ) def _ohlcv_load(self, pair: str, timeframe: str, - timerange: Optional[TimeRange] = None) -> pd.DataFrame: + timerange: Optional[TimeRange], candle_type: CandleType + ) -> pd.DataFrame: """ Internal method used to load data for one pair from disk. Implements the loading and conversion to a Pandas dataframe. @@ -76,10 +97,16 @@ class HDF5DataHandler(IDataHandler): :param timerange: Limit data to be loaded to this timerange. Optionally implemented by subclasses to avoid loading all data where possible. + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: DataFrame with ohlcv data, or empty DataFrame """ key = self._pair_ohlcv_key(pair, timeframe) - filename = self._pair_data_filename(self._datadir, pair, timeframe) + filename = self._pair_data_filename( + self._datadir, + pair, + timeframe, + candle_type=candle_type + ) if not filename.exists(): return pd.DataFrame(columns=self._columns) @@ -98,12 +125,19 @@ class HDF5DataHandler(IDataHandler): 'low': 'float', 'close': 'float', 'volume': 'float'}) return pairdata - def ohlcv_append(self, pair: str, timeframe: str, data: pd.DataFrame) -> None: + def ohlcv_append( + self, + pair: str, + timeframe: str, + data: pd.DataFrame, + candle_type: CandleType + ) -> None: """ Append data to existing data structures :param pair: Pair :param timeframe: Timeframe this ohlcv data is for :param data: Data to append. + :param candle_type: Any of the enum CandleType (must match trading mode!) """ raise NotImplementedError() @@ -117,7 +151,7 @@ class HDF5DataHandler(IDataHandler): _tmp = [re.search(r'^(\S+)(?=\-trades.h5)', p.name) for p in datadir.glob("*trades.h5")] # Check if regex found something and only return these results to avoid exceptions. - return [match[0].replace('_', '/') for match in _tmp if match] + return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match] def trades_store(self, pair: str, data: TradeList) -> None: """ @@ -172,7 +206,9 @@ class HDF5DataHandler(IDataHandler): @classmethod def _pair_ohlcv_key(cls, pair: str, timeframe: str) -> str: - return f"{pair}/ohlcv/tf_{timeframe}" + # Escape futures pairs to avoid warnings + pair_esc = pair.replace(':', '_') + return f"{pair_esc}/ohlcv/tf_{timeframe}" @classmethod def _pair_trades_key(cls, pair: str) -> str: diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index 7cdee8b71..515a345f1 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -12,6 +12,7 @@ from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS from freqtrade.data.converter import (clean_ohlcv_dataframe, ohlcv_to_dataframe, trades_remove_duplicates, trades_to_ohlcv) from freqtrade.data.history.idatahandler import IDataHandler, get_datahandler +from freqtrade.enums import CandleType from freqtrade.exceptions import OperationalException from freqtrade.exchange import Exchange from freqtrade.misc import format_ms_time @@ -29,6 +30,7 @@ def load_pair_history(pair: str, startup_candles: int = 0, data_format: str = None, data_handler: IDataHandler = None, + candle_type: CandleType = CandleType.SPOT ) -> DataFrame: """ Load cached ohlcv history for the given pair. @@ -43,6 +45,7 @@ def load_pair_history(pair: str, :param startup_candles: Additional candles to load at the start of the period :param data_handler: Initialized data-handler to use. Will be initialized from data_format if not set + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: DataFrame with ohlcv data, or empty DataFrame """ data_handler = get_datahandler(datadir, data_format, data_handler) @@ -53,6 +56,7 @@ def load_pair_history(pair: str, fill_missing=fill_up_missing, drop_incomplete=drop_incomplete, startup_candles=startup_candles, + candle_type=candle_type ) @@ -64,6 +68,7 @@ def load_data(datadir: Path, startup_candles: int = 0, fail_without_data: bool = False, data_format: str = 'json', + candle_type: CandleType = CandleType.SPOT ) -> Dict[str, DataFrame]: """ Load ohlcv history data for a list of pairs. @@ -76,6 +81,7 @@ def load_data(datadir: Path, :param startup_candles: Additional candles to load at the start of the period :param fail_without_data: Raise OperationalException if no data is found. :param data_format: Data format which should be used. Defaults to json + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: dict(:) """ result: Dict[str, DataFrame] = {} @@ -89,7 +95,8 @@ def load_data(datadir: Path, datadir=datadir, timerange=timerange, fill_up_missing=fill_up_missing, startup_candles=startup_candles, - data_handler=data_handler + data_handler=data_handler, + candle_type=candle_type ) if not hist.empty: result[pair] = hist @@ -99,12 +106,13 @@ def load_data(datadir: Path, return result -def refresh_data(datadir: Path, +def refresh_data(*, datadir: Path, timeframe: str, pairs: List[str], exchange: Exchange, data_format: str = None, timerange: Optional[TimeRange] = None, + candle_type: CandleType, ) -> None: """ Refresh ohlcv history data for a list of pairs. @@ -115,17 +123,24 @@ def refresh_data(datadir: Path, :param exchange: Exchange object :param data_format: dataformat to use :param timerange: Limit data to be loaded to this timerange + :param candle_type: Any of the enum CandleType (must match trading mode!) """ data_handler = get_datahandler(datadir, data_format) for idx, pair in enumerate(pairs): process = f'{idx}/{len(pairs)}' _download_pair_history(pair=pair, process=process, timeframe=timeframe, datadir=datadir, - timerange=timerange, exchange=exchange, data_handler=data_handler) + timerange=timerange, exchange=exchange, data_handler=data_handler, + candle_type=candle_type) -def _load_cached_data_for_updating(pair: str, timeframe: str, timerange: Optional[TimeRange], - data_handler: IDataHandler) -> Tuple[DataFrame, Optional[int]]: +def _load_cached_data_for_updating( + pair: str, + timeframe: str, + timerange: Optional[TimeRange], + data_handler: IDataHandler, + candle_type: CandleType +) -> Tuple[DataFrame, Optional[int]]: """ Load cached data to download more data. If timerange is passed in, checks whether data from an before the stored data will be @@ -142,7 +157,8 @@ def _load_cached_data_for_updating(pair: str, timeframe: str, timerange: Optiona # Intentionally don't pass timerange in - since we need to load the full dataset. data = data_handler.ohlcv_load(pair, timeframe=timeframe, timerange=None, fill_missing=False, - drop_incomplete=True, warn_no_data=False) + drop_incomplete=True, warn_no_data=False, + candle_type=candle_type) if not data.empty: if start and start < data.iloc[0]['date']: # Earlier data than existing data requested, redownload all @@ -161,7 +177,9 @@ def _download_pair_history(pair: str, *, process: str = '', new_pairs_days: int = 30, data_handler: IDataHandler = None, - timerange: Optional[TimeRange] = None) -> bool: + timerange: Optional[TimeRange] = None, + candle_type: CandleType, + ) -> bool: """ Download latest candles from the exchange for the pair and timeframe passed in parameters The data is downloaded starting from the last correct data that @@ -173,19 +191,20 @@ def _download_pair_history(pair: str, *, :param pair: pair to download :param timeframe: Timeframe (e.g "5m") :param timerange: range of time to download + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: bool with success state """ data_handler = get_datahandler(datadir, data_handler=data_handler) try: logger.info( - f'Download history data for pair: "{pair}" ({process}), timeframe: {timeframe} ' - f'and store in {datadir}.' + f'Download history data for pair: "{pair}" ({process}), timeframe: {timeframe}, ' + f'candle type: {candle_type} and store in {datadir}.' ) - # data, since_ms = _load_cached_data_for_updating_old(datadir, pair, timeframe, timerange) data, since_ms = _load_cached_data_for_updating(pair, timeframe, timerange, - data_handler=data_handler) + data_handler=data_handler, + candle_type=candle_type) logger.debug("Current Start: %s", f"{data.iloc[0]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None') @@ -198,7 +217,8 @@ def _download_pair_history(pair: str, *, since_ms=since_ms if since_ms else arrow.utcnow().shift( days=-new_pairs_days).int_timestamp * 1000, - is_new_pair=data.empty + is_new_pair=data.empty, + candle_type=candle_type, ) # TODO: Maybe move parsing to exchange class (?) new_dataframe = ohlcv_to_dataframe(new_data, timeframe, pair, @@ -216,7 +236,7 @@ def _download_pair_history(pair: str, *, logger.debug("New End: %s", f"{data.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None') - data_handler.ohlcv_store(pair, timeframe, data=data) + data_handler.ohlcv_store(pair, timeframe, data=data, candle_type=candle_type) return True except Exception: @@ -227,9 +247,11 @@ def _download_pair_history(pair: str, *, def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes: List[str], - datadir: Path, timerange: Optional[TimeRange] = None, + datadir: Path, trading_mode: str, + timerange: Optional[TimeRange] = None, new_pairs_days: int = 30, erase: bool = False, - data_format: str = None) -> List[str]: + data_format: str = None, + ) -> List[str]: """ Refresh stored ohlcv data for backtesting and hyperopt operations. Used by freqtrade download-data subcommand. @@ -237,6 +259,7 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes """ pairs_not_available = [] data_handler = get_datahandler(datadir, data_format) + candle_type = CandleType.get_default(trading_mode) for idx, pair in enumerate(pairs, start=1): if pair not in exchange.markets: pairs_not_available.append(pair) @@ -245,16 +268,35 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes for timeframe in timeframes: if erase: - if data_handler.ohlcv_purge(pair, timeframe): - logger.info( - f'Deleting existing data for pair {pair}, interval {timeframe}.') + if data_handler.ohlcv_purge(pair, timeframe, candle_type=candle_type): + logger.info(f'Deleting existing data for pair {pair}, interval {timeframe}.') logger.info(f'Downloading pair {pair}, interval {timeframe}.') process = f'{idx}/{len(pairs)}' _download_pair_history(pair=pair, process=process, datadir=datadir, exchange=exchange, timerange=timerange, data_handler=data_handler, - timeframe=str(timeframe), new_pairs_days=new_pairs_days) + timeframe=str(timeframe), new_pairs_days=new_pairs_days, + candle_type=candle_type) + if trading_mode == 'futures': + # Predefined candletype (and timeframe) depending on exchange + # Downloads what is necessary to backtest based on futures data. + timeframe = exchange._ft_has['mark_ohlcv_timeframe'] + fr_candle_type = CandleType.from_string(exchange._ft_has['mark_ohlcv_price']) + # All exchanges need FundingRate for futures trading. + # The timeframe is aligned to the mark-price timeframe. + for funding_candle_type in (CandleType.FUNDING_RATE, fr_candle_type): + # TODO: this could be in most parts to the above. + if erase: + if data_handler.ohlcv_purge(pair, timeframe, candle_type=funding_candle_type): + logger.info( + f'Deleting existing data for pair {pair}, interval {timeframe}.') + _download_pair_history(pair=pair, process=process, + datadir=datadir, exchange=exchange, + timerange=timerange, data_handler=data_handler, + timeframe=str(timeframe), new_pairs_days=new_pairs_days, + candle_type=funding_candle_type) + return pairs_not_available @@ -353,10 +395,16 @@ def refresh_backtest_trades_data(exchange: Exchange, pairs: List[str], datadir: return pairs_not_available -def convert_trades_to_ohlcv(pairs: List[str], timeframes: List[str], - datadir: Path, timerange: TimeRange, erase: bool = False, - data_format_ohlcv: str = 'json', - data_format_trades: str = 'jsongz') -> None: +def convert_trades_to_ohlcv( + pairs: List[str], + timeframes: List[str], + datadir: Path, + timerange: TimeRange, + erase: bool = False, + data_format_ohlcv: str = 'json', + data_format_trades: str = 'jsongz', + candle_type: CandleType = CandleType.SPOT +) -> None: """ Convert stored trades data to ohlcv data """ @@ -367,12 +415,12 @@ def convert_trades_to_ohlcv(pairs: List[str], timeframes: List[str], trades = data_handler_trades.trades_load(pair) for timeframe in timeframes: if erase: - if data_handler_ohlcv.ohlcv_purge(pair, timeframe): + if data_handler_ohlcv.ohlcv_purge(pair, timeframe, candle_type=candle_type): logger.info(f'Deleting existing data for pair {pair}, interval {timeframe}.') try: ohlcv = trades_to_ohlcv(trades, timeframe) # Store ohlcv - data_handler_ohlcv.ohlcv_store(pair, timeframe, data=ohlcv) + data_handler_ohlcv.ohlcv_store(pair, timeframe, data=ohlcv, candle_type=candle_type) except ValueError: logger.exception(f'Could not convert {pair} to OHLCV.') diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py index cb02f98e3..4a5eb6bc2 100644 --- a/freqtrade/data/history/idatahandler.py +++ b/freqtrade/data/history/idatahandler.py @@ -4,6 +4,7 @@ It's subclasses handle and storing data from disk. """ import logging +import re from abc import ABC, abstractclassmethod, abstractmethod from copy import deepcopy from datetime import datetime, timezone @@ -16,6 +17,7 @@ from freqtrade import misc from freqtrade.configuration import TimeRange from freqtrade.constants import ListPairsWithTimeframes, TradeList from freqtrade.data.converter import clean_ohlcv_dataframe, trades_remove_duplicates, trim_dataframe +from freqtrade.enums import CandleType, TradingMode from freqtrade.exchange import timeframe_to_seconds @@ -24,6 +26,8 @@ logger = logging.getLogger(__name__) class IDataHandler(ABC): + _OHLCV_REGEX = r'^([a-zA-Z_-]+)\-(\d+\S)\-?([a-zA-Z_]*)?(?=\.)' + def __init__(self, datadir: Path) -> None: self._datadir = datadir @@ -35,36 +39,41 @@ class IDataHandler(ABC): raise NotImplementedError() @abstractclassmethod - def ohlcv_get_available_data(cls, datadir: Path) -> ListPairsWithTimeframes: + def ohlcv_get_available_data( + cls, datadir: Path, trading_mode: TradingMode) -> ListPairsWithTimeframes: """ Returns a list of all pairs with ohlcv data available in this datadir :param datadir: Directory to search for ohlcv files + :param trading_mode: trading-mode to be used :return: List of Tuples of (pair, timeframe) """ @abstractclassmethod - def ohlcv_get_pairs(cls, datadir: Path, timeframe: str) -> List[str]: + def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> List[str]: """ Returns a list of all pairs with ohlcv data available in this datadir for the specified timeframe :param datadir: Directory to search for ohlcv files :param timeframe: Timeframe to search pairs for + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: List of Pairs """ @abstractmethod - def ohlcv_store(self, pair: str, timeframe: str, data: DataFrame) -> None: + def ohlcv_store( + self, pair: str, timeframe: str, data: DataFrame, candle_type: CandleType) -> None: """ Store ohlcv data. :param pair: Pair - used to generate filename :param timeframe: Timeframe - used to generate filename :param data: Dataframe containing OHLCV data + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: None """ @abstractmethod - def _ohlcv_load(self, pair: str, timeframe: str, - timerange: Optional[TimeRange] = None, + def _ohlcv_load(self, pair: str, timeframe: str, timerange: Optional[TimeRange], + candle_type: CandleType ) -> DataFrame: """ Internal method used to load data for one pair from disk. @@ -75,29 +84,38 @@ class IDataHandler(ABC): :param timerange: Limit data to be loaded to this timerange. Optionally implemented by subclasses to avoid loading all data where possible. + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: DataFrame with ohlcv data, or empty DataFrame """ - def ohlcv_purge(self, pair: str, timeframe: str) -> bool: + def ohlcv_purge(self, pair: str, timeframe: str, candle_type: CandleType) -> bool: """ Remove data for this pair :param pair: Delete data for this pair. :param timeframe: Timeframe (e.g. "5m") + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: True when deleted, false if file did not exist. """ - filename = self._pair_data_filename(self._datadir, pair, timeframe) + filename = self._pair_data_filename(self._datadir, pair, timeframe, candle_type) if filename.exists(): filename.unlink() return True return False @abstractmethod - def ohlcv_append(self, pair: str, timeframe: str, data: DataFrame) -> None: + def ohlcv_append( + self, + pair: str, + timeframe: str, + data: DataFrame, + candle_type: CandleType + ) -> None: """ Append data to existing data structures :param pair: Pair :param timeframe: Timeframe this ohlcv data is for :param data: Data to append. + :param candle_type: Any of the enum CandleType (must match trading mode!) """ @abstractclassmethod @@ -158,9 +176,29 @@ class IDataHandler(ABC): return trades_remove_duplicates(self._trades_load(pair, timerange=timerange)) @classmethod - def _pair_data_filename(cls, datadir: Path, pair: str, timeframe: str) -> Path: + def create_dir_if_needed(cls, datadir: Path): + """ + Creates datadir if necessary + should only create directories for "futures" mode at the moment. + """ + if not datadir.parent.is_dir(): + datadir.parent.mkdir() + + @classmethod + def _pair_data_filename( + cls, + datadir: Path, + pair: str, + timeframe: str, + candle_type: CandleType + ) -> Path: pair_s = misc.pair_to_filename(pair) - filename = datadir.joinpath(f'{pair_s}-{timeframe}.{cls._get_file_extension()}') + candle = "" + if candle_type != CandleType.SPOT: + datadir = datadir.joinpath('futures') + candle = f"-{candle_type}" + filename = datadir.joinpath( + f'{pair_s}-{timeframe}{candle}.{cls._get_file_extension()}') return filename @classmethod @@ -169,12 +207,23 @@ class IDataHandler(ABC): filename = datadir.joinpath(f'{pair_s}-trades.{cls._get_file_extension()}') return filename + @staticmethod + def rebuild_pair_from_filename(pair: str) -> str: + """ + Rebuild pair name from filename + Assumes a asset name of max. 7 length to also support BTC-PERP and BTC-PERP:USD names. + """ + res = re.sub(r'^(([A-Za-z]{1,10})|^([A-Za-z\-]{1,6}))(_)', r'\g<1>/', pair, 1) + res = re.sub('_', ':', res, 1) + return res + def ohlcv_load(self, pair, timeframe: str, + candle_type: CandleType, timerange: Optional[TimeRange] = None, fill_missing: bool = True, drop_incomplete: bool = True, startup_candles: int = 0, - warn_no_data: bool = True + warn_no_data: bool = True, ) -> DataFrame: """ Load cached candle (OHLCV) data for the given pair. @@ -186,6 +235,7 @@ class IDataHandler(ABC): :param drop_incomplete: Drop last candle assuming it may be incomplete. :param startup_candles: Additional candles to load at the start of the period :param warn_no_data: Log a warning message when no data is found + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: DataFrame with ohlcv data, or empty DataFrame """ # Fix startup period @@ -193,17 +243,21 @@ class IDataHandler(ABC): if startup_candles > 0 and timerange_startup: timerange_startup.subtract_start(timeframe_to_seconds(timeframe) * startup_candles) - pairdf = self._ohlcv_load(pair, timeframe, - timerange=timerange_startup) - if self._check_empty_df(pairdf, pair, timeframe, warn_no_data): + pairdf = self._ohlcv_load( + pair, + timeframe, + timerange=timerange_startup, + candle_type=candle_type + ) + if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data): return pairdf else: enddate = pairdf.iloc[-1]['date'] if timerange_startup: - self._validate_pairdata(pair, pairdf, timeframe, timerange_startup) + self._validate_pairdata(pair, pairdf, timeframe, candle_type, timerange_startup) pairdf = trim_dataframe(pairdf, timerange_startup) - if self._check_empty_df(pairdf, pair, timeframe, warn_no_data): + if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data): return pairdf # incomplete candles should only be dropped if we didn't trim the end beforehand. @@ -212,23 +266,25 @@ class IDataHandler(ABC): fill_missing=fill_missing, drop_incomplete=(drop_incomplete and enddate == pairdf.iloc[-1]['date'])) - self._check_empty_df(pairdf, pair, timeframe, warn_no_data) + self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data) return pairdf - def _check_empty_df(self, pairdf: DataFrame, pair: str, timeframe: str, warn_no_data: bool): + def _check_empty_df(self, pairdf: DataFrame, pair: str, timeframe: str, + candle_type: CandleType, warn_no_data: bool): """ Warn on empty dataframe """ if pairdf.empty: if warn_no_data: logger.warning( - f'No history data for pair: "{pair}", timeframe: {timeframe}. ' - 'Use `freqtrade download-data` to download the data' + f"No history for {pair}, {candle_type}, {timeframe} found. " + "Use `freqtrade download-data` to download the data" ) return True return False - def _validate_pairdata(self, pair, pairdata: DataFrame, timeframe: str, timerange: TimeRange): + def _validate_pairdata(self, pair, pairdata: DataFrame, timeframe: str, + candle_type: CandleType, timerange: TimeRange): """ Validates pairdata for missing data at start end end and logs warnings. :param pairdata: Dataframe to validate @@ -238,12 +294,12 @@ class IDataHandler(ABC): if timerange.starttype == 'date': start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc) if pairdata.iloc[0]['date'] > start: - logger.warning(f"Missing data at start for pair {pair} at {timeframe}, " + logger.warning(f"{pair}, {candle_type}, {timeframe}, " f"data starts at {pairdata.iloc[0]['date']:%Y-%m-%d %H:%M:%S}") if timerange.stoptype == 'date': stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc) if pairdata.iloc[-1]['date'] < stop: - logger.warning(f"Missing data at end for pair {pair} at {timeframe}, " + logger.warning(f"{pair}, {candle_type}, {timeframe}, " f"data ends at {pairdata.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}") diff --git a/freqtrade/data/history/jsondatahandler.py b/freqtrade/data/history/jsondatahandler.py index ccefc8356..23054ac51 100644 --- a/freqtrade/data/history/jsondatahandler.py +++ b/freqtrade/data/history/jsondatahandler.py @@ -10,6 +10,7 @@ from freqtrade import misc from freqtrade.configuration import TimeRange from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, ListPairsWithTimeframes, TradeList from freqtrade.data.converter import trades_dict_to_list +from freqtrade.enums import CandleType, TradingMode from .idatahandler import IDataHandler @@ -23,33 +24,49 @@ class JsonDataHandler(IDataHandler): _columns = DEFAULT_DATAFRAME_COLUMNS @classmethod - def ohlcv_get_available_data(cls, datadir: Path) -> ListPairsWithTimeframes: + def ohlcv_get_available_data( + cls, datadir: Path, trading_mode: TradingMode) -> ListPairsWithTimeframes: """ Returns a list of all pairs with ohlcv data available in this datadir :param datadir: Directory to search for ohlcv files + :param trading_mode: trading-mode to be used :return: List of Tuples of (pair, timeframe) """ - _tmp = [re.search(r'^([a-zA-Z_]+)\-(\d+\S+)(?=.json)', p.name) - for p in datadir.glob(f"*.{cls._get_file_extension()}")] - return [(match[1].replace('_', '/'), match[2]) for match in _tmp - if match and len(match.groups()) > 1] + if trading_mode == 'futures': + datadir = datadir.joinpath('futures') + _tmp = [ + re.search( + cls._OHLCV_REGEX, p.name + ) for p in datadir.glob(f"*.{cls._get_file_extension()}")] + return [ + ( + cls.rebuild_pair_from_filename(match[1]), + match[2], + CandleType.from_string(match[3]) + ) for match in _tmp if match and len(match.groups()) > 1] @classmethod - def ohlcv_get_pairs(cls, datadir: Path, timeframe: str) -> List[str]: + def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> List[str]: """ Returns a list of all pairs with ohlcv data available in this datadir for the specified timeframe :param datadir: Directory to search for ohlcv files :param timeframe: Timeframe to search pairs for + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: List of Pairs """ + candle = "" + if candle_type != CandleType.SPOT: + datadir = datadir.joinpath('futures') + candle = f"-{candle_type}" - _tmp = [re.search(r'^(\S+)(?=\-' + timeframe + '.json)', p.name) - for p in datadir.glob(f"*{timeframe}.{cls._get_file_extension()}")] + _tmp = [re.search(r'^(\S+)(?=\-' + timeframe + candle + '.json)', p.name) + for p in datadir.glob(f"*{timeframe}{candle}.{cls._get_file_extension()}")] # Check if regex found something and only return these results - return [match[0].replace('_', '/') for match in _tmp if match] + return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match] - def ohlcv_store(self, pair: str, timeframe: str, data: DataFrame) -> None: + def ohlcv_store( + self, pair: str, timeframe: str, data: DataFrame, candle_type: CandleType) -> None: """ Store data in json format "values". format looks as follows: @@ -57,9 +74,11 @@ class JsonDataHandler(IDataHandler): :param pair: Pair - used to generate filename :param timeframe: Timeframe - used to generate filename :param data: Dataframe containing OHLCV data + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: None """ - filename = self._pair_data_filename(self._datadir, pair, timeframe) + filename = self._pair_data_filename(self._datadir, pair, timeframe, candle_type) + self.create_dir_if_needed(filename) _data = data.copy() # Convert date to int _data['date'] = _data['date'].view(np.int64) // 1000 // 1000 @@ -70,7 +89,7 @@ class JsonDataHandler(IDataHandler): compression='gzip' if self._use_zip else None) def _ohlcv_load(self, pair: str, timeframe: str, - timerange: Optional[TimeRange] = None, + timerange: Optional[TimeRange], candle_type: CandleType ) -> DataFrame: """ Internal method used to load data for one pair from disk. @@ -81,9 +100,10 @@ class JsonDataHandler(IDataHandler): :param timerange: Limit data to be loaded to this timerange. Optionally implemented by subclasses to avoid loading all data where possible. + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: DataFrame with ohlcv data, or empty DataFrame """ - filename = self._pair_data_filename(self._datadir, pair, timeframe) + filename = self._pair_data_filename(self._datadir, pair, timeframe, candle_type=candle_type) if not filename.exists(): return DataFrame(columns=self._columns) try: @@ -100,25 +120,19 @@ class JsonDataHandler(IDataHandler): infer_datetime_format=True) return pairdata - def ohlcv_purge(self, pair: str, timeframe: str) -> bool: - """ - Remove data for this pair - :param pair: Delete data for this pair. - :param timeframe: Timeframe (e.g. "5m") - :return: True when deleted, false if file did not exist. - """ - filename = self._pair_data_filename(self._datadir, pair, timeframe) - if filename.exists(): - filename.unlink() - return True - return False - - def ohlcv_append(self, pair: str, timeframe: str, data: DataFrame) -> None: + def ohlcv_append( + self, + pair: str, + timeframe: str, + data: DataFrame, + candle_type: CandleType + ) -> None: """ Append data to existing data structures :param pair: Pair :param timeframe: Timeframe this ohlcv data is for :param data: Data to append. + :param candle_type: Any of the enum CandleType (must match trading mode!) """ raise NotImplementedError() @@ -132,7 +146,7 @@ class JsonDataHandler(IDataHandler): _tmp = [re.search(r'^(\S+)(?=\-trades.json)', p.name) for p in datadir.glob(f"*trades.{cls._get_file_extension()}")] # Check if regex found something and only return these results to avoid exceptions. - return [match[0].replace('_', '/') for match in _tmp if match] + return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match] def trades_store(self, pair: str, data: TradeList) -> None: """ diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 08f43598d..8116949cf 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -13,7 +13,7 @@ from pandas import DataFrame from freqtrade.configuration import TimeRange from freqtrade.constants import DATETIME_PRINT_FORMAT, UNLIMITED_STAKE_AMOUNT from freqtrade.data.history import get_timerange, load_data, refresh_data -from freqtrade.enums import RunMode, SellType +from freqtrade.enums import CandleType, ExitType, RunMode from freqtrade.exceptions import OperationalException from freqtrade.exchange.exchange import timeframe_to_seconds from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist @@ -116,11 +116,12 @@ class Edge: timeframe=self.strategy.timeframe, timerange=timerange_startup, data_format=self.config.get('dataformat_ohlcv', 'json'), + candle_type=self.config.get('candle_type_def', CandleType.SPOT), ) # Download informative pairs too res = defaultdict(list) - for p, t in self.strategy.gather_informative_pairs(): - res[t].append(p) + for pair, timeframe, _ in self.strategy.gather_informative_pairs(): + res[timeframe].append(pair) for timeframe, inf_pairs in res.items(): timerange_startup = deepcopy(self._timerange) timerange_startup.subtract_start(timeframe_to_seconds( @@ -132,6 +133,7 @@ class Edge: timeframe=timeframe, timerange=timerange_startup, data_format=self.config.get('dataformat_ohlcv', 'json'), + candle_type=self.config.get('candle_type_def', CandleType.SPOT), ) data = load_data( @@ -141,6 +143,7 @@ class Edge: timerange=self._timerange, startup_candles=self.strategy.startup_candle_count, data_format=self.config.get('dataformat_ohlcv', 'json'), + candle_type=self.config.get('candle_type_def', CandleType.SPOT), ) if not data: @@ -159,7 +162,9 @@ class Edge: logger.info(f'Measuring data from {min_date.strftime(DATETIME_PRINT_FORMAT)} ' f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} ' f'({(max_date - min_date).days} days)..') - headers = ['date', 'buy', 'open', 'close', 'sell', 'high', 'low'] + # TODO: Should edge support shorts? needs to be investigated further + # * (add enter_short exit_short) + headers = ['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long'] trades: list = [] for pair, pair_data in preprocessed.items(): @@ -167,8 +172,13 @@ class Edge: pair_data = pair_data.sort_values(by=['date']) pair_data = pair_data.reset_index(drop=True) - df_analyzed = self.strategy.advise_sell( - self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() + df_analyzed = self.strategy.advise_exit( + dataframe=self.strategy.advise_entry( + dataframe=pair_data, + metadata={'pair': pair} + ), + metadata={'pair': pair} + )[headers].copy() trades += self._find_trades_for_stoploss_range(df_analyzed, pair, self._stoploss_range) @@ -384,8 +394,8 @@ class Edge: return final def _find_trades_for_stoploss_range(self, df, pair, stoploss_range): - buy_column = df['buy'].values - sell_column = df['sell'].values + buy_column = df['enter_long'].values + sell_column = df['exit_long'].values date_column = df['date'].values ohlc_columns = df[['open', 'high', 'low', 'close']].values @@ -450,7 +460,7 @@ class Edge: if stop_index <= sell_index: exit_index = open_trade_index + stop_index - exit_type = SellType.STOP_LOSS + exit_type = ExitType.STOP_LOSS exit_price = stop_price elif stop_index > sell_index: # If exit is SELL then we exit at the next candle @@ -460,7 +470,7 @@ class Edge: if len(ohlc_columns) - 1 < exit_index: break - exit_type = SellType.SELL_SIGNAL + exit_type = ExitType.SELL_SIGNAL exit_price = ohlc_columns[exit_index, 0] trade = {'pair': pair, diff --git a/freqtrade/enums/__init__.py b/freqtrade/enums/__init__.py index eab483db3..e50ebc4a4 100644 --- a/freqtrade/enums/__init__.py +++ b/freqtrade/enums/__init__.py @@ -1,8 +1,12 @@ # flake8: noqa: F401 from freqtrade.enums.backteststate import BacktestState +from freqtrade.enums.candletype import CandleType +from freqtrade.enums.exitchecktuple import ExitCheckTuple +from freqtrade.enums.exittype import ExitType +from freqtrade.enums.marginmode import MarginMode from freqtrade.enums.ordertypevalue import OrderTypeValues from freqtrade.enums.rpcmessagetype import RPCMessageType from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode -from freqtrade.enums.selltype import SellType -from freqtrade.enums.signaltype import SignalTagType, SignalType +from freqtrade.enums.signaltype import SignalDirection, SignalTagType, SignalType from freqtrade.enums.state import State +from freqtrade.enums.tradingmode import TradingMode diff --git a/freqtrade/enums/candletype.py b/freqtrade/enums/candletype.py new file mode 100644 index 000000000..9d05ff6d7 --- /dev/null +++ b/freqtrade/enums/candletype.py @@ -0,0 +1,27 @@ +from enum import Enum + + +class CandleType(str, Enum): + """Enum to distinguish candle types""" + SPOT = "spot" + FUTURES = "futures" + MARK = "mark" + INDEX = "index" + PREMIUMINDEX = "premiumIndex" + + # TODO: Could take up less memory if these weren't a CandleType + FUNDING_RATE = "funding_rate" + # BORROW_RATE = "borrow_rate" # * unimplemented + + @staticmethod + def from_string(value: str) -> 'CandleType': + if not value: + # Default to spot + return CandleType.SPOT + return CandleType(value) + + @staticmethod + def get_default(trading_mode: str) -> 'CandleType': + if trading_mode == 'futures': + return CandleType.FUTURES + return CandleType.SPOT diff --git a/freqtrade/enums/exitchecktuple.py b/freqtrade/enums/exitchecktuple.py new file mode 100644 index 000000000..c245a05da --- /dev/null +++ b/freqtrade/enums/exitchecktuple.py @@ -0,0 +1,17 @@ +from freqtrade.enums.exittype import ExitType + + +class ExitCheckTuple: + """ + NamedTuple for Exit type + reason + """ + exit_type: ExitType + exit_reason: str = '' + + def __init__(self, exit_type: ExitType, exit_reason: str = ''): + self.exit_type = exit_type + self.exit_reason = exit_reason or exit_type.value + + @property + def exit_flag(self): + return self.exit_type != ExitType.NONE diff --git a/freqtrade/enums/selltype.py b/freqtrade/enums/exittype.py similarity index 95% rename from freqtrade/enums/selltype.py rename to freqtrade/enums/exittype.py index 015c30186..36d2a4f9e 100644 --- a/freqtrade/enums/selltype.py +++ b/freqtrade/enums/exittype.py @@ -1,7 +1,7 @@ from enum import Enum -class SellType(Enum): +class ExitType(Enum): """ Enum to distinguish between sell reasons """ diff --git a/freqtrade/enums/marginmode.py b/freqtrade/enums/marginmode.py new file mode 100644 index 000000000..1e42809ea --- /dev/null +++ b/freqtrade/enums/marginmode.py @@ -0,0 +1,12 @@ +from enum import Enum + + +class MarginMode(Enum): + """ + Enum to distinguish between + cross margin/futures margin_mode and + isolated margin/futures margin_mode + """ + CROSS = "cross" + ISOLATED = "isolated" + NONE = '' diff --git a/freqtrade/enums/rpcmessagetype.py b/freqtrade/enums/rpcmessagetype.py index 4e3f693e5..661f9ce5c 100644 --- a/freqtrade/enums/rpcmessagetype.py +++ b/freqtrade/enums/rpcmessagetype.py @@ -5,12 +5,21 @@ class RPCMessageType(Enum): STATUS = 'status' WARNING = 'warning' STARTUP = 'startup' + BUY = 'buy' BUY_FILL = 'buy_fill' BUY_CANCEL = 'buy_cancel' + + SHORT = 'short' + SHORT_FILL = 'short_fill' + SHORT_CANCEL = 'short_cancel' + + # TODO: The below messagetypes should be renamed to "exit"! + # Careful - has an impact on webhooks, therefore needs proper communication SELL = 'sell' SELL_FILL = 'sell_fill' SELL_CANCEL = 'sell_cancel' + PROTECTION_TRIGGER = 'protection_trigger' PROTECTION_TRIGGER_GLOBAL = 'protection_trigger_global' diff --git a/freqtrade/enums/signaltype.py b/freqtrade/enums/signaltype.py index 4437f49e3..f706fd4dc 100644 --- a/freqtrade/enums/signaltype.py +++ b/freqtrade/enums/signaltype.py @@ -3,15 +3,22 @@ from enum import Enum class SignalType(Enum): """ - Enum to distinguish between buy and sell signals + Enum to distinguish between enter and exit signals """ - BUY = "buy" - SELL = "sell" + ENTER_LONG = "enter_long" + EXIT_LONG = "exit_long" + ENTER_SHORT = "enter_short" + EXIT_SHORT = "exit_short" class SignalTagType(Enum): """ Enum for signal columns """ - BUY_TAG = "buy_tag" + ENTER_TAG = "enter_tag" EXIT_TAG = "exit_tag" + + +class SignalDirection(str, Enum): + LONG = 'long' + SHORT = 'short' diff --git a/freqtrade/enums/tradingmode.py b/freqtrade/enums/tradingmode.py new file mode 100644 index 000000000..2f838b7c6 --- /dev/null +++ b/freqtrade/enums/tradingmode.py @@ -0,0 +1,11 @@ +from enum import Enum + + +class TradingMode(str, Enum): + """ + Enum to distinguish between + spot, margin, futures or any other trading method + """ + SPOT = "spot" + MARGIN = "margin" + FUTURES = "futures" diff --git a/freqtrade/exchange/bibox.py b/freqtrade/exchange/bibox.py index f0c2dd00b..da1effbfe 100644 --- a/freqtrade/exchange/bibox.py +++ b/freqtrade/exchange/bibox.py @@ -20,4 +20,9 @@ class Bibox(Exchange): # fetchCurrencies API point requires authentication for Bibox, # so switch it off for Freqtrade load_markets() - _ccxt_config: Dict = {"has": {"fetchCurrencies": False}} + @property + def _ccxt_config(self) -> Dict: + # Parameters to add directly to ccxt sync/async initialization. + config = {"has": {"fetchCurrencies": False}} + config.update(super()._ccxt_config) + return config diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 37ead6dd8..8c442cd26 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -1,10 +1,18 @@ """ Binance exchange subclass """ +import json import logging -from typing import Dict, List, Tuple +from datetime import datetime +from pathlib import Path +from typing import Dict, List, Optional, Tuple import arrow +import ccxt +from freqtrade.enums import CandleType, MarginMode, TradingMode +from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exchange import Exchange +from freqtrade.exchange.common import retrier +from freqtrade.misc import deep_merge_dicts logger = logging.getLogger(__name__) @@ -21,30 +29,179 @@ class Binance(Exchange): "trades_pagination": "id", "trades_pagination_arg": "fromId", "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], + "ccxt_futures_name": "future" + } + _ft_has_futures: Dict = { + "stoploss_order_types": {"limit": "stop"}, + "tickers_have_price": False, } - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + # TradingMode.SPOT always supported and not required in this list + # (TradingMode.MARGIN, MarginMode.CROSS), + # (TradingMode.FUTURES, MarginMode.CROSS), + (TradingMode.FUTURES, MarginMode.ISOLATED) + ] + + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. + :param side: "buy" or "sell" """ - return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice']) + + ordertype = 'stop' if self.trading_mode == TradingMode.FUTURES else 'stop_loss_limit' + + return order['type'] == ordertype and ( + (side == "sell" and stop_loss > float(order['info']['stopPrice'])) or + (side == "buy" and stop_loss < float(order['info']['stopPrice'])) + ) + + def get_tickers(self, symbols: List[str] = None, cached: bool = False) -> Dict: + tickers = super().get_tickers(symbols=symbols, cached=cached) + if self.trading_mode == TradingMode.FUTURES: + # Binance's future result has no bid/ask values. + # Therefore we must fetch that from fetch_bids_asks and combine the two results. + bidsasks = self.fetch_bids_asks(symbols, cached) + tickers = deep_merge_dicts(bidsasks, tickers, allow_null_overrides=False) + return tickers + + @retrier + def _set_leverage( + self, + leverage: float, + pair: Optional[str] = None, + trading_mode: Optional[TradingMode] = None + ): + """ + Set's the leverage before making a trade, in order to not + have the same leverage on every trade + """ + trading_mode = trading_mode or self.trading_mode + + if self._config['dry_run'] or trading_mode != TradingMode.FUTURES: + return + + try: + self._api.set_leverage(symbol=pair, leverage=round(leverage)) + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, - since_ms: int, is_new_pair: bool = False, - raise_: bool = False - ) -> Tuple[str, str, List]: + since_ms: int, candle_type: CandleType, + is_new_pair: bool = False, raise_: bool = False, + ) -> Tuple[str, str, str, List]: """ Overwrite to introduce "fast new pair" functionality by detecting the pair's listing date Does not work for other exchanges, which don't return the earliest data when called with "0" + :param candle_type: Any of the enum CandleType (must match trading mode!) """ if is_new_pair: - x = await self._async_get_candle_history(pair, timeframe, 0) - if x and x[2] and x[2][0] and x[2][0][0] > since_ms: + x = await self._async_get_candle_history(pair, timeframe, candle_type, 0) + if x and x[3] and x[3][0] and x[3][0][0] > since_ms: # Set starting date to first available candle. - since_ms = x[2][0][0] + since_ms = x[3][0][0] logger.info(f"Candle-data for {pair} available starting with " f"{arrow.get(since_ms // 1000).isoformat()}.") + return await super()._async_get_historic_ohlcv( - pair=pair, timeframe=timeframe, since_ms=since_ms, is_new_pair=is_new_pair, - raise_=raise_) + pair=pair, + timeframe=timeframe, + since_ms=since_ms, + is_new_pair=is_new_pair, + raise_=raise_, + candle_type=candle_type + ) + + def funding_fee_cutoff(self, open_date: datetime): + """ + :param open_date: The open date for a trade + :return: The cutoff open time for when a funding fee is charged + """ + return open_date.minute > 0 or (open_date.minute == 0 and open_date.second > 15) + + def dry_run_liquidation_price( + self, + pair: str, + open_rate: float, # Entry price of position + is_short: bool, + position: float, # Absolute value of position size + wallet_balance: float, # Or margin balance + mm_ex_1: float = 0.0, # (Binance) Cross only + upnl_ex_1: float = 0.0, # (Binance) Cross only + ) -> Optional[float]: + """ + MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed + PERPETUAL: https://www.binance.com/en/support/faq/b3c689c1f50a44cabb3a84e663b81d93 + + :param exchange_name: + :param open_rate: (EP1) Entry price of position + :param is_short: True if the trade is a short, false otherwise + :param position: Absolute value of position size (in base currency) + :param wallet_balance: (WB) + Cross-Margin Mode: crossWalletBalance + Isolated-Margin Mode: isolatedWalletBalance + :param maintenance_amt: + + # * Only required for Cross + :param mm_ex_1: (TMM) + Cross-Margin Mode: Maintenance Margin of all other contracts, excluding Contract 1 + Isolated-Margin Mode: 0 + :param upnl_ex_1: (UPNL) + Cross-Margin Mode: Unrealized PNL of all other contracts, excluding Contract 1. + Isolated-Margin Mode: 0 + """ + + side_1 = -1 if is_short else 1 + position = abs(position) + cross_vars = upnl_ex_1 - mm_ex_1 if self.margin_mode == MarginMode.CROSS else 0.0 + + # mm_ratio: Binance's formula specifies maintenance margin rate which is mm_ratio * 100% + # maintenance_amt: (CUM) Maintenance Amount of position + mm_ratio, maintenance_amt = self.get_maintenance_ratio_and_amt(pair, position) + + if (maintenance_amt is None): + raise OperationalException( + "Parameter maintenance_amt is required by Binance.liquidation_price" + f"for {self.trading_mode.value}" + ) + + if self.trading_mode == TradingMode.FUTURES: + return ( + ( + (wallet_balance + cross_vars + maintenance_amt) - + (side_1 * position * open_rate) + ) / ( + (position * mm_ratio) - (side_1 * position) + ) + ) + else: + raise OperationalException( + "Freqtrade only supports isolated futures for leverage trading") + + @retrier + def load_leverage_tiers(self) -> Dict[str, List[Dict]]: + if self.trading_mode == TradingMode.FUTURES: + if self._config['dry_run']: + leverage_tiers_path = ( + Path(__file__).parent / 'binance_leverage_tiers.json' + ) + with open(leverage_tiers_path) as json_file: + return json.load(json_file) + else: + try: + return self._api.fetch_leverage_tiers() + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError(f'Could not fetch leverage amounts due to' + f'{e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + else: + return {} diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json new file mode 100644 index 000000000..c0bb965d0 --- /dev/null +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -0,0 +1,16481 @@ +{ + "RAY/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "SUSHI/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "CVC/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "BTS/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "HOT/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ZRX/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "QTUM/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "IOTA/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "BTC/BUSD": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.004, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.004", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.005, + "maxLeverage": 25, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.005", + "cum": "50.0" + } + }, + { + "tier": 3, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 20, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.01", + "cum": "1300.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 7500000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "7500000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.025", + "cum": "16300.0" + } + }, + { + "tier": 5, + "notionalFloor": 7500000, + "notionalCap": 40000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 6, + "info": { + "bracket": "5", + "initialLeverage": "6", + "notionalCap": "40000000", + "notionalFloor": "7500000", + "maintMarginRatio": "0.05", + "cum": "203800.0" + } + }, + { + "tier": 6, + "notionalFloor": 40000000, + "notionalCap": 100000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "100000000", + "notionalFloor": "40000000", + "maintMarginRatio": "0.1", + "cum": "2203800.0" + } + }, + { + "tier": 7, + "notionalFloor": 100000000, + "notionalCap": 200000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "200000000", + "notionalFloor": "100000000", + "maintMarginRatio": "0.125", + "cum": "4703800.0" + } + }, + { + "tier": 8, + "notionalFloor": 200000000, + "notionalCap": 400000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "8", + "initialLeverage": "3", + "notionalCap": "400000000", + "notionalFloor": "200000000", + "maintMarginRatio": "0.15", + "cum": "9703800.0" + } + }, + { + "tier": 9, + "notionalFloor": 400000000, + "notionalCap": 600000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "600000000", + "notionalFloor": "400000000", + "maintMarginRatio": "0.25", + "cum": "4.97038E7" + } + }, + { + "tier": 10, + "notionalFloor": 600000000, + "notionalCap": 1000000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "10", + "initialLeverage": "1", + "notionalCap": "1000000000", + "notionalFloor": "600000000", + "maintMarginRatio": "0.5", + "cum": "1.997038E8" + } + } + ], + "WAVES/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ADA/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + }, + { + "tier": 9, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6233035.0" + } + } + ], + "LIT/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "NU/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "XTZ/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + }, + { + "tier": 9, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6233035.0" + } + } + ], + "BNB/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + }, + { + "tier": 9, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6233035.0" + } + } + ], + "AKRO/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.012, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.012", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "65.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "690.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5690.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11940.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386940.0" + } + } + ], + "HNT/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ETC/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + }, + { + "tier": 9, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6233035.0" + } + } + ], + "XMR/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + }, + { + "tier": 9, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6233035.0" + } + } + ], + "YFI/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "FTT/BUSD": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 100000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "0", + "maintMarginRatio": "0.025", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 100000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.05", + "cum": "2500.0" + } + }, + { + "tier": 3, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.1", + "cum": "27500.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "4", + "initialLeverage": "3", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.15", + "cum": "77500.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "277500.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.5", + "cum": "1527500.0" + } + } + ], + "BTCUSDT_210326": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 9223372036854776000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "9223372036854775807", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + } + ], + "ETH/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.005, + "maxLeverage": 100, + "info": { + "bracket": "1", + "initialLeverage": "100", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.005", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "2", + "initialLeverage": "75", + "notionalCap": "100000", + "notionalFloor": "10000", + "maintMarginRatio": "0.0065", + "cum": "15.0" + } + }, + { + "tier": 3, + "notionalFloor": 100000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "3", + "initialLeverage": "50", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.01", + "cum": "365.0" + } + }, + { + "tier": 4, + "notionalFloor": 500000, + "notionalCap": 1500000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "4", + "initialLeverage": "25", + "notionalCap": "1500000", + "notionalFloor": "500000", + "maintMarginRatio": "0.02", + "cum": "5365.0" + } + }, + { + "tier": 5, + "notionalFloor": 1500000, + "notionalCap": 4000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "4000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.05", + "cum": "50365.0" + } + }, + { + "tier": 6, + "notionalFloor": 4000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "10000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.1", + "cum": "250365.0" + } + }, + { + "tier": 7, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.125", + "cum": "500365.0" + } + }, + { + "tier": 8, + "notionalFloor": 20000000, + "notionalCap": 40000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "8", + "initialLeverage": "3", + "notionalCap": "40000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.15", + "cum": "1000365.0" + } + }, + { + "tier": 9, + "notionalFloor": 40000000, + "notionalCap": 150000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "150000000", + "notionalFloor": "40000000", + "maintMarginRatio": "0.25", + "cum": "5000365.0" + } + }, + { + "tier": 10, + "notionalFloor": 150000000, + "notionalCap": 500000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "10", + "initialLeverage": "1", + "notionalCap": "500000000", + "notionalFloor": "150000000", + "maintMarginRatio": "0.5", + "cum": "4.2500365E7" + } + } + ], + "ALICE/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "ALPHA/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "SFP/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "REEF/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "BAT/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "DOGE/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "7000.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "57000.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "107000.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "732000.0" + } + }, + { + "tier": 7, + "notionalFloor": 10000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.5", + "cum": "3232000.0" + } + } + ], + "TRX/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + }, + { + "tier": 9, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6233035.0" + } + } + ], + "RLC/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "DOTECOUSDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.012, + "maxLeverage": 20, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.012", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "65.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "690.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 4, + "info": { + "bracket": "4", + "initialLeverage": "4", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5690.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11940.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 9223372036854776000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "9223372036854775807", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386940.0" + } + } + ], + "BTCSTUSDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 9223372036854776000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "9223372036854775807", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "STORJ/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "SNX/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ETHUSDT_210625": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "7500.0" + } + }, + { + "tier": 3, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "57500.0" + } + }, + { + "tier": 4, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "4", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "107500.0" + } + }, + { + "tier": 5, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "5", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "232500.0" + } + }, + { + "tier": 6, + "notionalFloor": 10000000, + "notionalCap": 9223372036854776000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "9223372036854775807", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1232500.0" + } + } + ], + "1000XEC/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "AUDIO/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "XLM/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + }, + { + "tier": 9, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6233035.0" + } + } + ], + "BTCBUSD_210129": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.004, + "maxLeverage": 20, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.004", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.005, + "maxLeverage": 15, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.005", + "cum": "5.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.01", + "cum": "130.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 7, + "info": { + "bracket": "4", + "initialLeverage": "7", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.025", + "cum": "1630.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 6, + "info": { + "bracket": "5", + "initialLeverage": "6", + "notionalCap": "2000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.05", + "cum": "14130.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "114130.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.125", + "cum": "239130.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "8", + "initialLeverage": "3", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.15", + "cum": "489130.0" + } + }, + { + "tier": 9, + "notionalFloor": 20000000, + "notionalCap": 9223372036854776000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "9223372036854775807", + "notionalFloor": "20000000", + "maintMarginRatio": "0.25", + "cum": "2489130.0" + } + } + ], + "IOTX/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "NEO/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "UNFI/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "SAND/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "DASH/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "KAVA/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "RUNE/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "CTK/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "LINK/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + }, + { + "tier": 9, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6233035.0" + } + } + ], + "CELR/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "RSR/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ADA/BUSD": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 100000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "0", + "maintMarginRatio": "0.025", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 100000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.05", + "cum": "2500.0" + } + }, + { + "tier": 3, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.1", + "cum": "27500.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "4", + "initialLeverage": "3", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.15", + "cum": "77500.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "277500.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.5", + "cum": "1527500.0" + } + } + ], + "DGB/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "SKL/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "REN/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "LPT/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "TOMO/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "MTL/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "LTC/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + }, + { + "tier": 9, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6233035.0" + } + } + ], + "DODO/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "EGLD/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "KSM/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "BNB/BUSD": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 100000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "0", + "maintMarginRatio": "0.025", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 100000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.05", + "cum": "2500.0" + } + }, + { + "tier": 3, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.1", + "cum": "27500.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "4", + "initialLeverage": "3", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.15", + "cum": "77500.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "277500.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.5", + "cum": "1527500.0" + } + } + ], + "BTCUSDT_210625": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "7500.0" + } + }, + { + "tier": 3, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "57500.0" + } + }, + { + "tier": 4, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "4", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "107500.0" + } + }, + { + "tier": 5, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "5", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "232500.0" + } + }, + { + "tier": 6, + "notionalFloor": 10000000, + "notionalCap": 9223372036854776000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "9223372036854775807", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1232500.0" + } + } + ], + "ONT/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "VET/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "TRB/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "MANA/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "COTI/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "CHR/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ETHUSDT_210924": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "7500.0" + } + }, + { + "tier": 3, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "57500.0" + } + }, + { + "tier": 4, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "4", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "107500.0" + } + }, + { + "tier": 5, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "5", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "232500.0" + } + }, + { + "tier": 6, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1232500.0" + } + }, + { + "tier": 7, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6232500.0" + } + } + ], + "BAKE/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "GRT/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ETHUSDT_220325": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 375000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "375000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 375000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "2000000", + "notionalFloor": "375000", + "maintMarginRatio": "0.05", + "cum": "11250.0" + } + }, + { + "tier": 3, + "notionalFloor": 2000000, + "notionalCap": 4000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "111250.0" + } + }, + { + "tier": 4, + "notionalFloor": 4000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "4", + "initialLeverage": "4", + "notionalCap": "10000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.125", + "cum": "211250.0" + } + }, + { + "tier": 5, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "5", + "initialLeverage": "3", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.15", + "cum": "461250.0" + } + }, + { + "tier": 6, + "notionalFloor": 20000000, + "notionalCap": 40000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "40000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.25", + "cum": "2461250.0" + } + }, + { + "tier": 7, + "notionalFloor": 40000000, + "notionalCap": 400000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "400000000", + "notionalFloor": "40000000", + "maintMarginRatio": "0.5", + "cum": "1.246125E7" + } + } + ], + "FLM/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "MASK/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "EOS/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + }, + { + "tier": 9, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6233035.0" + } + } + ], + "ETHUSDT_211231": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 375000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "375000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 375000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "2000000", + "notionalFloor": "375000", + "maintMarginRatio": "0.05", + "cum": "11250.0" + } + }, + { + "tier": 3, + "notionalFloor": 2000000, + "notionalCap": 4000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "111250.0" + } + }, + { + "tier": 4, + "notionalFloor": 4000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "4", + "initialLeverage": "4", + "notionalCap": "10000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.125", + "cum": "211250.0" + } + }, + { + "tier": 5, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "5", + "initialLeverage": "3", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.15", + "cum": "461250.0" + } + }, + { + "tier": 6, + "notionalFloor": 20000000, + "notionalCap": 40000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "40000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.25", + "cum": "2461250.0" + } + }, + { + "tier": 7, + "notionalFloor": 40000000, + "notionalCap": 400000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "400000000", + "notionalFloor": "40000000", + "maintMarginRatio": "0.5", + "cum": "1.246125E7" + } + } + ], + "OGN/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "SC/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "BAL/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "STMX/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "BTTUSDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "LUNA/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "500.0" + } + }, + { + "tier": 3, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8000.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58000.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108000.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.1665, + "maxLeverage": 3, + "info": { + "bracket": "6", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.1665", + "cum": "315500.0" + } + }, + { + "tier": 7, + "notionalFloor": 10000000, + "notionalCap": 15000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "15000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1150500.0" + } + }, + { + "tier": 8, + "notionalFloor": 15000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "15000000", + "maintMarginRatio": "0.5", + "cum": "4900500.0" + } + } + ], + "DENT/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "1000BTTC/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "KNC/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "SRM/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ENJ/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "C98/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ZEN/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ATOM/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "NEAR/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "SOL/BUSD": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 100000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "0", + "maintMarginRatio": "0.025", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 100000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.05", + "cum": "2500.0" + } + }, + { + "tier": 3, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.1", + "cum": "27500.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "4", + "initialLeverage": "3", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.15", + "cum": "77500.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "277500.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.5", + "cum": "1527500.0" + } + } + ], + "ENS/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "BCH/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + }, + { + "tier": 9, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6233035.0" + } + } + ], + "ATA/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "IOST/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "HBAR/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ZEC/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "1000SHIB/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "TLM/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ANT/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "BZRXUSDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ETH/BUSD": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 25000, + "maintenanceMarginRate": 0.004, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "25000", + "notionalFloor": "0", + "maintMarginRatio": "0.004", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.005, + "maxLeverage": 25, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.005", + "cum": "25.0" + } + }, + { + "tier": 3, + "notionalFloor": 100000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 20, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.01", + "cum": "525.0" + } + }, + { + "tier": 4, + "notionalFloor": 500000, + "notionalCap": 1500000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1500000", + "notionalFloor": "500000", + "maintMarginRatio": "0.025", + "cum": "8025.0" + } + }, + { + "tier": 5, + "notionalFloor": 1500000, + "notionalCap": 4000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 6, + "info": { + "bracket": "5", + "initialLeverage": "6", + "notionalCap": "4000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.05", + "cum": "45525.0" + } + }, + { + "tier": 6, + "notionalFloor": 4000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "10000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.1", + "cum": "245525.0" + } + }, + { + "tier": 7, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.125", + "cum": "495525.0" + } + }, + { + "tier": 8, + "notionalFloor": 20000000, + "notionalCap": 40000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "8", + "initialLeverage": "3", + "notionalCap": "40000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.15", + "cum": "995525.0" + } + }, + { + "tier": 9, + "notionalFloor": 40000000, + "notionalCap": 150000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "150000000", + "notionalFloor": "40000000", + "maintMarginRatio": "0.25", + "cum": "4995525.0" + } + }, + { + "tier": 10, + "notionalFloor": 150000000, + "notionalCap": 500000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "10", + "initialLeverage": "1", + "notionalCap": "500000000", + "notionalFloor": "150000000", + "maintMarginRatio": "0.5", + "cum": "4.2495525E7" + } + } + ], + "GALA/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "AAVE/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "500.0" + } + }, + { + "tier": 3, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8000.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58000.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108000.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.1665, + "maxLeverage": 3, + "info": { + "bracket": "6", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.1665", + "cum": "315500.0" + } + }, + { + "tier": 7, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1150500.0" + } + }, + { + "tier": 8, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6150500.0" + } + } + ], + "GTC/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ALGO/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "ICP/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "BTCUSDT_210924": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "7500.0" + } + }, + { + "tier": 3, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "57500.0" + } + }, + { + "tier": 4, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "4", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "107500.0" + } + }, + { + "tier": 5, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "5", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "232500.0" + } + }, + { + "tier": 6, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1232500.0" + } + }, + { + "tier": 7, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6232500.0" + } + } + ], + "LRC/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "AVAX/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 750000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 750000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "750000", + "maintMarginRatio": "0.25", + "cum": "123250.0" + } + }, + { + "tier": 7, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "373250.0" + } + } + ], + "BTCUSDT_220325": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 375000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "375000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 375000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "2000000", + "notionalFloor": "375000", + "maintMarginRatio": "0.05", + "cum": "11250.0" + } + }, + { + "tier": 3, + "notionalFloor": 2000000, + "notionalCap": 4000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "111250.0" + } + }, + { + "tier": 4, + "notionalFloor": 4000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "4", + "initialLeverage": "4", + "notionalCap": "10000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.125", + "cum": "211250.0" + } + }, + { + "tier": 5, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "5", + "initialLeverage": "3", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.15", + "cum": "461250.0" + } + }, + { + "tier": 6, + "notionalFloor": 20000000, + "notionalCap": 40000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "40000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.25", + "cum": "2461250.0" + } + }, + { + "tier": 7, + "notionalFloor": 40000000, + "notionalCap": 400000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "400000000", + "notionalFloor": "40000000", + "maintMarginRatio": "0.5", + "cum": "1.246125E7" + } + } + ], + "ARPA/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "CELO/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ROSE/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "MATIC/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 750000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 750000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "750000", + "maintMarginRatio": "0.25", + "cum": "123250.0" + } + }, + { + "tier": 7, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "373250.0" + } + } + ], + "1INCH/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.012, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.012", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "65.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "690.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5690.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11940.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 100000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "100000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386940.0" + } + } + ], + "MKR/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "PEOPLE/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "THETA/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "500.0" + } + }, + { + "tier": 3, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8000.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58000.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108000.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.1665, + "maxLeverage": 3, + "info": { + "bracket": "6", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.1665", + "cum": "315500.0" + } + }, + { + "tier": 7, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1150500.0" + } + }, + { + "tier": 8, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6150500.0" + } + } + ], + "UNI/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "500.0" + } + }, + { + "tier": 3, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8000.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58000.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108000.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.1665, + "maxLeverage": 3, + "info": { + "bracket": "6", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.1665", + "cum": "315500.0" + } + }, + { + "tier": 7, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1150500.0" + } + }, + { + "tier": 8, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6150500.0" + } + } + ], + "ETHUSDT_210326": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 9223372036854776000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "9223372036854775807", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + } + ], + "LINA/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "AR/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "RVN/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "FIL/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "500.0" + } + }, + { + "tier": 3, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8000.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58000.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108000.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.1665, + "maxLeverage": 3, + "info": { + "bracket": "6", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.1665", + "cum": "315500.0" + } + }, + { + "tier": 7, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1150500.0" + } + }, + { + "tier": 8, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6150500.0" + } + } + ], + "NKN/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "KLAY/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "DEFI/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "COMP/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "BTCDOM/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "SOL/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "7000.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "57000.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "107000.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "732000.0" + } + }, + { + "tier": 7, + "notionalFloor": 10000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.5", + "cum": "3232000.0" + } + } + ], + "BTC/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.004, + "maxLeverage": 125, + "info": { + "bracket": "1", + "initialLeverage": "125", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.004", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.005, + "maxLeverage": 100, + "info": { + "bracket": "2", + "initialLeverage": "100", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.005", + "cum": "50.0" + } + }, + { + "tier": 3, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "3", + "initialLeverage": "50", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.01", + "cum": "1300.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 7500000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "7500000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.025", + "cum": "16300.0" + } + }, + { + "tier": 5, + "notionalFloor": 7500000, + "notionalCap": 40000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "40000000", + "notionalFloor": "7500000", + "maintMarginRatio": "0.05", + "cum": "203800.0" + } + }, + { + "tier": 6, + "notionalFloor": 40000000, + "notionalCap": 100000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "100000000", + "notionalFloor": "40000000", + "maintMarginRatio": "0.1", + "cum": "2203800.0" + } + }, + { + "tier": 7, + "notionalFloor": 100000000, + "notionalCap": 200000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "200000000", + "notionalFloor": "100000000", + "maintMarginRatio": "0.125", + "cum": "4703800.0" + } + }, + { + "tier": 8, + "notionalFloor": 200000000, + "notionalCap": 400000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "8", + "initialLeverage": "3", + "notionalCap": "400000000", + "notionalFloor": "200000000", + "maintMarginRatio": "0.15", + "cum": "9703800.0" + } + }, + { + "tier": 9, + "notionalFloor": 400000000, + "notionalCap": 600000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "600000000", + "notionalFloor": "400000000", + "maintMarginRatio": "0.25", + "cum": "4.97038E7" + } + }, + { + "tier": 10, + "notionalFloor": 600000000, + "notionalCap": 1000000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "10", + "initialLeverage": "1", + "notionalCap": "1000000000", + "notionalFloor": "600000000", + "maintMarginRatio": "0.5", + "cum": "1.997038E8" + } + } + ], + "OMG/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.024, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.024", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "5.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "630.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5630.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11880.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "10000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386880.0" + } + } + ], + "ICX/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "BLZ/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "BTCUSDT_211231": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 375000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "375000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 375000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "2000000", + "notionalFloor": "375000", + "maintMarginRatio": "0.05", + "cum": "11250.0" + } + }, + { + "tier": 3, + "notionalFloor": 2000000, + "notionalCap": 4000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "111250.0" + } + }, + { + "tier": 4, + "notionalFloor": 4000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "4", + "initialLeverage": "4", + "notionalCap": "10000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.125", + "cum": "211250.0" + } + }, + { + "tier": 5, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "5", + "initialLeverage": "3", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.15", + "cum": "461250.0" + } + }, + { + "tier": 6, + "notionalFloor": 20000000, + "notionalCap": 40000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "40000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.25", + "cum": "2461250.0" + } + }, + { + "tier": 7, + "notionalFloor": 40000000, + "notionalCap": 400000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "400000000", + "notionalFloor": "40000000", + "maintMarginRatio": "0.5", + "cum": "1.246125E7" + } + } + ], + "FTM/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 750000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 750000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "750000", + "maintMarginRatio": "0.25", + "cum": "123250.0" + } + }, + { + "tier": 7, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "373250.0" + } + } + ], + "YFII/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "KEEP/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "BAND/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "BTCBUSD_210226": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.004, + "maxLeverage": 20, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.004", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.005, + "maxLeverage": 15, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.005", + "cum": "5.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.01", + "cum": "130.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 7, + "info": { + "bracket": "4", + "initialLeverage": "7", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.025", + "cum": "1630.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 6, + "info": { + "bracket": "5", + "initialLeverage": "6", + "notionalCap": "2000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.05", + "cum": "14130.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "114130.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.125", + "cum": "239130.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "8", + "initialLeverage": "3", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.15", + "cum": "489130.0" + } + }, + { + "tier": 9, + "notionalFloor": 20000000, + "notionalCap": 9223372036854776000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "9223372036854775807", + "notionalFloor": "20000000", + "maintMarginRatio": "0.25", + "cum": "2489130.0" + } + } + ], + "XRP/BUSD": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 100000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "0", + "maintMarginRatio": "0.025", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 100000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.05", + "cum": "2500.0" + } + }, + { + "tier": 3, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.1", + "cum": "27500.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "4", + "initialLeverage": "3", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.15", + "cum": "77500.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "277500.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.5", + "cum": "1527500.0" + } + } + ], + "DOGE/BUSD": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 100000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "0", + "maintMarginRatio": "0.025", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 100000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.05", + "cum": "2500.0" + } + }, + { + "tier": 3, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.1", + "cum": "27500.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "4", + "initialLeverage": "3", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.15", + "cum": "77500.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "277500.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.5", + "cum": "1527500.0" + } + } + ], + "XRP/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 20000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + }, + { + "tier": 9, + "notionalFloor": 20000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.5", + "cum": "6233035.0" + } + } + ], + "SXP/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "CRV/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "BEL/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "DOT/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 10000, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 10000, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "35.0" + } + }, + { + "tier": 3, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "535.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8035.0" + } + }, + { + "tier": 5, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58035.0" + } + }, + { + "tier": 6, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108035.0" + } + }, + { + "tier": 7, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "7", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.15", + "cum": "233035.0" + } + }, + { + "tier": 8, + "notionalFloor": 10000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "50000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1233035.0" + } + }, + { + "tier": 9, + "notionalFloor": 50000000, + "notionalCap": 100000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "100000000", + "notionalFloor": "50000000", + "maintMarginRatio": "0.5", + "cum": "1.3733035E7" + } + } + ], + "XEM/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ONE/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + "ZIL/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "AXS/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "500.0" + } + }, + { + "tier": 3, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "8000.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "58000.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "108000.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 10000000, + "maintenanceMarginRate": 0.1665, + "maxLeverage": 3, + "info": { + "bracket": "6", + "initialLeverage": "3", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.1665", + "cum": "315500.0" + } + }, + { + "tier": 7, + "notionalFloor": 10000000, + "notionalCap": 15000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "15000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1150500.0" + } + }, + { + "tier": 8, + "notionalFloor": 15000000, + "notionalCap": 50000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "15000000", + "maintMarginRatio": "0.5", + "cum": "4900500.0" + } + } + ], + "DYDX/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 4000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "4000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 4000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.5", + "cum": "1154500.0" + } + } + ], + "OCEAN/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "CHZ/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.012, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.012", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "65.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "690.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5690.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11940.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386940.0" + } + } + ], + "LENDUSDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 9223372036854776000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "9223372036854775807", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "ANKR/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.012, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.012", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "65.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "690.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5690.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11940.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386940.0" + } + } + ], + "DUSK/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ], + "CTSI/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 5000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 5000, + "notionalCap": 25000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "75.0" + } + }, + { + "tier": 3, + "notionalFloor": 25000, + "notionalCap": 100000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "700.0" + } + }, + { + "tier": 4, + "notionalFloor": 100000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5700.0" + } + }, + { + "tier": 5, + "notionalFloor": 250000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11950.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386950.0" + } + } + ] +} diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index 163f8c44e..484b8b9d3 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -1,7 +1,8 @@ """ Bybit exchange subclass """ import logging -from typing import Dict +from typing import Dict, List, Tuple +from freqtrade.enums import MarginMode, TradingMode from freqtrade.exchange import Exchange @@ -20,4 +21,11 @@ class Bybit(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 200, + "ccxt_futures_name": "linear" } + + _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + # TradingMode.SPOT always supported and not required in this list + # (TradingMode.FUTURES, MarginMode.CROSS), + # (TradingMode.FUTURES, MarginMode.ISOLATED) + ] diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index fad905b04..997c16ff1 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -35,9 +35,19 @@ BAD_EXCHANGES = { MAP_EXCHANGE_CHILDCLASS = { 'binanceus': 'binance', 'binanceje': 'binance', + 'binanceusdm': 'binance', 'okex': 'okx', } +SUPPORTED_EXCHANGES = [ + 'binance', + 'bittrex', + 'ftx', + 'gateio', + 'huobi', + 'kraken', + 'okx', +] EXCHANGE_HAS_REQUIRED = [ # Required / private @@ -55,10 +65,17 @@ EXCHANGE_HAS_REQUIRED = [ EXCHANGE_HAS_OPTIONAL = [ # Private 'fetchMyTrades', # Trades for order - fee detection + # 'setLeverage', # Margin/Futures trading + # 'setMarginMode', # Margin/Futures trading + # 'fetchFundingHistory', # Futures trading # Public 'fetchOrderBook', 'fetchL2OrderBook', 'fetchTicker', # OR for pricing 'fetchTickers', # For volumepairlist? 'fetchTrades', # Downloading trades data + # 'fetchFundingRateHistory', # Futures trading + # 'fetchPositions', # Futures trading + # 'fetchLeverageTiers', # Futures initialization + # 'fetchMarketLeverageTiers', # Futures initialization ] diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index a502ad034..09ada4452 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -9,7 +9,7 @@ import logging from copy import deepcopy from datetime import datetime, timedelta, timezone from math import ceil -from typing import Any, Coroutine, Dict, List, Optional, Tuple +from typing import Any, Coroutine, Dict, List, Literal, Optional, Tuple, Union import arrow import ccxt @@ -20,14 +20,16 @@ from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE, TRU from pandas import DataFrame from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, - ListPairsWithTimeframes) + ListPairsWithTimeframes, PairWithTimeframe) from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list +from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError, InvalidOrderException, OperationalException, PricingError, RetryableOrderError, TemporaryError) from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, BAD_EXCHANGES, EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED, - remove_credentials, retrier, retrier_async) + SUPPORTED_EXCHANGES, remove_credentials, retrier, + retrier_async) from freqtrade.misc import chunks, deep_merge_dicts, safe_value_fallback2 from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist @@ -46,11 +48,6 @@ http.cookies.Morsel._reserved["samesite"] = "SameSite" # type: ignore class Exchange: - _config: Dict = {} - - # Parameters to add directly to ccxt sync/async initialization. - _ccxt_config: Dict = {} - # Parameters to add directly to buy/sell calls (like agreeing to trading agreement) _params: Dict = {} @@ -69,12 +66,23 @@ class Exchange: "ohlcv_partial_candle": True, # Check https://github.com/ccxt/ccxt/issues/10767 for removal of ohlcv_volume_currency "ohlcv_volume_currency": "base", # "base" or "quote" + "tickers_have_quoteVolume": True, + "tickers_have_price": True, "trades_pagination": "time", # Possible are "time" or "id" "trades_pagination_arg": "since", "l2_limit_range": None, "l2_limit_range_required": True, # Allow Empty L2 limit (kucoin) + "mark_ohlcv_price": "mark", + "mark_ohlcv_timeframe": "8h", + "ccxt_futures_name": "swap", + "needs_trading_fees": False, # use fetch_trading_fees to cache fees } _ft_has: Dict = {} + _ft_has_futures: Dict = {} + + _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + # TradingMode.SPOT always supported and not required in this list + ] def __init__(self, config: Dict[str, Any], validate: bool = True) -> None: """ @@ -85,26 +93,29 @@ class Exchange: self._api: ccxt.Exchange = None self._api_async: ccxt_async.Exchange = None self._markets: Dict = {} + self._trading_fees: Dict[str, Any] = {} + self._leverage_tiers: Dict[str, List[Dict]] = {} self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) + self._config: Dict = {} self._config.update(config) # Holds last candle refreshed time of each pair - self._pairs_last_refresh_time: Dict[Tuple[str, str], int] = {} + self._pairs_last_refresh_time: Dict[PairWithTimeframe, int] = {} # Timestamp of last markets refresh self._last_markets_refresh: int = 0 # Cache for 10 minutes ... - self._fetch_tickers_cache: TTLCache = TTLCache(maxsize=1, ttl=60 * 10) + self._fetch_tickers_cache: TTLCache = TTLCache(maxsize=2, ttl=60 * 10) # Cache values for 1800 to avoid frequent polling of the exchange for prices # Caching only applies to RPC methods, so prices for open trades are still # refreshed once every iteration. - self._sell_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800) - self._buy_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800) + self._exit_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800) + self._entry_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800) # Holds candles - self._klines: Dict[Tuple[str, str], DataFrame] = {} + self._klines: Dict[PairWithTimeframe, DataFrame] = {} # Holds all open sell orders for dry_run self._dry_run_open_orders: Dict[str, Any] = {} @@ -116,8 +127,19 @@ class Exchange: exchange_config = config['exchange'] self.log_responses = exchange_config.get('log_responses', False) + # Leverage properties + self.trading_mode: TradingMode = config.get('trading_mode', TradingMode.SPOT) + self.margin_mode: MarginMode = ( + MarginMode(config.get('margin_mode')) + if config.get('margin_mode') + else MarginMode.NONE + ) + self.liquidation_buffer = config.get('liquidation_buffer', 0.05) + # Deep merge ft_has with default ft_has options self._ft_has = deep_merge_dicts(self._ft_has, deepcopy(self._ft_has_default)) + if self.trading_mode == TradingMode.FUTURES: + self._ft_has = deep_merge_dicts(self._ft_has_futures, self._ft_has) if exchange_config.get('_ft_has_params'): self._ft_has = deep_merge_dicts(exchange_config.get('_ft_has_params'), self._ft_has) @@ -130,13 +152,13 @@ class Exchange: self._trades_pagination_arg = self._ft_has['trades_pagination_arg'] # Initialize ccxt objects - ccxt_config = self._ccxt_config.copy() + ccxt_config = self._ccxt_config ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}), ccxt_config) ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_sync_config', {}), ccxt_config) self._api = self._init_ccxt(exchange_config, ccxt_kwargs=ccxt_config) - ccxt_async_config = self._ccxt_config.copy() + ccxt_async_config = self._ccxt_config ccxt_async_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}), ccxt_async_config) ccxt_async_config = deep_merge_dicts(exchange_config.get('ccxt_async_config', {}), @@ -161,11 +183,17 @@ class Exchange: self.validate_order_time_in_force(config.get('order_time_in_force', {})) self.required_candle_call_count = self.validate_required_startup_candles( config.get('startup_candle_count', 0), config.get('timeframe', '')) + self.validate_trading_mode_and_margin_mode(self.trading_mode, self.margin_mode) + self.validate_pricing(config['exit_pricing']) + self.validate_pricing(config['entry_pricing']) # Converts the interval provided in minutes in config to seconds self.markets_refresh_interval: int = exchange_config.get( "markets_refresh_interval", 60) * 60 + if self.trading_mode != TradingMode.SPOT: + self.fill_leverage_tiers() + def __del__(self): """ Destructor - clean up async stuff @@ -216,6 +244,24 @@ class Exchange: return api + @property + def _ccxt_config(self) -> Dict: + # Parameters to add directly to ccxt sync/async initialization. + if self.trading_mode == TradingMode.MARGIN: + return { + "options": { + "defaultType": "margin" + } + } + elif self.trading_mode == TradingMode.FUTURES: + return { + "options": { + "defaultType": self._ft_has["ccxt_futures_name"] + } + } + else: + return {} + @property def name(self) -> str: """exchange Name (from ccxt)""" @@ -260,12 +306,12 @@ class Exchange: timeframe, self._ft_has.get('ohlcv_candle_limit'))) def get_markets(self, base_currencies: List[str] = None, quote_currencies: List[str] = None, - pairs_only: bool = False, active_only: bool = False) -> Dict[str, Any]: + spot_only: bool = False, margin_only: bool = False, futures_only: bool = False, + tradable_only: bool = True, + active_only: bool = False) -> Dict[str, Any]: """ Return exchange ccxt markets, filtered out by base currency and quote currency if this was requested in parameters. - - TODO: consider moving it to the Dataprovider """ markets = self.markets if not markets: @@ -275,8 +321,14 @@ class Exchange: markets = {k: v for k, v in markets.items() if v['base'] in base_currencies} if quote_currencies: markets = {k: v for k, v in markets.items() if v['quote'] in quote_currencies} - if pairs_only: + if tradable_only: markets = {k: v for k, v in markets.items() if self.market_is_tradable(v)} + if spot_only: + markets = {k: v for k, v in markets.items() if self.market_is_spot(v)} + if margin_only: + markets = {k: v for k, v in markets.items() if self.market_is_margin(v)} + if futures_only: + markets = {k: v for k, v in markets.items() if self.market_is_future(v)} if active_only: markets = {k: v for k, v in markets.items() if market_is_active(v)} return markets @@ -296,29 +348,85 @@ class Exchange: def get_pair_base_currency(self, pair: str) -> str: """ - Return a pair's quote currency + Return a pair's base currency """ return self.markets.get(pair, {}).get('base', '') + def market_is_future(self, market: Dict[str, Any]) -> bool: + return ( + market.get(self._ft_has["ccxt_futures_name"], False) is True and + market.get('linear', False) is True + ) + + def market_is_spot(self, market: Dict[str, Any]) -> bool: + return market.get('spot', False) is True + + def market_is_margin(self, market: Dict[str, Any]) -> bool: + return market.get('margin', False) is True + def market_is_tradable(self, market: Dict[str, Any]) -> bool: """ Check if the market symbol is tradable by Freqtrade. - By default, checks if it's splittable by `/` and both sides correspond to base / quote + Ensures that Configured mode aligns to """ - symbol_parts = market['symbol'].split('/') - return (len(symbol_parts) == 2 and - len(symbol_parts[0]) > 0 and - len(symbol_parts[1]) > 0 and - symbol_parts[0] == market.get('base') and - symbol_parts[1] == market.get('quote') - ) + return ( + market.get('quote', None) is not None + and market.get('base', None) is not None + and ((self.trading_mode == TradingMode.SPOT and self.market_is_spot(market)) + or (self.trading_mode == TradingMode.MARGIN and self.market_is_margin(market)) + or (self.trading_mode == TradingMode.FUTURES and self.market_is_future(market))) + ) - def klines(self, pair_interval: Tuple[str, str], copy: bool = True) -> DataFrame: + def klines(self, pair_interval: PairWithTimeframe, copy: bool = True) -> DataFrame: if pair_interval in self._klines: return self._klines[pair_interval].copy() if copy else self._klines[pair_interval] else: return DataFrame() + def _get_contract_size(self, pair: str) -> float: + if self.trading_mode == TradingMode.FUTURES: + market = self.markets[pair] + contract_size: float = 1.0 + if market['contractSize'] is not None: + # ccxt has contractSize in markets as string + contract_size = float(market['contractSize']) + return contract_size + else: + return 1 + + def _trades_contracts_to_amount(self, trades: List) -> List: + if len(trades) > 0 and 'symbol' in trades[0]: + contract_size = self._get_contract_size(trades[0]['symbol']) + if contract_size != 1: + for trade in trades: + trade['amount'] = trade['amount'] * contract_size + return trades + + def _order_contracts_to_amount(self, order: Dict) -> Dict: + if 'symbol' in order and order['symbol'] is not None: + contract_size = self._get_contract_size(order['symbol']) + if contract_size != 1: + for prop in ['amount', 'cost', 'filled', 'remaining']: + if prop in order and order[prop] is not None: + order[prop] = order[prop] * contract_size + return order + + def _amount_to_contracts(self, pair: str, amount: float) -> float: + + contract_size = self._get_contract_size(pair) + if contract_size and contract_size != 1: + return amount / contract_size + else: + return amount + + def _contracts_to_amount(self, pair: str, num_contracts: float) -> float: + + contract_size = self._get_contract_size(pair) + if contract_size and contract_size != 1: + return num_contracts * contract_size + else: + return num_contracts + def set_sandbox(self, api: ccxt.Exchange, exchange_config: dict, name: str) -> None: if exchange_config.get('sandbox'): if api.urls.get('test'): @@ -345,6 +453,9 @@ class Exchange: self._markets = self._api.load_markets() self._load_async_markets() self._last_markets_refresh = arrow.utcnow().int_timestamp + if self._ft_has['needs_trading_fees']: + self._trading_fees = self.fetch_trading_fees() + except ccxt.BaseError: logger.exception('Unable to initialize markets.') @@ -361,6 +472,7 @@ class Exchange: # Also reload async markets to avoid issues with newly listed pairs self._load_async_markets(reload=True) self._last_markets_refresh = arrow.utcnow().int_timestamp + self.fill_leverage_tiers() except ccxt.BaseError: logger.exception("Could not reload markets.") @@ -400,7 +512,7 @@ class Exchange: # Note: ccxt has BaseCurrency/QuoteCurrency format for pairs if self.markets and pair not in self.markets: raise OperationalException( - f'Pair {pair} is not available on {self.name}. ' + f'Pair {pair} is not available on {self.name} {self.trading_mode.value}. ' f'Please remove {pair} from your whitelist.') # From ccxt Documentation: @@ -468,6 +580,14 @@ class Exchange: f'On exchange stoploss is not supported for {self.name}.' ) + def validate_pricing(self, pricing: Dict) -> None: + if pricing.get('use_order_book', False) and not self.exchange_has('fetchL2OrderBook'): + raise OperationalException(f'Orderbook not available for {self.name}.') + if (not pricing.get('use_order_book', False) and ( + not self.exchange_has('fetchTicker') + or not self._ft_has['tickers_have_price'])): + raise OperationalException(f'Ticker pricing not available for {self.name}.') + def validate_order_time_in_force(self, order_time_in_force: Dict) -> None: """ Checks if order time in force configured in strategy/config are supported @@ -501,6 +621,25 @@ class Exchange: f"if you really need {startup_candles} candles for your strategy") return required_candle_call_count + def validate_trading_mode_and_margin_mode( + self, + trading_mode: TradingMode, + margin_mode: Optional[MarginMode] # Only None when trading_mode = TradingMode.SPOT + ): + """ + Checks if freqtrade can perform trades using the configured + trading mode(Margin, Futures) and MarginMode(Cross, Isolated) + Throws OperationalException: + If the trading_mode/margin_mode type are not supported by freqtrade on this exchange + """ + if trading_mode != TradingMode.SPOT and ( + (trading_mode, margin_mode) not in self._supported_trading_mode_margin_pairs + ): + mm_value = margin_mode and margin_mode.value + raise OperationalException( + f"Freqtrade does not support {mm_value} {trading_mode.value} on {self.name}" + ) + def exchange_has(self, endpoint: str) -> bool: """ Checks if exchange implements a specific API endpoint. @@ -560,28 +699,59 @@ class Exchange: else: return 1 / pow(10, precision) - def get_min_pair_stake_amount(self, pair: str, price: float, - stoploss: float) -> Optional[float]: + def get_min_pair_stake_amount( + self, + pair: str, + price: float, + stoploss: float, + leverage: Optional[float] = 1.0 + ) -> Optional[float]: + return self._get_stake_amount_limit(pair, price, stoploss, 'min', leverage) + + def get_max_pair_stake_amount(self, pair: str, price: float, leverage: float = 1.0) -> float: + max_stake_amount = self._get_stake_amount_limit(pair, price, 0.0, 'max') + if max_stake_amount is None: + # * Should never be executed + raise OperationalException(f'{self.name}.get_max_pair_stake_amount should' + 'never set max_stake_amount to None') + return max_stake_amount / leverage + + def _get_stake_amount_limit( + self, + pair: str, + price: float, + stoploss: float, + limit: Literal['min', 'max'], + leverage: Optional[float] = 1.0 + ) -> Optional[float]: + + isMin = limit == 'min' + try: market = self.markets[pair] except KeyError: raise ValueError(f"Can't get market information for symbol {pair}") - if 'limits' not in market: - return None - - min_stake_amounts = [] + stake_limits = [] limits = market['limits'] - if ('cost' in limits and 'min' in limits['cost'] - and limits['cost']['min'] is not None): - min_stake_amounts.append(limits['cost']['min']) + if (limits['cost'][limit] is not None): + stake_limits.append( + self._contracts_to_amount( + pair, + limits['cost'][limit] + ) + ) - if ('amount' in limits and 'min' in limits['amount'] - and limits['amount']['min'] is not None): - min_stake_amounts.append(limits['amount']['min'] * price) + if (limits['amount'][limit] is not None): + stake_limits.append( + self._contracts_to_amount( + pair, + limits['amount'][limit] * price + ) + ) - if not min_stake_amounts: - return None + if not stake_limits: + return None if isMin else float('inf') # reserve some percent defined in config (5% default) + stoploss amount_reserve_percent = 1.0 + self._config.get('amount_reserve_percent', @@ -595,12 +765,24 @@ class Exchange: # The value returned should satisfy both limits: for amount (base currency) and # for cost (quote, stake currency), so max() is used here. # See also #2575 at github. - return max(min_stake_amounts) * amount_reserve_percent + return self._get_stake_amount_considering_leverage( + max(stake_limits) * amount_reserve_percent, + leverage or 1.0 + ) if isMin else min(stake_limits) + + def _get_stake_amount_considering_leverage(self, stake_amount: float, leverage: float) -> float: + """ + Takes the minimum stake amount for a pair with no leverage and returns the minimum + stake amount when leverage is considered + :param stake_amount: The stake amount for a pair before leverage is considered + :param leverage: The amount of leverage being used on the current trade + """ + return stake_amount / leverage # Dry-run methods def create_dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, - rate: float, params: Dict = {}, + rate: float, leverage: float, params: Dict = {}, stop_loss: bool = False) -> Dict[str, Any]: order_id = f'dry_run_{side}_{datetime.now().timestamp()}' _amount = self.amount_to_precision(pair, amount) @@ -610,7 +792,7 @@ class Exchange: 'price': rate, 'average': rate, 'amount': _amount, - 'cost': _amount * rate, + 'cost': _amount * rate / leverage, 'type': ordertype, 'side': side, 'filled': 0, @@ -619,7 +801,8 @@ class Exchange: 'timestamp': arrow.utcnow().int_timestamp * 1000, 'status': "closed" if ordertype == "market" and not stop_loss else "open", 'fee': None, - 'info': {} + 'info': {}, + 'leverage': leverage } if stop_loss: dry_order["info"] = {"stopPrice": dry_order["price"]} @@ -633,7 +816,7 @@ class Exchange: dry_order.update({ 'average': average, 'filled': _amount, - 'cost': dry_order['amount'] * average, + 'cost': (dry_order['amount'] * average) / leverage }) dry_order = self.add_dry_order_fee(pair, dry_order) @@ -748,28 +931,64 @@ class Exchange: # Order handling - def create_order(self, pair: str, ordertype: str, side: str, amount: float, - rate: float, time_in_force: str = 'gtc') -> Dict: - - if self._config['dry_run']: - dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate) - return dry_order + def _lev_prep(self, pair: str, leverage: float, side: str): + if self.trading_mode != TradingMode.SPOT: + self.set_margin_mode(pair, self.margin_mode) + self._set_leverage(leverage, pair) + def _get_params( + self, + ordertype: str, + leverage: float, + reduceOnly: bool, + time_in_force: str = 'gtc', + ) -> Dict: params = self._params.copy() if time_in_force != 'gtc' and ordertype != 'market': param = self._ft_has.get('time_in_force_parameter', '') params.update({param: time_in_force}) + if reduceOnly: + params.update({'reduceOnly': True}) + return params + + def create_order( + self, + *, + pair: str, + ordertype: str, + side: str, + amount: float, + rate: float, + leverage: float, + reduceOnly: bool = False, + time_in_force: str = 'gtc', + ) -> Dict: + if self._config['dry_run']: + dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate, leverage) + return dry_order + + params = self._get_params(ordertype, leverage, reduceOnly, time_in_force) try: # Set the precision for amount and price(rate) as accepted by the exchange - amount = self.amount_to_precision(pair, amount) + amount = self.amount_to_precision(pair, self._amount_to_contracts(pair, amount)) needs_price = (ordertype != 'market' or self._api.options.get("createMarketBuyOrderRequiresPrice", False)) rate_for_order = self.price_to_precision(pair, rate) if needs_price else None - order = self._api.create_order(pair, ordertype, side, - amount, rate_for_order, params) + if not reduceOnly: + self._lev_prep(pair, leverage, side) + + order = self._api.create_order( + pair, + ordertype, + side, + amount, + rate_for_order, + params, + ) self._log_exchange_response('create_order', order) + order = self._order_contracts_to_amount(order) return order except ccxt.InsufficientFunds as e: @@ -790,13 +1009,41 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ raise OperationalException(f"stoploss is not implemented for {self.name}.") + def _get_stop_order_type(self, user_order_type) -> Tuple[str, str]: + + available_order_Types: Dict[str, str] = self._ft_has["stoploss_order_types"] + + if user_order_type in available_order_Types.keys(): + ordertype = available_order_Types[user_order_type] + else: + # Otherwise pick only one available + ordertype = list(available_order_Types.values())[0] + user_order_type = list(available_order_Types.keys())[0] + return ordertype, user_order_type + + def _get_stop_limit_rate(self, stop_price: float, order_types: Dict, side: str) -> float: + # Limit price threshold: As limit price should always be below stop-price + limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) + if side == "sell": + limit_rate = stop_price * limit_price_pct + else: + limit_rate = stop_price * (2 - limit_price_pct) + + bad_stop_price = ((stop_price <= limit_rate) if side == + "sell" else (stop_price >= limit_rate)) + # Ensure rate is less than stop price + if bad_stop_price: + raise OperationalException( + 'In stoploss limit order, stop price should be more than limit price') + return limit_rate + def _get_stop_params(self, ordertype: str, stop_price: float) -> Dict: params = self._params.copy() # Verify if stopPrice works for your exchange! @@ -804,7 +1051,8 @@ class Exchange: return params @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: + def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, + side: str, leverage: float) -> Dict: """ creates a stoploss order. requires `_ft_has['stoploss_order_types']` to be set as a dict mapping limit and market @@ -824,53 +1072,52 @@ class Exchange: raise OperationalException(f"stoploss is not implemented for {self.name}.") user_order_type = order_types.get('stoploss', 'market') - if user_order_type in self._ft_has["stoploss_order_types"].keys(): - ordertype = self._ft_has["stoploss_order_types"][user_order_type] - else: - # Otherwise pick only one available - ordertype = list(self._ft_has["stoploss_order_types"].values())[0] - user_order_type = list(self._ft_has["stoploss_order_types"].keys())[0] + ordertype, user_order_type = self._get_stop_order_type(user_order_type) stop_price_norm = self.price_to_precision(pair, stop_price) - rate = None + limit_rate = None if user_order_type == 'limit': - # Limit price threshold: As limit price should always be below stop-price - limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) - rate = stop_price * limit_price_pct - - # Ensure rate is less than stop price - if stop_price_norm <= rate: - raise OperationalException( - 'In stoploss limit order, stop price should be more than limit price') - rate = self.price_to_precision(pair, rate) + limit_rate = self._get_stop_limit_rate(stop_price, order_types, side) + limit_rate = self.price_to_precision(pair, limit_rate) if self._config['dry_run']: dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price_norm, stop_loss=True) + pair, + ordertype, + side, + amount, + stop_price_norm, + stop_loss=True, + leverage=leverage, + ) return dry_order try: params = self._get_stop_params(ordertype=ordertype, stop_price=stop_price_norm) + if self.trading_mode == TradingMode.FUTURES: + params['reduceOnly'] = True - amount = self.amount_to_precision(pair, amount) + amount = self.amount_to_precision(pair, self._amount_to_contracts(pair, amount)) - order = self._api.create_order(symbol=pair, type=ordertype, side='sell', - amount=amount, price=rate, params=params) - logger.info(f"stoploss {user_order_type} order added for {pair}. " - f"stop price: {stop_price}. limit: {rate}") + self._lev_prep(pair, leverage, side) + order = self._api.create_order(symbol=pair, type=ordertype, side=side, + amount=amount, price=limit_rate, params=params) self._log_exchange_response('create_stoploss_order', order) + order = self._order_contracts_to_amount(order) + logger.info(f"stoploss {user_order_type} order added for {pair}. " + f"stop price: {stop_price}. limit: {limit_rate}") return order except ccxt.InsufficientFunds as e: raise InsufficientFundsError( f'Insufficient funds to create {ordertype} sell order on market {pair}. ' - f'Tried to sell amount {amount} at rate {rate}. ' + f'Tried to sell amount {amount} at rate {limit_rate}. ' f'Message: {e}') from e except ccxt.InvalidOrder as e: # Errors: # `Order would trigger immediately.` raise InvalidOrderException( f'Could not create {ordertype} sell order on market {pair}. ' - f'Tried to sell amount {amount} at rate {rate}. ' + f'Tried to sell amount {amount} at rate {limit_rate}. ' f'Message: {e}') from e except ccxt.DDoSProtection as e: raise DDosProtection(e) from e @@ -888,6 +1135,7 @@ class Exchange: try: order = self._api.fetch_order(order_id, pair, params=params) self._log_exchange_response('fetch_order', order) + order = self._order_contracts_to_amount(order) return order except ccxt.OrderNotFound as e: raise RetryableOrderError( @@ -942,6 +1190,7 @@ class Exchange: try: order = self._api.cancel_order(order_id, pair, params=params) self._log_exchange_response('cancel_order', order) + order = self._order_contracts_to_amount(order) return order except ccxt.InvalidOrder as e: raise InvalidOrderException( @@ -1029,6 +1278,71 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e + @retrier + def fetch_positions(self) -> List[Dict]: + if self._config['dry_run'] or self.trading_mode != TradingMode.FUTURES: + return [] + try: + positions: List[Dict] = self._api.fetch_positions() + self._log_exchange_response('fetch_positions', positions) + return positions + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not get positions due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + + @retrier + def fetch_trading_fees(self) -> Dict[str, Any]: + """ + Fetch user account trading fees + Can be cached, should not update often. + """ + if (self._config['dry_run'] or self.trading_mode != TradingMode.FUTURES + or not self.exchange_has('fetchTradingFees')): + return {} + try: + trading_fees: Dict[str, Any] = self._api.fetch_trading_fees() + self._log_exchange_response('fetch_trading_fees', trading_fees) + return trading_fees + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not fetch trading fees due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + + @retrier + def fetch_bids_asks(self, symbols: List[str] = None, cached: bool = False) -> Dict: + """ + :param cached: Allow cached result + :return: fetch_tickers result + """ + if not self.exchange_has('fetchBidsAsks'): + return {} + if cached: + tickers = self._fetch_tickers_cache.get('fetch_bids_asks') + if tickers: + return tickers + try: + tickers = self._api.fetch_bids_asks(symbols) + self._fetch_tickers_cache['fetch_bids_asks'] = tickers + return tickers + except ccxt.NotSupported as e: + raise OperationalException( + f'Exchange {self._api.name} does not support fetching bids/asks in batch. ' + f'Message: {e}') from e + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not load bids/asks due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + @retrier def get_tickers(self, symbols: List[str] = None, cached: bool = False) -> Dict: """ @@ -1114,7 +1428,8 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e - def get_rate(self, pair: str, refresh: bool, side: str) -> float: + def get_rate(self, pair: str, refresh: bool, + side: Literal['entry', 'exit'], is_short: bool) -> float: """ Calculates bid/ask target bid rate - between current ask price and last price @@ -1126,9 +1441,10 @@ class Exchange: :return: float: Price :raises PricingError if orderbook price could not be determined. """ - cache_rate: TTLCache = self._buy_rate_cache if side == "buy" else self._sell_rate_cache - [strat_name, name] = ['bid_strategy', 'Buy'] if side == "buy" else ['ask_strategy', 'Sell'] + name = side.capitalize() + strat_name = 'entry_pricing' if side == "entry" else 'exit_pricing' + cache_rate: TTLCache = self._entry_rate_cache if side == "entry" else self._exit_rate_cache if not refresh: rate = cache_rate.get(pair) # Check if cache has been invalidated @@ -1138,33 +1454,49 @@ class Exchange: conf_strategy = self._config.get(strat_name, {}) - if conf_strategy.get('use_order_book', False) and ('use_order_book' in conf_strategy): + price_side = conf_strategy['price_side'] + + if price_side in ('same', 'other'): + price_map = { + ('entry', 'long', 'same'): 'bid', + ('entry', 'long', 'other'): 'ask', + ('entry', 'short', 'same'): 'ask', + ('entry', 'short', 'other'): 'bid', + ('exit', 'long', 'same'): 'ask', + ('exit', 'long', 'other'): 'bid', + ('exit', 'short', 'same'): 'bid', + ('exit', 'short', 'other'): 'ask', + } + price_side = price_map[(side, 'short' if is_short else 'long', price_side)] + + price_side_word = price_side.capitalize() + + if conf_strategy.get('use_order_book', False): order_book_top = conf_strategy.get('order_book_top', 1) order_book = self.fetch_l2_order_book(pair, order_book_top) logger.debug('order_book %s', order_book) # top 1 = index 0 try: - rate = order_book[f"{conf_strategy['price_side']}s"][order_book_top - 1][0] + rate = order_book[f"{price_side}s"][order_book_top - 1][0] except (IndexError, KeyError) as e: logger.warning( f"{name} Price at location {order_book_top} from orderbook could not be " f"determined. Orderbook: {order_book}" ) raise PricingError from e - price_side = {conf_strategy['price_side'].capitalize()} - logger.debug(f"{name} price from orderbook {price_side}" + logger.debug(f"{name} price from orderbook {price_side_word}" f"side - top {order_book_top} order book {side} rate {rate:.8f}") else: - logger.debug(f"Using Last {conf_strategy['price_side'].capitalize()} / Last Price") + logger.debug(f"Using Last {price_side_word} / Last Price") ticker = self.fetch_ticker(pair) - ticker_rate = ticker[conf_strategy['price_side']] + ticker_rate = ticker[price_side] if ticker['last'] and ticker_rate: - if side == 'buy' and ticker_rate > ticker['last']: - balance = conf_strategy.get('ask_last_balance', 0.0) + if side == 'entry' and ticker_rate > ticker['last']: + balance = conf_strategy.get('price_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) + elif side == 'exit' and ticker_rate < ticker['last']: + balance = conf_strategy.get('price_last_balance', 0.0) ticker_rate = ticker_rate - balance * (ticker_rate - ticker['last']) rate = ticker_rate @@ -1209,6 +1541,9 @@ class Exchange: matched_trades = [trade for trade in my_trades if trade['order'] == order_id] self._log_exchange_response('get_trades_for_order', matched_trades) + + matched_trades = self._trades_contracts_to_amount(matched_trades) + return matched_trades except ccxt.DDoSProtection as e: raise DDosProtection(e) from e @@ -1303,7 +1638,8 @@ class Exchange: # Historic data def get_historic_ohlcv(self, pair: str, timeframe: str, - since_ms: int, is_new_pair: bool = False) -> List: + since_ms: int, candle_type: CandleType, + is_new_pair: bool = False) -> List: """ Get candle history using asyncio and returns the list of candles. Handles all async work for this. @@ -1311,34 +1647,38 @@ class Exchange: :param pair: Pair to download :param timeframe: Timeframe to get data for :param since_ms: Timestamp in milliseconds to get history from + :param candle_type: '', mark, index, premiumIndex, or funding_rate :return: List with candle (OHLCV) data """ - pair, timeframe, data = self.loop.run_until_complete( + pair, _, _, data = self.loop.run_until_complete( self._async_get_historic_ohlcv(pair=pair, timeframe=timeframe, - since_ms=since_ms, is_new_pair=is_new_pair)) + since_ms=since_ms, is_new_pair=is_new_pair, + candle_type=candle_type)) logger.info(f"Downloaded data for {pair} with length {len(data)}.") return data def get_historic_ohlcv_as_df(self, pair: str, timeframe: str, - since_ms: int) -> DataFrame: + since_ms: int, candle_type: CandleType) -> DataFrame: """ Minimal wrapper around get_historic_ohlcv - converting the result into a dataframe :param pair: Pair to download :param timeframe: Timeframe to get data for :param since_ms: Timestamp in milliseconds to get history from + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: OHLCV DataFrame """ - ticks = self.get_historic_ohlcv(pair, timeframe, since_ms=since_ms) + ticks = self.get_historic_ohlcv(pair, timeframe, since_ms=since_ms, candle_type=candle_type) return ohlcv_to_dataframe(ticks, timeframe, pair=pair, fill_missing=True, drop_incomplete=self._ohlcv_partial_candle) async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, - since_ms: int, is_new_pair: bool = False, - raise_: bool = False - ) -> Tuple[str, str, List]: + since_ms: int, candle_type: CandleType, + is_new_pair: bool = False, raise_: bool = False, + ) -> Tuple[str, str, str, List]: """ Download historic ohlcv :param is_new_pair: used by binance subclass to allow "fast" new pair downloading + :param candle_type: Any of the enum CandleType (must match trading mode!) """ one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe) @@ -1348,7 +1688,7 @@ class Exchange: arrow.utcnow().shift(seconds=one_call // 1000).humanize(only_distance=True) ) input_coroutines = [self._async_get_candle_history( - pair, timeframe, since) for since in + pair, timeframe, candle_type, since) for since in range(since_ms, arrow.utcnow().int_timestamp * 1000, one_call)] data: List = [] @@ -1364,14 +1704,16 @@ class Exchange: continue else: # Deconstruct tuple if it's not an exception - p, _, new_data = res - if p == pair: + p, _, c, new_data = res + if p == pair and c == candle_type: data.extend(new_data) # Sort data again after extending the result - above calls return in "async order" data = sorted(data, key=lambda x: x[0]) - return pair, timeframe, data + return pair, timeframe, candle_type, data + + def _build_coroutine(self, pair: str, timeframe: str, candle_type: CandleType, + since_ms: Optional[int]) -> Coroutine: - def _build_coroutine(self, pair: str, timeframe: str, since_ms: Optional[int]) -> Coroutine: if not since_ms and self.required_candle_call_count > 1: # Multiple calls for one pair - to get more history one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe) @@ -1381,15 +1723,16 @@ class Exchange: if since_ms: return self._async_get_historic_ohlcv( - pair, timeframe, since_ms=since_ms, raise_=True) + pair, timeframe, since_ms=since_ms, raise_=True, candle_type=candle_type) else: # One call ... "regular" refresh return self._async_get_candle_history( - pair, timeframe, since_ms=since_ms) + pair, timeframe, since_ms=since_ms, candle_type=candle_type) def refresh_latest_ohlcv(self, pair_list: ListPairsWithTimeframes, *, - since_ms: Optional[int] = None, cache: bool = True - ) -> Dict[Tuple[str, str], DataFrame]: + since_ms: Optional[int] = None, cache: bool = True, + drop_incomplete: bool = None + ) -> Dict[PairWithTimeframe, DataFrame]: """ Refresh in-memory OHLCV asynchronously and set `_klines` with the result Loops asynchronously over pair_list and downloads all pairs async (semi-parallel). @@ -1397,29 +1740,33 @@ class Exchange: :param pair_list: List of 2 element tuples containing pair, interval to refresh :param since_ms: time since when to download, in milliseconds :param cache: Assign result to _klines. Usefull for one-off downloads like for pairlists + :param drop_incomplete: Control candle dropping. + Specifying None defaults to _ohlcv_partial_candle :return: Dict of [{(pair, timeframe): Dataframe}] """ logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list)) - + drop_incomplete = self._ohlcv_partial_candle if drop_incomplete is None else drop_incomplete input_coroutines = [] cached_pairs = [] # Gather coroutines to run - for pair, timeframe in set(pair_list): - if timeframe not in self.timeframes: + for pair, timeframe, candle_type in set(pair_list): + if (timeframe not in self.timeframes + and candle_type in (CandleType.SPOT, CandleType.FUTURES)): logger.warning( f"Cannot download ({pair}, {timeframe}) combination as this timeframe is " f"not available on {self.name}. Available timeframes are " f"{', '.join(self.timeframes)}.") continue - if ((pair, timeframe) not in self._klines or not cache - or self._now_is_time_to_refresh(pair, timeframe)): - input_coroutines.append(self._build_coroutine(pair, timeframe, since_ms)) + if ((pair, timeframe, candle_type) not in self._klines or not cache + or self._now_is_time_to_refresh(pair, timeframe, candle_type)): + input_coroutines.append(self._build_coroutine( + pair, timeframe, candle_type=candle_type, since_ms=since_ms)) + else: logger.debug( - "Using cached candle (OHLCV) data for pair %s, timeframe %s ...", - pair, timeframe + f"Using cached candle (OHLCV) data for {pair}, {timeframe}, {candle_type} ..." ) - cached_pairs.append((pair, timeframe)) + cached_pairs.append((pair, timeframe, candle_type)) results_df = {} # Chunk requests into batches of 100 to avoid overwelming ccxt Throttling @@ -1429,42 +1776,53 @@ class Exchange: results = self.loop.run_until_complete(gather_stuff()) - # handle caching for res in results: if isinstance(res, Exception): logger.warning(f"Async code raised an exception: {repr(res)}") continue - # Deconstruct tuple (has 3 elements) - pair, timeframe, ticks = res + # Deconstruct tuple (has 4 elements) + pair, timeframe, c_type, ticks = res # keeping last candle time as last refreshed time of the pair if ticks: - self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000 + self._pairs_last_refresh_time[(pair, timeframe, c_type)] = ticks[-1][0] // 1000 # keeping parsed dataframe in cache ohlcv_df = ohlcv_to_dataframe( ticks, timeframe, pair=pair, fill_missing=True, - drop_incomplete=self._ohlcv_partial_candle) - results_df[(pair, timeframe)] = ohlcv_df + drop_incomplete=drop_incomplete) + results_df[(pair, timeframe, c_type)] = ohlcv_df if cache: - self._klines[(pair, timeframe)] = ohlcv_df - + self._klines[(pair, timeframe, c_type)] = ohlcv_df # Return cached klines - for pair, timeframe in cached_pairs: - results_df[(pair, timeframe)] = self.klines((pair, timeframe), copy=False) + for pair, timeframe, c_type in cached_pairs: + results_df[(pair, timeframe, c_type)] = self.klines( + (pair, timeframe, c_type), + copy=False + ) return results_df - def _now_is_time_to_refresh(self, pair: str, timeframe: str) -> bool: + def _now_is_time_to_refresh(self, pair: str, timeframe: str, candle_type: CandleType) -> bool: # Timeframe in seconds interval_in_sec = timeframe_to_seconds(timeframe) - return not ((self._pairs_last_refresh_time.get((pair, timeframe), 0) - + interval_in_sec) >= arrow.utcnow().int_timestamp) + return not ( + (self._pairs_last_refresh_time.get( + (pair, timeframe, candle_type), + 0 + ) + interval_in_sec) >= arrow.utcnow().int_timestamp + ) @retrier_async - async def _async_get_candle_history(self, pair: str, timeframe: str, - since_ms: Optional[int] = None) -> Tuple[str, str, List]: + async def _async_get_candle_history( + self, + pair: str, + timeframe: str, + candle_type: CandleType, + since_ms: Optional[int] = None, + ) -> Tuple[str, str, str, List]: """ Asynchronously get candle history data using fetch_ohlcv + :param candle_type: '', mark, index, premiumIndex, or funding_rate returns tuple: (pair, timeframe, ohlcv_list) """ try: @@ -1474,12 +1832,20 @@ class Exchange: "Fetching pair %s, interval %s, since %s %s...", pair, timeframe, since_ms, s ) - params = self._ft_has.get('ohlcv_params', {}) - data = await self._api_async.fetch_ohlcv(pair, timeframe=timeframe, - since=since_ms, - limit=self.ohlcv_candle_limit(timeframe), - params=params) - + params = deepcopy(self._ft_has.get('ohlcv_params', {})) + if candle_type != CandleType.SPOT: + params.update({'price': candle_type}) + if candle_type != CandleType.FUNDING_RATE: + data = await self._api_async.fetch_ohlcv( + pair, timeframe=timeframe, since=since_ms, + limit=self.ohlcv_candle_limit(timeframe), params=params) + else: + # Funding rate + data = await self._api_async.fetch_funding_rate_history( + pair, since=since_ms, + limit=self.ohlcv_candle_limit(timeframe)) + # Convert funding rate to candle pattern + data = [[x['timestamp'], x['fundingRate'], 0, 0, 0, 0] for x in data] # Some exchanges sort OHLCV in ASC order and others in DESC. # Ex: Bittrex returns the list of OHLCV in ASC order (oldest first, newest last) # while GDAX returns the list of OHLCV in DESC order (newest first, oldest last) @@ -1489,9 +1855,9 @@ class Exchange: data = sorted(data, key=lambda x: x[0]) except IndexError: logger.exception("Error loading %s. Result was %s.", pair, data) - return pair, timeframe, [] + return pair, timeframe, candle_type, [] logger.debug("Done fetching pair %s, interval %s ...", pair, timeframe) - return pair, timeframe, data + return pair, timeframe, candle_type, data except ccxt.NotSupported as e: raise OperationalException( @@ -1532,6 +1898,7 @@ class Exchange: '(' + arrow.get(since // 1000).isoformat() + ') ' if since is not None else '' ) trades = await self._api_async.fetch_trades(pair, since=since, limit=1000) + trades = self._trades_contracts_to_amount(trades) return trades_dict_to_list(trades) except ccxt.NotSupported as e: raise OperationalException( @@ -1667,13 +2034,549 @@ 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 + Dry-run handling happens as part of _calculate_funding_fees. + :param pair: (e.g. ADA/USDT) + :param since: The earliest time of consideration for calculating funding fees, + in unix time or as a datetime + """ + if not self.exchange_has("fetchFundingHistory"): + raise OperationalException( + f"fetch_funding_history() is not available using {self.name}" + ) + + if type(since) is datetime: + since = int(since.timestamp()) * 1000 # * 1000 for ms + + try: + funding_history = self._api.fetch_funding_history( + symbol=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 + + @retrier + def get_leverage_tiers(self) -> Dict[str, List[Dict]]: + try: + return self._api.fetch_leverage_tiers() + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not load leverage tiers due to {e.__class__.__name__}. Message: {e}' + ) from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + + @retrier + def get_market_leverage_tiers(self, symbol) -> List[Dict]: + try: + return self._api.fetch_market_leverage_tiers(symbol) + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not load leverage tiers for {symbol}' + f' due to {e.__class__.__name__}. Message: {e}' + ) from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + + def load_leverage_tiers(self) -> Dict[str, List[Dict]]: + if self.trading_mode == TradingMode.FUTURES: + if self.exchange_has('fetchLeverageTiers'): + # Fetch all leverage tiers at once + return self.get_leverage_tiers() + elif self.exchange_has('fetchMarketLeverageTiers'): + # Must fetch the leverage tiers for each market separately + # * This is slow(~45s) on Okx, makes ~90 api calls to load all linear swap markets + markets = self.markets + symbols = [] + + for symbol, market in markets.items(): + if (self.market_is_future(market) + and market['quote'] == self._config['stake_currency']): + symbols.append(symbol) + + tiers: Dict[str, List[Dict]] = {} + + # Be verbose here, as this delays startup by ~1 minute. + logger.info( + f"Initializing leverage_tiers for {len(symbols)} markets. " + "This will take about a minute.") + + for symbol in sorted(symbols): + tiers[symbol] = self.get_market_leverage_tiers(symbol) + + logger.info(f"Done initializing {len(symbols)} markets.") + + return tiers + else: + return {} + else: + return {} + + def fill_leverage_tiers(self) -> None: + """ + Assigns property _leverage_tiers to a dictionary of information about the leverage + allowed on each pair + """ + leverage_tiers = self.load_leverage_tiers() + for pair, tiers in leverage_tiers.items(): + pair_tiers = [] + for tier in tiers: + pair_tiers.append(self.parse_leverage_tier(tier)) + self._leverage_tiers[pair] = pair_tiers + + def parse_leverage_tier(self, tier) -> Dict: + info = tier.get('info', {}) + return { + 'min': tier['notionalFloor'], + 'max': tier['notionalCap'], + 'mmr': tier['maintenanceMarginRate'], + 'lev': tier['maxLeverage'], + 'maintAmt': float(info['cum']) if 'cum' in info else None, + } + + def get_max_leverage(self, pair: str, stake_amount: Optional[float]) -> float: + """ + Returns the maximum leverage that a pair can be traded at + :param pair: The base/quote currency pair being traded + :stake_amount: The total value of the traders margin_mode in quote currency + """ + + if self.trading_mode == TradingMode.SPOT: + return 1.0 + + if self.trading_mode == TradingMode.FUTURES: + + # Checks and edge cases + if stake_amount is None: + raise OperationalException( + f'{self.name}.get_max_leverage requires argument stake_amount' + ) + + if pair not in self._leverage_tiers: + # Maybe raise exception because it can't be traded on futures? + return 1.0 + + pair_tiers = self._leverage_tiers[pair] + + if stake_amount == 0: + return self._leverage_tiers[pair][0]['lev'] # Max lev for lowest amount + + for tier_index in range(len(pair_tiers)): + + tier = pair_tiers[tier_index] + lev = tier['lev'] + + if tier_index < len(pair_tiers) - 1: + next_tier = pair_tiers[tier_index+1] + next_floor = next_tier['min'] / next_tier['lev'] + if next_floor > stake_amount: # Next tier min too high for stake amount + return min((tier['max'] / stake_amount), lev) + # + # With the two leverage tiers below, + # - a stake amount of 150 would mean a max leverage of (10000 / 150) = 66.66 + # - stakes below 133.33 = max_lev of 75 + # - stakes between 133.33-200 = max_lev of 10000/stake = 50.01-74.99 + # - stakes from 200 + 1000 = max_lev of 50 + # + # { + # "min": 0, # stake = 0.0 + # "max": 10000, # max_stake@75 = 10000/75 = 133.33333333333334 + # "lev": 75, + # }, + # { + # "min": 10000, # stake = 200.0 + # "max": 50000, # max_stake@50 = 50000/50 = 1000.0 + # "lev": 50, + # } + # + + else: # if on the last tier + if stake_amount > tier['max']: # If stake is > than max tradeable amount + raise InvalidOrderException(f'Amount {stake_amount} too high for {pair}') + else: + return tier['lev'] + + raise OperationalException( + 'Looped through all tiers without finding a max leverage. Should never be reached' + ) + + elif self.trading_mode == TradingMode.MARGIN: # Search markets.limits for max lev + market = self.markets[pair] + if market['limits']['leverage']['max'] is not None: + return market['limits']['leverage']['max'] + else: + return 1.0 # Default if max leverage cannot be found + else: + return 1.0 + + @retrier + def _set_leverage( + self, + leverage: float, + pair: Optional[str] = None, + trading_mode: Optional[TradingMode] = None + ): + """ + Set's the leverage before making a trade, in order to not + have the same leverage on every trade + """ + if self._config['dry_run'] or not self.exchange_has("setLeverage"): + # Some exchanges only support one margin_mode type + return + + try: + self._api.set_leverage(symbol=pair, leverage=leverage) + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + + def get_interest_rate(self) -> float: + """ + Retrieve interest rate - necessary for Margin trading. + Should not call the exchange directly when used from backtesting. + """ + return 0.0 + + def get_liquidation_price( + self, + pair: str, + open_rate: float, + amount: float, # quote currency, includes leverage + leverage: float, + is_short: bool + ) -> Optional[float]: + + if self.trading_mode in TradingMode.SPOT: + return None + elif ( + self.margin_mode == MarginMode.ISOLATED and + self.trading_mode == TradingMode.FUTURES + ): + wallet_balance = (amount * open_rate) / leverage + isolated_liq = self.get_or_calculate_liquidation_price( + pair=pair, + open_rate=open_rate, + is_short=is_short, + position=amount, + wallet_balance=wallet_balance, + mm_ex_1=0.0, + upnl_ex_1=0.0, + ) + return isolated_liq + else: + raise OperationalException( + "Freqtrade only supports isolated futures for leverage trading") + + def funding_fee_cutoff(self, open_date: datetime): + """ + :param open_date: The open date for a trade + :return: The cutoff open time for when a funding fee is charged + """ + return open_date.minute > 0 or open_date.second > 0 + + @retrier + def set_margin_mode(self, pair: str, margin_mode: MarginMode, params: dict = {}): + """ + Set's the margin mode on the exchange to cross or isolated for a specific pair + :param pair: base/quote currency pair (e.g. "ADA/USDT") + """ + if self._config['dry_run'] or not self.exchange_has("setMarginMode"): + # Some exchanges only support one margin_mode type + return + + try: + self._api.set_margin_mode(margin_mode.value, pair, params) + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not set margin mode due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + + def _fetch_and_calculate_funding_fees( + self, + pair: str, + amount: float, + is_short: bool, + open_date: datetime, + close_date: Optional[datetime] = None + ) -> float: + """ + Fetches and calculates the sum of all funding fees that occurred for a pair + during a futures trade. + Only used during dry-run or if the exchange does not provide a funding_rates endpoint. + :param pair: The quote/base pair of the trade + :param amount: The quantity of the trade + :param is_short: trade direction + :param open_date: The date and time that the trade started + :param close_date: The date and time that the trade ended + """ + + if self.funding_fee_cutoff(open_date): + open_date += timedelta(hours=1) + timeframe = self._ft_has['mark_ohlcv_timeframe'] + timeframe_ff = self._ft_has.get('funding_fee_timeframe', + self._ft_has['mark_ohlcv_timeframe']) + + if not close_date: + close_date = datetime.now(timezone.utc) + open_timestamp = int(timeframe_to_prev_date(timeframe, open_date).timestamp()) * 1000 + # close_timestamp = int(close_date.timestamp()) * 1000 + + mark_comb: PairWithTimeframe = ( + pair, timeframe, CandleType.from_string(self._ft_has["mark_ohlcv_price"])) + + funding_comb: PairWithTimeframe = (pair, timeframe_ff, CandleType.FUNDING_RATE) + candle_histories = self.refresh_latest_ohlcv( + [mark_comb, funding_comb], + since_ms=open_timestamp, + cache=False, + drop_incomplete=False, + ) + funding_rates = candle_histories[funding_comb] + mark_rates = candle_histories[mark_comb] + funding_mark_rates = self.combine_funding_and_mark( + funding_rates=funding_rates, mark_rates=mark_rates) + + return self.calculate_funding_fees( + funding_mark_rates, + amount=amount, + is_short=is_short, + open_date=open_date, + close_date=close_date + ) + + @staticmethod + def combine_funding_and_mark(funding_rates: DataFrame, mark_rates: DataFrame) -> DataFrame: + """ + Combine funding-rates and mark-rates dataframes + :param funding_rates: Dataframe containing Funding rates (Type FUNDING_RATE) + :param mark_rates: Dataframe containing Mark rates (Type mark_ohlcv_price) + """ + + return funding_rates.merge(mark_rates, on='date', how="inner", suffixes=["_fund", "_mark"]) + + def calculate_funding_fees( + self, + df: DataFrame, + amount: float, + is_short: bool, + open_date: datetime, + close_date: Optional[datetime] = None, + time_in_ratio: Optional[float] = None + ) -> float: + """ + calculates the sum of all funding fees that occurred for a pair during a futures trade + :param df: Dataframe containing combined funding and mark rates + as `open_fund` and `open_mark`. + :param amount: The quantity of the trade + :param is_short: trade direction + :param open_date: The date and time that the trade started + :param close_date: The date and time that the trade ended + :param time_in_ratio: Not used by most exchange classes + """ + fees: float = 0 + + if not df.empty: + df = df[(df['date'] >= open_date) & (df['date'] <= close_date)] + fees = sum(df['open_fund'] * df['open_mark'] * amount) + + # Negate fees for longs as funding_fees expects it this way based on live endpoints. + return fees if is_short else -fees + + def get_funding_fees( + self, pair: str, amount: float, is_short: bool, open_date: datetime) -> float: + """ + Fetch funding fees, either from the exchange (live) or calculates them + based on funding rate/mark price history + :param pair: The quote/base pair of the trade + :param is_short: trade direction + :param amount: Trade amount + :param open_date: Open date of the trade + """ + if self.trading_mode == TradingMode.FUTURES: + if self._config['dry_run']: + funding_fees = self._fetch_and_calculate_funding_fees( + pair, amount, is_short, open_date) + else: + funding_fees = self._get_funding_fees_from_exchange(pair, open_date) + return funding_fees + else: + return 0.0 + + @retrier + def get_or_calculate_liquidation_price( + self, + pair: str, + # Dry-run + open_rate: float, # Entry price of position + is_short: bool, + position: float, # Absolute value of position size + wallet_balance: float, # Or margin balance + mm_ex_1: float = 0.0, # (Binance) Cross only + upnl_ex_1: float = 0.0, # (Binance) Cross only + ) -> Optional[float]: + """ + Set's the margin mode on the exchange to cross or isolated for a specific pair + :param pair: base/quote currency pair (e.g. "ADA/USDT") + """ + if self.trading_mode == TradingMode.SPOT: + return None + elif (self.trading_mode != TradingMode.FUTURES and self.margin_mode != MarginMode.ISOLATED): + raise OperationalException( + f"{self.name} does not support {self.margin_mode.value} {self.trading_mode.value}") + + if self._config['dry_run'] or not self.exchange_has("fetchPositions"): + + isolated_liq = self.dry_run_liquidation_price( + pair=pair, + open_rate=open_rate, + is_short=is_short, + position=position, + wallet_balance=wallet_balance, + mm_ex_1=mm_ex_1, + upnl_ex_1=upnl_ex_1 + ) + else: + try: + positions = self._api.fetch_positions([pair]) + if len(positions) > 0: + pos = positions[0] + isolated_liq = pos['liquidationPrice'] + else: + return None + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not set margin mode due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + + if isolated_liq: + buffer_amount = abs(open_rate - isolated_liq) * self.liquidation_buffer + isolated_liq = ( + isolated_liq - buffer_amount + if is_short else + isolated_liq + buffer_amount + ) + return isolated_liq + else: + return None + + def dry_run_liquidation_price( + self, + pair: str, + open_rate: float, # Entry price of position + is_short: bool, + position: float, # Absolute value of position size + wallet_balance: float, # Or margin balance + mm_ex_1: float = 0.0, # (Binance) Cross only + upnl_ex_1: float = 0.0, # (Binance) Cross only + ) -> Optional[float]: + """ + PERPETUAL: + gateio: https://www.gate.io/help/futures/perpetual/22160/calculation-of-liquidation-price + okex: https://www.okex.com/support/hc/en-us/articles/ + 360053909592-VI-Introduction-to-the-isolated-mode-of-Single-Multi-currency-Portfolio-margin + Important: Must be fetching data from cached values as this is used by backtesting! + + :param exchange_name: + :param open_rate: Entry price of position + :param is_short: True if the trade is a short, false otherwise + :param position: Absolute value of position size incl. leverage (in base currency) + :param trading_mode: SPOT, MARGIN, FUTURES, etc. + :param margin_mode: Either ISOLATED or CROSS + :param wallet_balance: Amount of margin_mode in the wallet being used to trade + Cross-Margin Mode: crossWalletBalance + Isolated-Margin Mode: isolatedWalletBalance + + # * Not required by Gateio or OKX + :param mm_ex_1: + :param upnl_ex_1: + """ + + market = self.markets[pair] + taker_fee_rate = market['taker'] + mm_ratio, _ = self.get_maintenance_ratio_and_amt(pair, position) + + if self.trading_mode == TradingMode.FUTURES and self.margin_mode == MarginMode.ISOLATED: + + if market['inverse']: + raise OperationalException( + "Freqtrade does not yet support inverse contracts") + + value = wallet_balance / position + + mm_ratio_taker = (mm_ratio + taker_fee_rate) + if is_short: + return (open_rate + value) / (1 + mm_ratio_taker) + else: + return (open_rate - value) / (1 - mm_ratio_taker) + else: + raise OperationalException( + "Freqtrade only supports isolated futures for leverage trading") + + def get_maintenance_ratio_and_amt( + self, + pair: str, + nominal_value: float = 0.0, + ) -> Tuple[float, Optional[float]]: + """ + Important: Must be fetching data from cached values as this is used by backtesting! + :param pair: Market symbol + :param nominal_value: The total trade amount in quote currency including leverage + maintenance amount only on Binance + :return: (maintenance margin ratio, maintenance amount) + """ + + if (self._config.get('runmode') in OPTIMIZE_MODES + or self.exchange_has('fetchLeverageTiers') + or self.exchange_has('fetchMarketLeverageTiers')): + + if pair not in self._leverage_tiers: + raise InvalidOrderException( + f"Maintenance margin rate for {pair} is unavailable for {self.name}" + ) + + pair_tiers = self._leverage_tiers[pair] + + for tier in reversed(pair_tiers): + if nominal_value >= tier['min']: + return (tier['mmr'], tier['maintAmt']) + + raise OperationalException("nominal value can not be lower than 0") + # The lowest notional_floor for any pair in fetch_leverage_tiers is always 0 because it + # describes the min amt for a tier, and the lowest tier will always go down to 0 + else: + raise OperationalException(f"Cannot get maintenance ratio using {self.name}") + def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool: return exchange_name in ccxt_exchanges(ccxt_module) def is_exchange_officially_supported(exchange_name: str) -> bool: - return exchange_name in ['binance', 'bittrex', 'ftx', 'gateio', 'huobi', 'kraken', 'okx'] + return exchange_name in SUPPORTED_EXCHANGES def ccxt_exchanges(ccxt_module: CcxtModuleType = None) -> List[str]: diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index a346216b3..f20aab138 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -1,9 +1,10 @@ """ FTX exchange subclass """ import logging -from typing import Any, Dict +from typing import Any, Dict, List, Tuple import ccxt +from freqtrade.enums import MarginMode, TradingMode from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, OperationalException, TemporaryError) from freqtrade.exchange import Exchange @@ -20,27 +21,29 @@ class Ftx(Exchange): "stoploss_on_exchange": True, "ohlcv_candle_limit": 1500, "ohlcv_volume_currency": "quote", + "mark_ohlcv_price": "index", + "mark_ohlcv_timeframe": "1h", } - def market_is_tradable(self, market: Dict[str, Any]) -> bool: - """ - Check if the market symbol is tradable by Freqtrade. - Default checks + check if pair is spot pair (no futures trading yet). - """ - parent_check = super().market_is_tradable(market) + _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + # TradingMode.SPOT always supported and not required in this list + # (TradingMode.MARGIN, MarginMode.CROSS), + # (TradingMode.FUTURES, MarginMode.CROSS) + ] - return (parent_check and - market.get('spot', False) is True) - - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ - return order['type'] == 'stop' and stop_loss > float(order['price']) + return order['type'] == 'stop' and ( + side == "sell" and stop_loss > float(order['price']) or + side == "buy" and stop_loss < float(order['price']) + ) @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: + def stoploss(self, pair: str, amount: float, stop_price: float, + order_types: Dict, side: str, leverage: float) -> Dict: """ Creates a stoploss order. depending on order_types.stoploss configuration, uses 'market' or limit order. @@ -48,7 +51,10 @@ class Ftx(Exchange): Limit orders are defined by having orderPrice set, otherwise a market order is used. """ limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) - limit_rate = stop_price * limit_price_pct + if side == "sell": + limit_rate = stop_price * limit_price_pct + else: + limit_rate = stop_price * (2 - limit_price_pct) ordertype = "stop" @@ -56,7 +62,7 @@ class Ftx(Exchange): if self._config['dry_run']: dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price, stop_loss=True) + pair, ordertype, side, amount, stop_price, leverage, stop_loss=True) return dry_order try: @@ -64,11 +70,14 @@ class Ftx(Exchange): if order_types.get('stoploss', 'market') == 'limit': # set orderPrice to place limit order, otherwise it's a market order params['orderPrice'] = limit_rate + if self.trading_mode == TradingMode.FUTURES: + params.update({'reduceOnly': True}) params['stopPrice'] = stop_price amount = self.amount_to_precision(pair, amount) - order = self._api.create_order(symbol=pair, type=ordertype, side='sell', + self._lev_prep(pair, leverage, side) + order = self._api.create_order(symbol=pair, type=ordertype, side=side, amount=amount, params=params) self._log_exchange_response('create_stoploss_order', order) logger.info('stoploss order added for %s. ' @@ -76,19 +85,19 @@ class Ftx(Exchange): return order except ccxt.InsufficientFunds as e: raise InsufficientFundsError( - f'Insufficient funds to create {ordertype} sell order on market {pair}. ' + f'Insufficient funds to create {ordertype} {side} order on market {pair}. ' f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' f'Message: {e}') from e except ccxt.InvalidOrder as e: raise InvalidOrderException( - f'Could not create {ordertype} sell order on market {pair}. ' + f'Could not create {ordertype} {side} order on market {pair}. ' f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' f'Message: {e}') from e except ccxt.DDoSProtection as e: raise DDosProtection(e) from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( - f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e + f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py index d0fd787b7..609cf4901 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gateio.py @@ -1,7 +1,9 @@ """ Gate.io exchange subclass """ import logging -from typing import Dict +from datetime import datetime +from typing import Dict, List, Optional, Tuple +from freqtrade.enums import MarginMode, TradingMode from freqtrade.exceptions import OperationalException from freqtrade.exchange import Exchange @@ -26,12 +28,48 @@ class Gateio(Exchange): "stoploss_on_exchange": True, } + _ft_has_futures: Dict = { + "needs_trading_fees": True + } + + _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + # TradingMode.SPOT always supported and not required in this list + # (TradingMode.MARGIN, MarginMode.CROSS), + # (TradingMode.FUTURES, MarginMode.CROSS), + (TradingMode.FUTURES, MarginMode.ISOLATED) + ] + 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.') + if self.trading_mode != TradingMode.FUTURES: + if any(v == 'market' for k, v in order_types.items()): + raise OperationalException( + f'Exchange {self.name} does not support market orders.') + + def get_trades_for_order(self, order_id: str, pair: str, since: datetime, + params: Optional[Dict] = None) -> List: + trades = super().get_trades_for_order(order_id, pair, since, params) + + if self.trading_mode == TradingMode.FUTURES: + # Futures usually don't contain fees in the response. + # As such, futures orders on gateio will not contain a fee, which causes + # a repeated "update fee" cycle and wrong calculations. + # Therefore we patch the response with fees if it's not available. + # An alternative also contianing fees would be + # privateFuturesGetSettleAccountBook({"settle": "usdt"}) + pair_fees = self._trading_fees.get(pair, {}) + if pair_fees: + for idx, trade in enumerate(trades): + if trade.get('fee', {}).get('cost') is None: + takerOrMaker = trade.get('takerOrMaker', 'taker') + if pair_fees.get(takerOrMaker) is not None: + trades[idx]['fee'] = { + 'currency': self.get_pair_quote_currency(pair), + 'cost': trade['cost'] * pair_fees[takerOrMaker], + 'rate': pair_fees[takerOrMaker], + } + return trades def fetch_stoploss_order(self, order_id: str, pair: str, params={}) -> Dict: return self.fetch_order( @@ -47,9 +85,10 @@ class Gateio(Exchange): params={'stop': True} ) - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ - return stop_loss > float(order['stopPrice']) + return ((side == "sell" and stop_loss > float(order['stopPrice'])) or + (side == "buy" and stop_loss < float(order['stopPrice']))) diff --git a/freqtrade/exchange/huobi.py b/freqtrade/exchange/huobi.py index d07e13497..71c4d1cf6 100644 --- a/freqtrade/exchange/huobi.py +++ b/freqtrade/exchange/huobi.py @@ -22,7 +22,7 @@ class Huobi(Exchange): "l2_limit_range_required": False, } - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 8cec2500e..94727afa6 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -1,9 +1,12 @@ """ Kraken exchange subclass """ import logging -from typing import Any, Dict, List +from datetime import datetime +from typing import Any, Dict, List, Optional, Tuple import ccxt +from pandas import DataFrame +from freqtrade.enums import MarginMode, TradingMode from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, OperationalException, TemporaryError) from freqtrade.exchange import Exchange @@ -21,8 +24,15 @@ class Kraken(Exchange): "ohlcv_candle_limit": 720, "trades_pagination": "id", "trades_pagination_arg": "since", + "mark_ohlcv_timeframe": "4h", } + _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + # TradingMode.SPOT always supported and not required in this list + # (TradingMode.MARGIN, MarginMode.CROSS), + # (TradingMode.FUTURES, MarginMode.CROSS) + ] + def market_is_tradable(self, market: Dict[str, Any]) -> bool: """ Check if the market symbol is tradable by Freqtrade. @@ -73,16 +83,19 @@ class Kraken(Exchange): except ccxt.BaseError as e: raise OperationalException(e) from e - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ - return (order['type'] in ('stop-loss', 'stop-loss-limit') - and stop_loss > float(order['price'])) + return (order['type'] in ('stop-loss', 'stop-loss-limit') and ( + (side == "sell" and stop_loss > float(order['price'])) or + (side == "buy" and stop_loss < float(order['price'])) + )) @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: + def stoploss(self, pair: str, amount: float, stop_price: float, + order_types: Dict, side: str, leverage: float) -> Dict: """ Creates a stoploss market order. Stoploss market orders is the only stoploss type supported by kraken. @@ -90,11 +103,16 @@ class Kraken(Exchange): (careful, prices are reversed) """ params = self._params.copy() + if self.trading_mode == TradingMode.FUTURES: + params.update({'reduceOnly': True}) if order_types.get('stoploss', 'market') == 'limit': ordertype = "stop-loss-limit" limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) - limit_rate = stop_price * limit_price_pct + if side == "sell": + limit_rate = stop_price * limit_price_pct + else: + limit_rate = stop_price * (2 - limit_price_pct) params['price2'] = self.price_to_precision(pair, limit_rate) else: ordertype = "stop-loss" @@ -103,13 +121,13 @@ class Kraken(Exchange): if self._config['dry_run']: dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price, stop_loss=True) + pair, ordertype, side, amount, stop_price, leverage, stop_loss=True) return dry_order try: amount = self.amount_to_precision(pair, amount) - order = self._api.create_order(symbol=pair, type=ordertype, side='sell', + order = self._api.create_order(symbol=pair, type=ordertype, side=side, amount=amount, price=stop_price, params=params) self._log_exchange_response('create_stoploss_order', order) logger.info('stoploss order added for %s. ' @@ -117,18 +135,81 @@ class Kraken(Exchange): return order except ccxt.InsufficientFunds as e: raise InsufficientFundsError( - f'Insufficient funds to create {ordertype} sell order on market {pair}. ' + f'Insufficient funds to create {ordertype} {side} order on market {pair}. ' f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' f'Message: {e}') from e except ccxt.InvalidOrder as e: raise InvalidOrderException( - f'Could not create {ordertype} sell order on market {pair}. ' + f'Could not create {ordertype} {side} order on market {pair}. ' f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' f'Message: {e}') from e except ccxt.DDoSProtection as e: raise DDosProtection(e) from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( - f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e + f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e + + def _set_leverage( + self, + leverage: float, + pair: Optional[str] = None, + trading_mode: Optional[TradingMode] = None + ): + """ + Kraken set's the leverage as an option in the order object, so we need to + add it to params + """ + return + + def _get_params( + self, + ordertype: str, + leverage: float, + reduceOnly: bool, + time_in_force: str = 'gtc' + ) -> Dict: + params = super()._get_params( + ordertype=ordertype, + leverage=leverage, + reduceOnly=reduceOnly, + time_in_force=time_in_force, + ) + if leverage > 1.0: + params['leverage'] = round(leverage) + return params + + def calculate_funding_fees( + self, + df: DataFrame, + amount: float, + is_short: bool, + open_date: datetime, + close_date: Optional[datetime] = None, + time_in_ratio: Optional[float] = None + ) -> float: + """ + # ! This method will always error when run by Freqtrade because time_in_ratio is never + # ! passed to _get_funding_fee. For kraken futures to work in dry run and backtesting + # ! functionality must be added that passes the parameter time_in_ratio to + # ! _get_funding_fee when using Kraken + calculates the sum of all funding fees that occurred for a pair during a futures trade + :param df: Dataframe containing combined funding and mark rates + as `open_fund` and `open_mark`. + :param amount: The quantity of the trade + :param is_short: trade direction + :param open_date: The date and time that the trade started + :param close_date: The date and time that the trade ended + :param time_in_ratio: Not used by most exchange classes + """ + if not time_in_ratio: + raise OperationalException( + f"time_in_ratio is required for {self.name}._get_funding_fee") + fees: float = 0 + + if not df.empty: + df = df[(df['date'] >= open_date) & (df['date'] <= close_date)] + fees = sum(df['open_fund'] * df['open_mark'] * amount * time_in_ratio) + + return fees if is_short else -fees diff --git a/freqtrade/exchange/kucoin.py b/freqtrade/exchange/kucoin.py index 5272fdb73..f23189b3c 100644 --- a/freqtrade/exchange/kucoin.py +++ b/freqtrade/exchange/kucoin.py @@ -28,7 +28,7 @@ class Kucoin(Exchange): "ohlcv_candle_limit": 1500, } - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index 8e2cccf46..fb7388ee1 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -1,7 +1,12 @@ import logging -from typing import Dict +from typing import Dict, List, Tuple +import ccxt + +from freqtrade.enums import MarginMode, TradingMode +from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exchange import Exchange +from freqtrade.exchange.common import retrier logger = logging.getLogger(__name__) @@ -15,4 +20,69 @@ class Okx(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 300, + "mark_ohlcv_timeframe": "4h", + "funding_fee_timeframe": "8h", } + _ft_has_futures: Dict = { + "tickers_have_quoteVolume": False, + } + + _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + # TradingMode.SPOT always supported and not required in this list + # (TradingMode.MARGIN, MarginMode.CROSS), + # (TradingMode.FUTURES, MarginMode.CROSS), + (TradingMode.FUTURES, MarginMode.ISOLATED), + ] + + def _get_params( + self, + ordertype: str, + leverage: float, + reduceOnly: bool, + time_in_force: str = 'gtc', + ) -> Dict: + params = super()._get_params( + ordertype=ordertype, + leverage=leverage, + reduceOnly=reduceOnly, + time_in_force=time_in_force, + ) + if self.trading_mode == TradingMode.FUTURES and self.margin_mode: + params['tdMode'] = self.margin_mode.value + return params + + @retrier + def _lev_prep(self, pair: str, leverage: float, side: str): + if self.trading_mode != TradingMode.SPOT and self.margin_mode is not None: + try: + # TODO-lev: Test me properly (check mgnMode passed) + self._api.set_leverage( + leverage=leverage, + symbol=pair, + params={ + "mgnMode": self.margin_mode.value, + # "posSide": "net"", + }) + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + + def get_max_pair_stake_amount( + self, + pair: str, + price: float, + leverage: float = 1.0 + ) -> float: + + if self.trading_mode == TradingMode.SPOT: + return float('inf') # Not actually inf, but this probably won't matter for SPOT + + if pair not in self._leverage_tiers: + return float('inf') + + pair_tiers = self._leverage_tiers[pair] + return pair_tiers[-1]['max'] / leverage diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 1c2b7208f..9a07020ad 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -4,17 +4,20 @@ 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 +from typing import Any, Dict, List, Literal, Optional, Tuple + +from schedule import Scheduler from freqtrade import __version__, constants from freqtrade.configuration import validate_config_consistency from freqtrade.data.converter import order_book_to_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.edge import Edge -from freqtrade.enums import RPCMessageType, RunMode, SellType, State +from freqtrade.enums import (ExitCheckTuple, ExitType, RPCMessageType, RunMode, SignalDirection, + State, TradingMode) from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError, InvalidOrderException, PricingError) from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds @@ -25,7 +28,7 @@ from freqtrade.plugins.pairlistmanager import PairListManager from freqtrade.plugins.protectionmanager import ProtectionManager from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.rpc import RPCManager -from freqtrade.strategy.interface import IStrategy, SellCheckTuple +from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.wallets import Wallets @@ -96,10 +99,26 @@ class FreqtradeBot(LoggingMixin): initial_state = self.config.get('initial_state') self.state = State[initial_state.upper()] if initial_state else State.STOPPED - # Protect sell-logic from forcesell and vice versa + # Protect exit-logic from forcesell and vice versa self._exit_lock = Lock() LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe)) + self.trading_mode: TradingMode = self.config.get('trading_mode', TradingMode.SPOT) + + 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) self.last_process = datetime(1970, 1, 1, tzinfo=timezone.utc) def notify_status(self, msg: str) -> None: @@ -171,9 +190,9 @@ class FreqtradeBot(LoggingMixin): # Check and handle any timed out open orders self.check_handle_timedout() - # Protect from collisions with forcesell. + # Protect from collisions with forceexit. # Without this, freqtrade my try to recreate stoploss_on_exchange orders - # while selling is in process, since telegram messages arrive in an different thread. + # while exiting is in process, since telegram messages arrive in an different thread. with self._exit_lock: trades = Trade.get_open_trades() # First process current opened trades (positions) @@ -187,7 +206,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() self.last_process = datetime.now(timezone.utc) @@ -245,6 +265,20 @@ 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: + trades = Trade.get_open_trades() + for trade in trades: + funding_fees = self.exchange.get_funding_fees( + pair=trade.pair, + amount=trade.amount, + is_short=trade.is_short, + open_date=trade.open_date_utc + ) + trade.funding_fees = funding_fees + else: + return 0.0 + def startup_update_open_orders(self): """ Updates open orders based on order list kept in the database. @@ -267,6 +301,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. @@ -276,31 +313,36 @@ class FreqtradeBot(LoggingMixin): # Updating open orders in dry-run does not make sense and will fail. return - trades: List[Trade] = Trade.get_sold_trades_without_assigned_fees() + trades: List[Trade] = Trade.get_closed_trades_without_assigned_fees() for trade in trades: - - if not trade.is_open and not trade.fee_updated('sell'): + if not trade.is_open and not trade.fee_updated(trade.exit_side): # Get sell fee - order = trade.select_order('sell', False) + order = trade.select_order(trade.exit_side, False) if order: - logger.info(f"Updating sell-fee on trade {trade} for order {order.order_id}.") + logger.info( + f"Updating {trade.exit_side}-fee on trade {trade}" + f"for order {order.order_id}." + ) self.update_trade_state(trade, order.order_id, stoploss_order=order.ft_order_side == 'stoploss', send_msg=False) trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() for trade in trades: - if trade.is_open and not trade.fee_updated('buy'): - order = trade.select_order('buy', False) - open_order = trade.select_order('buy', True) + if trade.is_open and not trade.fee_updated(trade.enter_side): + order = trade.select_order(trade.enter_side, False) + open_order = trade.select_order(trade.enter_side, True) if order and open_order is None: - logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.") + logger.info( + f"Updating {trade.enter_side}-fee on trade {trade}" + f"for order {order.order_id}." + ) self.update_trade_state(trade, order.order_id, send_msg=False) def handle_insufficient_funds(self, trade: Trade): """ Try refinding a lost trade. - Only used when InsufficientFunds appears on sell orders (stoploss or sell). + Only used when InsufficientFunds appears on exit orders (stoploss or long sell/short buy). Tries to walk the stored orders and sell them off eventually. """ logger.info(f"Trying to refind lost order for {trade}") @@ -317,11 +359,11 @@ class FreqtradeBot(LoggingMixin): if fo and fo['status'] == 'open': # Assume this as the open stoploss order trade.stoploss_order_id = order.order_id - elif order.ft_order_side == 'sell': + elif order.ft_order_side == trade.exit_side: if fo and fo['status'] == 'open': # Assume this as the open order trade.open_order_id = order.order_id - elif order.ft_order_side == 'buy': + elif order.ft_order_side == trade.enter_side: if fo and fo['status'] == 'open': trade.open_order_id = order.order_id if fo: @@ -338,7 +380,7 @@ class FreqtradeBot(LoggingMixin): def enter_positions(self) -> int: """ - Tries to execute buy orders for new trades (positions) + Tries to execute entry orders for new trades (positions) """ trades_created = 0 @@ -354,7 +396,7 @@ class FreqtradeBot(LoggingMixin): if not whitelist: logger.info("No currency pair in active pair whitelist, " - "but checking to sell open trades.") + "but checking to exit open trades.") return trades_created if PairLocks.is_global_lock(): lock = PairLocks.get_pair_longest_lock('*') @@ -373,7 +415,7 @@ class FreqtradeBot(LoggingMixin): logger.warning('Unable to create trade for %s: %s', pair, exception) if not trades_created: - logger.debug("Found no buy signals for whitelisted currencies. Trying again...") + logger.debug("Found no enter signals for whitelisted currencies. Trying again...") return trades_created @@ -408,24 +450,34 @@ class FreqtradeBot(LoggingMixin): return False # running get_signal on historical data fetched - (buy, sell, buy_tag, _) = self.strategy.get_signal( + (signal, enter_tag) = self.strategy.get_entry_signal( pair, self.strategy.timeframe, analyzed_df ) - if buy and not sell: + if signal: stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge) - bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {}) + bid_check_dom = self.config.get('entry_pricing', {}).get('check_depth_of_market', {}) if ((bid_check_dom.get('enabled', False)) and (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): - if self._check_depth_of_market_buy(pair, bid_check_dom): - return self.execute_entry(pair, stake_amount, buy_tag=buy_tag) + if self._check_depth_of_market(pair, bid_check_dom, side=signal): + return self.execute_entry( + pair, + stake_amount, + enter_tag=enter_tag, + is_short=(signal == SignalDirection.SHORT) + ) else: return False - return self.execute_entry(pair, stake_amount, buy_tag=buy_tag) + return self.execute_entry( + pair, + stake_amount, + enter_tag=enter_tag, + is_short=(signal == SignalDirection.SHORT) + ) else: return False @@ -453,28 +505,31 @@ class FreqtradeBot(LoggingMixin): Once that completes, the existing trade is modified to match new data. """ if self.strategy.max_entry_position_adjustment > -1: - count_of_buys = trade.nr_of_successful_buys + count_of_buys = trade.nr_of_successful_entries if count_of_buys > self.strategy.max_entry_position_adjustment: logger.debug(f"Max adjustment entries for {trade.pair} has been reached.") return else: logger.debug("Max adjustment entries is set to unlimited.") - current_rate = self.exchange.get_rate(trade.pair, refresh=True, side="buy") + current_rate = self.exchange.get_rate( + trade.pair, side='entry', is_short=trade.is_short, refresh=True) current_profit = trade.calc_profit_ratio(current_rate) min_stake_amount = self.exchange.get_min_pair_stake_amount(trade.pair, current_rate, self.strategy.stoploss) - max_stake_amount = self.wallets.get_available_stake_amount() + max_stake_amount = self.exchange.get_max_pair_stake_amount(trade.pair, current_rate) + stake_available = self.wallets.get_available_stake_amount() logger.debug(f"Calling adjust_trade_position for pair {trade.pair}") stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, default_retval=None)( trade=trade, current_time=datetime.now(timezone.utc), current_rate=current_rate, - current_profit=current_profit, min_stake=min_stake_amount, max_stake=max_stake_amount) + current_profit=current_profit, min_stake=min_stake_amount, + max_stake=min(max_stake_amount, stake_available)) if stake_amount is not None and stake_amount > 0.0: # We should increase our position - self.execute_entry(trade.pair, stake_amount, trade=trade) + self.execute_entry(trade.pair, stake_amount, trade=trade, is_short=trade.is_short) if stake_amount is not None and stake_amount < 0.0: # We should decrease our position @@ -482,7 +537,7 @@ class FreqtradeBot(LoggingMixin): logger.error(f"Unable to decrease trade position / sell partially" f" for pair {trade.pair}, feature not implemented.") - def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool: + def _check_depth_of_market(self, pair: str, conf: Dict, side: SignalDirection) -> bool: """ Checks depth of market before executing a buy """ @@ -492,9 +547,17 @@ class FreqtradeBot(LoggingMixin): order_book_data_frame = order_book_to_dataframe(order_book['bids'], order_book['asks']) order_book_bids = order_book_data_frame['b_size'].sum() order_book_asks = order_book_data_frame['a_size'].sum() - bids_ask_delta = order_book_bids / order_book_asks + + enter_side = order_book_bids if side == SignalDirection.LONG else order_book_asks + exit_side = order_book_asks if side == SignalDirection.LONG else order_book_bids + bids_ask_delta = enter_side / exit_side + + bids = f"Bids: {order_book_bids}" + asks = f"Asks: {order_book_asks}" + delta = f"Delta: {bids_ask_delta}" + logger.info( - f"Bids: {order_book_bids}, Asks: {order_book_asks}, Delta: {bids_ask_delta}, " + f"{bids}, {asks}, {delta}, Direction: {side.value}" f"Bid Price: {order_book['bids'][0][0]}, Ask Price: {order_book['asks'][0][0]}, " f"Immediate Bid Quantity: {order_book['bids'][0][1]}, " f"Immediate Ask Quantity: {order_book['asks'][0][1]}." @@ -506,20 +569,32 @@ class FreqtradeBot(LoggingMixin): logger.info(f"Bids to asks delta for {pair} does not satisfy condition.") return False - def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None, *, - ordertype: Optional[str] = None, buy_tag: Optional[str] = None, - trade: Optional[Trade] = None) -> bool: + def execute_entry( + self, + pair: str, + stake_amount: float, + price: Optional[float] = None, + *, + is_short: bool = False, + ordertype: Optional[str] = None, + enter_tag: Optional[str] = None, + trade: Optional[Trade] = None, + ) -> bool: """ Executes a limit buy for the given pair :param pair: pair for which we want to create a LIMIT_BUY :param stake_amount: amount of stake-currency for the pair + :param leverage: amount of leverage applied to this trade :return: True if a buy order is created, false if it fails. """ + time_in_force = self.strategy.order_time_in_force['entry'] + [side, name] = ['sell', 'Short'] if is_short else ['buy', 'Long'] + trade_side: Literal['long', 'short'] = 'short' if is_short else 'long' pos_adjust = trade is not None - enter_limit_requested, stake_amount = self.get_valid_enter_price_and_stake( - pair, price, stake_amount, buy_tag, trade) + enter_limit_requested, stake_amount, leverage = self.get_valid_enter_price_and_stake( + pair, price, stake_amount, trade_side, enter_tag, trade) if not stake_amount: return False @@ -528,24 +603,31 @@ class FreqtradeBot(LoggingMixin): logger.info(f"Position adjust: about to create a new order for {pair} with stake: " f"{stake_amount} for {trade}") else: - logger.info(f"Buy signal found: about create a new trade for {pair} with stake_amount: " - f"{stake_amount} ...") + logger.info( + f"{name} signal found: about create a new trade for {pair} with stake_amount: " + f"{stake_amount} ...") - amount = stake_amount / enter_limit_requested - order_type = ordertype or self.strategy.order_types['buy'] - time_in_force = self.strategy.order_time_in_force['buy'] + amount = (stake_amount / enter_limit_requested) * leverage + order_type = ordertype or self.strategy.order_types['entry'] if not pos_adjust and not strategy_safe_wrapper( self.strategy.confirm_trade_entry, default_retval=True)( pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested, time_in_force=time_in_force, current_time=datetime.now(timezone.utc), - entry_tag=buy_tag): + entry_tag=enter_tag, side=trade_side): logger.info(f"User requested abortion of buying {pair}") return False - order = self.exchange.create_order(pair=pair, ordertype=order_type, side="buy", - amount=amount, rate=enter_limit_requested, - time_in_force=time_in_force) - order_obj = Order.parse_from_ccxt_object(order, pair, 'buy') + order = self.exchange.create_order( + pair=pair, + ordertype=order_type, + side=side, + amount=amount, + rate=enter_limit_requested, + reduceOnly=False, + time_in_force=time_in_force, + leverage=leverage + ) + order_obj = Order.parse_from_ccxt_object(order, pair, side) order_id = order['id'] order_status = order.get('status', None) logger.info(f"Order #{order_id} was created for {pair} and status is {order_status}.") @@ -555,35 +637,46 @@ class FreqtradeBot(LoggingMixin): amount_requested = amount if order_status == 'expired' or order_status == 'rejected': - order_tif = self.strategy.order_time_in_force['buy'] # return false if the order is not filled if float(order['filled']) == 0: - logger.warning('Buy %s order with time in force %s for %s is %s by %s.' - ' zero amount is fulfilled.', - order_tif, order_type, pair, order_status, self.exchange.name) + logger.warning(f'{name} {time_in_force} order with time in force {order_type} ' + f'for {pair} is {order_status} by {self.exchange.name}.' + ' zero amount is fulfilled.') return False else: # the order is partially fulfilled # in case of IOC orders we can check immediately # if the order is fulfilled fully or partially - logger.warning('Buy %s order with time in force %s for %s is %s by %s.' + logger.warning('%s %s order with time in force %s for %s is %s by %s.' ' %s amount fulfilled out of %s (%s remaining which is canceled).', - order_tif, order_type, pair, order_status, self.exchange.name, - order['filled'], order['amount'], order['remaining'] + name, time_in_force, order_type, pair, order_status, + self.exchange.name, order['filled'], order['amount'], + order['remaining'] ) - stake_amount = order['cost'] amount = safe_value_fallback(order, 'filled', 'amount') enter_limit_filled_price = safe_value_fallback(order, 'average', 'price') # in case of FOK the order may be filled immediately and fully elif order_status == 'closed': - stake_amount = order['cost'] amount = safe_value_fallback(order, 'filled', 'amount') enter_limit_filled_price = safe_value_fallback(order, 'average', 'price') + # TODO: this might be unnecessary, as we're calling it in update_trade_state. + isolated_liq = self.exchange.get_liquidation_price( + leverage=leverage, + pair=pair, + amount=amount, + open_rate=enter_limit_filled_price, + is_short=is_short + ) + interest_rate = self.exchange.get_interest_rate() + # 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) + funding_fees = self.exchange.get_funding_fees( + pair=pair, amount=amount, is_short=is_short, open_date=open_date) # This is a new trade if trade is None: trade = Trade( @@ -596,13 +689,18 @@ 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, - fee_open_currency=None, strategy=self.strategy.get_strategy_name(), - buy_tag=buy_tag, - timeframe=timeframe_to_minutes(self.config['timeframe']) + enter_tag=enter_tag, + timeframe=timeframe_to_minutes(self.config['timeframe']), + leverage=leverage, + is_short=is_short, + interest_rate=interest_rate, + liquidation_price=isolated_liq, + trading_mode=self.trading_mode, + funding_fees=funding_fees ) else: # This is additional buy, we reset fee_open_currency so timeout checking can work @@ -647,53 +745,99 @@ class FreqtradeBot(LoggingMixin): return trade def get_valid_enter_price_and_stake( - self, pair: str, price: Optional[float], stake_amount: float, - entry_tag: Optional[str], - trade: Optional[Trade]) -> Tuple[float, float]: + self, pair: str, price: Optional[float], stake_amount: float, + trade_side: Literal['long', 'short'], + entry_tag: Optional[str], + trade: Optional[Trade] + ) -> Tuple[float, float, float]: + if price: enter_limit_requested = price else: # Calculate price - proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side="buy") + proposed_enter_rate = self.exchange.get_rate( + pair, side='entry', is_short=(trade_side == 'short'), refresh=True) custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, default_retval=proposed_enter_rate)( pair=pair, current_time=datetime.now(timezone.utc), proposed_rate=proposed_enter_rate, entry_tag=entry_tag) enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate) + if not enter_limit_requested: - raise PricingError('Could not determine buy price.') - min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, enter_limit_requested, - self.strategy.stoploss) + raise PricingError('Could not determine entry price.') + + if trade is None: + max_leverage = self.exchange.get_max_leverage(pair, stake_amount) + leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)( + pair=pair, + current_time=datetime.now(timezone.utc), + current_rate=enter_limit_requested, + proposed_leverage=1.0, + max_leverage=max_leverage, + side=trade_side, + ) if self.trading_mode != TradingMode.SPOT else 1.0 + # Cap leverage between 1.0 and max_leverage. + leverage = min(max(leverage, 1.0), max_leverage) + else: + # Changing leverage currently not possible + leverage = trade.leverage if trade else 1.0 + + # Min-stake-amount should actually include Leverage - this way our "minimal" + # stake- amount might be higher than necessary. + # We do however also need min-stake to determine leverage, therefore this is ignored as + # edge-case for now. + min_stake_amount = self.exchange.get_min_pair_stake_amount( + pair, enter_limit_requested, self.strategy.stoploss, leverage) + max_stake_amount = self.exchange.get_max_pair_stake_amount( + pair, enter_limit_requested, leverage) + if not self.edge and trade is None: - max_stake_amount = self.wallets.get_available_stake_amount() + stake_available = self.wallets.get_available_stake_amount() stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, default_retval=stake_amount)( pair=pair, current_time=datetime.now(timezone.utc), current_rate=enter_limit_requested, proposed_stake=stake_amount, - min_stake=min_stake_amount, max_stake=max_stake_amount, entry_tag=entry_tag) - stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount) - return enter_limit_requested, stake_amount + min_stake=min_stake_amount, max_stake=min(max_stake_amount, stake_available), + entry_tag=entry_tag, side=trade_side + ) + + stake_amount = self.wallets.validate_stake_amount( + pair=pair, + stake_amount=stake_amount, + min_stake_amount=min_stake_amount, + max_stake_amount=max_stake_amount, + ) + + return enter_limit_requested, stake_amount, leverage def _notify_enter(self, trade: Trade, order: Dict, order_type: Optional[str] = None, fill: bool = False) -> None: """ - Sends rpc notification when a buy occurred. + Sends rpc notification when a entry order occurred. """ + if fill: + msg_type = RPCMessageType.SHORT_FILL if trade.is_short else RPCMessageType.BUY_FILL + else: + msg_type = RPCMessageType.SHORT if trade.is_short else RPCMessageType.BUY open_rate = safe_value_fallback(order, 'average', 'price') if open_rate is None: open_rate = trade.open_rate current_rate = trade.open_rate_requested if self.dataprovider.runmode in (RunMode.DRY_RUN, RunMode.LIVE): - current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="buy") + current_rate = self.exchange.get_rate( + trade.pair, side='entry', is_short=trade.is_short, refresh=False) msg = { 'trade_id': trade.id, - 'type': RPCMessageType.BUY_FILL if fill else RPCMessageType.BUY, - 'buy_tag': trade.buy_tag, + 'type': msg_type, + 'buy_tag': trade.enter_tag, + 'enter_tag': trade.enter_tag, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, + 'leverage': trade.leverage if trade.leverage else None, + 'direction': 'Short' if trade.is_short else 'Long', 'limit': open_rate, # Deprecated (?) 'open_rate': open_rate, 'order_type': order_type, @@ -710,16 +854,20 @@ class FreqtradeBot(LoggingMixin): def _notify_enter_cancel(self, trade: Trade, order_type: str, reason: str) -> None: """ - Sends rpc notification when a buy cancel occurred. + Sends rpc notification when a entry order cancel occurred. """ - current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="buy") - + current_rate = self.exchange.get_rate( + trade.pair, side='entry', is_short=trade.is_short, refresh=False) + msg_type = RPCMessageType.SHORT_CANCEL if trade.is_short else RPCMessageType.BUY_CANCEL msg = { 'trade_id': trade.id, - 'type': RPCMessageType.BUY_CANCEL, - 'buy_tag': trade.buy_tag, + 'type': msg_type, + 'buy_tag': trade.enter_tag, + 'enter_tag': trade.enter_tag, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, + 'leverage': trade.leverage, + 'direction': 'Short' if trade.is_short else 'Long', 'limit': trade.open_rate, 'order_type': order_type, 'stake_amount': trade.stake_amount, @@ -740,7 +888,7 @@ class FreqtradeBot(LoggingMixin): def exit_positions(self, trades: List[Any]) -> int: """ - Tries to execute sell orders for open trades (positions) + Tries to execute exit orders for open trades (positions) """ trades_closed = 0 for trade in trades: @@ -756,7 +904,7 @@ class FreqtradeBot(LoggingMixin): trades_closed += 1 except DependencyException as exception: - logger.warning(f'Unable to sell trade {trade.pair}: {exception}') + logger.warning(f'Unable to exit trade {trade.pair}: {exception}') # Updating wallets if any trade occurred if trades_closed: @@ -766,34 +914,37 @@ class FreqtradeBot(LoggingMixin): def handle_trade(self, trade: Trade) -> bool: """ - Sells the current pair if the threshold is reached and updates the trade record. - :return: True if trade has been sold, False otherwise + Sells/exits_short the current pair if the threshold is reached and updates the trade record. + :return: True if trade has been sold/exited_short, False otherwise """ if not trade.is_open: raise DependencyException(f'Attempt to handle closed trade: {trade}') logger.debug('Handling %s ...', trade) - (buy, sell) = (False, False) + (enter, exit_) = (False, False) exit_tag = None + exit_signal_type = "exit_short" if trade.is_short else "exit_long" if (self.config.get('use_sell_signal', True) or self.config.get('ignore_roi_if_buy_signal', False)): analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair, self.strategy.timeframe) - (buy, sell, _, exit_tag) = self.strategy.get_signal( + (enter, exit_, exit_tag) = self.strategy.get_exit_signal( trade.pair, self.strategy.timeframe, - analyzed_df + analyzed_df, + is_short=trade.is_short ) - logger.debug('checking sell') - sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell") - if self._check_and_execute_exit(trade, sell_rate, buy, sell, exit_tag): + logger.debug('checking exit') + exit_rate = self.exchange.get_rate( + trade.pair, side='exit', is_short=trade.is_short, refresh=True) + if self._check_and_execute_exit(trade, exit_rate, enter, exit_, exit_tag): return True - logger.debug('Found no sell signal for %s.', trade) + logger.debug(f'Found no {exit_signal_type} signal for %s.', trade) return False def create_stoploss_order(self, trade: Trade, stop_price: float) -> bool: @@ -804,9 +955,14 @@ class FreqtradeBot(LoggingMixin): :return: True if the order succeeded, and False in case of problems. """ try: - stoploss_order = self.exchange.stoploss(pair=trade.pair, amount=trade.amount, - stop_price=stop_price, - order_types=self.strategy.order_types) + stoploss_order = self.exchange.stoploss( + pair=trade.pair, + amount=trade.amount, + stop_price=stop_price, + order_types=self.strategy.order_types, + side=trade.exit_side, + leverage=trade.leverage + ) order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss') trade.orders.append(order_obj) @@ -821,8 +977,8 @@ class FreqtradeBot(LoggingMixin): trade.stoploss_order_id = None logger.error(f'Unable to place a stoploss order on exchange. {e}') logger.warning('Exiting the trade forcefully') - self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple( - sell_type=SellType.EMERGENCY_SELL)) + self.execute_trade_exit(trade, trade.stop_loss, exit_check=ExitCheckTuple( + exit_type=ExitType.EMERGENCY_SELL)) except ExchangeError: trade.stoploss_order_id = None @@ -834,6 +990,8 @@ class FreqtradeBot(LoggingMixin): Check if trade is fulfilled in which case the stoploss on exchange should be added immediately if stoploss on exchange is enabled. + # TODO: liquidation price always on exchange, even without stoploss_on_exchange + # Therefore fetching account liquidations for open pairs may make sense. """ logger.debug('Handling stoploss on exchange %s ...', trade) @@ -852,7 +1010,7 @@ class FreqtradeBot(LoggingMixin): # We check if stoploss order is fulfilled if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'): - trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value + trade.sell_reason = ExitType.STOPLOSS_ON_EXCHANGE.value self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order, stoploss_order=True) # Lock pair for one candle to prevent immediate rebuys @@ -867,10 +1025,17 @@ class FreqtradeBot(LoggingMixin): # The trade can be closed already (sell-order fill confirmation came in this iteration) return False - # If buy order is fulfilled but there is no stoploss, we add a stoploss on exchange + # If enter order is fulfilled but there is no stoploss, we add a stoploss on exchange if not stoploss_order: - stoploss = self.edge.stoploss(pair=trade.pair) if self.edge else self.strategy.stoploss - stop_price = trade.open_rate * (1 + stoploss) + stoploss = ( + self.edge.stoploss(pair=trade.pair) + if self.edge else + self.strategy.stoploss / trade.leverage + ) + if trade.is_short: + stop_price = trade.open_rate * (1 - stoploss) + else: + stop_price = trade.open_rate * (1 + stoploss) if self.create_stoploss_order(trade=trade, stop_price=stop_price): # The above will return False if the placement failed and the trade was force-sold. @@ -913,7 +1078,7 @@ class FreqtradeBot(LoggingMixin): """ stoploss_norm = self.exchange.price_to_precision(trade.pair, trade.stop_loss) - if self.exchange.stoploss_adjust(stoploss_norm, order): + if self.exchange.stoploss_adjust(stoploss_norm, order, side=trade.exit_side): # we check if the update is necessary update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60) if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat: @@ -934,21 +1099,23 @@ class FreqtradeBot(LoggingMixin): f"for pair {trade.pair}.") def _check_and_execute_exit(self, trade: Trade, exit_rate: float, - buy: bool, sell: bool, exit_tag: Optional[str]) -> bool: + enter: bool, exit_: bool, exit_tag: Optional[str]) -> bool: """ - Check and execute exit + Check and execute trade exit """ - - should_sell = self.strategy.should_sell( - trade, exit_rate, datetime.now(timezone.utc), buy, sell, + should_exit: ExitCheckTuple = self.strategy.should_exit( + trade, + exit_rate, + datetime.now(timezone.utc), + enter=enter, + exit_=exit_, force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0 ) - if should_sell.sell_flag: - logger.info( - f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}. ' - f'Tag: {exit_tag if exit_tag is not None else "None"}') - self.execute_trade_exit(trade, exit_rate, should_sell, exit_tag=exit_tag) + if should_exit.exit_flag: + logger.info(f'Exit for {trade.pair} detected. Reason: {should_exit.exit_type}' + f'Tag: {exit_tag if exit_tag is not None else "None"}') + self.execute_trade_exit(trade, exit_rate, should_exit, exit_tag=exit_tag) return True return False @@ -969,33 +1136,33 @@ class FreqtradeBot(LoggingMixin): continue fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order) + is_entering = order['side'] == trade.enter_side + not_closed = order['status'] == 'open' or fully_cancelled + max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0) order_obj = trade.select_order_by_order_id(trade.open_order_id) - if (order['side'] == 'buy' and (order['status'] == 'open' or fully_cancelled) and ( - fully_cancelled - or (order_obj and self.strategy.ft_check_timed_out( - 'buy', trade, order_obj, datetime.now(timezone.utc)) - ))): - self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) - - elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and ( - fully_cancelled - or (order_obj and self.strategy.ft_check_timed_out( - 'sell', trade, order_obj, datetime.now(timezone.utc)) - ))): - canceled = self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT']) - canceled_count = trade.get_exit_order_count() - max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0) - if canceled and max_timeouts > 0 and canceled_count >= max_timeouts: - logger.warning(f'Emergencyselling trade {trade}, as the sell order ' - f'timed out {max_timeouts} times.') - try: - self.execute_trade_exit( - trade, order.get('price'), - sell_reason=SellCheckTuple(sell_type=SellType.EMERGENCY_SELL)) - except DependencyException as exception: - logger.warning(f'Unable to emergency sell trade {trade.pair}: {exception}') + if not_closed and (fully_cancelled or (order_obj and self.strategy.ft_check_timed_out( + trade, order_obj, datetime.now(timezone.utc))) + ): + if is_entering: + self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) + else: + canceled = self.handle_cancel_exit( + trade, order, constants.CANCEL_REASON['TIMEOUT']) + canceled_count = trade.get_exit_order_count() + max_timeouts = self.config.get( + 'unfilledtimeout', {}).get('exit_timeout_count', 0) + if canceled and max_timeouts > 0 and canceled_count >= max_timeouts: + logger.warning(f'Emergency exiting trade {trade}, as the exit order ' + f'timed out {max_timeouts} times.') + try: + self.execute_trade_exit( + trade, order.get('price'), + exit_check=ExitCheckTuple(exit_type=ExitType.EMERGENCY_SELL)) + except DependencyException as exception: + logger.warning( + f'Unable to emergency sell trade {trade.pair}: {exception}') def cancel_all_open_orders(self) -> None: """ @@ -1010,10 +1177,10 @@ class FreqtradeBot(LoggingMixin): logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc()) continue - if order['side'] == 'buy': + if order['side'] == trade.enter_side: self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['ALL_CANCELLED']) - elif order['side'] == 'sell': + elif order['side'] == trade.exit_side: self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['ALL_CANCELLED']) Trade.commit() @@ -1034,7 +1201,7 @@ class FreqtradeBot(LoggingMixin): if filled_val > 0 and minstake and filled_stake < minstake: logger.warning( f"Order {trade.open_order_id} for {trade.pair} not cancelled, " - f"as the filled amount of {filled_val} would result in an unsellable trade.") + f"as the filled amount of {filled_val} would result in an unexitable trade.") return False corder = self.exchange.cancel_order_with_result(trade.open_order_id, trade.pair, trade.amount) @@ -1049,12 +1216,13 @@ class FreqtradeBot(LoggingMixin): corder = order reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE'] - logger.info('Buy order %s for %s.', reason, trade) + side = trade.enter_side.capitalize() + logger.info('%s order %s for %s.', side, reason, trade) # Using filled to determine the filled amount filled_amount = safe_value_fallback2(corder, order, 'filled', 'filled') if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC): - logger.info('Buy order fully cancelled. Removing %s from database.', trade) + logger.info(f'{side} order fully cancelled. Removing {trade} from database.') # if trade is not partially completed and it's the only order, just delete the trade if len(trade.orders) <= 1: trade.delete() @@ -1064,7 +1232,7 @@ class FreqtradeBot(LoggingMixin): # FIXME TODO: This could possibly reworked to not duplicate the code 15 lines below. self.update_trade_state(trade, trade.open_order_id, corder) trade.open_order_id = None - logger.info('Partial buy order timeout for %s.', trade) + logger.info(f'Partial {side} order timeout for {trade}.') else: # if trade is partially complete, edit the stake details for the trade # and close the order @@ -1072,21 +1240,24 @@ class FreqtradeBot(LoggingMixin): # to the order dict acquired before cancelling. # we need to fall back to the values from order if corder does not contain these keys. trade.amount = filled_amount - trade.stake_amount = trade.amount * trade.open_rate + # * Check edge cases, we don't want to make leverage > 1.0 if we don't have to + # * (for leverage modes which aren't isolated futures) + + trade.stake_amount = trade.amount * trade.open_rate / trade.leverage self.update_trade_state(trade, trade.open_order_id, corder) trade.open_order_id = None - logger.info('Partial buy order timeout for %s.', trade) + logger.info(f'Partial {trade.enter_side} order timeout for {trade}.') reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}" self.wallets.update() - self._notify_enter_cancel(trade, order_type=self.strategy.order_types['buy'], + self._notify_enter_cancel(trade, order_type=self.strategy.order_types['entry'], reason=reason) return was_trade_fully_canceled def handle_cancel_exit(self, trade: Trade, order: Dict, reason: str) -> bool: """ - Sell cancel - cancel order and update trade + exit order cancel - cancel order and update trade :return: True if exit order was cancelled, false otherwise """ cancelled = False @@ -1099,12 +1270,13 @@ class FreqtradeBot(LoggingMixin): trade.amount) trade.update_order(co) except InvalidOrderException: - logger.exception(f"Could not cancel sell order {trade.open_order_id}") + logger.exception( + f"Could not cancel {trade.exit_side} order {trade.open_order_id}") return False - logger.info('Sell order %s for %s.', reason, trade) + logger.info('%s order %s for %s.', trade.exit_side.capitalize(), reason, trade) else: reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE'] - logger.info('Sell order %s for %s.', reason, trade) + logger.info('%s order %s for %s.', trade.exit_side.capitalize(), reason, trade) trade.update_order(order) trade.close_rate = None @@ -1124,7 +1296,7 @@ class FreqtradeBot(LoggingMixin): self.wallets.update() self._notify_exit_cancel( trade, - order_type=self.strategy.order_types['sell'], + order_type=self.strategy.order_types['exit'], reason=reason ) return cancelled @@ -1142,42 +1314,52 @@ class FreqtradeBot(LoggingMixin): """ # Update wallets to ensure amounts tied up in a stoploss is now free! self.wallets.update() + if self.trading_mode == TradingMode.FUTURES: + return amount + trade_base_currency = self.exchange.get_pair_base_currency(pair) wallet_amount = self.wallets.get_free(trade_base_currency) logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}") if wallet_amount >= amount: + # A safe exit amount isn't needed for futures, you can just exit/close the position return amount elif wallet_amount > amount * 0.98: logger.info(f"{pair} - Falling back to wallet-amount {wallet_amount} -> {amount}.") return wallet_amount else: raise DependencyException( - f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}") + f"Not enough amount to exit trade. Trade-amount: {amount}, Wallet: {wallet_amount}") def execute_trade_exit( self, trade: Trade, limit: float, - sell_reason: SellCheckTuple, + exit_check: ExitCheckTuple, *, exit_tag: Optional[str] = None, ordertype: Optional[str] = None, - ) -> bool: + ) -> bool: """ Executes a trade exit for the given trade and limit :param trade: Trade instance :param limit: limit rate for the sell order - :param sell_reason: Reason the sell was triggered + :param exit_check: CheckTuple with signal and reason :return: True if it succeeds (supported) False (not supported) """ - sell_type = 'sell' - if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): - sell_type = 'stoploss' + trade.funding_fees = self.exchange.get_funding_fees( + pair=trade.pair, + amount=trade.amount, + is_short=trade.is_short, + open_date=trade.open_date_utc, + ) + exit_type = 'exit' + if exit_check.exit_type in (ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS): + exit_type = 'stoploss' # if stoploss is on exchange and we are on dry_run mode, # we consider the sell price stop price - if (self.config['dry_run'] and sell_type == 'stoploss' - and self.strategy.order_types['stoploss_on_exchange']): + if (self.config['dry_run'] and exit_type == 'stoploss' + and self.strategy.order_types['stoploss_on_exchange']): limit = trade.stop_loss # set custom_exit_price if available @@ -1194,43 +1376,49 @@ class FreqtradeBot(LoggingMixin): # First cancelling stoploss on exchange ... trade = self.cancel_stoploss_on_exchange(trade) - order_type = ordertype or self.strategy.order_types[sell_type] - if sell_reason.sell_type == SellType.EMERGENCY_SELL: + order_type = ordertype or self.strategy.order_types[exit_type] + if exit_check.exit_type == ExitType.EMERGENCY_SELL: # Emergency sells (default to market!) - order_type = self.strategy.order_types.get("emergencysell", "market") + order_type = self.strategy.order_types.get("emergencyexit", "market") amount = self._safe_exit_amount(trade.pair, trade.amount) - time_in_force = self.strategy.order_time_in_force['sell'] + time_in_force = self.strategy.order_time_in_force['exit'] if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit, - time_in_force=time_in_force, sell_reason=sell_reason.sell_reason, + time_in_force=time_in_force, exit_reason=exit_check.exit_reason, + sell_reason=exit_check.exit_reason, # sellreason -> compatibility current_time=datetime.now(timezone.utc)): - logger.info(f"User requested abortion of selling {trade.pair}") + logger.info(f"User requested abortion of exiting {trade.pair}") return False try: # Execute sell and update trade record - order = self.exchange.create_order(pair=trade.pair, - ordertype=order_type, side="sell", - amount=amount, rate=limit, - time_in_force=time_in_force - ) + order = self.exchange.create_order( + pair=trade.pair, + ordertype=order_type, + side=trade.exit_side, + amount=amount, + rate=limit, + leverage=trade.leverage, + reduceOnly=self.trading_mode == TradingMode.FUTURES, + time_in_force=time_in_force + ) except InsufficientFundsError as e: logger.warning(f"Unable to place order {e}.") # Try to figure out what went wrong self.handle_insufficient_funds(trade) return False - order_obj = Order.parse_from_ccxt_object(order, trade.pair, 'sell') + order_obj = Order.parse_from_ccxt_object(order, trade.pair, trade.exit_side) trade.orders.append(order_obj) trade.open_order_id = order['id'] trade.sell_order_status = '' trade.close_rate_requested = limit - trade.sell_reason = exit_tag or sell_reason.sell_reason + trade.sell_reason = exit_tag or exit_check.exit_reason - # Lock pair for one candle to prevent immediate re-buys + # Lock pair for one candle to prevent immediate re-trading self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc), reason='Auto lock') @@ -1250,7 +1438,7 @@ class FreqtradeBot(LoggingMixin): profit_trade = trade.calc_profit(rate=profit_rate) # Use cached rates here - it was updated seconds ago. current_rate = self.exchange.get_rate( - trade.pair, refresh=False, side="sell") if not fill else None + trade.pair, side='exit', is_short=trade.is_short, refresh=False) if not fill else None profit_ratio = trade.calc_profit_ratio(profit_rate) gain = "profit" if profit_ratio > 0 else "loss" @@ -1260,6 +1448,8 @@ class FreqtradeBot(LoggingMixin): 'trade_id': trade.id, 'exchange': trade.exchange.capitalize(), 'pair': trade.pair, + 'leverage': trade.leverage, + 'direction': 'Short' if trade.is_short else 'Long', 'gain': gain, 'limit': profit_rate, 'order_type': order_type, @@ -1269,7 +1459,8 @@ class FreqtradeBot(LoggingMixin): 'current_rate': current_rate, 'profit_amount': profit_trade, 'profit_ratio': profit_ratio, - 'buy_tag': trade.buy_tag, + 'buy_tag': trade.enter_tag, + 'enter_tag': trade.enter_tag, 'sell_reason': trade.sell_reason, 'open_date': trade.open_date, 'close_date': trade.close_date or datetime.utcnow(), @@ -1296,7 +1487,8 @@ class FreqtradeBot(LoggingMixin): profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested profit_trade = trade.calc_profit(rate=profit_rate) - current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="sell") + current_rate = self.exchange.get_rate( + trade.pair, side='exit', is_short=trade.is_short, refresh=False) profit_ratio = trade.calc_profit_ratio(profit_rate) gain = "profit" if profit_ratio > 0 else "loss" @@ -1305,6 +1497,8 @@ class FreqtradeBot(LoggingMixin): 'trade_id': trade.id, 'exchange': trade.exchange.capitalize(), 'pair': trade.pair, + 'leverage': trade.leverage, + 'direction': 'Short' if trade.is_short else 'Long', 'gain': gain, 'limit': profit_rate or 0, 'order_type': order_type, @@ -1313,7 +1507,8 @@ class FreqtradeBot(LoggingMixin): 'current_rate': current_rate, 'profit_amount': profit_trade, 'profit_ratio': profit_ratio, - 'buy_tag': trade.buy_tag, + 'buy_tag': trade.enter_tag, + 'enter_tag': trade.enter_tag, 'sell_reason': trade.sell_reason, 'open_date': trade.open_date, 'close_date': trade.close_date or datetime.now(timezone.utc), @@ -1370,6 +1565,7 @@ class FreqtradeBot(LoggingMixin): if not order_obj: raise DependencyException( f"Order_obj not found for {order_id}. This should not have happened.") + self.handle_order_fee(trade, order_obj, order) trade.update_trade(order_obj) @@ -1378,9 +1574,24 @@ class FreqtradeBot(LoggingMixin): Trade.commit() if order['status'] in constants.NON_OPEN_EXCHANGE_STATES: - # If a buy order was closed, force update on stoploss on exchange - if order.get('side', None) == 'buy': + # If a entry order was closed, force update on stoploss on exchange + if order.get('side', None) == trade.enter_side: trade = self.cancel_stoploss_on_exchange(trade) + # TODO: Margin will need to use interest_rate as well. + # interest_rate = self.exchange.get_interest_rate() + trade.set_isolated_liq(self.exchange.get_liquidation_price( + + leverage=trade.leverage, + pair=trade.pair, + amount=trade.amount, + open_rate=trade.open_rate, + is_short=trade.is_short + )) + if not self.edge: + # TODO: should shorting/leverage be supported by Edge, + # then this will need to be fixed. + trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True) + # Updating wallets when order is closed self.wallets.update() @@ -1389,7 +1600,7 @@ class FreqtradeBot(LoggingMixin): self._notify_exit(trade, '', True) self.handle_protections(trade.pair) elif send_msg and not trade.open_order_id: - # Buy fill + # Enter fill self._notify_enter(trade, order, fill=True) return False @@ -1412,6 +1623,8 @@ class FreqtradeBot(LoggingMixin): """ Applies the fee to amount (either from Order or from Trades). Can eat into dust if more than the required asset is available. + Can't happen in Futures mode - where Fees are always in settlement currency, + never in base currency. """ self.wallets.update() if fee_abs != 0 and self.wallets.get_free(trade_base_currency) >= amount: @@ -1506,6 +1719,7 @@ class FreqtradeBot(LoggingMixin): trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', '')) if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC): + # * Leverage could be a cause for this warning logger.warning(f"Amount {amount} does not match amount {trade.amount}") raise DependencyException("Half bought? Amounts don't match") diff --git a/freqtrade/leverage/__init__.py b/freqtrade/leverage/__init__.py new file mode 100644 index 000000000..ae78f4722 --- /dev/null +++ b/freqtrade/leverage/__init__.py @@ -0,0 +1,2 @@ +# flake8: noqa: F401 +from freqtrade.leverage.interest import interest diff --git a/freqtrade/leverage/interest.py b/freqtrade/leverage/interest.py new file mode 100644 index 000000000..ff375b05e --- /dev/null +++ b/freqtrade/leverage/interest.py @@ -0,0 +1,43 @@ +from decimal import Decimal +from math import ceil + +from freqtrade.exceptions import OperationalException + + +one = Decimal(1.0) +four = Decimal(4.0) +twenty_four = Decimal(24.0) + + +def interest( + exchange_name: str, + borrowed: Decimal, + rate: Decimal, + hours: Decimal +) -> Decimal: + """ + Equation to calculate interest on margin trades + + :param exchange_name: The exchanged being trading on + :param borrowed: The amount of currency being borrowed + :param rate: The rate of interest (i.e daily interest rate) + :param hours: The time in hours that the currency has been borrowed for + + Raises: + OperationalException: Raised if freqtrade does + not support margin trading for this exchange + + Returns: The amount of interest owed (currency matches borrowed) + """ + exchange_name = exchange_name.lower() + if exchange_name == "binance": + return borrowed * rate * ceil(hours)/twenty_four + elif exchange_name == "kraken": + # Rounded based on https://kraken-fees-calculator.github.io/ + return borrowed * rate * (one+ceil(hours/four)) + elif exchange_name == "ftx": + # As Explained under #Interest rates section in + # https://help.ftx.com/hc/en-us/articles/360053007671-Spot-Margin-Trading-Explainer + return borrowed * rate * ceil(hours)/twenty_four + else: + raise OperationalException(f"Leverage not available on {exchange_name} with freqtrade") diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 133014f39..acc7fc2e4 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -116,7 +116,7 @@ def file_load_json(file): def pair_to_filename(pair: str) -> str: - for ch in ['/', '-', ' ', '.', '@', '$', '+', ':']: + for ch in ['/', ' ', '.', '@', '$', '+', ':']: pair = pair.replace(ch, '_') return pair @@ -129,7 +129,7 @@ def format_ms_time(date: int) -> str: return datetime.fromtimestamp(date/1000.0).strftime('%Y-%m-%dT%H:%M:%S') -def deep_merge_dicts(source, destination): +def deep_merge_dicts(source, destination, allow_null_overrides: bool = True): """ Values from Source override destination, destination is returned (and modified!!) Sample: @@ -142,8 +142,8 @@ def deep_merge_dicts(source, destination): if isinstance(value, dict): # get node or create one node = destination.setdefault(key, {}) - deep_merge_dicts(value, node) - else: + deep_merge_dicts(value, node, allow_null_overrides) + elif value is not None or allow_null_overrides: destination[key] = value return destination diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 76f0e68f4..b63c404fc 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -9,6 +9,7 @@ from copy import deepcopy from datetime import datetime, timedelta, timezone from typing import Any, Dict, List, Optional, Tuple +from numpy import nan from pandas import DataFrame from freqtrade import constants @@ -18,7 +19,7 @@ from freqtrade.data import history from freqtrade.data.btanalysis import find_existing_backtest_stats, trade_list_to_dataframe from freqtrade.data.converter import trim_dataframe, trim_dataframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import BacktestState, SellType +from freqtrade.enums import BacktestState, CandleType, ExitCheckTuple, ExitType, TradingMode from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.misc import get_strategy_run_id @@ -30,7 +31,7 @@ from freqtrade.persistence import LocalTrade, Order, PairLocks, Trade from freqtrade.plugins.pairlistmanager import PairListManager from freqtrade.plugins.protectionmanager import ProtectionManager from freqtrade.resolvers import ExchangeResolver, StrategyResolver -from freqtrade.strategy.interface import IStrategy, SellCheckTuple +from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.wallets import Wallets @@ -39,14 +40,16 @@ logger = logging.getLogger(__name__) # Indexes for backtest tuples DATE_IDX = 0 -BUY_IDX = 1 -OPEN_IDX = 2 -CLOSE_IDX = 3 -SELL_IDX = 4 -LOW_IDX = 5 -HIGH_IDX = 6 -BUY_TAG_IDX = 7 -EXIT_TAG_IDX = 8 +OPEN_IDX = 1 +HIGH_IDX = 2 +LOW_IDX = 3 +CLOSE_IDX = 4 +LONG_IDX = 5 +ELONG_IDX = 6 # Exit long +SHORT_IDX = 7 +ESHORT_IDX = 8 # Exit short +ENTER_TAG_IDX = 9 +EXIT_TAG_IDX = 10 class Backtesting: @@ -70,8 +73,8 @@ class Backtesting: self.run_ids: Dict[str, str] = {} self.strategylist: List[IStrategy] = [] self.all_results: Dict[str, Dict] = {} - - self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config) + self._exchange_name = self.config['exchange']['name'] + self.exchange = ExchangeResolver.load_exchange(self._exchange_name, self.config) self.dataprovider = DataProvider(self.config, self.exchange) if self.config.get('strategy_list', None): @@ -123,6 +126,11 @@ class Backtesting: # Add maximum startup candle count to configuration for informative pairs support self.config['startup_candle_count'] = self.required_startup self.exchange.validate_required_startup_candles(self.required_startup, self.timeframe) + + self.trading_mode: TradingMode = config.get('trading_mode', TradingMode.SPOT) + # strategies which define "can_short=True" will fail to load in Spot mode. + self._can_short = self.trading_mode != TradingMode.SPOT + self.init_backtest() def __del__(self): @@ -146,6 +154,7 @@ class Backtesting: else: self.timeframe_detail_min = 0 self.detail_data: Dict[str, DataFrame] = {} + self.futures_data: Dict[str, DataFrame] = {} def init_backtest(self): @@ -192,6 +201,7 @@ class Backtesting: startup_candles=self.required_startup, fail_without_data=True, data_format=self.config.get('dataformat_ohlcv', 'json'), + candle_type=self.config.get('candle_type_def', CandleType.SPOT) ) min_date, max_date = history.get_timerange(data) @@ -220,9 +230,41 @@ class Backtesting: startup_candles=0, fail_without_data=True, data_format=self.config.get('dataformat_ohlcv', 'json'), + candle_type=self.config.get('candle_type_def', CandleType.SPOT) ) else: self.detail_data = {} + if self.trading_mode == TradingMode.FUTURES: + # Load additional futures data. + funding_rates_dict = history.load_data( + datadir=self.config['datadir'], + pairs=self.pairlists.whitelist, + timeframe=self.exchange._ft_has['mark_ohlcv_timeframe'], + timerange=self.timerange, + startup_candles=0, + fail_without_data=True, + data_format=self.config.get('dataformat_ohlcv', 'json'), + candle_type=CandleType.FUNDING_RATE + ) + + # For simplicity, assign to CandleType.Mark (might contian index candles!) + mark_rates_dict = history.load_data( + datadir=self.config['datadir'], + pairs=self.pairlists.whitelist, + timeframe=self.exchange._ft_has['mark_ohlcv_timeframe'], + timerange=self.timerange, + startup_candles=0, + fail_without_data=True, + data_format=self.config.get('dataformat_ohlcv', 'json'), + candle_type=CandleType.from_string(self.exchange._ft_has["mark_ohlcv_price"]) + ) + # Combine data to avoid combining the data per trade. + for pair in self.pairlists.whitelist: + self.futures_data[pair] = funding_rates_dict[pair].merge( + mark_rates_dict[pair], on='date', how="inner", suffixes=["_fund", "_mark"]) + + else: + self.futures_data = {} def prepare_backtest(self, enable_protections): """ @@ -260,7 +302,8 @@ class Backtesting: """ # Every change to this headers list must evaluate further usages of the resulting tuple # and eventually change the constants for indexes at the top - headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_tag', 'exit_tag'] + headers = ['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long', + 'enter_short', 'exit_short', 'enter_tag', 'exit_tag'] data: Dict = {} self.progress.init_step(BacktestState.CONVERT, len(processed)) @@ -269,19 +312,21 @@ class Backtesting: pair_data = processed[pair] self.check_abort() self.progress.increment() - if not pair_data.empty: - pair_data.loc[:, 'buy'] = 0 # cleanup if buy_signal is exist - pair_data.loc[:, 'sell'] = 0 # cleanup if sell_signal is exist - pair_data.loc[:, 'buy_tag'] = None # cleanup if buy_tag is exist - pair_data.loc[:, 'exit_tag'] = None # cleanup if exit_tag is exist - df_analyzed = self.strategy.advise_sell( - self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair}).copy() + if not pair_data.empty: + # Cleanup from prior runs + pair_data.drop(headers[5:] + ['buy', 'sell'], axis=1, errors='ignore') + + df_analyzed = self.strategy.advise_exit( + self.strategy.advise_entry(pair_data, {'pair': pair}), + {'pair': pair} + ).copy() # Trim startup period from analyzed dataframe df_analyzed = processed[pair] = pair_data = trim_dataframe( df_analyzed, self.timerange, startup_candles=self.required_startup) # Update dataprovider cache - self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed) + self.dataprovider._set_cached_df( + pair, self.timeframe, df_analyzed, self.config['candle_type_def']) # Create a copy of the dataframe before shifting, that way the buy signal/tag # remains on the correct candle for callbacks. @@ -289,10 +334,13 @@ class Backtesting: # To avoid using data from future, we use buy/sell signals shifted # from the previous candle - df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1) - df_analyzed.loc[:, 'sell'] = df_analyzed.loc[:, 'sell'].shift(1) - df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1) - df_analyzed.loc[:, 'exit_tag'] = df_analyzed.loc[:, 'exit_tag'].shift(1) + for col in headers[5:]: + tag_col = col in ('enter_tag', 'exit_tag') + if col in df_analyzed.columns: + df_analyzed.loc[:, col] = df_analyzed.loc[:, col].replace( + [nan], [0 if not tag_col else None]).shift(1) + else: + df_analyzed.loc[:, col] = 0 if not tag_col else None df_analyzed = df_analyzed.drop(df_analyzed.head(1).index) @@ -301,100 +349,144 @@ class Backtesting: data[pair] = df_analyzed[headers].values.tolist() return data - def _get_close_rate(self, sell_row: Tuple, trade: LocalTrade, sell: SellCheckTuple, + def _get_close_rate(self, sell_row: Tuple, trade: LocalTrade, sell: ExitCheckTuple, trade_dur: int) -> float: """ Get close rate for backtesting result """ # Special handling if high or low hit STOP_LOSS or ROI - if sell.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): - if trade.stop_loss > sell_row[HIGH_IDX]: - # our stoploss was already higher than candle high, - # possibly due to a cancelled trade exit. - # sell at open price. - return sell_row[OPEN_IDX] + if sell.exit_type in (ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS): + return self._get_close_rate_for_stoploss(sell_row, trade, sell, trade_dur) + elif sell.exit_type == (ExitType.ROI): + return self._get_close_rate_for_roi(sell_row, trade, sell, trade_dur) + else: + return sell_row[OPEN_IDX] - # Special case: trailing triggers within same candle as trade opened. Assume most - # pessimistic price movement, which is moving just enough to arm stoploss and - # immediately going down to stop price. - if sell.sell_type == SellType.TRAILING_STOP_LOSS and trade_dur == 0: - if ( - not self.strategy.use_custom_stoploss and self.strategy.trailing_stop - and self.strategy.trailing_only_offset_is_reached - and self.strategy.trailing_stop_positive_offset is not None - and self.strategy.trailing_stop_positive - ): - # Worst case: price reaches stop_positive_offset and dives down. - stop_rate = (sell_row[OPEN_IDX] * - (1 + abs(self.strategy.trailing_stop_positive_offset) - - abs(self.strategy.trailing_stop_positive))) - else: - # Worst case: price ticks tiny bit above open and dives down. - stop_rate = sell_row[OPEN_IDX] * (1 - abs(trade.stop_loss_pct)) - assert stop_rate < sell_row[HIGH_IDX] - # Limit lower-end to candle low to avoid sells below the low. - # This still remains "worst case" - but "worst realistic case". - return max(sell_row[LOW_IDX], stop_rate) - - # Set close_rate to stoploss - return trade.stop_loss - elif sell.sell_type == (SellType.ROI): - roi_entry, roi = self.strategy.min_roi_reached_entry(trade_dur) - if roi is not None and roi_entry is not None: - if roi == -1 and roi_entry % self.timeframe_min == 0: - # When forceselling with ROI=-1, the roi time will always be equal to trade_dur. - # If that entry is a multiple of the timeframe (so on candle open) - # - we'll use open instead of close - return sell_row[OPEN_IDX] - - # - (Expected abs profit + open_rate + open_fee) / (fee_close -1) - close_rate = - (trade.open_rate * roi + trade.open_rate * - (1 + trade.fee_open)) / (trade.fee_close - 1) - - if (trade_dur > 0 and trade_dur == roi_entry - and roi_entry % self.timeframe_min == 0 - and sell_row[OPEN_IDX] > close_rate): - # new ROI entry came into effect. - # use Open rate if open_rate > calculated sell rate - return sell_row[OPEN_IDX] - - if ( - trade_dur == 0 - # Red candle (for longs), TODO: green candle (for shorts) - and sell_row[OPEN_IDX] > sell_row[CLOSE_IDX] # Red candle - and trade.open_rate < sell_row[OPEN_IDX] # trade-open below open_rate - and close_rate > sell_row[CLOSE_IDX] - ): - # ROI on opening candles with custom pricing can only - # trigger if the entry was at Open or lower. - # details: https: // github.com/freqtrade/freqtrade/issues/6261 - # If open_rate is < open, only allow sells below the close on red candles. - raise ValueError("Opening candle ROI on red candles.") - # Use the maximum between close_rate and low as we - # cannot sell outside of a candle. - # Applies when a new ROI setting comes in place and the whole candle is above that. - return min(max(close_rate, sell_row[LOW_IDX]), sell_row[HIGH_IDX]) - - else: - # This should not be reached... + def _get_close_rate_for_stoploss(self, sell_row: Tuple, trade: LocalTrade, sell: ExitCheckTuple, + trade_dur: int) -> float: + # our stoploss was already lower than candle high, + # possibly due to a cancelled trade exit. + # sell at open price. + is_short = trade.is_short or False + leverage = trade.leverage or 1.0 + side_1 = -1 if is_short else 1 + if is_short: + if trade.stop_loss < sell_row[LOW_IDX]: return sell_row[OPEN_IDX] else: + if trade.stop_loss > sell_row[HIGH_IDX]: + return sell_row[OPEN_IDX] + + # Special case: trailing triggers within same candle as trade opened. Assume most + # pessimistic price movement, which is moving just enough to arm stoploss and + # immediately going down to stop price. + if sell.exit_type == ExitType.TRAILING_STOP_LOSS and trade_dur == 0: + if ( + not self.strategy.use_custom_stoploss and self.strategy.trailing_stop + and self.strategy.trailing_only_offset_is_reached + and self.strategy.trailing_stop_positive_offset is not None + and self.strategy.trailing_stop_positive + ): + # Worst case: price reaches stop_positive_offset and dives down. + stop_rate = (sell_row[OPEN_IDX] * + (1 + side_1 * abs(self.strategy.trailing_stop_positive_offset) - + side_1 * abs(self.strategy.trailing_stop_positive / leverage))) + else: + # Worst case: price ticks tiny bit above open and dives down. + stop_rate = sell_row[OPEN_IDX] * (1 - + side_1 * abs(trade.stop_loss_pct / leverage)) + if is_short: + assert stop_rate > sell_row[LOW_IDX] + else: + assert stop_rate < sell_row[HIGH_IDX] + + # Limit lower-end to candle low to avoid sells below the low. + # This still remains "worst case" - but "worst realistic case". + if is_short: + return min(sell_row[HIGH_IDX], stop_rate) + else: + return max(sell_row[LOW_IDX], stop_rate) + + # Set close_rate to stoploss + return trade.stop_loss + + def _get_close_rate_for_roi(self, sell_row: Tuple, trade: LocalTrade, sell: ExitCheckTuple, + trade_dur: int) -> float: + is_short = trade.is_short or False + leverage = trade.leverage or 1.0 + side_1 = -1 if is_short else 1 + roi_entry, roi = self.strategy.min_roi_reached_entry(trade_dur) + if roi is not None and roi_entry is not None: + if roi == -1 and roi_entry % self.timeframe_min == 0: + # When forceselling with ROI=-1, the roi time will always be equal to trade_dur. + # If that entry is a multiple of the timeframe (so on candle open) + # - we'll use open instead of close + return sell_row[OPEN_IDX] + + # - (Expected abs profit - open_rate - open_fee) / (fee_close -1) + roi_rate = trade.open_rate * roi / leverage + open_fee_rate = side_1 * trade.open_rate * (1 + side_1 * trade.fee_open) + close_rate = -(roi_rate + open_fee_rate) / (trade.fee_close - side_1 * 1) + if is_short: + is_new_roi = sell_row[OPEN_IDX] < close_rate + else: + is_new_roi = sell_row[OPEN_IDX] > close_rate + if (trade_dur > 0 and trade_dur == roi_entry + and roi_entry % self.timeframe_min == 0 + and is_new_roi): + # new ROI entry came into effect. + # use Open rate if open_rate > calculated sell rate + return sell_row[OPEN_IDX] + + if (trade_dur == 0 and ( + ( + is_short + # Red candle (for longs) + and sell_row[OPEN_IDX] < sell_row[CLOSE_IDX] # Red candle + and trade.open_rate > sell_row[OPEN_IDX] # trade-open above open_rate + and close_rate < sell_row[CLOSE_IDX] # closes below close + ) + or + ( + not is_short + # green candle (for shorts) + and sell_row[OPEN_IDX] > sell_row[CLOSE_IDX] # green candle + and trade.open_rate < sell_row[OPEN_IDX] # trade-open below open_rate + and close_rate > sell_row[CLOSE_IDX] # closes above close + ) + )): + # ROI on opening candles with custom pricing can only + # trigger if the entry was at Open or lower wick. + # details: https: // github.com/freqtrade/freqtrade/issues/6261 + # If open_rate is < open, only allow sells below the close on red candles. + raise ValueError("Opening candle ROI on red candles.") + + # Use the maximum between close_rate and low as we + # cannot sell outside of a candle. + # Applies when a new ROI setting comes in place and the whole candle is above that. + return min(max(close_rate, sell_row[LOW_IDX]), sell_row[HIGH_IDX]) + + else: + # This should not be reached... return sell_row[OPEN_IDX] def _get_adjust_trade_entry_for_candle(self, trade: LocalTrade, row: Tuple ) -> LocalTrade: - current_profit = trade.calc_profit_ratio(row[OPEN_IDX]) min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, row[OPEN_IDX], -0.1) - max_stake = self.wallets.get_available_stake_amount() + max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, row[OPEN_IDX]) + stake_available = self.wallets.get_available_stake_amount() stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, default_retval=None)( trade=trade, current_time=row[DATE_IDX].to_pydatetime(), current_rate=row[OPEN_IDX], - current_profit=current_profit, min_stake=min_stake, max_stake=max_stake) + current_profit=current_profit, min_stake=min_stake, + max_stake=min(max_stake, stake_available)) # Check if we should increase our position if stake_amount is not None and stake_amount > 0.0: - pos_trade = self._enter_trade(trade.pair, row, stake_amount, trade) + + pos_trade = self._enter_trade( + trade.pair, row, 'short' if trade.is_short else 'long', stake_amount, trade) if pos_trade is not None: self.wallets.update() return pos_trade @@ -410,20 +502,23 @@ class Backtesting: # Check if we need to adjust our current positions if self.strategy.position_adjustment_enable: - check_adjust_buy = True + check_adjust_entry = True if self.strategy.max_entry_position_adjustment > -1: - count_of_buys = trade.nr_of_successful_buys - check_adjust_buy = (count_of_buys <= self.strategy.max_entry_position_adjustment) - if check_adjust_buy: + entry_count = trade.nr_of_successful_entries + check_adjust_entry = (entry_count <= self.strategy.max_entry_position_adjustment) + if check_adjust_entry: trade = self._get_adjust_trade_entry_for_candle(trade, sell_row) - sell_candle_time = sell_row[DATE_IDX].to_pydatetime() - sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore - sell_candle_time, sell_row[BUY_IDX], - sell_row[SELL_IDX], - low=sell_row[LOW_IDX], high=sell_row[HIGH_IDX]) + sell_candle_time: datetime = sell_row[DATE_IDX].to_pydatetime() + enter = sell_row[SHORT_IDX] if trade.is_short else sell_row[LONG_IDX] + exit_ = sell_row[ESHORT_IDX] if trade.is_short else sell_row[ELONG_IDX] + sell = self.strategy.should_exit( + trade, sell_row[OPEN_IDX], sell_candle_time, # type: ignore + enter=enter, exit_=exit_, + low=sell_row[LOW_IDX], high=sell_row[HIGH_IDX] + ) - if sell.sell_flag: + if sell.exit_flag: trade.close_date = sell_candle_time trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60) @@ -433,8 +528,8 @@ class Backtesting: return None # call the custom exit price,with default value as previous closerate current_profit = trade.calc_profit_ratio(closerate) - order_type = self.strategy.order_types['sell'] - if sell.sell_type in (SellType.SELL_SIGNAL, SellType.CUSTOM_SELL): + order_type = self.strategy.order_types['exit'] + if sell.exit_type in (ExitType.SELL_SIGNAL, ExitType.CUSTOM_SELL): # Custom exit pricing only for sell-signals if order_type == 'limit': closerate = strategy_safe_wrapper(self.strategy.custom_exit_price, @@ -444,19 +539,23 @@ class Backtesting: proposed_rate=closerate, current_profit=current_profit) # We can't place orders lower than current low. # freqtrade does not support this in live, and the order would fill immediately - closerate = max(closerate, sell_row[LOW_IDX]) + if trade.is_short: + closerate = min(closerate, sell_row[HIGH_IDX]) + else: + closerate = max(closerate, sell_row[LOW_IDX]) # Confirm trade exit: - time_in_force = self.strategy.order_time_in_force['sell'] + time_in_force = self.strategy.order_time_in_force['exit'] if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( pair=trade.pair, trade=trade, order_type='limit', amount=trade.amount, rate=closerate, time_in_force=time_in_force, - sell_reason=sell.sell_reason, + sell_reason=sell.exit_reason, # deprecated + exit_reason=sell.exit_reason, current_time=sell_candle_time): return None - trade.sell_reason = sell.sell_reason + trade.sell_reason = sell.exit_reason # Checks and adds an exit tag, after checking that the length of the # sell_row has the length for an exit tag column @@ -477,8 +576,8 @@ class Backtesting: ft_pair=trade.pair, order_id=str(self.order_id_counter), symbol=trade.pair, - ft_order_side="sell", - side="sell", + ft_order_side=trade.exit_side, + side=trade.exit_side, order_type=order_type, status="open", price=closerate, @@ -494,8 +593,18 @@ class Backtesting: return None def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]: + sell_candle_time: datetime = sell_row[DATE_IDX].to_pydatetime() + + if self.trading_mode == TradingMode.FUTURES: + trade.funding_fees = self.exchange.calculate_funding_fees( + self.futures_data[trade.pair], + amount=trade.amount, + is_short=trade.is_short, + open_date=trade.open_date_utc, + close_date=sell_candle_time, + ) + if self.timeframe_detail and trade.pair in self.detail_data: - sell_candle_time = sell_row[DATE_IDX].to_pydatetime() sell_candle_end = sell_candle_time + timedelta(minutes=self.timeframe_min) detail_data = self.detail_data[trade.pair] @@ -506,11 +615,14 @@ class Backtesting: if len(detail_data) == 0: # Fall back to "regular" data if no detail data was found for this candle return self._get_sell_trade_entry_for_candle(trade, sell_row) - detail_data.loc[:, 'buy'] = sell_row[BUY_IDX] - detail_data.loc[:, 'sell'] = sell_row[SELL_IDX] - detail_data.loc[:, 'buy_tag'] = sell_row[BUY_TAG_IDX] + detail_data.loc[:, 'enter_long'] = sell_row[LONG_IDX] + detail_data.loc[:, 'exit_long'] = sell_row[ELONG_IDX] + detail_data.loc[:, 'enter_short'] = sell_row[SHORT_IDX] + detail_data.loc[:, 'exit_short'] = sell_row[ESHORT_IDX] + detail_data.loc[:, 'enter_tag'] = sell_row[ENTER_TAG_IDX] detail_data.loc[:, 'exit_tag'] = sell_row[EXIT_TAG_IDX] - headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_tag', 'exit_tag'] + headers = ['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long', + 'enter_short', 'exit_short', 'enter_tag', 'exit_tag'] for det_row in detail_data[headers].values.tolist(): res = self._get_sell_trade_entry_for_candle(trade, det_row) if res: @@ -521,58 +633,103 @@ class Backtesting: else: return self._get_sell_trade_entry_for_candle(trade, sell_row) - def _enter_trade(self, pair: str, row: Tuple, stake_amount: Optional[float] = None, - trade: Optional[LocalTrade] = None) -> Optional[LocalTrade]: + def get_valid_price_and_stake( + self, pair: str, row: Tuple, propose_rate: float, stake_amount: Optional[float], + direction: str, current_time: datetime, entry_tag: Optional[str], + trade: Optional[LocalTrade], order_type: str + ) -> Tuple[float, float, float, float]: - current_time = row[DATE_IDX].to_pydatetime() - entry_tag = row[BUY_TAG_IDX] if len(row) >= BUY_TAG_IDX + 1 else None - # let's call the custom entry price, using the open price as default price - order_type = self.strategy.order_types['buy'] - propose_rate = row[OPEN_IDX] if order_type == 'limit': propose_rate = strategy_safe_wrapper(self.strategy.custom_entry_price, - default_retval=row[OPEN_IDX])( + default_retval=propose_rate)( pair=pair, current_time=current_time, proposed_rate=propose_rate, entry_tag=entry_tag) # default value is the open rate # We can't place orders higher than current high (otherwise it'd be a stop limit buy) # which freqtrade does not support in live. - propose_rate = min(propose_rate, row[HIGH_IDX]) - - min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, propose_rate, -0.05) or 0 - max_stake_amount = self.wallets.get_available_stake_amount() + if direction == "short": + propose_rate = max(propose_rate, row[LOW_IDX]) + else: + propose_rate = min(propose_rate, row[HIGH_IDX]) pos_adjust = trade is not None + leverage = trade.leverage if trade else 1.0 if not pos_adjust: try: stake_amount = self.wallets.get_trade_stake_amount(pair, None, update=False) except DependencyException: - return None + return 0, 0, 0, 0 + max_leverage = self.exchange.get_max_leverage(pair, stake_amount) + leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)( + pair=pair, + current_time=current_time, + current_rate=row[OPEN_IDX], + proposed_leverage=1.0, + max_leverage=max_leverage, + side=direction, + ) if self._can_short else 1.0 + # Cap leverage between 1.0 and max_leverage. + leverage = min(max(leverage, 1.0), max_leverage) + + min_stake_amount = self.exchange.get_min_pair_stake_amount( + pair, propose_rate, -0.05, leverage=leverage) or 0 + max_stake_amount = self.exchange.get_max_pair_stake_amount( + pair, propose_rate, leverage=leverage) + stake_available = self.wallets.get_available_stake_amount() + + if not pos_adjust: stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, default_retval=stake_amount)( pair=pair, current_time=current_time, current_rate=propose_rate, - proposed_stake=stake_amount, min_stake=min_stake_amount, max_stake=max_stake_amount, - entry_tag=entry_tag) + proposed_stake=stake_amount, min_stake=min_stake_amount, + max_stake=min(stake_available, max_stake_amount), + entry_tag=entry_tag, side=direction) - stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount) + stake_amount_val = self.wallets.validate_stake_amount( + pair=pair, + stake_amount=stake_amount, + min_stake_amount=min_stake_amount, + max_stake_amount=max_stake_amount, + ) + + return propose_rate, stake_amount_val, leverage, min_stake_amount + + def _enter_trade(self, pair: str, row: Tuple, direction: str, + stake_amount: Optional[float] = None, + trade: Optional[LocalTrade] = None) -> Optional[LocalTrade]: + + current_time = row[DATE_IDX].to_pydatetime() + entry_tag = row[ENTER_TAG_IDX] if len(row) >= ENTER_TAG_IDX + 1 else None + # let's call the custom entry price, using the open price as default price + order_type = self.strategy.order_types['entry'] + pos_adjust = trade is not None + + propose_rate, stake_amount, leverage, min_stake_amount = self.get_valid_price_and_stake( + pair, row, row[OPEN_IDX], stake_amount, direction, current_time, entry_tag, trade, + order_type + ) if not stake_amount: # In case of pos adjust, still return the original trade # If not pos adjust, trade is None return trade + time_in_force = self.strategy.order_time_in_force['entry'] - time_in_force = self.strategy.order_time_in_force['buy'] - # Confirm trade entry: if not pos_adjust: + # Confirm trade entry: if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( pair=pair, order_type=order_type, amount=stake_amount, rate=propose_rate, time_in_force=time_in_force, current_time=current_time, - entry_tag=entry_tag): - return None + entry_tag=entry_tag, side=direction): + return trade if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount): self.order_id_counter += 1 - amount = round(stake_amount / propose_rate, 8) + amount = round((stake_amount / propose_rate) * leverage, 8) + is_short = (direction == 'short') + # Necessary for Margin trading. Disabled until support is enabled. + # interest_rate = self.exchange.get_interest_rate() + if trade is None: # Enter trade self.trade_id_counter += 1 @@ -589,13 +746,25 @@ class Backtesting: fee_open=self.fee, fee_close=self.fee, is_open=True, - buy_tag=entry_tag, - exchange='backtesting', - orders=[] + enter_tag=entry_tag, + exchange=self._exchange_name, + is_short=is_short, + trading_mode=self.trading_mode, + leverage=leverage, + # interest_rate=interest_rate, + orders=[], ) trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True) + trade.set_isolated_liq(self.exchange.get_liquidation_price( + pair=pair, + open_rate=propose_rate, + amount=amount, + leverage=leverage, + is_short=is_short, + )) + order = Order( id=self.order_id_counter, ft_trade_id=trade.id, @@ -603,8 +772,8 @@ class Backtesting: ft_pair=trade.pair, order_id=str(self.order_id_counter), symbol=trade.pair, - ft_order_side="buy", - side="buy", + ft_order_side=trade.enter_side, + side=trade.enter_side, order_type=order_type, status="open", order_date=current_time, @@ -635,13 +804,13 @@ class Backtesting: for pair in open_trades.keys(): if len(open_trades[pair]) > 0: for trade in open_trades[pair]: - if trade.open_order_id and trade.nr_of_successful_buys == 0: + if trade.open_order_id and trade.nr_of_successful_entries == 0: # Ignore trade if buy-order did not fill yet continue sell_row = data[pair][-1] trade.close_date = sell_row[DATE_IDX].to_pydatetime() - trade.sell_reason = SellType.FORCE_SELL.value + trade.sell_reason = ExitType.FORCE_SELL.value trade.close(sell_row[OPEN_IDX], show_msg=False) LocalTrade.close_bt_trade(trade) # Deepcopy object to have wallets update correctly @@ -658,6 +827,20 @@ class Backtesting: self.rejected_trades += 1 return False + def check_for_trade_entry(self, row) -> Optional[str]: + enter_long = row[LONG_IDX] == 1 + exit_long = row[ELONG_IDX] == 1 + enter_short = self._can_short and row[SHORT_IDX] == 1 + exit_short = self._can_short and row[ESHORT_IDX] == 1 + + if enter_long == 1 and not any([exit_long, enter_short]): + # Long + return 'long' + if enter_short == 1 and not any([exit_short, enter_long]): + # Short + return 'short' + return None + def run_protections(self, enable_protections, pair: str, current_time: datetime): if enable_protections: self.protections.stop_per_pair(pair, current_time) @@ -670,19 +853,19 @@ class Backtesting: """ for order in [o for o in trade.orders if o.ft_is_open]: - timedout = self.strategy.ft_check_timed_out(order.side, trade, order, current_time) + timedout = self.strategy.ft_check_timed_out(trade, order, current_time) if timedout: - if order.side == 'buy': + if order.side == trade.enter_side: self.timedout_entry_orders += 1 - if trade.nr_of_successful_buys == 0: - # Remove trade due to buy timeout expiration. + if trade.nr_of_successful_entries == 0: + # Remove trade due to entry timeout expiration. return True else: # Close additional buy order del trade.orders[trade.orders.index(order)] - if order.side == 'sell': + if order.side == trade.exit_side: self.timedout_exit_orders += 1 - # Close sell order and retry selling on next signal. + # Close exit order and retry exiting on next signal. del trade.orders[trade.orders.index(order)] return False @@ -755,19 +938,27 @@ class Backtesting: indexes[pair] = row_index self.dataprovider._set_dataframe_max_index(row_index) - # 1. Process buys. + for t in list(open_trades[pair]): + # 1. Cancel expired buy/sell orders. + if self.check_order_cancel(t, current_time): + # Close trade due to buy timeout expiration. + open_trade_count -= 1 + open_trades[pair].remove(t) + self.wallets.update() + + # 2. Process buys. # without positionstacking, we can only have one open trade per pair. # max_open_trades must be respected # don't open on the last row + trade_dir = self.check_for_trade_entry(row) if ( (position_stacking or len(open_trades[pair]) == 0) and self.trade_slot_available(max_open_trades, open_trade_count_start) and current_time != end_date - and row[BUY_IDX] == 1 - and row[SELL_IDX] != 1 + and trade_dir is not None and not PairLocks.is_pair_locked(pair, row[DATE_IDX]) ): - trade = self._enter_trade(pair, row) + trade = self._enter_trade(pair, row, trade_dir) if trade: # TODO: hacky workaround to avoid opening > max_open_trades # This emulates previous behavior - not sure if this is correct @@ -778,20 +969,20 @@ class Backtesting: open_trades[pair].append(trade) for trade in list(open_trades[pair]): - # 2. Process buy orders. - order = trade.select_order('buy', is_open=True) + # 3. Process entry orders. + order = trade.select_order(trade.enter_side, is_open=True) if order and self._get_order_filled(order.price, row): order.close_bt_order(current_time) trade.open_order_id = None LocalTrade.add_bt_trade(trade) self.wallets.update() - # 3. Create sell orders (if any) + # 4. Create sell orders (if any) if not trade.open_order_id: self._get_sell_trade_entry(trade, row) # Place sell order if necessary - # 4. Process sell orders. - order = trade.select_order('sell', is_open=True) + # 5. Process sell orders. + order = trade.select_order(trade.exit_side, is_open=True) if order and self._get_order_filled(order.price, row): trade.open_order_id = None trade.close_date = current_time @@ -805,13 +996,6 @@ class Backtesting: self.wallets.update() self.run_protections(enable_protections, pair, current_time) - # 5. Cancel expired buy/sell orders. - if self.check_order_cancel(trade, current_time): - # Close trade due to buy timeout expiration. - open_trade_count -= 1 - open_trades[pair].remove(trade) - self.wallets.update() - # Move time one configured time_interval ahead. self.progress.increment() current_time += timedelta(minutes=self.timeframe_min) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 9664e6f07..de1817eed 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -115,9 +115,7 @@ class Hyperopt: if HyperoptTools.has_space(self.config, 'sell'): # Make sure use_sell_signal is enabled - if 'ask_strategy' not in self.config: - self.config['ask_strategy'] = {} - self.config['ask_strategy']['use_sell_signal'] = True + self.config['use_sell_signal'] = True self.print_all = self.config.get('print_all', False) self.hyperopt_table_header = 0 @@ -396,6 +394,7 @@ class Hyperopt: def prepare_hyperopt_data(self) -> None: data, timerange = self.backtesting.load_bt_data() + self.backtesting.load_bt_data_detail() logger.info("Dataload complete. Calculating indicators") preprocessed = self.backtesting.strategy.advise_all_indicators(data) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 5b1c2e135..97cadd683 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -372,20 +372,20 @@ def generate_strategy_stats(pairlist: List[str], return {} config = content['config'] max_open_trades = min(config['max_open_trades'], len(pairlist)) - starting_balance = config['dry_run_wallet'] + start_balance = config['dry_run_wallet'] stake_currency = config['stake_currency'] pair_results = generate_pair_metrics(pairlist, stake_currency=stake_currency, - starting_balance=starting_balance, + starting_balance=start_balance, results=results, skip_nan=False) - buy_tag_results = generate_tag_metrics("buy_tag", starting_balance=starting_balance, - results=results, skip_nan=False) + enter_tag_results = generate_tag_metrics("enter_tag", starting_balance=start_balance, + results=results, skip_nan=False) - sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades, + exit_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades, results=results) left_open_results = generate_pair_metrics(pairlist, stake_currency=stake_currency, - starting_balance=starting_balance, + starting_balance=start_balance, results=results.loc[results['is_open']], skip_nan=True) daily_stats = generate_daily_stats(results) @@ -405,18 +405,24 @@ def generate_strategy_stats(pairlist: List[str], 'best_pair': best_pair, 'worst_pair': worst_pair, 'results_per_pair': pair_results, - 'results_per_buy_tag': buy_tag_results, - 'sell_reason_summary': sell_reason_stats, + 'results_per_enter_tag': enter_tag_results, + 'sell_reason_summary': exit_reason_stats, 'left_open_trades': left_open_results, # 'days_breakdown_stats': days_breakdown_stats, 'total_trades': len(results), + 'trade_count_long': len(results.loc[~results['is_short']]), + 'trade_count_short': len(results.loc[results['is_short']]), 'total_volume': float(results['stake_amount'].sum()), 'avg_stake_amount': results['stake_amount'].mean() if len(results) > 0 else 0, 'profit_mean': results['profit_ratio'].mean() if len(results) > 0 else 0, 'profit_median': results['profit_ratio'].median() if len(results) > 0 else 0, - 'profit_total': results['profit_abs'].sum() / starting_balance, + 'profit_total': results['profit_abs'].sum() / start_balance, + 'profit_total_long': results.loc[~results['is_short'], 'profit_abs'].sum() / start_balance, + 'profit_total_short': results.loc[results['is_short'], 'profit_abs'].sum() / start_balance, 'profit_total_abs': results['profit_abs'].sum(), + 'profit_total_long_abs': results.loc[~results['is_short'], 'profit_abs'].sum(), + 'profit_total_short_abs': results.loc[results['is_short'], 'profit_abs'].sum(), 'backtest_start': min_date.strftime(DATETIME_PRINT_FORMAT), 'backtest_start_ts': int(min_date.timestamp() * 1000), 'backtest_end': max_date.strftime(DATETIME_PRINT_FORMAT), @@ -432,8 +438,8 @@ def generate_strategy_stats(pairlist: List[str], 'stake_amount': config['stake_amount'], 'stake_currency': config['stake_currency'], 'stake_currency_decimals': decimals_per_coin(config['stake_currency']), - 'starting_balance': starting_balance, - 'dry_run_wallet': starting_balance, + 'starting_balance': start_balance, + 'dry_run_wallet': start_balance, 'final_balance': content['final_balance'], 'rejected_signals': content['rejected_signals'], 'timedout_entry_orders': content['timedout_entry_orders'], @@ -467,7 +473,7 @@ def generate_strategy_stats(pairlist: List[str], results, value_col='profit_ratio') (drawdown_abs, drawdown_start, drawdown_end, high_val, low_val, max_drawdown) = calculate_max_drawdown( - results, value_col='profit_abs', starting_balance=starting_balance) + results, value_col='profit_abs', starting_balance=start_balance) strat_stats.update({ 'max_drawdown': max_drawdown_legacy, # Deprecated - do not use 'max_drawdown_account': max_drawdown, @@ -481,7 +487,7 @@ def generate_strategy_stats(pairlist: List[str], 'max_drawdown_high': high_val, }) - csum_min, csum_max = calculate_csum(results, starting_balance) + csum_min, csum_max = calculate_csum(results, start_balance) strat_stats.update({ 'csum_min': csum_min, 'csum_max': csum_max @@ -566,16 +572,16 @@ def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: st floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") -def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_currency: str) -> str: +def text_table_exit_reason(sell_reason_stats: List[Dict[str, Any]], stake_currency: str) -> str: """ Generate small table outlining Backtest results - :param sell_reason_stats: Sell reason metrics + :param sell_reason_stats: Exit reason metrics :param stake_currency: Stakecurrency used :return: pretty printed table with tabulate as string """ headers = [ - 'Sell Reason', - 'Sells', + 'Exit Reason', + 'Exits', 'Win Draws Loss Win%', 'Avg Profit %', 'Cum Profit %', @@ -600,7 +606,7 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr :param stake_currency: stake-currency - used to correctly name headers :return: pretty printed table with tabulate as string """ - if(tag_type == "buy_tag"): + if(tag_type == "enter_tag"): headers = _get_line_header("TAG", stake_currency) else: headers = _get_line_header("TAG", stake_currency, 'Sells') @@ -686,6 +692,19 @@ def text_table_add_metrics(strat_results: Dict) -> str: best_trade = max(strat_results['trades'], key=lambda x: x['profit_ratio']) worst_trade = min(strat_results['trades'], key=lambda x: x['profit_ratio']) + short_metrics = [ + ('', ''), # Empty line to improve readability + ('Long / Short', + f"{strat_results.get('trade_count_long', 'total_trades')} / " + f"{strat_results.get('trade_count_short', 0)}"), + ('Total profit Long %', f"{strat_results['profit_total_long']:.2%}"), + ('Total profit Short %', f"{strat_results['profit_total_short']:.2%}"), + ('Absolute profit Long', round_coin_value(strat_results['profit_total_long_abs'], + strat_results['stake_currency'])), + ('Absolute profit Short', round_coin_value(strat_results['profit_total_short_abs'], + strat_results['stake_currency'])), + ] if strat_results.get('trade_count_short', 0) > 0 else [] + # Newly added fields should be ignored if they are missing in strat_results. hyperopt-show # command stores these results and newer version of freqtrade must be able to handle old # results with missing new fields. @@ -696,6 +715,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: ('', ''), # Empty line to improve readability ('Total/Daily Avg Trades', f"{strat_results['total_trades']} / {strat_results['trades_per_day']}"), + ('Starting balance', round_coin_value(strat_results['starting_balance'], strat_results['stake_currency'])), ('Final balance', round_coin_value(strat_results['final_balance'], @@ -710,6 +730,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: strat_results['stake_currency'])), ('Total trade volume', round_coin_value(strat_results['total_volume'], strat_results['stake_currency'])), + *short_metrics, ('', ''), # Empty line to improve readability ('Best Pair', f"{strat_results['best_pair']['key']} " f"{strat_results['best_pair']['profit_sum']:.2%}"), @@ -727,7 +748,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: f"{strat_results['draw_days']} / {strat_results['losing_days']}"), ('Avg. Duration Winners', f"{strat_results['winner_holding_avg']}"), ('Avg. Duration Loser', f"{strat_results['loser_holding_avg']}"), - ('Rejected Buy signals', strat_results.get('rejected_signals', 'N/A')), + ('Rejected Entry signals', strat_results.get('rejected_signals', 'N/A')), ('Entry/Exit Timeouts', f"{strat_results.get('timedout_entry_orders', 'N/A')} / " f"{strat_results.get('timedout_exit_orders', 'N/A')}"), @@ -780,20 +801,22 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '=')) print(table) - if results.get('results_per_buy_tag') is not None: + if (results.get('results_per_enter_tag') is not None + or results.get('results_per_buy_tag') is not None): + # results_per_buy_tag is deprecated and should be removed 2 versions after short golive. table = text_table_tags( - "buy_tag", - results['results_per_buy_tag'], + "enter_tag", + results.get('results_per_enter_tag', results.get('results_per_buy_tag')), stake_currency=stake_currency) if isinstance(table, str) and len(table) > 0: - print(' BUY TAG STATS '.center(len(table.splitlines()[0]), '=')) + print(' ENTER TAG STATS '.center(len(table.splitlines()[0]), '=')) print(table) - table = text_table_sell_reason(sell_reason_stats=results['sell_reason_summary'], + table = text_table_exit_reason(sell_reason_stats=results['sell_reason_summary'], stake_currency=stake_currency) if isinstance(table, str) and len(table) > 0: - print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '=')) + print(' EXIT REASON STATS '.center(len(table.splitlines()[0]), '=')) print(table) table = text_table_bt_results(results['left_open_trades'], stake_currency=stake_currency) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 2da24b748..a84503c74 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -76,7 +76,23 @@ def migrate_trades_and_orders_table( min_rate = get_column_def(cols, 'min_rate', 'null') sell_reason = get_column_def(cols, 'sell_reason', 'null') strategy = get_column_def(cols, 'strategy', 'null') - buy_tag = get_column_def(cols, 'buy_tag', 'null') + enter_tag = get_column_def(cols, 'buy_tag', get_column_def(cols, 'enter_tag', 'null')) + + trading_mode = get_column_def(cols, 'trading_mode', 'null') + + # Leverage Properties + leverage = get_column_def(cols, 'leverage', '1.0') + liquidation_price = get_column_def(cols, 'liquidation_price', + 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') @@ -120,8 +136,10 @@ def migrate_trades_and_orders_table( stake_amount, amount, amount_requested, open_date, close_date, open_order_id, stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, 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 + max_rate, min_rate, sell_reason, sell_order_status, strategy, enter_tag, + timeframe, open_trade_value, close_profit_abs, + trading_mode, leverage, liquidation_price, is_short, + interest_rate, funding_fees ) select id, lower(exchange), pair, is_open, {fee_open} fee_open, {fee_open_cost} fee_open_cost, @@ -136,8 +154,11 @@ def migrate_trades_and_orders_table( {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, {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 + {strategy} strategy, {enter_tag} enter_tag, {timeframe} timeframe, + {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs, + {trading_mode} trading_mode, {leverage} leverage, {liquidation_price} liquidation_price, + {is_short} is_short, {interest_rate} interest_rate, + {funding_fees} funding_fees from {trade_back_name} """)) @@ -176,12 +197,12 @@ def migrate_orders_table(engine, table_back_name: str, cols_order: List): ft_fee_base = get_column_def(cols_order, 'ft_fee_base', 'null') average = get_column_def(cols_order, 'average', 'null') - # let SQLAlchemy create the schema as required + # sqlite does not support literals for booleans with engine.begin() as connection: connection.execute(text(f""" insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, - status, symbol, order_type, side, price, amount, filled, average, remaining, - cost, order_date, order_filled_date, order_update_date, ft_fee_base) + status, symbol, order_type, side, price, amount, filled, average, remaining, cost, + order_date, order_filled_date, order_update_date, ft_fee_base) select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status, symbol, order_type, side, price, amount, filled, {average} average, remaining, cost, order_date, order_filled_date, order_update_date, {ft_fee_base} ft_fee_base @@ -211,8 +232,9 @@ def check_migrate(engine, decl_base, previous_tables) -> None: # Check if migration necessary # Migrates both trades and orders table! - # if not has_column(cols, 'buy_tag'): - if 'orders' not in previous_tables or not has_column(cols_orders, 'ft_fee_base'): + # if ('orders' not in previous_tables + # or not has_column(cols_orders, 'leverage')): + if not has_column(cols, 'liquidation_price'): logger.info(f"Running database migration for trades - " f"backup: {table_back_name}, {order_table_bak_name}") migrate_trades_and_orders_table( diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 92d1def30..a23c8e43e 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -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,8 +14,9 @@ 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 ExitType, TradingMode from freqtrade.exceptions import DependencyException, OperationalException +from freqtrade.leverage import interest from freqtrade.persistence.migrations import check_migrate @@ -181,6 +182,7 @@ class Order(_DECL_BASE): self.average = order.get('average', self.average) self.remaining = order.get('remaining', self.remaining) self.cost = order.get('cost', self.cost) + if 'timestamp' in order and order['timestamp'] is not None: self.order_date = datetime.fromtimestamp(order['timestamp'] / 1000, tz=timezone.utc) @@ -191,7 +193,7 @@ class Order(_DECL_BASE): self.order_filled_date = datetime.now(timezone.utc) self.order_update_date = datetime.now(timezone.utc) - def to_json(self) -> Dict[str, Any]: + def to_json(self, entry_side: str) -> Dict[str, Any]: return { 'pair': self.ft_pair, 'order_id': self.order_id, @@ -213,6 +215,7 @@ class Order(_DECL_BASE): tzinfo=timezone.utc).timestamp() * 1000) if self.order_filled_date else None, 'order_type': self.order_type, 'price': self.price, + 'ft_is_entry': self.ft_order_side == entry_side, 'remaining': self.remaining, } @@ -316,19 +319,48 @@ class LocalTrade(): sell_reason: str = '' sell_order_status: str = '' strategy: str = '' - buy_tag: Optional[str] = None + enter_tag: Optional[str] = None timeframe: Optional[int] = None - def __init__(self, **kwargs): - for key in kwargs: - setattr(self, key, kwargs[key]) - self.recalc_open_trade_value() + trading_mode: TradingMode = TradingMode.SPOT - def __repr__(self): - open_since = self.open_date.strftime(DATETIME_PRINT_FORMAT) if self.is_open else 'closed' + # Leverage trading properties + liquidation_price: Optional[float] = None + is_short: bool = False + leverage: float = 1.0 - return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, ' - f'open_rate={self.open_rate:.8f}, open_since={open_since})') + # Margin trading properties + interest_rate: float = 0.0 + + # Futures properties + funding_fees: Optional[float] = None + + @property + def buy_tag(self) -> Optional[str]: + """ + Compatibility between buy_tag (old) and enter_tag (new) + Consider buy_tag deprecated + """ + return self.enter_tag + + @property + def has_no_leverage(self) -> bool: + """Returns true if this is a non-leverage, non-short trade""" + return ((self.leverage == 1.0 or self.leverage is None) and not self.is_short) + + @property + def borrowed(self) -> float: + """ + The amount of currency borrowed from the exchange for leverage trades + If a long trade, the amount is in base currency + If a short trade, the amount is in the other currency being traded + """ + if self.has_no_leverage: + return 0.0 + elif not self.is_short: + return (self.amount * self.open_rate) * ((self.leverage-1)/self.leverage) + else: + return self.amount @property def open_date_utc(self): @@ -338,9 +370,49 @@ class LocalTrade(): def close_date_utc(self): return self.close_date.replace(tzinfo=timezone.utc) + @property + def enter_side(self) -> str: + if self.is_short: + return "sell" + else: + return "buy" + + @property + def exit_side(self) -> str: + if self.is_short: + return "buy" + else: + return "sell" + + @property + def trade_direction(self) -> str: + if self.is_short: + return "short" + else: + return "long" + + def __init__(self, **kwargs): + for key in kwargs: + setattr(self, key, kwargs[key]) + self.recalc_open_trade_value() + if self.trading_mode == TradingMode.MARGIN and self.interest_rate is None: + raise OperationalException( + f"{self.trading_mode.value} trading requires param interest_rate on trades") + + def __repr__(self): + open_since = self.open_date.strftime(DATETIME_PRINT_FORMAT) if self.is_open else 'closed' + leverage = self.leverage or 1.0 + is_short = self.is_short or False + + return ( + f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, ' + f'is_short={is_short}, leverage={leverage}, ' + f'open_rate={self.open_rate:.8f}, open_since={open_since})' + ) + def to_json(self) -> Dict[str, Any]: filled_orders = self.select_filled_orders() - orders = [order.to_json() for order in filled_orders] + orders = [order.to_json(self.enter_side) for order in filled_orders] return { 'trade_id': self.id, @@ -351,7 +423,8 @@ class LocalTrade(): 'amount_requested': round(self.amount_requested, 8) if self.amount_requested else None, 'stake_amount': round(self.stake_amount, 8), 'strategy': self.strategy, - 'buy_tag': self.buy_tag, + 'buy_tag': self.enter_tag, + 'enter_tag': self.enter_tag, 'timeframe': self.timeframe, 'fee_open': self.fee_open, @@ -404,6 +477,12 @@ class LocalTrade(): 'min_rate': self.min_rate, 'max_rate': self.max_rate, + 'leverage': self.leverage, + 'interest_rate': self.interest_rate, + 'liquidation_price': self.liquidation_price, + 'is_short': self.is_short, + 'trading_mode': self.trading_mode, + 'funding_fees': self.funding_fees, 'open_order_id': self.open_order_id, 'orders': orders, } @@ -424,10 +503,33 @@ class LocalTrade(): self.max_rate = max(current_price, self.max_rate or self.open_rate) self.min_rate = min(current_price_low, self.min_rate or self.open_rate) - def _set_new_stoploss(self, new_loss: float, stoploss: float): - """Assign new stop value""" - self.stop_loss = new_loss - self.stop_loss_pct = -1 * abs(stoploss) + def set_isolated_liq(self, liquidation_price: Optional[float]): + """ + Method you should use to set self.liquidation price. + Assures stop_loss is not passed the liquidation price + """ + if not liquidation_price: + return + self.liquidation_price = liquidation_price + + def _set_stop_loss(self, stop_loss: float, percent: float): + """ + Method you should use to set self.stop_loss. + Assures stop_loss is not passed the liquidation price + """ + if self.liquidation_price is not None: + if self.is_short: + sl = min(stop_loss, self.liquidation_price) + else: + sl = max(stop_loss, self.liquidation_price) + else: + sl = stop_loss + + if not self.stop_loss: + self.initial_stop_loss = sl + self.stop_loss = sl + + self.stop_loss_pct = -1 * abs(percent) self.stoploss_last_update = datetime.utcnow() def adjust_stop_loss(self, current_price: float, stoploss: float, @@ -443,27 +545,43 @@ class LocalTrade(): # Don't modify if called with initial and nothing to do return - new_loss = float(current_price * (1 - abs(stoploss))) + leverage = self.leverage or 1.0 + if self.is_short: + new_loss = float(current_price * (1 + abs(stoploss / leverage))) + # If trading with leverage, don't set the stoploss below the liquidation price + if self.liquidation_price: + new_loss = min(self.liquidation_price, new_loss) + else: + new_loss = float(current_price * (1 - abs(stoploss / leverage))) + # If trading with leverage, don't set the stoploss below the liquidation price + if self.liquidation_price: + new_loss = max(self.liquidation_price, new_loss) # no stop loss assigned yet - # if not self.stop_loss: if self.initial_stop_loss_pct is None: logger.debug(f"{self.pair} - Assigning new stoploss...") - self._set_new_stoploss(new_loss, stoploss) + self._set_stop_loss(new_loss, stoploss) self.initial_stop_loss = new_loss self.initial_stop_loss_pct = -1 * abs(stoploss) # evaluate if the stop loss needs to be updated else: - if new_loss > self.stop_loss: # stop losses only walk up, never down! + + higher_stop = new_loss > self.stop_loss + lower_stop = new_loss < self.stop_loss + + # stop losses only walk up, never down!, + # ? But adding more to a leveraged trade would create a lower liquidation price, + # ? decreasing the minimum stoploss + if (higher_stop and not self.is_short) or (lower_stop and self.is_short): logger.debug(f"{self.pair} - Adjusting stoploss...") - self._set_new_stoploss(new_loss, stoploss) + self._set_stop_loss(new_loss, stoploss) else: logger.debug(f"{self.pair} - Keeping current stoploss...") logger.debug( f"{self.pair} - Stoploss adjusted. current_price={current_price:.8f}, " - f"open_rate={self.open_rate:.8f}, max_rate={self.max_rate:.8f}, " + f"open_rate={self.open_rate:.8f}, max_rate={self.max_rate or self.open_rate:.8f}, " f"initial_stop_loss={self.initial_stop_loss:.8f}, " f"stop_loss={self.stop_loss:.8f}. " f"Trailing stoploss saved us: " @@ -475,28 +593,32 @@ class LocalTrade(): :param order: order retrieved by exchange.fetch_order() :return: None """ + # Ignore open and cancelled orders if order.status == 'open' or order.safe_price is None: return logger.info(f'Updating trade (id={self.id}) ...') - if order.ft_order_side == 'buy': + if order.ft_order_side == self.enter_side: # Update open rate and actual amount self.open_rate = order.safe_price self.amount = order.safe_amount_after_fee if self.is_open: - logger.info(f'{order.order_type.upper()}_BUY has been fulfilled for {self}.') + payment = "SELL" if self.is_short else "BUY" + logger.info(f'{order.order_type.upper()}_{payment} has been fulfilled for {self}.') self.open_order_id = None self.recalc_trade_from_orders() - elif order.ft_order_side == 'sell': + elif order.ft_order_side == self.exit_side: if self.is_open: - logger.info(f'{order.order_type.upper()}_SELL has been fulfilled for {self}.') + payment = "BUY" if self.is_short else "SELL" + # * On margin shorts, you buy a little bit more than the amount (amount + interest) + logger.info(f'{order.order_type.upper()}_{payment} has been fulfilled for {self}.') self.close(order.safe_price) elif order.ft_order_side == 'stoploss': self.stoploss_order_id = None self.close_rate_requested = self.stop_loss - self.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value + self.sell_reason = ExitType.STOPLOSS_ON_EXCHANGE.value if self.is_open: logger.info(f'{order.order_type.upper()} is hit for {self}.') self.close(order.safe_price) @@ -510,9 +632,9 @@ class LocalTrade(): and marks trade as closed """ self.close_rate = rate + self.close_date = self.close_date or datetime.utcnow() self.close_profit = self.calc_profit_ratio() self.close_profit_abs = self.calc_profit() - self.close_date = self.close_date or datetime.utcnow() self.is_open = False self.sell_order_status = 'closed' self.open_order_id = None @@ -527,14 +649,14 @@ class LocalTrade(): """ Update Fee parameters. Only acts once per side """ - if side == 'buy' and self.fee_open_currency is None: + if self.enter_side == side and self.fee_open_currency is None: self.fee_open_cost = fee_cost self.fee_open_currency = fee_currency if fee_rate is not None: self.fee_open = fee_rate # Assume close-fee will fall into the same fee category and take an educated guess self.fee_close = fee_rate - elif side == 'sell' and self.fee_close_currency is None: + elif self.exit_side == side and self.fee_close_currency is None: self.fee_close_cost = fee_cost self.fee_close_currency = fee_currency if fee_rate is not None: @@ -544,9 +666,9 @@ class LocalTrade(): """ Verify if this side (buy / sell) has already been updated """ - if side == 'buy': + if self.enter_side == side: return self.fee_open_currency is not None - elif side == 'sell': + elif self.exit_side == side: return self.fee_close_currency is not None else: return False @@ -559,79 +681,167 @@ class LocalTrade(): Get amount of failed exiting orders assumes full exits. """ - return len([o for o in self.orders if o.ft_order_side == 'sell']) + return len([o for o in self.orders if o.ft_order_side == self.exit_side]) def _calc_open_trade_value(self) -> float: """ Calculate the open_rate including open_fee. :return: Price in of the open trade incl. Fees """ - buy_trade = Decimal(self.amount) * Decimal(self.open_rate) - fees = buy_trade * Decimal(self.fee_open) - return float(buy_trade + fees) + open_trade = Decimal(self.amount) * Decimal(self.open_rate) + fees = open_trade * Decimal(self.fee_open) + if self.is_short: + return float(open_trade - fees) + else: + return float(open_trade + fees) def recalc_open_trade_value(self) -> None: """ Recalculate open_trade_value. - Must be called whenever open_rate or fee_open is changed. + Must be called whenever open_rate, fee_open or is_short is changed. """ self.open_trade_value = self._calc_open_trade_value() + def calculate_interest(self, interest_rate: Optional[float] = None) -> Decimal: + """ + :param interest_rate: interest_charge for borrowing this coin(optional). + If interest_rate is not set self.interest_rate will be used + """ + zero = Decimal(0.0) + # If nothing was borrowed + if self.trading_mode != TradingMode.MARGIN or self.has_no_leverage: + return zero + + open_date = self.open_date.replace(tzinfo=None) + now = (self.close_date or datetime.now(timezone.utc)).replace(tzinfo=None) + sec_per_hour = Decimal(3600) + total_seconds = Decimal((now - open_date).total_seconds()) + hours = total_seconds/sec_per_hour or zero + + rate = Decimal(interest_rate or self.interest_rate) + borrowed = Decimal(self.borrowed) + + 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) -> float: + fee: Optional[float] = None, + interest_rate: Optional[float] = None) -> float: """ Calculate the close_rate including fee :param fee: fee to use on the close rate (optional). If rate is not set self.fee will be used :param rate: rate to compare with (optional). If rate is not set self.close_rate will be used + :param interest_rate: interest_charge for borrowing this coin (optional). + If interest_rate is not set self.interest_rate will be used :return: Price in BTC of the open trade """ if rate is None and not self.close_rate: return 0.0 - sell_trade = Decimal(self.amount) * Decimal(rate or self.close_rate) # type: ignore - fees = sell_trade * Decimal(fee or self.fee_close) - return float(sell_trade - fees) + amount = Decimal(self.amount) + trading_mode = self.trading_mode or TradingMode.SPOT + + if trading_mode == TradingMode.SPOT: + return float(self._calc_base_close(amount, rate, fee)) + + 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 + # Positive funding_fees -> Trade has gained from fees. + # Negative funding_fees -> Trade had to pay the fees. + 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: + 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) -> float: + fee: Optional[float] = None, + interest_rate: Optional[float] = None) -> float: """ Calculate the absolute profit in stake currency between Close and Open trade :param fee: fee to use on the close rate (optional). - If rate is not set self.fee will be used + If fee is not set self.fee will be used :param rate: close rate to compare with (optional). If rate is not set self.close_rate will be used + :param interest_rate: interest_charge for borrowing this coin (optional). + If interest_rate is not set self.interest_rate will be used :return: profit in stake currency as float """ close_trade_value = self.calc_close_trade_value( rate=(rate or self.close_rate), - fee=(fee or self.fee_close) + fee=(fee or self.fee_close), + interest_rate=(interest_rate or self.interest_rate) ) - profit = close_trade_value - self.open_trade_value + + if self.is_short: + profit = self.open_trade_value - close_trade_value + else: + profit = close_trade_value - self.open_trade_value return float(f"{profit:.8f}") def calc_profit_ratio(self, rate: Optional[float] = None, - fee: Optional[float] = None) -> float: + fee: Optional[float] = None, + interest_rate: Optional[float] = None) -> float: """ Calculates the profit as ratio (including fee). :param rate: rate to compare with (optional). If rate is not set self.close_rate will be used :param fee: fee to use on the close rate (optional). + :param interest_rate: interest_charge for borrowing this coin (optional). + If interest_rate is not set self.interest_rate will be used :return: profit ratio as float """ close_trade_value = self.calc_close_trade_value( rate=(rate or self.close_rate), - fee=(fee or self.fee_close) + fee=(fee or self.fee_close), + interest_rate=(interest_rate or self.interest_rate) ) - if self.open_trade_value == 0.0: + + short_close_zero = (self.is_short and close_trade_value == 0.0) + long_close_zero = (not self.is_short and self.open_trade_value == 0.0) + leverage = self.leverage or 1.0 + + if (short_close_zero or long_close_zero): return 0.0 - profit_ratio = (close_trade_value / self.open_trade_value) - 1 + else: + if self.is_short: + profit_ratio = (1 - (close_trade_value/self.open_trade_value)) * leverage + else: + profit_ratio = ((close_trade_value/self.open_trade_value) - 1) * leverage + return float(f"{profit_ratio:.8f}") def recalc_trade_from_orders(self): # We need at least 2 entry orders for averaging amounts and rates. - if len(self.select_filled_orders('buy')) < 2: + # TODO: this condition could probably be removed + if len(self.select_filled_orders(self.enter_side)) < 2: + self.stake_amount = self.amount * self.open_rate / self.leverage + # Just in case, still recalc open trade value self.recalc_open_trade_value() return @@ -640,7 +850,7 @@ class LocalTrade(): total_stake = 0.0 for o in self.orders: if (o.ft_is_open or - (o.ft_order_side != 'buy') or + (o.ft_order_side != self.enter_side) or (o.status not in NON_OPEN_EXCHANGE_STATES)): continue @@ -653,8 +863,9 @@ class LocalTrade(): total_stake += tmp_price * tmp_amount if total_amount > 0: + # Leverage not updated, as we don't allow changing leverage through DCA at the moment. self.open_rate = total_stake / total_amount - self.stake_amount = total_stake + self.stake_amount = total_stake / (self.leverage or 1.0) self.amount = total_amount self.fee_open_cost = self.fee_open * self.stake_amount self.recalc_open_trade_value() @@ -700,10 +911,28 @@ class LocalTrade(): (o.filled or 0) > 0 and o.status in NON_OPEN_EXCHANGE_STATES] + @property + def nr_of_successful_entries(self) -> int: + """ + Helper function to count the number of entry orders that have been filled. + :return: int count of entry orders that have been filled for this trade. + """ + + return len(self.select_filled_orders(self.enter_side)) + + @property + def nr_of_successful_exits(self) -> int: + """ + Helper function to count the number of exit orders that have been filled. + :return: int count of exit orders that have been filled for this trade. + """ + return len(self.select_filled_orders(self.exit_side)) + @property def nr_of_successful_buys(self) -> int: """ Helper function to count the number of buy orders that have been filled. + WARNING: Please use nr_of_successful_entries for short support. :return: int count of buy orders that have been filled for this trade. """ @@ -713,6 +942,7 @@ class LocalTrade(): def nr_of_successful_sells(self) -> int: """ Helper function to count the number of sell orders that have been filled. + WARNING: Please use nr_of_successful_exits for short support. :return: int count of sell orders that have been filled for this trade. """ return len(self.select_filled_orders('sell')) @@ -849,9 +1079,22 @@ class Trade(_DECL_BASE, LocalTrade): sell_reason = Column(String(100), nullable=True) sell_order_status = Column(String(100), nullable=True) strategy = Column(String(100), nullable=True) - buy_tag = Column(String(100), nullable=True) + enter_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) + liquidation_price = Column(Float, nullable=True) + + # 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() @@ -938,7 +1181,7 @@ class Trade(_DECL_BASE, LocalTrade): ]).all() @staticmethod - def get_sold_trades_without_assigned_fees(): + def get_closed_trades_without_assigned_fees(): """ Returns all closed trades which don't have fees set correctly NOTE: Not supported in Backtesting. @@ -1007,7 +1250,7 @@ class Trade(_DECL_BASE, LocalTrade): ] @staticmethod - def get_buy_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]: + def get_enter_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]: """ Returns List of dicts containing all Trades, based on buy tag performance Can either be average for all pairs or a specific pair provided @@ -1018,25 +1261,25 @@ class Trade(_DECL_BASE, LocalTrade): if(pair is not None): filters.append(Trade.pair == pair) - buy_tag_perf = Trade.query.with_entities( - Trade.buy_tag, + enter_tag_perf = Trade.query.with_entities( + Trade.enter_tag, func.sum(Trade.close_profit).label('profit_sum'), func.sum(Trade.close_profit_abs).label('profit_sum_abs'), func.count(Trade.pair).label('count') ).filter(*filters)\ - .group_by(Trade.buy_tag) \ + .group_by(Trade.enter_tag) \ .order_by(desc('profit_sum_abs')) \ .all() return [ { - 'buy_tag': buy_tag if buy_tag is not None else "Other", + 'enter_tag': enter_tag if enter_tag is not None else "Other", 'profit_ratio': profit, 'profit_pct': round(profit * 100, 2), 'profit_abs': profit_abs, 'count': count } - for buy_tag, profit, profit_abs, count in buy_tag_perf + for enter_tag, profit, profit_abs, count in enter_tag_perf ] @staticmethod @@ -1086,7 +1329,7 @@ class Trade(_DECL_BASE, LocalTrade): mix_tag_perf = Trade.query.with_entities( Trade.id, - Trade.buy_tag, + Trade.enter_tag, Trade.sell_reason, func.sum(Trade.close_profit).label('profit_sum'), func.sum(Trade.close_profit_abs).label('profit_sum_abs'), @@ -1097,12 +1340,12 @@ class Trade(_DECL_BASE, LocalTrade): .all() return_list: List[Dict] = [] - for id, buy_tag, sell_reason, profit, profit_abs, count in mix_tag_perf: - buy_tag = buy_tag if buy_tag is not None else "Other" + for id, enter_tag, sell_reason, profit, profit_abs, count in mix_tag_perf: + enter_tag = enter_tag if enter_tag is not None else "Other" sell_reason = sell_reason if sell_reason is not None else "Other" - if(sell_reason is not None and buy_tag is not None): - mix_tag = buy_tag + " " + sell_reason + if(sell_reason is not None and enter_tag is not None): + mix_tag = enter_tag + " " + sell_reason i = 0 if not any(item["mix_tag"] == mix_tag for item in return_list): return_list.append({'mix_tag': mix_tag, diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 90c6c1bce..9f6b193d3 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -237,7 +237,7 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots: # Create description for sell summarizing the trade trades['desc'] = trades.apply( lambda row: f"{row['profit_ratio']:.2%}, " + - (f"{row['buy_tag']}, " if row['buy_tag'] is not None else "") + + (f"{row['enter_tag']}, " if row['enter_tag'] is not None else "") + f"{row['sell_reason']}, " + f"{row['trade_duration']} min", axis=1) @@ -431,8 +431,8 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra ) fig.add_trace(candles, 1, 1) - if 'buy' in data.columns: - df_buy = data[data['buy'] == 1] + if 'enter_long' in data.columns: + df_buy = data[data['enter_long'] == 1] if len(df_buy) > 0: buys = go.Scatter( x=df_buy.date, @@ -450,8 +450,8 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra else: logger.warning("No buy-signals found.") - if 'sell' in data.columns: - df_sell = data[data['sell'] == 1] + if 'exit_long' in data.columns: + df_sell = data[data['exit_long'] == 1] if len(df_sell) > 0: sells = go.Scatter( x=df_sell.date, @@ -536,7 +536,7 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame], "Profit per pair", "Parallelism", "Underwater", - ]) + ]) fig['layout'].update(title="Freqtrade Profit plot") fig['layout']['yaxis1'].update(title='Price') fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}') diff --git a/freqtrade/plugins/pairlist/AgeFilter.py b/freqtrade/plugins/pairlist/AgeFilter.py index a6d5ec79b..bb6f75012 100644 --- a/freqtrade/plugins/pairlist/AgeFilter.py +++ b/freqtrade/plugins/pairlist/AgeFilter.py @@ -9,6 +9,7 @@ import arrow from pandas import DataFrame from freqtrade.configuration import PeriodicCache +from freqtrade.constants import ListPairsWithTimeframes from freqtrade.exceptions import OperationalException from freqtrade.misc import plural from freqtrade.plugins.pairlist.IPairList import IPairList @@ -71,8 +72,8 @@ class AgeFilter(IPairList): :param tickers: Tickers (from exchange.get_tickers()). May be cached. :return: new allowlist """ - needed_pairs = [ - (p, '1d') for p in pairlist + needed_pairs: ListPairsWithTimeframes = [ + (p, '1d', self._config['candle_type_def']) for p in pairlist if p not in self._symbolsChecked and p not in self._symbolsCheckFailed] if not needed_pairs: # Remove pairs that have been removed before @@ -88,7 +89,8 @@ class AgeFilter(IPairList): candles = self._exchange.refresh_latest_ohlcv(needed_pairs, since_ms=since_ms, cache=False) if self._enabled: for p in deepcopy(pairlist): - daily_candles = candles[(p, '1d')] if (p, '1d') in candles else None + daily_candles = candles[(p, '1d', self._config['candle_type_def'])] if ( + p, '1d', self._config['candle_type_def']) in candles else None if not self._validate_pair_loc(p, daily_candles): pairlist.remove(p) self.log_once(f"Validated {len(pairlist)} pairs.", logger.info) diff --git a/freqtrade/plugins/pairlist/PriceFilter.py b/freqtrade/plugins/pairlist/PriceFilter.py index 63623d8c8..009789eaf 100644 --- a/freqtrade/plugins/pairlist/PriceFilter.py +++ b/freqtrade/plugins/pairlist/PriceFilter.py @@ -90,8 +90,7 @@ class PriceFilter(IPairList): price = ticker['last'] market = self._exchange.markets[pair] limits = market['limits'] - if ('amount' in limits and 'min' in limits['amount'] - and limits['amount']['min'] is not None): + if (limits['amount']['min'] is not None): min_amount = limits['amount']['min'] min_precision = market['precision']['amount'] diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py index 8a7eeeca8..7a355c291 100644 --- a/freqtrade/plugins/pairlist/VolatilityFilter.py +++ b/freqtrade/plugins/pairlist/VolatilityFilter.py @@ -11,6 +11,7 @@ import numpy as np from cachetools import TTLCache from pandas import DataFrame +from freqtrade.constants import ListPairsWithTimeframes from freqtrade.exceptions import OperationalException from freqtrade.misc import plural from freqtrade.plugins.pairlist.IPairList import IPairList @@ -33,6 +34,7 @@ class VolatilityFilter(IPairList): self._min_volatility = pairlistconfig.get('min_volatility', 0) self._max_volatility = pairlistconfig.get('max_volatility', sys.maxsize) self._refresh_period = pairlistconfig.get('refresh_period', 1440) + self._def_candletype = self._config['candle_type_def'] self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period) @@ -67,7 +69,8 @@ class VolatilityFilter(IPairList): :param tickers: Tickers (from exchange.get_tickers()). May be cached. :return: new allowlist """ - needed_pairs = [(p, '1d') for p in pairlist if p not in self._pair_cache] + needed_pairs: ListPairsWithTimeframes = [ + (p, '1d', self._def_candletype) for p in pairlist if p not in self._pair_cache] since_ms = (arrow.utcnow() .floor('day') @@ -81,7 +84,8 @@ class VolatilityFilter(IPairList): if self._enabled: for p in deepcopy(pairlist): - daily_candles = candles[(p, '1d')] if (p, '1d') in candles else None + daily_candles = candles[(p, '1d', self._def_candletype)] if ( + p, '1d', self._def_candletype) in candles else None if not self._validate_pair_loc(p, daily_candles): pairlist.remove(p) return pairlist diff --git a/freqtrade/plugins/pairlist/VolumePairList.py b/freqtrade/plugins/pairlist/VolumePairList.py index 5d78422bb..26e7d45be 100644 --- a/freqtrade/plugins/pairlist/VolumePairList.py +++ b/freqtrade/plugins/pairlist/VolumePairList.py @@ -9,6 +9,7 @@ from typing import Any, Dict, List import arrow from cachetools import TTLCache +from freqtrade.constants import ListPairsWithTimeframes from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes from freqtrade.misc import format_ms_time @@ -42,6 +43,7 @@ class VolumePairList(IPairList): self._lookback_days = self._pairlistconfig.get('lookback_days', 0) self._lookback_timeframe = self._pairlistconfig.get('lookback_timeframe', '1d') self._lookback_period = self._pairlistconfig.get('lookback_period', 0) + self._def_candletype = self._config['candle_type_def'] if (self._lookback_days > 0) & (self._lookback_period > 0): raise OperationalException( @@ -69,10 +71,13 @@ class VolumePairList(IPairList): f'to at least {self._tf_in_sec} and restart the bot.' ) - if not self._exchange.exchange_has('fetchTickers'): + if (not self._use_range and not ( + self._exchange.exchange_has('fetchTickers') + and self._exchange._ft_has["tickers_have_quoteVolume"])): raise OperationalException( - 'Exchange does not support dynamic whitelist. ' - 'Please edit your config and restart the bot.' + "Exchange does not support dynamic whitelist in this configuration. " + "Please edit your config and either remove Volumepairlist, " + "or switch to using candles. and restart the bot." ) if not self._validate_keys(self._sort_key): @@ -93,7 +98,7 @@ class VolumePairList(IPairList): If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ - return True + return not self._use_range def _validate_keys(self, key): return key in SORT_VALUES @@ -121,16 +126,18 @@ class VolumePairList(IPairList): # Check if pair quote currency equals to the stake currency. _pairlist = [k for k in self._exchange.get_markets( quote_currencies=[self._stake_currency], - pairs_only=True, active_only=True).keys()] + tradable_only=True, active_only=True).keys()] # No point in testing for blacklisted pairs... _pairlist = self.verify_blacklist(_pairlist, logger.info) - - filtered_tickers = [ - v for k, v in tickers.items() - if (self._exchange.get_pair_quote_currency(k) == self._stake_currency - and (self._use_range or v[self._sort_key] is not None) - and v['symbol'] in _pairlist)] - pairlist = [s['symbol'] for s in filtered_tickers] + if not self._use_range: + filtered_tickers = [ + v for k, v in tickers.items() + if (self._exchange.get_pair_quote_currency(k) == self._stake_currency + and (self._use_range or v[self._sort_key] is not None) + and v['symbol'] in _pairlist)] + pairlist = [s['symbol'] for s in filtered_tickers] + else: + pairlist = _pairlist pairlist = self.filter_pairlist(pairlist, tickers) self._pair_cache['pairlist'] = pairlist.copy() @@ -145,11 +152,11 @@ class VolumePairList(IPairList): :param tickers: Tickers (from exchange.get_tickers()). May be cached. :return: new whitelist """ - # Use the incoming pairlist. - filtered_tickers = [v for k, v in tickers.items() if k in pairlist] - - # get lookback period in ms, for exchange ohlcv fetch if self._use_range: + # Create bare minimum from tickers structure. + filtered_tickers: List[Dict[str, Any]] = [{'symbol': k} for k in pairlist] + + # get lookback period in ms, for exchange ohlcv fetch since_ms = int(arrow.utcnow() .floor('minute') .shift(minutes=-(self._lookback_period * self._tf_in_min) @@ -165,11 +172,10 @@ class VolumePairList(IPairList): self.log_once(f"Using volume range of {self._lookback_period} candles, timeframe: " f"{self._lookback_timeframe}, starting from {format_ms_time(since_ms)} " f"till {format_ms_time(to_ms)}", logger.info) - needed_pairs = [ - (p, self._lookback_timeframe) for p in - [ - s['symbol'] for s in filtered_tickers - ] if p not in self._pair_cache + needed_pairs: ListPairsWithTimeframes = [ + (p, self._lookback_timeframe, self._def_candletype) for p in + [s['symbol'] for s in filtered_tickers] + if p not in self._pair_cache ] # Get all candles @@ -180,8 +186,10 @@ class VolumePairList(IPairList): ) for i, p in enumerate(filtered_tickers): pair_candles = candles[ - (p['symbol'], self._lookback_timeframe) - ] if (p['symbol'], self._lookback_timeframe) in candles else None + (p['symbol'], self._lookback_timeframe, self._def_candletype) + ] if ( + p['symbol'], self._lookback_timeframe, self._def_candletype + ) in candles else None # in case of candle data calculate typical price and quoteVolume for candle if pair_candles is not None and not pair_candles.empty: if self._exchange._ft_has["ohlcv_volume_currency"] == "base": @@ -205,6 +213,9 @@ class VolumePairList(IPairList): filtered_tickers[i]['quoteVolume'] = quoteVolume else: filtered_tickers[i]['quoteVolume'] = 0 + else: + # Tickers mode - filter based on incomming pairlist. + filtered_tickers = [v for k, v in tickers.items() if k in pairlist] if self._min_value > 0: filtered_tickers = [ diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index e17ec2dab..c9edfd13d 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -9,6 +9,7 @@ import arrow from cachetools import TTLCache from pandas import DataFrame +from freqtrade.constants import ListPairsWithTimeframes from freqtrade.exceptions import OperationalException from freqtrade.misc import plural from freqtrade.plugins.pairlist.IPairList import IPairList @@ -28,6 +29,7 @@ class RangeStabilityFilter(IPairList): self._min_rate_of_change = pairlistconfig.get('min_rate_of_change', 0.01) self._max_rate_of_change = pairlistconfig.get('max_rate_of_change', None) self._refresh_period = pairlistconfig.get('refresh_period', 1440) + self._def_candletype = self._config['candle_type_def'] self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period) @@ -65,7 +67,8 @@ class RangeStabilityFilter(IPairList): :param tickers: Tickers (from exchange.get_tickers()). May be cached. :return: new allowlist """ - needed_pairs = [(p, '1d') for p in pairlist if p not in self._pair_cache] + needed_pairs: ListPairsWithTimeframes = [ + (p, '1d', self._def_candletype) for p in pairlist if p not in self._pair_cache] since_ms = (arrow.utcnow() .floor('day') @@ -79,7 +82,8 @@ class RangeStabilityFilter(IPairList): if self._enabled: for p in deepcopy(pairlist): - daily_candles = candles[(p, '1d')] if (p, '1d') in candles else None + daily_candles = candles[(p, '1d', self._def_candletype)] if ( + p, '1d', self._def_candletype) in candles else None if not self._validate_pair_loc(p, daily_candles): pairlist.remove(p) return pairlist diff --git a/freqtrade/plugins/pairlistmanager.py b/freqtrade/plugins/pairlistmanager.py index 1e79dc743..2ae67a157 100644 --- a/freqtrade/plugins/pairlistmanager.py +++ b/freqtrade/plugins/pairlistmanager.py @@ -8,6 +8,7 @@ from typing import Dict, List from cachetools import TTLCache, cached from freqtrade.constants import ListPairsWithTimeframes +from freqtrade.enums import CandleType from freqtrade.exceptions import OperationalException from freqtrade.mixins import LoggingMixin from freqtrade.plugins.pairlist.IPairList import IPairList @@ -132,7 +133,6 @@ class PairListManager(LoggingMixin): :return: pairlist - whitelisted pairs """ try: - whitelist = expand_pairlist(pairlist, self._exchange.get_markets().keys(), keep_invalid) except ValueError as err: logger.error(f"Pair whitelist contains an invalid Wildcard: {err}") @@ -143,4 +143,10 @@ class PairListManager(LoggingMixin): """ Create list of pair tuples with (pair, timeframe) """ - return [(pair, timeframe or self._config['timeframe']) for pair in pairs] + return [ + ( + pair, + timeframe or self._config['timeframe'], + self._config.get('candle_type_def', CandleType.SPOT) + ) for pair in pairs + ] diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index c5d390f52..b6ef92bd5 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -36,7 +36,7 @@ class MaxDrawdown(IProtection): """ LockReason to use """ - return (f'{drawdown} > {self._max_allowed_drawdown} in {self.lookback_period_str}, ' + return (f'{drawdown} passed {self._max_allowed_drawdown} in {self.lookback_period_str}, ' f'locking for {self.stop_duration_str}.') def _max_drawdown(self, date_now: datetime) -> ProtectionReturn: diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index 40edf1204..7a29c20b1 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -3,7 +3,7 @@ import logging from datetime import datetime, timedelta from typing import Any, Dict -from freqtrade.enums import SellType +from freqtrade.enums import ExitType from freqtrade.persistence import Trade from freqtrade.plugins.protections import IProtection, ProtectionReturn @@ -44,8 +44,8 @@ class StoplossGuard(IProtection): # filters = [ # Trade.is_open.is_(False), # Trade.close_date > look_back_until, - # or_(Trade.sell_reason == SellType.STOP_LOSS.value, - # and_(Trade.sell_reason == SellType.TRAILING_STOP_LOSS.value, + # or_(Trade.sell_reason == ExitType.STOP_LOSS.value, + # and_(Trade.sell_reason == ExitType.TRAILING_STOP_LOSS.value, # Trade.close_profit < 0)) # ] # if pair: @@ -54,8 +54,8 @@ class StoplossGuard(IProtection): trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until) trades = [trade for trade in trades1 if (str(trade.sell_reason) in ( - SellType.TRAILING_STOP_LOSS.value, SellType.STOP_LOSS.value, - SellType.STOPLOSS_ON_EXCHANGE.value) + ExitType.TRAILING_STOP_LOSS.value, ExitType.STOP_LOSS.value, + ExitType.STOPLOSS_ON_EXCHANGE.value) and trade.close_profit and trade.close_profit < 0)] if len(trades) < self._trade_limit: diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 8ad7cdb59..87a9cc4b3 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -10,7 +10,9 @@ from inspect import getfullargspec from pathlib import Path from typing import Any, Dict, Optional +from freqtrade.configuration.config_validation import validate_migrated_strategy_settings from freqtrade.constants import REQUIRED_ORDERTIF, REQUIRED_ORDERTYPES, USERPATH_STRATEGIES +from freqtrade.enums import TradingMode from freqtrade.exceptions import OperationalException from freqtrade.resolvers import IResolver from freqtrade.strategy.interface import IStrategy @@ -147,14 +149,70 @@ class StrategyResolver(IResolver): return strategy @staticmethod - def _strategy_sanity_validations(strategy): + def _strategy_sanity_validations(strategy: IStrategy): + # Ensure necessary migrations are performed first. + validate_migrated_strategy_settings(strategy.config) + if not all(k in strategy.order_types for k in REQUIRED_ORDERTYPES): raise ImportError(f"Impossible to load Strategy '{strategy.__class__.__name__}'. " f"Order-types mapping is incomplete.") - if not all(k in strategy.order_time_in_force for k in REQUIRED_ORDERTIF): raise ImportError(f"Impossible to load Strategy '{strategy.__class__.__name__}'. " f"Order-time-in-force mapping is incomplete.") + trading_mode = strategy.config.get('trading_mode', TradingMode.SPOT) + + if (strategy.can_short and trading_mode == TradingMode.SPOT): + raise ImportError( + "Short strategies cannot run in spot markets. Please make sure that this " + "is the correct strategy and that your trading mode configuration is correct. " + "You can run this strategy in spot markets by setting `can_short=False`" + " in your strategy. Please note that short signals will be ignored in that case." + ) + + @staticmethod + def validate_strategy(strategy: IStrategy) -> IStrategy: + if strategy.config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT: + # Require new method + if not check_override(strategy, IStrategy, 'populate_entry_trend'): + raise OperationalException("`populate_entry_trend` must be implemented.") + if not check_override(strategy, IStrategy, 'populate_exit_trend'): + raise OperationalException("`populate_exit_trend` must be implemented.") + if check_override(strategy, IStrategy, 'check_buy_timeout'): + raise OperationalException("Please migrate your implementation " + "of `check_buy_timeout` to `check_entry_timeout`.") + if check_override(strategy, IStrategy, 'check_sell_timeout'): + raise OperationalException("Please migrate your implementation " + "of `check_sell_timeout` to `check_exit_timeout`.") + + if check_override(strategy, IStrategy, 'custom_sell'): + raise OperationalException( + "Please migrate your implementation of `custom_sell` to `custom_exit`.") + else: + # TODO: Implementing one of the following methods should show a deprecation warning + # buy_trend and sell_trend, custom_sell + if ( + not check_override(strategy, IStrategy, 'populate_buy_trend') + and not check_override(strategy, IStrategy, 'populate_entry_trend') + ): + raise OperationalException( + "`populate_entry_trend` or `populate_buy_trend` must be implemented.") + if ( + not check_override(strategy, IStrategy, 'populate_sell_trend') + and not check_override(strategy, IStrategy, 'populate_exit_trend') + ): + raise OperationalException( + "`populate_exit_trend` or `populate_sell_trend` must be implemented.") + + strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args) + strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args) + strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args) + if any(x == 2 for x in [ + strategy._populate_fun_len, + strategy._buy_fun_len, + strategy._sell_fun_len + ]): + strategy.INTERFACE_VERSION = 1 + return strategy @staticmethod def _load_strategy(strategy_name: str, @@ -187,23 +245,26 @@ class StrategyResolver(IResolver): # register temp path with the bot abs_paths.insert(0, temp.resolve()) - strategy = StrategyResolver._load_object(paths=abs_paths, - object_name=strategy_name, - add_source=True, - kwargs={'config': config}, - ) - if strategy: - strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args) - strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args) - strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args) - if any(x == 2 for x in [strategy._populate_fun_len, - strategy._buy_fun_len, - strategy._sell_fun_len]): - strategy.INTERFACE_VERSION = 1 + strategy = StrategyResolver._load_object( + paths=abs_paths, + object_name=strategy_name, + add_source=True, + kwargs={'config': config}, + ) - return strategy + if strategy: + + return StrategyResolver.validate_strategy(strategy) raise OperationalException( f"Impossible to load Strategy '{strategy_name}'. This class does not exist " "or contains Python code errors." ) + + +def check_override(object, parentclass, attribute): + """ + Checks if a object overrides the parent class attribute. + :returns: True if the object is overridden. + """ + return getattr(type(object), attribute) != getattr(parentclass, attribute) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 32c7e9214..11baa9560 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional, Union from pydantic import BaseModel from freqtrade.constants import DATETIME_PRINT_FORMAT -from freqtrade.enums import OrderTypeValues +from freqtrade.enums import OrderTypeValues, SignalDirection, TradingMode class Ping(BaseModel): @@ -38,6 +38,11 @@ class Balance(BaseModel): used: float est_stake: float stake: str + # Starting with 2.x + side: str + leverage: float + is_position: bool + position: float class Balances(BaseModel): @@ -126,18 +131,18 @@ class Daily(BaseModel): class UnfilledTimeout(BaseModel): - buy: Optional[int] - sell: Optional[int] + entry: Optional[int] + exit: Optional[int] unit: Optional[str] exit_timeout_count: Optional[int] class OrderTypes(BaseModel): - buy: OrderTypeValues - sell: OrderTypeValues - emergencysell: Optional[OrderTypeValues] - forcesell: Optional[OrderTypeValues] - forcebuy: Optional[OrderTypeValues] + entry: OrderTypeValues + exit: OrderTypeValues + emergencyexit: Optional[OrderTypeValues] + forceexit: Optional[OrderTypeValues] + forceentry: Optional[OrderTypeValues] stoploss: OrderTypeValues stoploss_on_exchange: bool stoploss_on_exchange_interval: Optional[int] @@ -148,6 +153,8 @@ class ShowConfig(BaseModel): strategy_version: Optional[str] api_version: float dry_run: bool + trading_mode: str + short_allowed: bool stake_currency: str stake_amount: str available_capital: Optional[float] @@ -168,8 +175,8 @@ class ShowConfig(BaseModel): exchange: str strategy: Optional[str] forcebuy_enabled: bool - ask_strategy: Dict[str, Any] - bid_strategy: Dict[str, Any] + exit_pricing: Dict[str, Any] + entry_pricing: Dict[str, Any] bot_name: str state: str runmode: str @@ -197,12 +204,14 @@ class TradeSchema(BaseModel): trade_id: int pair: str is_open: bool + is_short: bool exchange: str amount: float amount_requested: float stake_amount: float strategy: str - buy_tag: Optional[str] + buy_tag: Optional[str] # Deprecated + enter_tag: Optional[str] timeframe: int fee_open: Optional[float] fee_open_cost: Optional[float] @@ -242,6 +251,11 @@ class TradeSchema(BaseModel): open_order_id: Optional[str] orders: List[OrderSchema] + leverage: Optional[float] + interest_rate: Optional[float] + funding_fees: Optional[float] + trading_mode: Optional[TradingMode] + class OpenTradeSchema(TradeSchema): stoploss_current_dist: Optional[float] @@ -262,7 +276,7 @@ class TradeResponse(BaseModel): total_trades: int -class ForceBuyResponse(BaseModel): +class ForceEnterResponse(BaseModel): __root__: Union[TradeSchema, StatusMsg] @@ -292,15 +306,16 @@ class Logs(BaseModel): logs: List[List] -class ForceBuyPayload(BaseModel): +class ForceEnterPayload(BaseModel): pair: str + side: SignalDirection = SignalDirection.LONG price: Optional[float] ordertype: Optional[OrderTypeValues] stakeamount: Optional[float] entry_tag: Optional[str] -class ForceSellPayload(BaseModel): +class ForceExitPayload(BaseModel): tradeid: str ordertype: Optional[OrderTypeValues] @@ -364,6 +379,10 @@ class PairHistory(BaseModel): length: int buy_signals: int sell_signals: int + enter_long_signals: int + exit_long_signals: int + enter_short_signals: int + exit_short_signals: int last_analyzed: datetime last_analyzed_ts: int data_start_ts: int diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 5a34385da..61c5243aa 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -9,13 +9,14 @@ from fastapi.exceptions import HTTPException from freqtrade import __version__ from freqtrade.constants import USERPATH_STRATEGIES from freqtrade.data.history import get_datahandler +from freqtrade.enums import CandleType, TradingMode from freqtrade.exceptions import OperationalException from freqtrade.rpc import RPC from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload, BlacklistResponse, Count, Daily, - DeleteLockRequest, DeleteTrade, ForceBuyPayload, - ForceBuyResponse, ForceSellPayload, Health, Locks, - Logs, OpenTradeSchema, PairHistory, + DeleteLockRequest, DeleteTrade, ForceEnterPayload, + ForceEnterResponse, ForceExitPayload, Health, + Locks, Logs, OpenTradeSchema, PairHistory, PerformanceEntry, Ping, PlotConfig, Profit, ResultMsg, ShowConfig, Stats, StatusMsg, StrategyListResponse, StrategyResponse, SysInfo, @@ -32,8 +33,9 @@ logger = logging.getLogger(__name__) # 1.11: forcebuy and forcesell accept ordertype # 1.12: add blacklist delete endpoint # 1.13: forcebuy supports stake_amount -# 1.14: Add entry/exit orders to trade response -API_VERSION = 1.14 +# versions 2.xx -> futures/short branch +# 2.14: Add entry/exit orders to trade response +API_VERSION = 2.14 # Public API, requires no auth. router_public = APIRouter() @@ -133,24 +135,30 @@ def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(g return resp -@router.post('/forcebuy', response_model=ForceBuyResponse, tags=['trading']) -def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)): +# /forcebuy is deprecated with short addition. use ForceEntry instead +@router.post('/forceenter', response_model=ForceEnterResponse, tags=['trading']) +@router.post('/forcebuy', response_model=ForceEnterResponse, tags=['trading']) +def forceentry(payload: ForceEnterPayload, rpc: RPC = Depends(get_rpc)): ordertype = payload.ordertype.value if payload.ordertype else None stake_amount = payload.stakeamount if payload.stakeamount else None entry_tag = payload.entry_tag if payload.entry_tag else 'forceentry' - trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype, stake_amount, entry_tag) + trade = rpc._rpc_force_entry(payload.pair, payload.price, order_side=payload.side, + order_type=ordertype, stake_amount=stake_amount, + enter_tag=entry_tag) if trade: - return ForceBuyResponse.parse_obj(trade.to_json()) + return ForceEnterResponse.parse_obj(trade.to_json()) else: - return ForceBuyResponse.parse_obj({"status": f"Error buying pair {payload.pair}."}) + return ForceEnterResponse.parse_obj( + {"status": f"Error entering {payload.side} trade for pair {payload.pair}."}) +@router.post('/forceexit', response_model=ResultMsg, tags=['trading']) @router.post('/forcesell', response_model=ResultMsg, tags=['trading']) -def forcesell(payload: ForceSellPayload, rpc: RPC = Depends(get_rpc)): +def forcesell(payload: ForceExitPayload, rpc: RPC = Depends(get_rpc)): ordertype = payload.ordertype.value if payload.ordertype else None - return rpc._rpc_forcesell(payload.tradeid, ordertype) + return rpc._rpc_forceexit(payload.tradeid, ordertype) @router.get('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist']) @@ -268,16 +276,22 @@ def get_strategy(strategy: str, config=Depends(get_config)): @router.get('/available_pairs', response_model=AvailablePairs, tags=['candle data']) def list_available_pairs(timeframe: Optional[str] = None, stake_currency: Optional[str] = None, - config=Depends(get_config)): + candletype: Optional[CandleType] = None, config=Depends(get_config)): dh = get_datahandler(config['datadir'], config.get('dataformat_ohlcv', None)) - - pair_interval = dh.ohlcv_get_available_data(config['datadir']) + trading_mode: TradingMode = config.get('trading_mode', TradingMode.SPOT) + pair_interval = dh.ohlcv_get_available_data(config['datadir'], trading_mode) if timeframe: pair_interval = [pair for pair in pair_interval if pair[1] == timeframe] if stake_currency: pair_interval = [pair for pair in pair_interval if pair[0].endswith(stake_currency)] + if candletype: + pair_interval = [pair for pair in pair_interval if pair[2] == candletype] + else: + candle_type = CandleType.get_default(trading_mode) + pair_interval = [pair for pair in pair_interval if pair[2] == candle_type] + pair_interval = sorted(pair_interval, key=lambda x: x[0]) pairs = list({x[0] for x in pair_interval}) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 7fd419a5b..94bc513fb 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -18,7 +18,7 @@ from freqtrade import __version__ from freqtrade.configuration.timerange import TimeRange from freqtrade.constants import CANCEL_REASON, DATETIME_PRINT_FORMAT from freqtrade.data.history import load_data -from freqtrade.enums import SellType, State +from freqtrade.enums import ExitCheckTuple, ExitType, SignalDirection, State, TradingMode from freqtrade.exceptions import ExchangeError, PricingError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs from freqtrade.loggers import bufferHandler @@ -27,7 +27,7 @@ from freqtrade.persistence import PairLocks, Trade from freqtrade.persistence.models import PairLock from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.rpc.fiat_convert import CryptoToFiatConverter -from freqtrade.strategy.interface import SellCheckTuple +from freqtrade.wallets import PositionWallet, Wallet logger = logging.getLogger(__name__) @@ -110,6 +110,8 @@ class RPC: 'version': __version__, 'strategy_version': strategy_version, 'dry_run': config['dry_run'], + 'trading_mode': config.get('trading_mode', 'spot'), + 'short_allowed': config.get('trading_mode', 'spot') != 'spot', 'stake_currency': config['stake_currency'], 'stake_currency_decimals': decimals_per_coin(config['stake_currency']), 'stake_amount': str(config['stake_amount']), @@ -134,8 +136,8 @@ class RPC: 'exchange': config['exchange']['name'], 'strategy': config['strategy'], 'forcebuy_enabled': config.get('forcebuy_enable', False), - 'ask_strategy': config.get('ask_strategy', {}), - 'bid_strategy': config.get('bid_strategy', {}), + 'exit_pricing': config.get('exit_pricing', {}), + 'entry_pricing': config.get('entry_pricing', {}), 'state': str(botstate), 'runmode': config['runmode'].value, 'position_adjustment_enable': config.get('position_adjustment_enable', False), @@ -153,7 +155,7 @@ class RPC: """ # Fetch open trades if trade_ids: - trades = Trade.get_trades(trade_filter=Trade.id.in_(trade_ids)).all() + trades: List[Trade] = Trade.get_trades(trade_filter=Trade.id.in_(trade_ids)).all() else: trades = Trade.get_open_trades() @@ -169,7 +171,7 @@ class RPC: if trade.is_open: try: current_rate = self._freqtrade.exchange.get_rate( - trade.pair, refresh=False, side="sell") + trade.pair, side='exit', is_short=trade.is_short, refresh=False) except (ExchangeError, PricingError): current_rate = NAN else: @@ -219,7 +221,7 @@ class RPC: def _rpc_status_table(self, stake_currency: str, fiat_display_currency: str) -> Tuple[List, List, float]: - trades = Trade.get_open_trades() + trades: List[Trade] = Trade.get_open_trades() if not trades: raise RPCException('no active trade') else: @@ -229,11 +231,12 @@ class RPC: # calculate profit and send message to user try: current_rate = self._freqtrade.exchange.get_rate( - trade.pair, refresh=False, side="sell") + trade.pair, side='exit', is_short=trade.is_short, refresh=False) except (PricingError, ExchangeError): current_rate = NAN trade_profit = trade.calc_profit(current_rate) profit_str = f'{trade.calc_profit_ratio(current_rate):.2%}' + direction_str = 'S' if trade.is_short else 'L' if self._fiat_converter: fiat_profit = self._fiat_converter.convert_amount( trade_profit, @@ -245,7 +248,7 @@ class RPC: fiat_profit_sum = fiat_profit if isnan(fiat_profit_sum) \ else fiat_profit_sum + fiat_profit detail_trade = [ - trade.id, + f'{trade.id} {direction_str}', trade.pair + ('*' if (trade.open_order_id is not None and trade.close_rate_requested is None) else '') + ('**' if (trade.close_rate_requested is not None) else ''), @@ -253,20 +256,19 @@ class RPC: profit_str ] if self._config.get('position_adjustment_enable', False): - max_buy_str = '' + max_entry_str = '' if self._config.get('max_entry_position_adjustment', -1) > 0: - max_buy_str = f"/{self._config['max_entry_position_adjustment'] + 1}" - filled_buys = trade.nr_of_successful_buys - detail_trade.append(f"{filled_buys}{max_buy_str}") + max_entry_str = f"/{self._config['max_entry_position_adjustment'] + 1}" + filled_entries = trade.nr_of_successful_entries + detail_trade.append(f"{filled_entries}{max_entry_str}") trades_list.append(detail_trade) profitcol = "Profit" if self._fiat_converter: profitcol += " (" + fiat_display_currency + ")" + columns = ['ID L/S', 'Pair', 'Since', profitcol] if self._config.get('position_adjustment_enable', False): - columns = ['ID', 'Pair', 'Since', profitcol, '# Entries'] - else: - columns = ['ID', 'Pair', 'Since', profitcol] + columns.append('# Entries') return trades_list, columns, fiat_profit_sum def _rpc_daily_profit( @@ -453,7 +455,7 @@ class RPC: """ Returns cumulative profit statistics """ trade_filter = ((Trade.is_open.is_(False) & (Trade.close_date >= start_date)) | Trade.is_open.is_(True)) - trades = Trade.get_trades(trade_filter).order_by(Trade.id).all() + trades: List[Trade] = Trade.get_trades(trade_filter).order_by(Trade.id).all() profit_all_coin = [] profit_all_ratio = [] @@ -483,7 +485,7 @@ class RPC: # Get current rate try: current_rate = self._freqtrade.exchange.get_rate( - trade.pair, refresh=False, side="sell") + trade.pair, side='exit', is_short=trade.is_short, refresh=False) except (PricingError, ExchangeError): current_rate = NAN profit_ratio = trade.calc_profit_ratio(rate=current_rate) @@ -559,7 +561,7 @@ class RPC: def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict: """ Returns current account balance per crypto """ - output = [] + currencies = [] total = 0.0 try: tickers = self._freqtrade.exchange.get_tickers(cached=True) @@ -570,7 +572,8 @@ class RPC: starting_capital = self._freqtrade.wallets.get_starting_balance() starting_cap_fiat = self._fiat_converter.convert_amount( starting_capital, stake_currency, fiat_display_currency) if self._fiat_converter else 0 - + coin: str + balance: Wallet for coin, balance in self._freqtrade.wallets.get_all_balances().items(): if not balance.total: continue @@ -579,6 +582,9 @@ class RPC: if coin == stake_currency: rate = 1.0 est_stake = balance.total + if self._config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT: + # in Futures, "total" includes the locked stake, and therefore all positions + est_stake = balance.free else: try: pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency) @@ -591,13 +597,35 @@ class RPC: logger.warning(f" Could not get rate for pair {coin}.") continue total = total + (est_stake or 0) - output.append({ + currencies.append({ 'currency': coin, + # TODO: The below can be simplified if we don't assign None to values. 'free': balance.free if balance.free is not None else 0, 'balance': balance.total if balance.total is not None else 0, 'used': balance.used if balance.used is not None else 0, 'est_stake': est_stake or 0, 'stake': stake_currency, + 'side': 'long', + 'leverage': 1, + 'position': 0, + 'is_position': False, + }) + symbol: str + position: PositionWallet + for symbol, position in self._freqtrade.wallets.get_all_positions().items(): + total += position.collateral + + currencies.append({ + 'currency': symbol, + 'free': 0, + 'balance': 0, + 'used': 0, + 'position': position.position, + 'est_stake': position.collateral, + 'stake': stake_currency, + 'leverage': position.leverage, + 'side': position.side, + 'is_position': True }) value = self._fiat_converter.convert_amount( @@ -609,7 +637,7 @@ class RPC: starting_cap_fiat_ratio = (value / starting_cap_fiat) - 1 if starting_cap_fiat else 0.0 return { - 'currencies': output, + 'currencies': currencies, 'total': total, 'symbol': fiat_display_currency, 'value': value, @@ -655,7 +683,7 @@ class RPC: return {'status': 'No more buy will occur from now. Run /reload_config to reset.'} - def _rpc_forcesell(self, trade_id: str, ordertype: Optional[str] = None) -> Dict[str, str]: + def _rpc_forceexit(self, trade_id: str, ordertype: Optional[str] = None) -> Dict[str, str]: """ Handler for forcesell . Sells the given trade at current price @@ -666,24 +694,24 @@ class RPC: if trade.open_order_id: order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair) - if order['side'] == 'buy': + if order['side'] == trade.enter_side: fully_canceled = self._freqtrade.handle_cancel_enter( trade, order, CANCEL_REASON['FORCE_SELL']) - if order['side'] == 'sell': + if order['side'] == trade.exit_side: # Cancel order - so it is placed anew with a fresh price. self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_SELL']) if not fully_canceled: # Get current rate and execute sell current_rate = self._freqtrade.exchange.get_rate( - trade.pair, refresh=False, side="sell") - sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL) + trade.pair, side='exit', is_short=trade.is_short, refresh=True) + exit_check = ExitCheckTuple(exit_type=ExitType.FORCE_SELL) order_type = ordertype or self._freqtrade.strategy.order_types.get( - "forcesell", self._freqtrade.strategy.order_types["sell"]) + "forceexit", self._freqtrade.strategy.order_types["exit"]) self._freqtrade.execute_trade_exit( - trade, current_rate, sell_reason, ordertype=order_type) + trade, current_rate, exit_check, ordertype=order_type) # ---- EOF def _exec_forcesell ---- if self._freqtrade.state != State.RUNNING: @@ -703,7 +731,7 @@ class RPC: trade_filter=[Trade.id == trade_id, Trade.is_open.is_(True), ] ).first() if not trade: - logger.warning('forcesell: Invalid argument received') + logger.warning('forceexit: Invalid argument received') raise RPCException('invalid argument') _exec_forcesell(trade) @@ -711,20 +739,25 @@ class RPC: self._freqtrade.wallets.update() return {'result': f'Created sell order for trade {trade_id}.'} - def _rpc_forcebuy(self, pair: str, price: Optional[float], order_type: Optional[str] = None, - stake_amount: Optional[float] = None, - buy_tag: Optional[str] = 'forceentry') -> Optional[Trade]: + def _rpc_force_entry(self, pair: str, price: Optional[float], *, + order_type: Optional[str] = None, + order_side: SignalDirection = SignalDirection.LONG, + stake_amount: Optional[float] = None, + enter_tag: Optional[str] = 'forceentry') -> Optional[Trade]: """ Handler for forcebuy Buys a pair trade at the given or current price """ if not self._freqtrade.config.get('forcebuy_enable', False): - raise RPCException('Forcebuy not enabled.') + raise RPCException('Forceentry not enabled.') if self._freqtrade.state != State.RUNNING: raise RPCException('trader is not running') + if order_side == SignalDirection.SHORT and self._freqtrade.trading_mode == TradingMode.SPOT: + raise RPCException("Can't go short on Spot markets.") + # Check if pair quote currency equals to the stake currency. stake_currency = self._freqtrade.config.get('stake_currency') if not self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency: @@ -733,8 +766,10 @@ class RPC: # check if valid pair # check if pair already has an open pair - trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() + trade: Trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() + is_short = (order_side == SignalDirection.SHORT) if trade: + is_short = trade.is_short if not self._freqtrade.strategy.position_adjustment_enable: raise RPCException(f'position for {pair} already open - id: {trade.id}') @@ -745,9 +780,12 @@ class RPC: # execute buy if not order_type: order_type = self._freqtrade.strategy.order_types.get( - 'forcebuy', self._freqtrade.strategy.order_types['buy']) + 'forceentry', self._freqtrade.strategy.order_types['entry']) if self._freqtrade.execute_entry(pair, stake_amount, price, - ordertype=order_type, trade=trade, buy_tag=buy_tag): + ordertype=order_type, trade=trade, + is_short=is_short, + enter_tag=enter_tag, + ): Trade.commit() trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() return trade @@ -802,27 +840,23 @@ class RPC: return pair_rates - def _rpc_buy_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: + def _rpc_enter_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: """ Handler for buy tag performance. Shows a performance statistic from finished trades """ - buy_tags = Trade.get_buy_tag_performance(pair) - - return buy_tags + return Trade.get_enter_tag_performance(pair) def _rpc_sell_reason_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: """ Handler for sell reason performance. Shows a performance statistic from finished trades """ - sell_reasons = Trade.get_sell_reason_performance(pair) - - return sell_reasons + return Trade.get_sell_reason_performance(pair) def _rpc_mix_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: """ - Handler for mix tag (buy_tag + sell_reason) performance. + Handler for mix tag (enter_tag + sell_reason) performance. Shows a performance statistic from finished trades """ mix_tags = Trade.get_mix_tag_performance(pair) @@ -945,20 +979,21 @@ class RPC: def _convert_dataframe_to_dict(strategy: str, pair: str, timeframe: str, dataframe: DataFrame, last_analyzed: datetime) -> Dict[str, Any]: has_content = len(dataframe) != 0 - buy_signals = 0 - sell_signals = 0 + signals = { + 'enter_long': 0, + 'exit_long': 0, + 'enter_short': 0, + 'exit_short': 0, + } if has_content: dataframe.loc[:, '__date_ts'] = dataframe.loc[:, 'date'].view(int64) // 1000 // 1000 # Move signal close to separate column when signal for easy plotting - if 'buy' in dataframe.columns: - buy_mask = (dataframe['buy'] == 1) - buy_signals = int(buy_mask.sum()) - dataframe.loc[buy_mask, '_buy_signal_close'] = dataframe.loc[buy_mask, 'close'] - if 'sell' in dataframe.columns: - sell_mask = (dataframe['sell'] == 1) - sell_signals = int(sell_mask.sum()) - dataframe.loc[sell_mask, '_sell_signal_close'] = dataframe.loc[sell_mask, 'close'] + for sig_type in signals.keys(): + if sig_type in dataframe.columns: + mask = (dataframe[sig_type] == 1) + signals[sig_type] = int(mask.sum()) + dataframe.loc[mask, f'_{sig_type}_signal_close'] = dataframe.loc[mask, 'close'] # band-aid until this is fixed: # https://github.com/pandas-dev/pandas/issues/45836 @@ -978,8 +1013,12 @@ class RPC: 'columns': list(dataframe.columns), 'data': dataframe.values.tolist(), 'length': len(dataframe), - 'buy_signals': buy_signals, - 'sell_signals': sell_signals, + 'buy_signals': signals['enter_long'], # Deprecated + 'sell_signals': signals['exit_long'], # Deprecated + 'enter_long_signals': signals['enter_long'], + 'exit_long_signals': signals['exit_long'], + 'enter_short_signals': signals['enter_short'], + 'exit_short_signals': signals['exit_short'], 'last_analyzed': last_analyzed, 'last_analyzed_ts': int(last_analyzed.timestamp()), 'data_start': '', diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 5a20520dd..20ab86aeb 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -7,6 +7,7 @@ import json import logging import re from datetime import date, datetime, timedelta +from functools import partial from html import escape from itertools import chain from math import isnan @@ -22,7 +23,7 @@ from telegram.utils.helpers import escape_markdown from freqtrade.__init__ import __version__ from freqtrade.constants import DUST_PER_COIN -from freqtrade.enums import RPCMessageType +from freqtrade.enums import RPCMessageType, SignalDirection, TradingMode from freqtrade.exceptions import OperationalException from freqtrade.misc import chunks, plural, round_coin_value from freqtrade.persistence import Trade @@ -113,7 +114,8 @@ class Telegram(RPCHandler): r'/stopbuy$', r'/reload_config$', r'/show_config$', r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$', r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', - r'/forcebuy$', r'/edge$', r'/health$', r'/help$', r'/version$'] + r'/forcebuy$', r'/forcelong$', r'/forceshort$', + r'/edge$', r'/health$', r'/help$', r'/version$'] # Create keys for generation valid_keys_print = [k.replace('$', '') for k in valid_keys] @@ -150,12 +152,15 @@ class Telegram(RPCHandler): CommandHandler('balance', self._balance), CommandHandler('start', self._start), CommandHandler('stop', self._stop), - CommandHandler('forcesell', self._forcesell), - CommandHandler('forcebuy', self._forcebuy), + CommandHandler(['forcesell', 'forceexit'], self._forceexit), + CommandHandler(['forcebuy', 'forcelong'], partial( + self._forceenter, order_side=SignalDirection.LONG)), + CommandHandler('forceshort', partial( + self._forceenter, order_side=SignalDirection.SHORT)), CommandHandler('trades', self._trades), CommandHandler('delete', self._delete_trade), CommandHandler('performance', self._performance), - CommandHandler('buys', self._buy_tag_performance), + CommandHandler(['buys', 'entries'], self._enter_tag_performance), CommandHandler('sells', self._sell_reason_performance), CommandHandler('mix_tags', self._mix_tag_performance), CommandHandler('stats', self._stats), @@ -185,12 +190,13 @@ class Telegram(RPCHandler): CallbackQueryHandler(self._profit, pattern='update_profit'), CallbackQueryHandler(self._balance, pattern='update_balance'), CallbackQueryHandler(self._performance, pattern='update_performance'), - CallbackQueryHandler(self._buy_tag_performance, pattern='update_buy_tag_performance'), + CallbackQueryHandler(self._enter_tag_performance, + pattern='update_enter_tag_performance'), CallbackQueryHandler(self._sell_reason_performance, pattern='update_sell_reason_performance'), CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'), CallbackQueryHandler(self._count, pattern='update_count'), - CallbackQueryHandler(self._forcebuy_inline), + CallbackQueryHandler(self._forceenter_inline), ] for handle in handles: self._updater.dispatcher.add_handler(handle) @@ -223,20 +229,25 @@ class Telegram(RPCHandler): msg['stake_amount'], msg['stake_currency'], msg['fiat_currency']) else: msg['stake_amount_fiat'] = 0 - is_fill = msg['type'] == RPCMessageType.BUY_FILL + is_fill = msg['type'] in [RPCMessageType.BUY_FILL, RPCMessageType.SHORT_FILL] emoji = '\N{CHECK MARK}' if is_fill else '\N{LARGE BLUE CIRCLE}' + enter_side = ({'enter': 'Long', 'entered': 'Longed'} if msg['type'] + in [RPCMessageType.BUY_FILL, RPCMessageType.BUY] + else {'enter': 'Short', 'entered': 'Shorted'}) message = ( - f"{emoji} *{msg['exchange']}:* {'Bought' if is_fill else 'Buying'} {msg['pair']}" + f"{emoji} *{msg['exchange']}:*" + f" {enter_side['entered'] if is_fill else enter_side['enter']} {msg['pair']}" f" (#{msg['trade_id']})\n" ) - message += f"*Buy Tag:* `{msg['buy_tag']}`\n" if msg.get('buy_tag', None) else "" + message += f"*Enter Tag:* `{msg['enter_tag']}`\n" if msg.get('enter_tag', None) else "" message += f"*Amount:* `{msg['amount']:.8f}`\n" + if msg.get('leverage') and msg.get('leverage', 1.0) != 1.0: + message += f"*Leverage:* `{msg['leverage']}`\n" - if msg['type'] == RPCMessageType.BUY_FILL: + if msg['type'] in [RPCMessageType.BUY_FILL, RPCMessageType.SHORT_FILL]: message += f"*Open Rate:* `{msg['open_rate']:.8f}`\n" - - elif msg['type'] == RPCMessageType.BUY: + elif msg['type'] in [RPCMessageType.BUY, RPCMessageType.SHORT]: message += f"*Open Rate:* `{msg['limit']:.8f}`\n"\ f"*Current Rate:* `{msg['current_rate']:.8f}`\n" @@ -255,8 +266,11 @@ class Telegram(RPCHandler): microsecond=0) - msg['open_date'].replace(microsecond=0) msg['duration_min'] = msg['duration'].total_seconds() / 60 - msg['buy_tag'] = msg['buy_tag'] if "buy_tag" in msg.keys() else None + msg['enter_tag'] = msg['enter_tag'] if "enter_tag" in msg.keys() else None msg['emoji'] = self._get_sell_emoji(msg) + msg['leverage_text'] = (f"*Leverage:* `{msg['leverage']:.1f}`\n" + if msg.get('leverage', None) and msg.get('leverage', 1.0) != 1.0 + else "") # Check if all sell properties are available. # This might not be the case if the message origin is triggered by /forcesell @@ -272,15 +286,17 @@ class Telegram(RPCHandler): is_fill = msg['type'] == RPCMessageType.SELL_FILL message = ( f"{msg['emoji']} *{msg['exchange']}:* " - f"{'Sold' if is_fill else 'Selling'} {msg['pair']} (#{msg['trade_id']})\n" + f"{'Exited' if is_fill else 'Exiting'} {msg['pair']} (#{msg['trade_id']})\n" f"*{'Profit' if is_fill else 'Unrealized Profit'}:* " f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n" - f"*Buy Tag:* `{msg['buy_tag']}`\n" - f"*Sell Reason:* `{msg['sell_reason']}`\n" + f"*Enter Tag:* `{msg['enter_tag']}`\n" + f"*Exit Reason:* `{msg['sell_reason']}`\n" f"*Duration:* `{msg['duration']} ({msg['duration_min']:.1f} min)`\n" + f"*Direction:* `{msg['direction']}`\n" + f"{msg['leverage_text']}" f"*Amount:* `{msg['amount']:.8f}`\n" - f"*Open Rate:* `{msg['open_rate']:.8f}`\n") - + f"*Open Rate:* `{msg['open_rate']:.8f}`\n" + ) if msg['type'] == RPCMessageType.SELL: message += (f"*Current Rate:* `{msg['current_rate']:.8f}`\n" f"*Close Rate:* `{msg['limit']:.8f}`") @@ -291,16 +307,19 @@ class Telegram(RPCHandler): return message def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str: - if msg_type in [RPCMessageType.BUY, RPCMessageType.BUY_FILL]: + if msg_type in [RPCMessageType.BUY, RPCMessageType.BUY_FILL, RPCMessageType.SHORT, + RPCMessageType.SHORT_FILL]: message = self._format_buy_msg(msg) elif msg_type in [RPCMessageType.SELL, RPCMessageType.SELL_FILL]: message = self._format_sell_msg(msg) - elif msg_type in (RPCMessageType.BUY_CANCEL, RPCMessageType.SELL_CANCEL): - msg['message_side'] = 'buy' if msg_type == RPCMessageType.BUY_CANCEL else 'sell' + elif msg_type in (RPCMessageType.BUY_CANCEL, RPCMessageType.SHORT_CANCEL, + RPCMessageType.SELL_CANCEL): + msg['message_side'] = 'enter' if msg_type in [RPCMessageType.BUY_CANCEL, + RPCMessageType.SHORT_CANCEL] else 'exit' message = ("\N{WARNING SIGN} *{exchange}:* " - "Cancelling open {message_side} Order for {pair} (#{trade_id}). " + "Cancelling {message_side} Order for {pair} (#{trade_id}). " "Reason: {reason}.".format(**msg)) elif msg_type == RPCMessageType.PROTECTION_TRIGGER: @@ -379,7 +398,7 @@ class Telegram(RPCHandler): first_avg = filled_orders[0]["safe_price"] for x, order in enumerate(filled_orders): - if order['ft_order_side'] != 'buy': + if not order['ft_is_entry']: continue cur_entry_datetime = arrow.get(order["order_filled_date"]) cur_entry_amount = order["amount"] @@ -446,14 +465,16 @@ class Telegram(RPCHandler): messages = [] for r in results: r['open_date_hum'] = arrow.get(r['open_date']).humanize() - r['num_entries'] = len([o for o in r['orders'] if o['ft_order_side'] == 'buy']) + r['num_entries'] = len([o for o in r['orders'] if o['ft_is_entry']]) r['sell_reason'] = r.get('sell_reason', "") lines = [ "*Trade ID:* `{trade_id}`" + ("` (since {open_date_hum})`" if r['is_open'] else ""), "*Current Pair:* {pair}", + "*Direction:* " + ("`Short`" if r.get('is_short') else "`Long`"), + "*Leverage:* `{leverage}`" if r.get('leverage') else "", "*Amount:* `{amount} ({stake_amount} {base_currency})`", - "*Entry Tag:* `{buy_tag}`" if r['buy_tag'] else "", + "*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "", "*Exit Reason:* `{sell_reason}`" if r['sell_reason'] else "", ] @@ -810,13 +831,21 @@ class Telegram(RPCHandler): for curr in result['currencies']: curr_output = '' if curr['est_stake'] > balance_dust_level: - curr_output = ( - f"*{curr['currency']}:*\n" - f"\t`Available: {curr['free']:.8f}`\n" - f"\t`Balance: {curr['balance']:.8f}`\n" - f"\t`Pending: {curr['used']:.8f}`\n" - f"\t`Est. {curr['stake']}: " - f"{round_coin_value(curr['est_stake'], curr['stake'], False)}`\n") + if curr['is_position']: + curr_output = ( + f"*{curr['currency']}:*\n" + f"\t`{curr['side']}: {curr['position']:.8f}`\n" + f"\t`Leverage: {curr['leverage']:.1f}`\n" + f"\t`Est. {curr['stake']}: " + f"{round_coin_value(curr['est_stake'], curr['stake'], False)}`\n") + else: + curr_output = ( + f"*{curr['currency']}:*\n" + f"\t`Available: {curr['free']:.8f}`\n" + f"\t`Balance: {curr['balance']:.8f}`\n" + f"\t`Pending: {curr['used']:.8f}`\n" + f"\t`Est. {curr['stake']}: " + f"{round_coin_value(curr['est_stake'], curr['stake'], False)}`\n") elif curr['est_stake'] <= balance_dust_level: total_dust_balance += curr['est_stake'] total_dust_currencies += 1 @@ -900,7 +929,7 @@ class Telegram(RPCHandler): self._send_msg('Status: `{status}`'.format(**msg)) @authorized_only - def _forcesell(self, update: Update, context: CallbackContext) -> None: + def _forceexit(self, update: Update, context: CallbackContext) -> None: """ Handler for /forcesell . Sells the given trade at current price @@ -914,26 +943,28 @@ class Telegram(RPCHandler): self._send_msg("You must specify a trade-id or 'all'.") return try: - msg = self._rpc._rpc_forcesell(trade_id) - self._send_msg('Forcesell Result: `{result}`'.format(**msg)) + msg = self._rpc._rpc_forceexit(trade_id) + self._send_msg('Forceexit Result: `{result}`'.format(**msg)) except RPCException as e: self._send_msg(str(e)) - def _forcebuy_action(self, pair, price=None): + def _forceenter_action(self, pair, price: Optional[float], order_side: SignalDirection): if pair != 'cancel': try: - self._rpc._rpc_forcebuy(pair, price) + self._rpc._rpc_force_entry(pair, price, order_side=order_side) except RPCException as e: self._send_msg(str(e)) - def _forcebuy_inline(self, update: Update, _: CallbackContext) -> None: + def _forceenter_inline(self, update: Update, _: CallbackContext) -> None: if update.callback_query: query = update.callback_query - pair = query.data - query.answer() - query.edit_message_text(text=f"Force Buying: {pair}") - self._forcebuy_action(pair) + if query.data and '_||_' in query.data: + pair, side = query.data.split('_||_') + order_side = SignalDirection(side) + query.answer() + query.edit_message_text(text=f"Manually entering {order_side} for {pair}") + self._forceenter_action(pair, None, order_side) @staticmethod def _layout_inline_keyboard(buttons: List[InlineKeyboardButton], @@ -941,9 +972,10 @@ class Telegram(RPCHandler): return [buttons[i:i + cols] for i in range(0, len(buttons), cols)] @authorized_only - def _forcebuy(self, update: Update, context: CallbackContext) -> None: + def _forceenter( + self, update: Update, context: CallbackContext, order_side: SignalDirection) -> None: """ - Handler for /forcebuy . + Handler for /forcelong and `/forceshort Buys a pair trade at the given or current price :param bot: telegram bot :param update: message update @@ -952,16 +984,19 @@ class Telegram(RPCHandler): if context.args: pair = context.args[0] price = float(context.args[1]) if len(context.args) > 1 else None - self._forcebuy_action(pair, price) + self._forceenter_action(pair, price, order_side) else: whitelist = self._rpc._rpc_whitelist()['whitelist'] pair_buttons = [ - InlineKeyboardButton(text=pair, callback_data=pair) for pair in sorted(whitelist)] + InlineKeyboardButton(text=pair, callback_data=f"{pair}_||_{order_side}") + for pair in sorted(whitelist) + ] buttons_aligned = self._layout_inline_keyboard(pair_buttons) buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) self._send_msg(msg="Which pair?", - keyboard=buttons_aligned) + keyboard=buttons_aligned, + query=update.callback_query) @authorized_only def _trades(self, update: Update, context: CallbackContext) -> None: @@ -1052,7 +1087,7 @@ class Telegram(RPCHandler): self._send_msg(str(e)) @authorized_only - def _buy_tag_performance(self, update: Update, context: CallbackContext) -> None: + def _enter_tag_performance(self, update: Update, context: CallbackContext) -> None: """ Handler for /buys PAIR . Shows a performance statistic from finished trades @@ -1065,11 +1100,11 @@ class Telegram(RPCHandler): if context.args and isinstance(context.args[0], str): pair = context.args[0] - trades = self._rpc._rpc_buy_tag_performance(pair) + trades = self._rpc._rpc_enter_tag_performance(pair) output = "Buy Tag Performance:\n" for i, trade in enumerate(trades): stat_line = ( - f"{i+1}.\t {trade['buy_tag']}\t" + f"{i+1}.\t {trade['enter_tag']}\t" f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit_ratio']:.2%}) " f"({trade['count']})\n") @@ -1081,7 +1116,7 @@ class Telegram(RPCHandler): output += stat_line self._send_msg(output, parse_mode=ParseMode.HTML, - reload_able=True, callback_path="update_buy_tag_performance", + reload_able=True, callback_path="update_enter_tag_performance", query=update.callback_query) except RPCException as e: self._send_msg(str(e)) @@ -1327,18 +1362,23 @@ class Telegram(RPCHandler): :param update: message update :return: None """ - forcebuy_text = ("*/forcebuy []:* `Instantly buys the given pair. " - "Optionally takes a rate at which to buy " - "(only applies to limit orders).` \n") + forceenter_text = ("*/forcelong []:* `Instantly buys the given pair. " + "Optionally takes a rate at which to buy " + "(only applies to limit orders).` \n" + ) + if self._rpc._freqtrade.trading_mode != TradingMode.SPOT: + forceenter_text += ("*/forceshort []:* `Instantly shorts the given pair. " + "Optionally takes a rate at which to sell " + "(only applies to limit orders).` \n") message = ( "_BotControl_\n" "------------\n" "*/start:* `Starts the trader`\n" "*/stop:* Stops the trader\n" "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" - "*/forcesell |all:* `Instantly sells the given trade or all trades, " + "*/forceexit |all:* `Instantly exits the given trade or all trades, " "regardless of profit`\n" - f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}" + f"{forceenter_text if self._config.get('forcebuy_enable', False) else ''}" "*/delete :* `Instantly delete the given trade in the database`\n" "*/whitelist:* `Show current whitelist` \n" "*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " @@ -1366,7 +1406,7 @@ class Telegram(RPCHandler): " *table :* `will display trades in a table`\n" " `pending buy orders are marked with an asterisk (*)`\n" " `pending sell orders are marked with a double asterisk (**)`\n" - "*/buys :* `Shows the buy_tag performance`\n" + "*/buys :* `Shows the enter_tag performance`\n" "*/sells :* `Shows the sell reason performance`\n" "*/mix_tags :* `Shows combined buy tag + sell reason performance`\n" "*/trades [limit]:* `Lists last closed trades (limited to 10 by default)`\n" @@ -1446,11 +1486,12 @@ class Telegram(RPCHandler): self._send_msg( f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n" f"*Exchange:* `{val['exchange']}`\n" + f"*Market: * `{val['trading_mode']}`\n" f"*Stake per trade:* `{val['stake_amount']} {val['stake_currency']}`\n" f"*Max open Trades:* `{val['max_open_trades']}`\n" f"*Minimum ROI:* `{val['minimal_roi']}`\n" - f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n" - f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n" + f"*Entry strategy:* ```\n{json.dumps(val['entry_pricing'])}```\n" + f"*Exit strategy:* ```\n{json.dumps(val['exit_pricing'])}```\n" f"{sl_info}" f"{pa_info}" f"*Timeframe:* `{val['timeframe']}`\n" diff --git a/freqtrade/rpc/webhook.py b/freqtrade/rpc/webhook.py index 58b75769e..b0a884a88 100644 --- a/freqtrade/rpc/webhook.py +++ b/freqtrade/rpc/webhook.py @@ -44,11 +44,11 @@ class Webhook(RPCHandler): """ Send a message to telegram channel """ try: - if msg['type'] == RPCMessageType.BUY: + if msg['type'] in [RPCMessageType.BUY, RPCMessageType.SHORT]: valuedict = self._config['webhook'].get('webhookbuy', None) - elif msg['type'] == RPCMessageType.BUY_CANCEL: + elif msg['type'] in [RPCMessageType.BUY_CANCEL, RPCMessageType.SHORT_CANCEL]: valuedict = self._config['webhook'].get('webhookbuycancel', None) - elif msg['type'] == RPCMessageType.BUY_FILL: + elif msg['type'] in [RPCMessageType.BUY_FILL, RPCMessageType.SHORT_FILL]: valuedict = self._config['webhook'].get('webhookbuyfill', None) elif msg['type'] == RPCMessageType.SELL: valuedict = self._config['webhook'].get('webhooksell', None) diff --git a/freqtrade/strategy/informative_decorator.py b/freqtrade/strategy/informative_decorator.py index 722e7a128..0dd5320cd 100644 --- a/freqtrade/strategy/informative_decorator.py +++ b/freqtrade/strategy/informative_decorator.py @@ -1,7 +1,9 @@ -from typing import Any, Callable, NamedTuple, Optional, Union +from dataclasses import dataclass +from typing import Any, Callable, Optional, Union from pandas import DataFrame +from freqtrade.enums import CandleType from freqtrade.exceptions import OperationalException from freqtrade.strategy.strategy_helper import merge_informative_pair @@ -9,15 +11,19 @@ from freqtrade.strategy.strategy_helper import merge_informative_pair PopulateIndicators = Callable[[Any, DataFrame, dict], DataFrame] -class InformativeData(NamedTuple): +@dataclass +class InformativeData: asset: Optional[str] timeframe: str fmt: Union[str, Callable[[Any], str], None] ffill: bool + candle_type: Optional[CandleType] def informative(timeframe: str, asset: str = '', fmt: Optional[Union[str, Callable[[Any], str]]] = None, + *, + candle_type: Optional[CandleType] = None, ffill: bool = True) -> Callable[[PopulateIndicators], PopulateIndicators]: """ A decorator for populate_indicators_Nn(self, dataframe, metadata), allowing these functions to @@ -46,15 +52,17 @@ def informative(timeframe: str, asset: str = '', * {column} - name of dataframe column. * {timeframe} - timeframe of informative dataframe. :param ffill: ffill dataframe after merging informative pair. + :param candle_type: '', mark, index, premiumIndex, or funding_rate """ _asset = asset _timeframe = timeframe _fmt = fmt _ffill = ffill + _candle_type = CandleType.from_string(candle_type) if candle_type else None def decorator(fn: PopulateIndicators): informative_pairs = getattr(fn, '_ft_informative', []) - informative_pairs.append(InformativeData(_asset, _timeframe, _fmt, _ffill)) + informative_pairs.append(InformativeData(_asset, _timeframe, _fmt, _ffill, _candle_type)) setattr(fn, '_ft_informative', informative_pairs) return fn return decorator @@ -71,6 +79,8 @@ def _create_and_merge_informative_pair(strategy, dataframe: DataFrame, metadata: asset = inf_data.asset or '' timeframe = inf_data.timeframe fmt = inf_data.fmt + candle_type = inf_data.candle_type + config = strategy.config if asset: @@ -97,7 +107,7 @@ def _create_and_merge_informative_pair(strategy, dataframe: DataFrame, metadata: fmt = '{base}_{quote}_' + fmt # Informatives of other pairs inf_metadata = {'pair': asset, 'timeframe': timeframe} - inf_dataframe = strategy.dp.get_pair_dataframe(asset, timeframe) + inf_dataframe = strategy.dp.get_pair_dataframe(asset, timeframe, candle_type) inf_dataframe = populate_indicators(strategy, inf_dataframe, inf_metadata) formatter: Any = None diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 1eea84676..06fa121b3 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -13,7 +13,8 @@ from pandas import DataFrame from freqtrade.constants import ListPairsWithTimeframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import SellType, SignalTagType, SignalType +from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, SignalTagType, + SignalType, TradingMode) from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.exchange.exchange import timeframe_to_next_date @@ -28,23 +29,7 @@ from freqtrade.wallets import Wallets logger = logging.getLogger(__name__) -CUSTOM_SELL_MAX_LENGTH = 64 - - -class SellCheckTuple: - """ - NamedTuple for Sell type + reason - """ - sell_type: SellType - sell_reason: str = '' - - def __init__(self, sell_type: SellType, sell_reason: str = ''): - self.sell_type = sell_type - self.sell_reason = sell_reason or sell_type.value - - @property - def sell_flag(self): - return self.sell_type != SellType.NONE +CUSTOM_EXIT_MAX_LENGTH = 64 class IStrategy(ABC, HyperStrategyMixin): @@ -61,7 +46,8 @@ class IStrategy(ABC, HyperStrategyMixin): # Default to version 2 # Version 1 is the initial interface without metadata dict # Version 2 populate_* include metadata dict - INTERFACE_VERSION: int = 2 + # Version 3 - First version with short and leverage support + INTERFACE_VERSION: int = 3 _populate_fun_len: int = 0 _buy_fun_len: int = 0 @@ -80,13 +66,16 @@ class IStrategy(ABC, HyperStrategyMixin): trailing_only_offset_is_reached = False use_custom_stoploss: bool = False + # Can this strategy go short? + can_short: bool = False + # associated timeframe timeframe: str # Optional order types order_types: Dict = { - 'buy': 'limit', - 'sell': 'limit', + 'entry': 'limit', + 'exit': 'limit', 'stoploss': 'limit', 'stoploss_on_exchange': False, 'stoploss_on_exchange_interval': 60, @@ -94,8 +83,8 @@ class IStrategy(ABC, HyperStrategyMixin): # Optional time in force order_time_in_force: Dict = { - 'buy': 'gtc', - 'sell': 'gtc', + 'entry': 'gtc', + 'exit': 'gtc', } # run "populate_indicators" only for new candle @@ -157,31 +146,41 @@ class IStrategy(ABC, HyperStrategyMixin): if timeframe_to_minutes(informative_data.timeframe) < strategy_timeframe_minutes: raise OperationalException('Informative timeframe must be equal or higher than ' 'strategy timeframe!') + if not informative_data.candle_type: + informative_data.candle_type = config['candle_type_def'] self._ft_informative.append((informative_data, cls_method)) @abstractmethod def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Populate indicators that will be used in the Buy and Sell strategy + Populate indicators that will be used in the Buy, Sell, Short, Exit_short strategy :param dataframe: DataFrame with data from the exchange :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ return dataframe - @abstractmethod def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Based on TA indicators, populates the buy signal for the given dataframe + DEPRECATED - please migrate to populate_entry_trend :param dataframe: DataFrame :param metadata: Additional information, like the currently traded pair :return: DataFrame with buy column """ return dataframe - @abstractmethod + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Based on TA indicators, populates the entry signal for the given dataframe + :param dataframe: DataFrame + :param metadata: Additional information, like the currently traded pair + :return: DataFrame with entry columns populated + """ + return self.populate_buy_trend(dataframe, metadata) + def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ + DEPRECATED - please migrate to populate_exit_trend Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame :param metadata: Additional information, like the currently traded pair @@ -189,6 +188,15 @@ class IStrategy(ABC, HyperStrategyMixin): """ return dataframe + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Based on TA indicators, populates the exit signal for the given dataframe + :param dataframe: DataFrame + :param metadata: Additional information, like the currently traded pair + :return: DataFrame with exit columns populated + """ + return self.populate_sell_trend(dataframe, metadata) + def bot_loop_start(self, **kwargs) -> None: """ Called at the start of the bot iteration (one loop). @@ -201,9 +209,16 @@ class IStrategy(ABC, HyperStrategyMixin): def check_buy_timeout(self, pair: str, trade: Trade, order: dict, current_time: datetime, **kwargs) -> bool: """ - Check buy timeout function callback. - This method can be used to override the buy-timeout. - It is called whenever a limit buy order has been created, + DEPRECATED: Please use `check_entry_timeout` instead. + """ + return False + + def check_entry_timeout(self, pair: str, trade: Trade, order: dict, + current_time: datetime, **kwargs) -> bool: + """ + Check entry timeout function callback. + This method can be used to override the enter-timeout. + It is called whenever a limit entry order has been created, and is not yet fully filled. Configuration options in `unfilledtimeout` will be verified before this, so ensure to set these timeouts high enough. @@ -214,17 +229,25 @@ class IStrategy(ABC, HyperStrategyMixin): :param order: Order dictionary as returned from CCXT. :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - :return bool: When True is returned, then the buy-order is cancelled. + :return bool: When True is returned, then the entry order is cancelled. """ - return False + return self.check_buy_timeout( + pair=pair, trade=trade, order=order, current_time=current_time) def check_sell_timeout(self, pair: str, trade: Trade, order: dict, current_time: datetime, **kwargs) -> bool: """ + DEPRECATED: Please use `check_exit_timeout` instead. + """ + return False + + def check_exit_timeout(self, pair: str, trade: Trade, order: dict, + current_time: datetime, **kwargs) -> bool: + """ Check sell timeout function callback. - This method can be used to override the sell-timeout. - It is called whenever a limit sell order has been created, - and is not yet fully filled. + This method can be used to override the exit-timeout. + It is called whenever a (long) limit sell order or (short) limit buy + has been created, and is not yet fully filled. Configuration options in `unfilledtimeout` will be verified before this, so ensure to set these timeouts high enough. @@ -234,15 +257,16 @@ class IStrategy(ABC, HyperStrategyMixin): :param order: Order dictionary as returned from CCXT. :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - :return bool: When True is returned, then the sell-order is cancelled. + :return bool: When True is returned, then the (long)sell/(short)buy-order is cancelled. """ - return False + return self.check_sell_timeout( + pair=pair, trade=trade, order=order, current_time=current_time) def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, current_time: datetime, entry_tag: Optional[str], - **kwargs) -> bool: + side: str, **kwargs) -> bool: """ - Called right before placing a buy order. + Called right before placing a entry order. Timing for this function is critical, so avoid doing heavy computations or network requests in this method. @@ -250,13 +274,14 @@ class IStrategy(ABC, HyperStrategyMixin): When not implemented by a strategy, returns True (always confirming). - :param pair: Pair that's about to be bought. + :param pair: Pair that's about to be bought/shorted. :param order_type: Order type (as configured in order_types). usually limit or market. :param amount: Amount in target (quote) currency that's going to be traded. :param rate: Rate that's going to be used when using limit orders :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param current_time: datetime object, containing the current datetime :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. + :param side: 'long' or 'short' - indicating the direction of the proposed trade :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the buy-order is placed on the exchange. False aborts the process @@ -264,10 +289,10 @@ class IStrategy(ABC, HyperStrategyMixin): return True def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, - rate: float, time_in_force: str, sell_reason: str, + rate: float, time_in_force: str, exit_reason: str, current_time: datetime, **kwargs) -> bool: """ - Called right before placing a regular sell order. + Called right before placing a regular exit order. Timing for this function is critical, so avoid doing heavy computations or network requests in this method. @@ -275,18 +300,18 @@ class IStrategy(ABC, HyperStrategyMixin): When not implemented by a strategy, returns True (always confirming). - :param pair: Pair that's about to be sold. + :param pair: Pair for trade that's about to be exited. :param trade: trade object. :param order_type: Order type (as configured in order_types). usually limit or market. :param amount: Amount in quote currency. :param rate: Rate that's going to be used when using limit orders :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). - :param sell_reason: Sell reason. + :param exit_reason: Exit reason. Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', 'sell_signal', 'force_sell', 'emergency_sell'] :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - :return bool: When True is returned, then the sell-order is placed on the exchange. + :return bool: When True, then the sell-order/exit_short-order is placed on the exchange. False aborts the process """ return True @@ -306,7 +331,7 @@ class IStrategy(ABC, HyperStrategyMixin): :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_rate: Rate, calculated based on pricing settings in exit_pricing. :param current_profit: Current profit (as ratio), calculated based on current_rate. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: New stoploss value, relative to the current_rate @@ -324,7 +349,7 @@ class IStrategy(ABC, HyperStrategyMixin): :param pair: Pair that's currently analyzed :param current_time: datetime object, containing the current datetime - :param proposed_rate: Rate, calculated based on pricing settings in ask_strategy. + :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing. :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: New entry price value if provided @@ -344,7 +369,7 @@ class IStrategy(ABC, HyperStrategyMixin): :param pair: Pair that's currently analyzed :param trade: trade object. :param current_time: datetime object, containing the current datetime - :param proposed_rate: Rate, calculated based on pricing settings in ask_strategy. + :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing. :param current_profit: Current profit (as ratio), calculated based on current_rate. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: New exit price value if provided @@ -354,41 +379,66 @@ class IStrategy(ABC, HyperStrategyMixin): def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> Optional[Union[str, bool]]: """ - Custom sell signal logic indicating that specified position should be sold. Returning a - string or True from this method is equal to setting sell signal on a candle at specified - time. This method is not called when sell signal is set. + DEPRECATED - please use custom_exit instead. + Custom exit signal logic indicating that specified position should be sold. Returning a + string or True from this method is equal to setting exit signal on a candle at specified + time. This method is not called when exit signal is set. - This method should be overridden to create sell signals that depend on trade parameters. For - example you could implement a sell relative to the candle when the trade was opened, + This method should be overridden to create exit signals that depend on trade parameters. For + example you could implement an exit relative to the candle when the trade was opened, or a custom 1:2 risk-reward ROI. - Custom sell reason max length is 64. Exceeding characters will be removed. + Custom exit reason max length is 64. Exceeding characters will be removed. :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_rate: Rate, calculated based on pricing settings in exit_pricing. :param current_profit: Current profit (as ratio), calculated based on current_rate. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - :return: To execute sell, return a string with custom sell reason or True. Otherwise return + :return: To execute exit, return a string with custom sell reason or True. Otherwise return None or False. """ return None + def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, + current_profit: float, **kwargs) -> Optional[Union[str, bool]]: + """ + Custom exit signal logic indicating that specified position should be sold. Returning a + string or True from this method is equal to setting exit signal on a candle at specified + time. This method is not called when exit signal is set. + + This method should be overridden to create exit signals that depend on trade parameters. For + example you could implement an exit relative to the candle when the trade was opened, + or a custom 1:2 risk-reward ROI. + + Custom exit reason max length is 64. Exceeding characters will be removed. + + :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 exit_pricing. + :param current_profit: Current profit (as ratio), calculated based on current_rate. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return: To execute exit, return a string with custom sell reason or True. Otherwise return + None or False. + """ + return self.custom_sell(pair, trade, current_time, current_rate, current_profit, **kwargs) + def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: float, max_stake: float, - entry_tag: Optional[str], **kwargs) -> float: + entry_tag: Optional[str], side: str, **kwargs) -> float: """ - Customize stake size for each new trade. This method is not called when edge module is - enabled. + Customize stake size for each new trade. :param pair: Pair that's currently analyzed :param current_time: datetime object, containing the current datetime - :param current_rate: Rate, calculated based on pricing settings in ask_strategy. + :param current_rate: Rate, calculated based on pricing settings in exit_pricing. :param proposed_stake: A stake amount proposed by the bot. :param min_stake: Minimal stake size allowed by exchange. :param max_stake: Balance available for trading. :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. + :param side: 'long' or 'short' - indicating the direction of the proposed trade :return: A stake size, which is between min_stake and max_stake. """ return proposed_stake @@ -416,6 +466,22 @@ class IStrategy(ABC, HyperStrategyMixin): """ return None + def leverage(self, pair: str, current_time: datetime, current_rate: float, + proposed_leverage: float, max_leverage: float, side: str, + **kwargs) -> float: + """ + Customize leverage for each new trade. This method is only called in futures mode. + + :param pair: Pair that's currently analyzed + :param current_time: datetime object, containing the current datetime + :param current_rate: Rate, calculated based on pricing settings in exit_pricing. + :param proposed_leverage: A leverage proposed by the bot. + :param max_leverage: Max leverage allowed on this pair + :param side: 'long' or 'short' - indicating the direction of the proposed trade + :return: A leverage amount, which is between 1.0 and max_leverage. + """ + return 1.0 + def informative_pairs(self) -> ListPairsWithTimeframes: """ Define additional, informative pair/interval combinations to be cached from the exchange. @@ -444,16 +510,28 @@ class IStrategy(ABC, HyperStrategyMixin): Internal method which gathers all informative pairs (user or automatically defined). """ informative_pairs = self.informative_pairs() + # Compatibility code for 2 tuple informative pairs + informative_pairs = [ + (p[0], p[1], CandleType.from_string(p[2]) if len( + p) > 2 and p[2] != '' else self.config.get('candle_type_def', CandleType.SPOT)) + for p in informative_pairs] for inf_data, _ in self._ft_informative: + # Get default candle type if not provided explicitly. + candle_type = (inf_data.candle_type if inf_data.candle_type + else self.config.get('candle_type_def', CandleType.SPOT)) if inf_data.asset: - pair_tf = (_format_pair_name(self.config, inf_data.asset), inf_data.timeframe) + pair_tf = ( + _format_pair_name(self.config, inf_data.asset), + inf_data.timeframe, + candle_type, + ) informative_pairs.append(pair_tf) else: if not self.dp: raise OperationalException('@informative decorator with unspecified asset ' 'requires DataProvider instance.') for pair in self.dp.current_whitelist(): - informative_pairs.append((pair, inf_data.timeframe)) + informative_pairs.append((pair, inf_data.timeframe, candle_type)) return list(set(informative_pairs)) def get_strategy_name(self) -> str: @@ -498,7 +576,7 @@ class IStrategy(ABC, HyperStrategyMixin): Checks if a pair is currently locked The 2nd, optional parameter ensures that locks are applied until the new candle arrives, and not stop at 14:00:00 - while the next candle arrives at 14:00:02 leaving a gap - of 2 seconds for a buy to happen on an old signal. + of 2 seconds for an entry order to happen on an old signal. :param pair: "Pair to check" :param candle_date: Date of the last candle. Optional, defaults to current date :returns: locking state of the pair in question. @@ -514,15 +592,15 @@ class IStrategy(ABC, HyperStrategyMixin): def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Parses the given candle (OHLCV) data and returns a populated DataFrame - add several TA indicators and buy signal to it + add several TA indicators and entry order signal to it :param dataframe: Dataframe containing data from exchange :param metadata: Metadata dictionary with additional data (e.g. 'pair') :return: DataFrame of candle (OHLCV) data with indicator data and signals added """ logger.debug("TA Analysis Launched") dataframe = self.advise_indicators(dataframe, metadata) - dataframe = self.advise_buy(dataframe, metadata) - dataframe = self.advise_sell(dataframe, metadata) + dataframe = self.advise_entry(dataframe, metadata) + dataframe = self.advise_exit(dataframe, metadata) return dataframe def _analyze_ticker_internal(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -544,13 +622,17 @@ class IStrategy(ABC, HyperStrategyMixin): dataframe = self.analyze_ticker(dataframe, metadata) self._last_candle_seen_per_pair[pair] = dataframe.iloc[-1]['date'] if self.dp: - self.dp._set_cached_df(pair, self.timeframe, dataframe) + self.dp._set_cached_df( + pair, self.timeframe, dataframe, + candle_type=self.config.get('candle_type_def', CandleType.SPOT)) else: logger.debug("Skipping TA Analysis for already analyzed candle") - dataframe['buy'] = 0 - dataframe['sell'] = 0 - dataframe['buy_tag'] = None - dataframe['exit_tag'] = None + dataframe[SignalType.ENTER_LONG.value] = 0 + dataframe[SignalType.EXIT_LONG.value] = 0 + dataframe[SignalType.ENTER_SHORT.value] = 0 + dataframe[SignalType.EXIT_SHORT.value] = 0 + dataframe[SignalTagType.ENTER_TAG.value] = None + dataframe[SignalTagType.EXIT_TAG.value] = None # Other Defs in strategy that want to be called every loop here # twitter_sell = self.watch_twitter_feed(dataframe, metadata) @@ -567,7 +649,9 @@ class IStrategy(ABC, HyperStrategyMixin): """ if not self.dp: raise OperationalException("DataProvider not found.") - dataframe = self.dp.ohlcv(pair, self.timeframe) + dataframe = self.dp.ohlcv( + pair, self.timeframe, candle_type=self.config.get('candle_type_def', CandleType.SPOT) + ) if not isinstance(dataframe, DataFrame) or dataframe.empty: logger.warning('Empty candle (OHLCV) data for pair %s', pair) return @@ -609,8 +693,8 @@ class IStrategy(ABC, HyperStrategyMixin): message = "" if dataframe is None: message = "No dataframe returned (return statement missing?)." - elif 'buy' not in dataframe: - message = "Buy column not set." + elif 'enter_long' not in dataframe: + message = "enter_long/buy column not set." elif df_len != len(dataframe): message = message_template.format("length") elif df_close != dataframe["close"].iloc[-1]: @@ -623,23 +707,24 @@ class IStrategy(ABC, HyperStrategyMixin): else: raise StrategyError(message) - def get_signal( + def get_latest_candle( self, pair: str, timeframe: str, - dataframe: DataFrame - ) -> Tuple[bool, bool, Optional[str], Optional[str]]: + dataframe: DataFrame, + ) -> Tuple[Optional[DataFrame], Optional[arrow.Arrow]]: """ - Calculates current signal based based on the buy / sell columns of the dataframe. - Used by Bot to get the signal to buy or sell + Calculates current signal based based on the entry order or exit order + columns of the dataframe. + Used by Bot to get the signal to buy, sell, short, or exit_short :param pair: pair in format ANT/BTC :param timeframe: timeframe to use :param dataframe: Analyzed dataframe to get signal from. - :return: (Buy, Sell) A bool-tuple indicating buy/sell signal + :return: (None, None) or (Dataframe, latest_date) - corresponding to the last candle """ if not isinstance(dataframe, DataFrame) or dataframe.empty: logger.warning(f'Empty candle (OHLCV) data for pair {pair}') - return False, False, None, None + return None, None latest_date = dataframe['date'].max() latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1] @@ -654,49 +739,124 @@ class IStrategy(ABC, HyperStrategyMixin): 'Outdated history for pair %s. Last tick is %s minutes old', pair, int((arrow.utcnow() - latest_date).total_seconds() // 60) ) - return False, False, None, None + return None, None + return latest, latest_date - buy = latest[SignalType.BUY.value] == 1 + def get_exit_signal( + self, + pair: str, + timeframe: str, + dataframe: DataFrame, + is_short: bool = None + ) -> Tuple[bool, bool, Optional[str]]: + """ + Calculates current exit signal based based on the buy/short or sell/exit_short + columns of the dataframe. + Used by Bot to get the signal to exit. + depending on is_short, looks at "short" or "long" columns. + :param pair: pair in format ANT/BTC + :param timeframe: timeframe to use + :param dataframe: Analyzed dataframe to get signal from. + :param is_short: Indicating existing trade direction. + :return: (enter, exit) A bool-tuple with enter / exit values. + """ + latest, latest_date = self.get_latest_candle(pair, timeframe, dataframe) + if latest is None: + return False, False, None - sell = False - if SignalType.SELL.value in latest: - sell = latest[SignalType.SELL.value] == 1 + if is_short: + enter = latest.get(SignalType.ENTER_SHORT.value, 0) == 1 + exit_ = latest.get(SignalType.EXIT_SHORT.value, 0) == 1 - buy_tag = latest.get(SignalTagType.BUY_TAG.value, None) + else: + enter = latest[SignalType.ENTER_LONG.value] == 1 + exit_ = latest.get(SignalType.EXIT_LONG.value, 0) == 1 exit_tag = latest.get(SignalTagType.EXIT_TAG.value, None) # Tags can be None, which does not resolve to False. - buy_tag = buy_tag if isinstance(buy_tag, str) else None exit_tag = exit_tag if isinstance(exit_tag, str) else None - logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', - latest['date'], pair, str(buy), str(sell)) - timeframe_seconds = timeframe_to_seconds(timeframe) - if self.ignore_expired_candle(latest_date=latest_date, - current_time=datetime.now(timezone.utc), - timeframe_seconds=timeframe_seconds, - buy=buy): - return False, sell, buy_tag, exit_tag - return buy, sell, buy_tag, exit_tag + logger.debug(f"exit-trigger: {latest['date']} (pair={pair}) " + f"enter={enter} exit={exit_}") - def ignore_expired_candle(self, latest_date: datetime, current_time: datetime, - timeframe_seconds: int, buy: bool): - if self.ignore_buying_expired_candle_after and buy: + return enter, exit_, exit_tag + + def get_entry_signal( + self, + pair: str, + timeframe: str, + dataframe: DataFrame, + ) -> Tuple[Optional[SignalDirection], Optional[str]]: + """ + Calculates current entry signal based based on the buy/short or sell/exit_short + columns of the dataframe. + Used by Bot to get the signal to buy, sell, short, or exit_short + :param pair: pair in format ANT/BTC + :param timeframe: timeframe to use + :param dataframe: Analyzed dataframe to get signal from. + :return: (SignalDirection, entry_tag) + """ + latest, latest_date = self.get_latest_candle(pair, timeframe, dataframe) + if latest is None or latest_date is None: + return None, None + + enter_long = latest[SignalType.ENTER_LONG.value] == 1 + exit_long = latest.get(SignalType.EXIT_LONG.value, 0) == 1 + enter_short = latest.get(SignalType.ENTER_SHORT.value, 0) == 1 + exit_short = latest.get(SignalType.EXIT_SHORT.value, 0) == 1 + + enter_signal: Optional[SignalDirection] = None + enter_tag_value: Optional[str] = None + if enter_long == 1 and not any([exit_long, enter_short]): + enter_signal = SignalDirection.LONG + enter_tag_value = latest.get(SignalTagType.ENTER_TAG.value, None) + if (self.config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT + and self.can_short + and enter_short == 1 and not any([exit_short, enter_long])): + enter_signal = SignalDirection.SHORT + enter_tag_value = latest.get(SignalTagType.ENTER_TAG.value, None) + + enter_tag_value = enter_tag_value if isinstance(enter_tag_value, str) else None + + timeframe_seconds = timeframe_to_seconds(timeframe) + + if self.ignore_expired_candle( + latest_date=latest_date.datetime, + current_time=datetime.now(timezone.utc), + timeframe_seconds=timeframe_seconds, + enter=bool(enter_signal) + ): + return None, enter_tag_value + + logger.debug(f"entry trigger: {latest['date']} (pair={pair}) " + f"enter={enter_long} enter_tag_value={enter_tag_value}") + return enter_signal, enter_tag_value + + def ignore_expired_candle( + self, + latest_date: datetime, + current_time: datetime, + timeframe_seconds: int, + enter: bool + ): + if self.ignore_buying_expired_candle_after and enter: time_delta = current_time - (latest_date + timedelta(seconds=timeframe_seconds)) return time_delta.total_seconds() > self.ignore_buying_expired_candle_after else: return False - def should_sell(self, trade: Trade, rate: float, current_time: datetime, buy: bool, - sell: bool, low: float = None, high: float = None, - force_stoploss: float = 0) -> SellCheckTuple: + def should_exit(self, trade: Trade, rate: float, current_time: datetime, *, + enter: bool, exit_: bool, + low: float = None, high: float = None, + force_stoploss: float = 0) -> ExitCheckTuple: """ - This function evaluates if one of the conditions required to trigger a sell - has been reached, which can either be a stop-loss, ROI or sell-signal. - :param low: Only used during backtesting to simulate stoploss - :param high: Only used during backtesting, to simulate ROI + This function evaluates if one of the conditions required to trigger an exit order + has been reached, which can either be a stop-loss, ROI or exit-signal. + :param low: Only used during backtesting to simulate (long)stoploss/(short)ROI + :param high: Only used during backtesting, to simulate (short)stoploss/(long)ROI :param force_stoploss: Externally provided stoploss - :return: True if trade should be sold, False otherwise + :return: True if trade should be exited, False otherwise """ + current_rate = rate current_profit = trade.calc_profit_ratio(current_rate) @@ -708,15 +868,15 @@ class IStrategy(ABC, HyperStrategyMixin): force_stoploss=force_stoploss, low=low, high=high) # Set current rate to high for backtesting sell - current_rate = high or rate + current_rate = (low if trade.is_short else high) or rate current_profit = trade.calc_profit_ratio(current_rate) - # if buy signal and ignore_roi is set, we don't need to evaluate min_roi. - roi_reached = (not (buy and self.ignore_roi_if_buy_signal) + # if enter signal and ignore_roi is set, we don't need to evaluate min_roi. + roi_reached = (not (enter and self.ignore_roi_if_buy_signal) and self.min_roi_reached(trade=trade, current_profit=current_profit, current_time=current_time)) - sell_signal = SellType.NONE + sell_signal = ExitType.NONE custom_reason = '' # use provided rate in backtesting, not high/low. current_rate = rate @@ -725,54 +885,54 @@ class IStrategy(ABC, HyperStrategyMixin): if (self.sell_profit_only and current_profit <= self.sell_profit_offset): # sell_profit_only and profit doesn't reach the offset - ignore sell signal pass - elif self.use_sell_signal and not buy: - if sell: - sell_signal = SellType.SELL_SIGNAL + elif self.use_sell_signal and not enter: + if exit_: + sell_signal = ExitType.SELL_SIGNAL else: - custom_reason = strategy_safe_wrapper(self.custom_sell, default_retval=False)( + trade_type = "exit_short" if trade.is_short else "sell" + custom_reason = strategy_safe_wrapper(self.custom_exit, default_retval=False)( pair=trade.pair, trade=trade, current_time=current_time, current_rate=current_rate, current_profit=current_profit) if custom_reason: - sell_signal = SellType.CUSTOM_SELL + sell_signal = ExitType.CUSTOM_SELL if isinstance(custom_reason, str): - if len(custom_reason) > CUSTOM_SELL_MAX_LENGTH: - logger.warning(f'Custom sell reason returned from custom_sell is too ' - f'long and was trimmed to {CUSTOM_SELL_MAX_LENGTH} ' - f'characters.') - custom_reason = custom_reason[:CUSTOM_SELL_MAX_LENGTH] + if len(custom_reason) > CUSTOM_EXIT_MAX_LENGTH: + logger.warning(f'Custom {trade_type} reason returned from ' + f'custom_exit is too long and was trimmed' + f'to {CUSTOM_EXIT_MAX_LENGTH} characters.') + custom_reason = custom_reason[:CUSTOM_EXIT_MAX_LENGTH] else: custom_reason = None - if sell_signal in (SellType.CUSTOM_SELL, SellType.SELL_SIGNAL): + if sell_signal in (ExitType.CUSTOM_SELL, ExitType.SELL_SIGNAL): logger.debug(f"{trade.pair} - Sell signal received. " - f"sell_type=SellType.{sell_signal.name}" + + f"sell_type=ExitType.{sell_signal.name}" + (f", custom_reason={custom_reason}" if custom_reason else "")) - return SellCheckTuple(sell_type=sell_signal, sell_reason=custom_reason) + return ExitCheckTuple(exit_type=sell_signal, exit_reason=custom_reason) - # Start evaluations # Sequence: - # Sell-signal + # Exit-signal # ROI (if not stoploss) # Stoploss - if roi_reached and stoplossflag.sell_type != SellType.STOP_LOSS: - logger.debug(f"{trade.pair} - Required profit reached. sell_type=SellType.ROI") - return SellCheckTuple(sell_type=SellType.ROI) + if roi_reached and stoplossflag.exit_type != ExitType.STOP_LOSS: + logger.debug(f"{trade.pair} - Required profit reached. sell_type=ExitType.ROI") + return ExitCheckTuple(exit_type=ExitType.ROI) - if stoplossflag.sell_flag: + if stoplossflag.exit_flag: - logger.debug(f"{trade.pair} - Stoploss hit. sell_type={stoplossflag.sell_type}") + logger.debug(f"{trade.pair} - Stoploss hit. sell_type={stoplossflag.exit_type}") return stoplossflag # This one is noisy, commented out... - # logger.debug(f"{trade.pair} - No sell signal.") - return SellCheckTuple(sell_type=SellType.NONE) + # logger.debug(f"{trade.pair} - No exit signal.") + return ExitCheckTuple(exit_type=ExitType.NONE) def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime, current_profit: float, force_stoploss: float, low: float = None, - high: float = None) -> SellCheckTuple: + high: float = None) -> ExitCheckTuple: """ Based on current profit of the trade and configured (trailing) stoploss, - decides to sell or not + decides to exit or not :param current_profit: current profit as ratio :param low: Low value of this candle, only set in backtesting :param high: High value of this candle, only set in backtesting @@ -782,7 +942,12 @@ class IStrategy(ABC, HyperStrategyMixin): # Initiate stoploss with open_rate. Does nothing if stoploss is already set. trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True) - if self.use_custom_stoploss and trade.stop_loss < (low or current_rate): + dir_correct = (trade.stop_loss < (low or current_rate) + if not trade.is_short else + trade.stop_loss > (high or current_rate) + ) + + if self.use_custom_stoploss and dir_correct: stop_loss_value = strategy_safe_wrapper(self.custom_stoploss, default_retval=None )(pair=trade.pair, trade=trade, current_time=current_time, @@ -795,45 +960,56 @@ class IStrategy(ABC, HyperStrategyMixin): else: logger.warning("CustomStoploss function did not return valid stoploss") - if self.trailing_stop and trade.stop_loss < (low or current_rate): + sl_lower_long = (trade.stop_loss < (low or current_rate) and not trade.is_short) + sl_higher_short = (trade.stop_loss > (high or current_rate) and trade.is_short) + if self.trailing_stop and (sl_lower_long or sl_higher_short): # trailing stoploss handling sl_offset = self.trailing_stop_positive_offset # Make sure current_profit is calculated using high for backtesting. - high_profit = current_profit if not high else trade.calc_profit_ratio(high) + bound = low if trade.is_short else high + bound_profit = current_profit if not bound else trade.calc_profit_ratio(bound) # Don't update stoploss if trailing_only_offset_is_reached is true. - if not (self.trailing_only_offset_is_reached and high_profit < sl_offset): + if not (self.trailing_only_offset_is_reached and bound_profit < sl_offset): # Specific handling for trailing_stop_positive - if self.trailing_stop_positive is not None and high_profit > sl_offset: + if self.trailing_stop_positive is not None and bound_profit > sl_offset: stop_loss_value = self.trailing_stop_positive logger.debug(f"{trade.pair} - Using positive stoploss: {stop_loss_value} " f"offset: {sl_offset:.4g} profit: {current_profit:.2%}") - trade.adjust_stop_loss(high or current_rate, stop_loss_value) + trade.adjust_stop_loss(bound or current_rate, stop_loss_value) + sl_higher_long = (trade.stop_loss >= (low or current_rate) and not trade.is_short) + sl_lower_short = (trade.stop_loss <= (high or current_rate) and trade.is_short) # evaluate if the stoploss was hit if stoploss is not on exchange # in Dry-Run, this handles stoploss logic as well, as the logic will not be different to # regular stoploss handling. - if ((trade.stop_loss >= (low or current_rate)) and + if ((sl_higher_long or sl_lower_short) and (not self.order_types.get('stoploss_on_exchange') or self.config['dry_run'])): - sell_type = SellType.STOP_LOSS + sell_type = ExitType.STOP_LOSS # If initial stoploss is not the same as current one then it is trailing. if trade.initial_stop_loss != trade.stop_loss: - sell_type = SellType.TRAILING_STOP_LOSS + sell_type = ExitType.TRAILING_STOP_LOSS logger.debug( - f"{trade.pair} - HIT STOP: current price at {(low or current_rate):.6f}, " + f"{trade.pair} - HIT STOP: current price at " + f"{((high if trade.is_short else low) or current_rate):.6f}, " f"stoploss is {trade.stop_loss:.6f}, " f"initial stoploss was at {trade.initial_stop_loss:.6f}, " f"trade opened at {trade.open_rate:.6f}") + new_stoploss = ( + trade.stop_loss + trade.initial_stop_loss + if trade.is_short else + trade.stop_loss - trade.initial_stop_loss + ) logger.debug(f"{trade.pair} - Trailing stop saved " - f"{trade.stop_loss - trade.initial_stop_loss:.6f}") + f"{new_stoploss:.6f}") - return SellCheckTuple(sell_type=sell_type) + return ExitCheckTuple(exit_type=sell_type) - return SellCheckTuple(sell_type=SellType.NONE) + return ExitCheckTuple(exit_type=ExitType.NONE) def min_roi_reached_entry(self, trade_dur: int) -> Tuple[Optional[int], Optional[float]]: """ @@ -863,22 +1039,24 @@ class IStrategy(ABC, HyperStrategyMixin): else: return current_profit > roi - def ft_check_timed_out(self, side: str, trade: LocalTrade, order: Order, + def ft_check_timed_out(self, trade: LocalTrade, order: Order, current_time: datetime) -> bool: """ FT Internal method. Check if timeout is active, and if the order is still open and timed out """ + side = 'entry' if order.ft_order_side == trade.enter_side else 'exit' + timeout = self.config.get('unfilledtimeout', {}).get(side) if timeout is not None: timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes') timeout_kwargs = {timeout_unit: -timeout} timeout_threshold = current_time + timedelta(**timeout_kwargs) - timedout = (order.status == 'open' and order.side == side - and order.order_date_utc < timeout_threshold) + timedout = (order.status == 'open' and order.order_date_utc < timeout_threshold) if timedout: return True - time_method = self.check_sell_timeout if order.side == 'sell' else self.check_buy_timeout + time_method = (self.check_exit_timeout if order.side == trade.exit_side + else self.check_entry_timeout) return strategy_safe_wrapper(time_method, default_retval=False)( @@ -888,7 +1066,7 @@ class IStrategy(ABC, HyperStrategyMixin): def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]: """ Populates indicators for given candle (OHLCV) data (for multiple pairs) - Does not run advise_buy or advise_sell! + Does not run advise_entry or advise_exit! Used by optimize operations only, not during dry / live runs. Using .copy() to get a fresh copy of the dataframe for every strategy run. Also copy on output to avoid PerformanceWarnings pandas 1.3.0 started to show. @@ -900,7 +1078,7 @@ class IStrategy(ABC, HyperStrategyMixin): def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Populate indicators that will be used in the Buy and Sell strategy + Populate indicators that will be used in the Buy, Sell, short, exit_short strategy This method should not be overridden. :param dataframe: Dataframe with data from the exchange :param metadata: Additional information, like the currently traded pair @@ -920,37 +1098,46 @@ class IStrategy(ABC, HyperStrategyMixin): else: return self.populate_indicators(dataframe, metadata) - def advise_buy(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def advise_entry(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Based on TA indicators, populates the buy signal for the given dataframe + Based on TA indicators, populates the entry order signal for the given dataframe This method should not be overridden. :param dataframe: DataFrame :param metadata: Additional information dictionary, with details like the currently traded pair :return: DataFrame with buy column """ - logger.debug(f"Populating buy signals for pair {metadata.get('pair')}.") + + logger.debug(f"Populating enter signals for pair {metadata.get('pair')}.") if self._buy_fun_len == 2: warnings.warn("deprecated - check out the Sample strategy to see " "the current function headers!", DeprecationWarning) - return self.populate_buy_trend(dataframe) # type: ignore + df = self.populate_buy_trend(dataframe) # type: ignore else: - return self.populate_buy_trend(dataframe, metadata) + df = self.populate_entry_trend(dataframe, metadata) + if 'enter_long' not in df.columns: + df = df.rename({'buy': 'enter_long', 'buy_tag': 'enter_tag'}, axis='columns') - def advise_sell(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + return df + + def advise_exit(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Based on TA indicators, populates the sell signal for the given dataframe + Based on TA indicators, populates the exit order signal for the given dataframe This method should not be overridden. :param dataframe: DataFrame :param metadata: Additional information dictionary, with details like the currently traded pair :return: DataFrame with sell column """ - logger.debug(f"Populating sell signals for pair {metadata.get('pair')}.") + + logger.debug(f"Populating exit signals for pair {metadata.get('pair')}.") if self._sell_fun_len == 2: warnings.warn("deprecated - check out the Sample strategy to see " "the current function headers!", DeprecationWarning) - return self.populate_sell_trend(dataframe) # type: ignore + df = self.populate_sell_trend(dataframe) # type: ignore else: - return self.populate_sell_trend(dataframe, metadata) + df = self.populate_exit_trend(dataframe, metadata) + if 'exit_long' not in df.columns: + df = df.rename({'sell': 'exit_long'}, axis='columns') + return df diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index 175bcaccb..f07c14e24 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -66,7 +66,11 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame, return dataframe -def stoploss_from_open(open_relative_stop: float, current_profit: float) -> float: +def stoploss_from_open( + open_relative_stop: float, + current_profit: float, + is_short: bool = False +) -> float: """ Given the current profit, and a desired stop loss value relative to the open price, @@ -76,24 +80,29 @@ def stoploss_from_open(open_relative_stop: float, current_profit: float) -> floa The requested stop can be positive for a stop above the open price, or negative for a stop below the open price. The return value is always >= 0. - Returns 0 if the resulting stop price would be above the current price. + Returns 0 if the resulting stop price would be above/below (longs/shorts) the current price :param open_relative_stop: Desired stop loss percentage relative to open price :param current_profit: The current profit percentage - :return: Positive stop loss value relative to current price + :param is_short: When true, perform the calculation for short instead of long + :return: Stop loss value relative to current price """ - # formula is undefined for current_profit -1, return maximum value - if current_profit == -1: + # formula is undefined for current_profit -1 (longs) or 1 (shorts), return maximum value + if (current_profit == -1 and not is_short) or (is_short and current_profit == 1): return 1 - stoploss = 1-((1+open_relative_stop)/(1+current_profit)) + if is_short is True: + stoploss = -1+((1-open_relative_stop)/(1-current_profit)) + else: + stoploss = 1-((1+open_relative_stop)/(1+current_profit)) - # negative stoploss values indicate the requested stop price is higher than the current price + # negative stoploss values indicate the requested stop price is higher/lower + # (long/short) than the current price return max(stoploss, 0.0) -def stoploss_from_absolute(stop_rate: float, current_rate: float) -> float: +def stoploss_from_absolute(stop_rate: float, current_rate: float, is_short: bool = False) -> float: """ Given current price and desired stop price, return a stop loss value that is relative to current price. @@ -105,6 +114,7 @@ def stoploss_from_absolute(stop_rate: float, current_rate: float) -> float: :param stop_rate: Stop loss price. :param current_rate: Current asset price. + :param is_short: When true, perform the calculation for short instead of long :return: Positive stop loss value relative to current price """ @@ -113,6 +123,10 @@ def stoploss_from_absolute(stop_rate: float, current_rate: float) -> float: return 1 stoploss = 1 - (stop_rate / current_rate) + if is_short: + stoploss = -stoploss - # negative stoploss values indicate the requested stop price is higher than the current price - return max(stoploss, 0.0) + # negative stoploss values indicate the requested stop price is higher/lower + # (long/short) than the current price + # shorts can yield stoploss values higher than 1, so limit that as well + return max(min(stoploss, 1.0), 0.0) diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index c91715b1f..f1f611a45 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -13,24 +13,26 @@ "fiat_display_currency": "{{ fiat_display_currency }}",{{ ('\n "timeframe": "' + timeframe + '",') if timeframe else '' }} "dry_run": {{ dry_run | lower }}, "cancel_open_orders_on_exit": false, + "trading_mode": "{{ trading_mode }}", + "margin_mode": "{{ margin_mode }}", "unfilledtimeout": { - "buy": 10, - "sell": 10, + "entry": 10, + "exit": 10, "exit_timeout_count": 0, "unit": "minutes" }, - "bid_strategy": { - "price_side": "bid", - "ask_last_balance": 0.0, + "entry_pricing": { + "price_side": "same", "use_order_book": true, "order_book_top": 1, + "price_last_balance": 0.0, "check_depth_of_market": { "enabled": false, "bids_to_ask_delta": 1 } }, - "ask_strategy": { - "price_side": "ask", + "exit_pricing":{ + "price_side": "same", "use_order_book": true, "order_book_top": 1 }, diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index 035468d58..e5eecf7eb 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -29,17 +29,20 @@ class {{ strategy }}(IStrategy): You must keep: - the lib in the section "Do not remove these libs" - - the methods: populate_indicators, populate_buy_trend, populate_sell_trend + - the methods: populate_indicators, populate_entry_trend, populate_exit_trend You should keep: - timeframe, minimal_roi, stoploss, trailing_* """ # Strategy interface version - allow new iterations of the strategy interface. # Check the documentation or the Sample strategy to get the latest version. - INTERFACE_VERSION = 2 + INTERFACE_VERSION = 3 # Optimal timeframe for the strategy. timeframe = '5m' + # Can this strategy go short? + can_short: bool = False + # Minimal ROI designed for the strategy. # This attribute will be overridden if the config file contains "minimal_roi". minimal_roi = { @@ -61,7 +64,7 @@ class {{ strategy }}(IStrategy): # Run "populate_indicators()" only for new candle. process_only_new_candles = False - # These values can be overridden in the "ask_strategy" section in the config. + # These values can be overridden in the config. use_sell_signal = True sell_profit_only = False ignore_roi_if_buy_signal = False @@ -75,16 +78,16 @@ class {{ strategy }}(IStrategy): # Optional order type mapping. order_types = { - 'buy': 'limit', - 'sell': 'limit', + 'entry': 'limit', + 'exit': 'limit', 'stoploss': 'market', 'stoploss_on_exchange': False } # Optional order time in force. order_time_in_force = { - 'buy': 'gtc', - 'sell': 'gtc' + 'entry': 'gtc', + 'exit': 'gtc' } {{ plot_config | indent(4) }} @@ -116,34 +119,52 @@ class {{ strategy }}(IStrategy): return dataframe - def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Based on TA indicators, populates the buy signal for the given dataframe - :param dataframe: DataFrame populated with indicators + Based on TA indicators, populates the entry signal for the given dataframe + :param dataframe: DataFrame :param metadata: Additional information, like the currently traded pair - :return: DataFrame with buy column + :return: DataFrame with entry columns populated """ dataframe.loc[ ( {{ buy_trend | indent(16) }} (dataframe['volume'] > 0) # Make sure Volume is not 0 ), - 'buy'] = 1 - - return dataframe - - def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Based on TA indicators, populates the sell signal for the given dataframe - :param dataframe: DataFrame populated with indicators - :param metadata: Additional information, like the currently traded pair - :return: DataFrame with buy column + 'enter_long'] = 1 + # Uncomment to use shorts (Only used in futures/margin mode. Check the documentation for more info) """ dataframe.loc[ ( {{ sell_trend | indent(16) }} (dataframe['volume'] > 0) # Make sure Volume is not 0 ), - 'sell'] = 1 + 'enter_short'] = 1 + """ + + return dataframe + + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Based on TA indicators, populates the exit signal for the given dataframe + :param dataframe: DataFrame + :param metadata: Additional information, like the currently traded pair + :return: DataFrame with exit columns populated + """ + dataframe.loc[ + ( + {{ sell_trend | indent(16) }} + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + 'exit_long'] = 1 + # Uncomment to use shorts (Only used in futures/margin mode. Check the documentation for more info) + """ + dataframe.loc[ + ( + {{ buy_trend | indent(16) }} + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + 'exit_short'] = 1 + """ return dataframe {{ additional_methods | indent(4) }} diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 574819949..0c5c501cf 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -29,13 +29,16 @@ class SampleStrategy(IStrategy): You must keep: - the lib in the section "Do not remove these libs" - - the methods: populate_indicators, populate_buy_trend, populate_sell_trend + - the methods: populate_indicators, populate_entry_trend, populate_exit_trend You should keep: - timeframe, minimal_roi, stoploss, trailing_* """ # Strategy interface version - allow new iterations of the strategy interface. # Check the documentation or the Sample strategy to get the latest version. - INTERFACE_VERSION = 2 + INTERFACE_VERSION = 3 + + # Can this strategy go short? + can_short: bool = False # Minimal ROI designed for the strategy. # This attribute will be overridden if the config file contains "minimal_roi". @@ -55,36 +58,38 @@ class SampleStrategy(IStrategy): # trailing_stop_positive = 0.01 # trailing_stop_positive_offset = 0.0 # Disabled / not configured - # Hyperoptable parameters - buy_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True) - sell_rsi = IntParameter(low=50, high=100, default=70, space='sell', optimize=True, load=True) - # Optimal timeframe for the strategy. timeframe = '5m' # Run "populate_indicators()" only for new candle. process_only_new_candles = False - # These values can be overridden in the "ask_strategy" section in the config. + # These values can be overridden in the config. use_sell_signal = True sell_profit_only = False ignore_roi_if_buy_signal = False + # Hyperoptable parameters + buy_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True) + sell_rsi = IntParameter(low=50, high=100, default=70, space='sell', optimize=True, load=True) + short_rsi = IntParameter(low=51, high=100, default=70, space='sell', optimize=True, load=True) + exit_short_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True) + # Number of candles the strategy requires before producing valid signals startup_candle_count: int = 30 # Optional order type mapping. order_types = { - 'buy': 'limit', - 'sell': 'limit', + 'entry': 'limit', + 'exit': 'limit', 'stoploss': 'market', 'stoploss_on_exchange': False } # Optional order time in force. order_time_in_force = { - 'buy': 'gtc', - 'sell': 'gtc' + 'entry': 'gtc', + 'exit': 'gtc' } plot_config = { @@ -337,12 +342,12 @@ class SampleStrategy(IStrategy): return dataframe - def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Based on TA indicators, populates the buy signal for the given dataframe - :param dataframe: DataFrame populated with indicators + Based on TA indicators, populates the entry signal for the given dataframe + :param dataframe: DataFrame :param metadata: Additional information, like the currently traded pair - :return: DataFrame with buy column + :return: DataFrame with entry columns populated """ dataframe.loc[ ( @@ -352,16 +357,26 @@ class SampleStrategy(IStrategy): (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising (dataframe['volume'] > 0) # Make sure Volume is not 0 ), - 'buy'] = 1 + 'enter_long'] = 1 + + dataframe.loc[ + ( + # Signal: RSI crosses above 70 + (qtpylib.crossed_above(dataframe['rsi'], self.short_rsi.value)) & + (dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle + (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + 'enter_short'] = 1 return dataframe - def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - Based on TA indicators, populates the sell signal for the given dataframe - :param dataframe: DataFrame populated with indicators + Based on TA indicators, populates the exit signal for the given dataframe + :param dataframe: DataFrame :param metadata: Additional information, like the currently traded pair - :return: DataFrame with sell column + :return: DataFrame with exit columns populated """ dataframe.loc[ ( @@ -371,5 +386,18 @@ class SampleStrategy(IStrategy): (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling (dataframe['volume'] > 0) # Make sure Volume is not 0 ), - 'sell'] = 1 + + 'exit_long'] = 1 + + dataframe.loc[ + ( + # Signal: RSI crosses above 30 + (qtpylib.crossed_above(dataframe['rsi'], self.exit_short_rsi.value)) & + # Guard: tema below BB middle + (dataframe['tema'] <= dataframe['bb_middleband']) & + (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + 'exit_short'] = 1 + return dataframe diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb index 3b937d1c5..dc20d71b8 100644 --- a/freqtrade/templates/strategy_analysis_example.ipynb +++ b/freqtrade/templates/strategy_analysis_example.ipynb @@ -110,7 +110,7 @@ "outputs": [], "source": [ "# Report results\n", - "print(f\"Generated {df['buy'].sum()} buy signals\")\n", + "print(f\"Generated {df['enter_long'].sum()} entry signals\")\n", "data = df.set_index('date', drop=False)\n", "data.tail()" ] @@ -348,7 +348,7 @@ "hist_data = [trades.profit_ratio]\n", "group_labels = ['profit_ratio'] # name of the dataset\n", "\n", - "fig = ff.create_distplot(hist_data, group_labels,bin_size=0.01)\n", + "fig = ff.create_distplot(hist_data, group_labels, bin_size=0.01)\n", "fig.show()\n" ] }, diff --git a/freqtrade/templates/subtemplates/exchange_bittrex.j2 b/freqtrade/templates/subtemplates/exchange_bittrex.j2 index 0394790ce..2d9afd578 100644 --- a/freqtrade/templates/subtemplates/exchange_bittrex.j2 +++ b/freqtrade/templates/subtemplates/exchange_bittrex.j2 @@ -1,7 +1,7 @@ "order_types": { - "buy": "limit", - "sell": "limit", - "emergencysell": "limit", + "entry": "limit", + "exit": "limit", + "emergencyexit": "limit", "stoploss": "limit", "stoploss_on_exchange": false }, diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 index db12094ed..b2dbb736d 100644 --- a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 @@ -23,7 +23,7 @@ def custom_entry_price(self, pair: str, current_time: 'datetime', proposed_rate: :param pair: Pair that's currently analyzed :param current_time: datetime object, containing the current datetime - :param proposed_rate: Rate, calculated based on pricing settings in ask_strategy. + :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing. :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: New entry price value if provided @@ -43,7 +43,7 @@ def custom_exit_price(self, pair: str, trade: 'Trade', :param pair: Pair that's currently analyzed :param trade: trade object. :param current_time: datetime object, containing the current datetime - :param proposed_rate: Rate, calculated based on pricing settings in ask_strategy. + :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing. :param current_profit: Current profit (as ratio), calculated based on current_rate. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: New exit price value if provided @@ -52,18 +52,18 @@ def custom_exit_price(self, pair: str, trade: 'Trade', def custom_stake_amount(self, pair: str, current_time: 'datetime', current_rate: float, proposed_stake: float, min_stake: float, max_stake: float, - entry_tag: 'Optional[str]', **kwargs) -> float: + side: str, entry_tag: 'Optional[str]', **kwargs) -> float: """ - Customize stake size for each new trade. This method is not called when edge module is - enabled. + Customize stake size for each new trade. :param pair: Pair that's currently analyzed :param current_time: datetime object, containing the current datetime - :param current_rate: Rate, calculated based on pricing settings in ask_strategy. + :param current_rate: Rate, calculated based on pricing settings in exit_pricing. :param proposed_stake: A stake amount proposed by the bot. :param min_stake: Minimal stake size allowed by exchange. :param max_stake: Balance available for trading. :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. + :param side: 'long' or 'short' - indicating the direction of the proposed trade :return: A stake size, which is between min_stake and max_stake. """ return proposed_stake @@ -85,14 +85,14 @@ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', :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_rate: Rate, calculated based on pricing settings in exit_pricing. :param current_profit: Current profit (as ratio), calculated based on current_rate. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: New stoploss value, relative to the current_rate """ return self.stoploss -def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, +def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, current_profit: float, **kwargs) -> 'Optional[Union[str, bool]]': """ Custom sell signal logic indicating that specified position should be sold. Returning a @@ -108,7 +108,7 @@ def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', curre :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_rate: Rate, calculated based on pricing settings in exit_pricing. :param current_profit: Current profit (as ratio), calculated based on current_rate. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return: To execute sell, return a string with custom sell reason or True. Otherwise return @@ -117,10 +117,10 @@ def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', curre return None def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, - time_in_force: str, current_time: 'datetime', entry_tag: 'Optional[str]', - **kwargs) -> bool: + time_in_force: str, current_time: datetime, entry_tag: 'Optional[str]', + side: str, **kwargs) -> bool: """ - Called right before placing a buy order. + Called right before placing a entry order. Timing for this function is critical, so avoid doing heavy computations or network requests in this method. @@ -128,13 +128,14 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f When not implemented by a strategy, returns True (always confirming). - :param pair: Pair that's about to be bought. + :param pair: Pair that's about to be bought/shorted. :param order_type: Order type (as configured in order_types). usually limit or market. :param amount: Amount in target (quote) currency that's going to be traded. :param rate: Rate that's going to be used when using limit orders :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param current_time: datetime object, containing the current datetime :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. + :param side: 'long' or 'short' - indicating the direction of the proposed trade :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the buy-order is placed on the exchange. False aborts the process @@ -142,7 +143,7 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f return True def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float, - rate: float, time_in_force: str, sell_reason: str, + rate: float, time_in_force: str, exit_reason: str, current_time: 'datetime', **kwargs) -> bool: """ Called right before placing a regular sell order. @@ -159,7 +160,7 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: :param amount: Amount in quote currency. :param rate: Rate that's going to be used when using limit orders :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). - :param sell_reason: Sell reason. + :param exit_reason: Exit reason. Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', 'sell_signal', 'force_sell', 'emergency_sell'] :param current_time: datetime object, containing the current datetime @@ -169,11 +170,11 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: """ return True -def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: +def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: """ - Check buy timeout function callback. - This method can be used to override the buy-timeout. - It is called whenever a limit buy order has been created, + Check entry timeout function callback. + This method can be used to override the entry-timeout. + It is called whenever a limit entry order has been created, and is not yet fully filled. Configuration options in `unfilledtimeout` will be verified before this, so ensure to set these timeouts high enough. @@ -189,11 +190,11 @@ def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> """ return False -def check_sell_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: +def check_exit_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: """ - Check sell timeout function callback. - This method can be used to override the sell-timeout. - It is called whenever a limit sell order has been created, + Check exit timeout function callback. + This method can be used to override the exit-timeout. + It is called whenever a limit exit order has been created, and is not yet fully filled. Configuration options in `unfilledtimeout` will be verified before this, so ensure to set these timeouts high enough. @@ -231,3 +232,19 @@ def adjust_trade_position(self, trade: 'Trade', current_time: 'datetime', :return float: Stake amount to adjust your trade """ return None + +def leverage(self, pair: str, current_time: datetime, current_rate: float, + proposed_leverage: float, max_leverage: float, side: str, + **kwargs) -> float: + """ + Customize leverage for each new trade. This method is only called in futures mode. + + :param pair: Pair that's currently analyzed + :param current_time: datetime object, containing the current datetime + :param current_rate: Rate, calculated based on pricing settings in exit_pricing. + :param proposed_leverage: A leverage proposed by the bot. + :param max_leverage: Max leverage allowed on this pair + :param side: 'long' or 'short' - indicating the direction of the proposed trade + :return: A leverage amount, which is between 1.0 and max_leverage. + """ + return 1.0 diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 93f3d3800..d93689a0e 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -3,12 +3,12 @@ import logging from copy import deepcopy -from typing import Any, Dict, NamedTuple, Optional +from typing import Dict, NamedTuple, Optional import arrow from freqtrade.constants import UNLIMITED_STAKE_AMOUNT -from freqtrade.enums import RunMode +from freqtrade.enums import RunMode, TradingMode from freqtrade.exceptions import DependencyException from freqtrade.exchange import Exchange from freqtrade.persistence import LocalTrade, Trade @@ -25,6 +25,14 @@ class Wallet(NamedTuple): total: float = 0 +class PositionWallet(NamedTuple): + symbol: str + position: float = 0 + leverage: float = 0 + collateral: float = 0 + side: str = 'long' + + class Wallets: def __init__(self, config: dict, exchange: Exchange, log: bool = True) -> None: @@ -32,6 +40,7 @@ class Wallets: self._log = log self._exchange = exchange self._wallets: Dict[str, Wallet] = {} + self._positions: Dict[str, PositionWallet] = {} self.start_cap = config['dry_run_wallet'] self._last_wallet_refresh = 0 self.update() @@ -66,6 +75,7 @@ class Wallets: """ # Recreate _wallets to reset closed trade balances _wallets = {} + _positions = {} open_trades = Trade.get_trades_proxy(is_open=True) # If not backtesting... # TODO: potentially remove the ._log workaround to determine backtest mode. @@ -74,24 +84,45 @@ class Wallets: else: tot_profit = LocalTrade.total_profit tot_in_trades = sum(trade.stake_amount for trade in open_trades) + used_stake = 0.0 + + if self._config.get('trading_mode', 'spot') != TradingMode.FUTURES: + current_stake = self.start_cap + tot_profit - tot_in_trades + total_stake = current_stake + for trade in open_trades: + curr = self._exchange.get_pair_base_currency(trade.pair) + _wallets[curr] = Wallet( + curr, + trade.amount, + 0, + trade.amount + ) + else: + tot_in_trades = 0 + for position in open_trades: + # size = self._exchange._contracts_to_amount(position.pair, position['contracts']) + size = position.amount + collateral = position.stake_amount + leverage = position.leverage + tot_in_trades += collateral + _positions[position.pair] = PositionWallet( + position.pair, position=size, + leverage=leverage, + collateral=collateral, + side=position.trade_direction + ) + current_stake = self.start_cap + tot_profit - tot_in_trades + used_stake = tot_in_trades + total_stake = current_stake + tot_in_trades - current_stake = self.start_cap + tot_profit - tot_in_trades _wallets[self._config['stake_currency']] = Wallet( - self._config['stake_currency'], - current_stake, - 0, - current_stake + currency=self._config['stake_currency'], + free=current_stake, + used=used_stake, + total=total_stake ) - - for trade in open_trades: - curr = self._exchange.get_pair_base_currency(trade.pair) - _wallets[curr] = Wallet( - curr, - trade.amount, - 0, - trade.amount - ) self._wallets = _wallets + self._positions = _positions def _update_live(self) -> None: balances = self._exchange.get_balances() @@ -109,6 +140,23 @@ class Wallets: if currency not in balances: del self._wallets[currency] + positions = self._exchange.fetch_positions() + self._positions = {} + for position in positions: + symbol = position['symbol'] + if position['side'] is None or position['collateral'] == 0.0: + # Position is not open ... + continue + size = self._exchange._contracts_to_amount(symbol, position['contracts']) + collateral = position['collateral'] + leverage = position['leverage'] + self._positions[symbol] = PositionWallet( + symbol, position=size, + leverage=leverage, + collateral=collateral, + side=position['side'] + ) + def update(self, require_update: bool = True) -> None: """ Updates wallets from the configured version. @@ -126,9 +174,12 @@ class Wallets: logger.info('Wallets synced.') self._last_wallet_refresh = arrow.utcnow().int_timestamp - def get_all_balances(self) -> Dict[str, Any]: + def get_all_balances(self) -> Dict[str, Wallet]: return self._wallets + def get_all_positions(self) -> Dict[str, PositionWallet]: + return self._positions + def get_starting_balance(self) -> float: """ Retrieves starting balance - based on either available capital, @@ -239,13 +290,13 @@ class Wallets: return self._check_available_stake_amount(stake_amount, available_amount) - def validate_stake_amount( - self, pair: str, stake_amount: Optional[float], min_stake_amount: Optional[float]): + def validate_stake_amount(self, pair: str, stake_amount: Optional[float], + min_stake_amount: Optional[float], max_stake_amount: float): if not stake_amount: logger.debug(f"Stake amount is {stake_amount}, ignoring possible trade for {pair}.") return 0 - max_stake_amount = self.get_available_stake_amount() + max_stake_amount = min(max_stake_amount, self.get_available_stake_amount()) if min_stake_amount is not None and min_stake_amount > max_stake_amount: if self._log: diff --git a/mkdocs.yml b/mkdocs.yml index fb1b80ebf..8b36ba699 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -22,6 +22,7 @@ nav: - Data Downloading: data-download.md - Backtesting: backtesting.md - Hyperopt: hyperopt.md + - Short / Leverage: leverage.md - Utility Sub-commands: utils.md - Plotting: plotting.md - Exchange-specific Notes: exchanges.md @@ -36,29 +37,30 @@ nav: - Sandbox Testing: sandbox-testing.md - FAQ: faq.md - SQL Cheat-sheet: sql_cheatsheet.md + - Strategy migration: strategy_migration.md - Updating Freqtrade: updating.md - Deprecated Features: deprecated.md - Contributors Guide: developer.md theme: name: material - logo: 'images/logo.png' - favicon: 'images/logo.png' - custom_dir: 'docs/overrides' + logo: "images/logo.png" + favicon: "images/logo.png" + custom_dir: "docs/overrides" palette: - scheme: default - primary: 'blue grey' - accent: 'tear' + primary: "blue grey" + accent: "tear" toggle: icon: material/toggle-switch-off-outline name: Switch to dark mode - scheme: slate - primary: 'blue grey' - accent: 'tear' + primary: "blue grey" + accent: "tear" toggle: icon: material/toggle-switch name: Switch to light mode extra_css: - - 'stylesheets/ft.extra.css' + - "stylesheets/ft.extra.css" extra_javascript: - javascripts/config.js - https://polyfill.io/v3/polyfill.min.js?features=es6 diff --git a/requirements.txt b/requirements.txt index b116b261f..d305f91b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -44,3 +44,6 @@ questionary==1.10.0 prompt-toolkit==3.0.28 # Extensions to datetime library python-dateutil==2.8.2 + +#Futures +schedule==1.1.0 diff --git a/scripts/rest_client.py b/scripts/rest_client.py index b1234d329..e23954dd4 100755 --- a/scripts/rest_client.py +++ b/scripts/rest_client.py @@ -261,6 +261,20 @@ class FtRestClient(): } return self._post("forcebuy", data=data) + def forceenter(self, pair, side, price=None): + """Force entering a trade + + :param pair: Pair to buy (ETH/BTC) + :param side: 'long' or 'short' + :param price: Optional - price to buy + :return: json object of the trade + """ + data = {"pair": pair, + "side": side, + "price": price, + } + return self._post("forceenter", data=data) + def forcesell(self, tradeid): """Force-sell a trade. diff --git a/setup.py b/setup.py index a89e717a1..640c8cc7b 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ hyperopt = [ 'filelock', 'joblib', 'progressbar2', - ] +] develop = [ 'coveralls', @@ -29,7 +29,7 @@ jupyter = [ 'nbstripout', 'ipykernel', 'nbconvert', - ] +] all_extra = plot + develop + jupyter + hyperopt @@ -39,10 +39,10 @@ setup( 'pytest-asyncio', 'pytest-cov', 'pytest-mock', - ], + ], install_requires=[ # from requirements.txt - 'ccxt>=1.76.5', + 'ccxt>=1.77.29', 'SQLAlchemy', 'python-telegram-bot>=13.4', 'arrow>=0.17.0', @@ -70,7 +70,8 @@ setup( 'uvicorn', 'psutil', 'pyjwt', - 'aiofiles' + 'aiofiles', + 'schedule' ], extras_require={ 'dev': all_extra, diff --git a/tests/commands/test_build_config.py b/tests/commands/test_build_config.py index 66c750e79..e30d5bf94 100644 --- a/tests/commands/test_build_config.py +++ b/tests/commands/test_build_config.py @@ -44,6 +44,8 @@ def test_start_new_config(mocker, caplog, exchange): 'fiat_display_currency': 'EUR', 'timeframe': '15m', 'dry_run': True, + 'trading_mode': 'spot', + 'margin_mode': '', 'exchange_name': exchange, 'exchange_key': 'sampleKey', 'exchange_secret': 'Samplesecret', diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index e0d0cc38d..7baa91720 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -19,8 +19,8 @@ from freqtrade.commands.deploy_commands import (clean_ui_subdir, download_and_in from freqtrade.configuration import setup_utils_configuration from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException -from tests.conftest import (create_mock_trades, get_args, log_has, log_has_re, patch_exchange, - patched_configuration_load_config_file) +from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_args, log_has, + log_has_re, patch_exchange, patched_configuration_load_config_file) from tests.conftest_trades import MOCK_TRADE_COUNT @@ -231,9 +231,9 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 10 active markets: " - "BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, NEO/BTC, " - "TKN/BTC, XLTCUSDT, XRP/BTC.\n" + assert ("Exchange Bittrex has 12 active markets: " + "ADA/USDT:USDT, BLK/BTC, ETH/BTC, ETH/USDT, ETH/USDT:USDT, LTC/BTC, " + "LTC/ETH, LTC/USD, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n" in captured.out) patch_exchange(mocker, api_mock=api_mock, id="binance", mock_markets=markets_static) @@ -246,7 +246,7 @@ def test_list_markets(mocker, markets_static, capsys): pargs['config'] = None start_list_markets(pargs, False) captured = capsys.readouterr() - assert re.match("\nExchange Binance has 10 active markets:\n", + assert re.match("\nExchange Binance has 12 active markets:\n", captured.out) patch_exchange(mocker, api_mock=api_mock, id="bittrex", mock_markets=markets_static) @@ -258,9 +258,9 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 12 markets: " - "BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, " - "TKN/BTC, XLTCUSDT, XRP/BTC.\n" + assert ("Exchange Bittrex has 14 markets: " + "ADA/USDT:USDT, BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, ETH/USDT:USDT, " + "LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n" in captured.out) # Test list-pairs subcommand: active pairs @@ -297,8 +297,8 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 6 active markets with ETH, LTC as base currencies: " - "ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n" + assert ("Exchange Bittrex has 7 active markets with ETH, LTC as base currencies: " + "ETH/BTC, ETH/USDT, ETH/USDT:USDT, LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n" in captured.out) # active markets, base=LTC @@ -323,8 +323,8 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 3 active markets with USDT, USD as quote currencies: " - "ETH/USDT, LTC/USD, XLTCUSDT.\n" + assert ("Exchange Bittrex has 5 active markets with USDT, USD as quote currencies: " + "ADA/USDT:USDT, ETH/USDT, ETH/USDT:USDT, LTC/USD, XLTCUSDT.\n" in captured.out) # active markets, quote=USDT @@ -336,8 +336,8 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 2 active markets with USDT as quote currency: " - "ETH/USDT, XLTCUSDT.\n" + assert ("Exchange Bittrex has 4 active markets with USDT as quote currency: " + "ADA/USDT:USDT, ETH/USDT, ETH/USDT:USDT, XLTCUSDT.\n" in captured.out) # active markets, base=LTC, quote=USDT @@ -399,7 +399,7 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Exchange Bittrex has 10 active markets:\n" + assert ("Exchange Bittrex has 12 active markets:\n" in captured.out) # Test tabular output, no markets found @@ -422,8 +422,8 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ('["BLK/BTC","ETH/BTC","ETH/USDT","LTC/BTC","LTC/ETH","LTC/USD","NEO/BTC",' - '"TKN/BTC","XLTCUSDT","XRP/BTC"]' + assert ('["ADA/USDT:USDT","BLK/BTC","ETH/BTC","ETH/USDT","ETH/USDT:USDT",' + '"LTC/BTC","LTC/ETH","LTC/USD","NEO/BTC","TKN/BTC","XLTCUSDT","XRP/BTC"]' in captured.out) # Test --print-csv @@ -434,9 +434,9 @@ def test_list_markets(mocker, markets_static, capsys): ] start_list_markets(get_args(args), False) captured = capsys.readouterr() - assert ("Id,Symbol,Base,Quote,Active,Is pair" in captured.out) - assert ("blkbtc,BLK/BTC,BLK,BTC,True,True" in captured.out) - assert ("USD-LTC,LTC/USD,LTC,USD,True,True" in captured.out) + assert ("Id,Symbol,Base,Quote,Active,Spot,Margin,Future,Leverage" in captured.out) + assert ("blkbtc,BLK/BTC,BLK,BTC,True,Spot" in captured.out) + assert ("USD-LTC,LTC/USD,LTC,USD,True,Spot" in captured.out) # Test --one-column args = [ @@ -810,10 +810,24 @@ def test_download_data_trades(mocker, caplog): "--days", "20", "--dl-trades" ] - start_download_data(get_args(args)) + pargs = get_args(args) + pargs['config'] = None + start_download_data(pargs) assert dl_mock.call_args[1]['timerange'].starttype == "date" assert dl_mock.call_count == 1 assert convert_mock.call_count == 1 + args = [ + "download-data", + "--exchange", "kraken", + "--pairs", "ETH/BTC", "XRP/BTC", + "--days", "20", + "--trading-mode", "futures", + "--dl-trades" + ] + with pytest.raises(OperationalException, + match="Trade download not supported for futures."): + + start_download_data(get_args(args)) def test_start_convert_trades(mocker, caplog): @@ -846,7 +860,7 @@ def test_start_list_strategies(mocker, caplog, capsys): captured = capsys.readouterr() assert "TestStrategyLegacyV1" in captured.out assert "legacy_strategy_v1.py" not in captured.out - assert "StrategyTestV2" in captured.out + assert CURRENT_TEST_STRATEGY in captured.out # Test regular output args = [ @@ -861,7 +875,7 @@ def test_start_list_strategies(mocker, caplog, capsys): captured = capsys.readouterr() assert "TestStrategyLegacyV1" in captured.out assert "legacy_strategy_v1.py" in captured.out - assert "StrategyTestV2" in captured.out + assert CURRENT_TEST_STRATEGY in captured.out # Test color output args = [ @@ -875,7 +889,7 @@ def test_start_list_strategies(mocker, caplog, capsys): captured = capsys.readouterr() assert "TestStrategyLegacyV1" in captured.out assert "legacy_strategy_v1.py" in captured.out - assert "StrategyTestV2" in captured.out + assert CURRENT_TEST_STRATEGY in captured.out assert "LOAD FAILED" in captured.out @@ -943,7 +957,7 @@ def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmpdir): mocker.patch( 'freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist', return_value=True - ) + ) def fake_iterator(*args, **kwargs): yield from [saved_hyperopt_results] @@ -1327,8 +1341,8 @@ def test_start_list_data(testdatadir, capsys): start_list_data(pargs) captured = capsys.readouterr() assert "Found 17 pair / timeframe combinations." in captured.out - assert "\n| Pair | Timeframe |\n" in captured.out - assert "\n| UNITTEST/BTC | 1m, 5m, 8m, 30m |\n" in captured.out + assert "\n| Pair | Timeframe | Type |\n" in captured.out + assert "\n| UNITTEST/BTC | 1m, 5m, 8m, 30m | spot |\n" in captured.out args = [ "list-data", @@ -1343,15 +1357,33 @@ def test_start_list_data(testdatadir, capsys): start_list_data(pargs) captured = capsys.readouterr() assert "Found 2 pair / timeframe combinations." in captured.out - assert "\n| Pair | Timeframe |\n" in captured.out + assert "\n| Pair | Timeframe | Type |\n" in captured.out assert "UNITTEST/BTC" not in captured.out - assert "\n| XRP/ETH | 1m, 5m |\n" in captured.out + assert "\n| XRP/ETH | 1m, 5m | spot |\n" in captured.out + + args = [ + "list-data", + "--data-format-ohlcv", + "json", + "--trading-mode", "futures", + "--datadir", + str(testdatadir), + ] + pargs = get_args(args) + pargs['config'] = None + start_list_data(pargs) + captured = capsys.readouterr() + + assert "Found 5 pair / timeframe combinations." in captured.out + assert "\n| Pair | Timeframe | Type |\n" in captured.out + assert "\n| XRP/USDT | 1h | futures |\n" in captured.out + assert "\n| XRP/USDT | 1h, 8h | mark |\n" in captured.out @pytest.mark.usefixtures("init_persistence") def test_show_trades(mocker, fee, capsys, caplog): mocker.patch("freqtrade.persistence.init_db") - create_mock_trades(fee) + create_mock_trades(fee, False) args = [ "show-trades", "--db-url", diff --git a/tests/conftest.py b/tests/conftest.py index 0f01d7e4a..7c3bbe4a5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,7 @@ import re from copy import deepcopy from datetime import datetime, timedelta from pathlib import Path +from typing import Optional from unittest.mock import MagicMock, Mock, PropertyMock import arrow @@ -16,14 +17,14 @@ from freqtrade import constants from freqtrade.commands import Arguments from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.edge import PairInfo -from freqtrade.enums import RunMode +from freqtrade.enums import CandleType, MarginMode, RunMode, SignalDirection, TradingMode from freqtrade.exchange import Exchange from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import LocalTrade, Order, Trade, init_db from freqtrade.resolvers import ExchangeResolver from freqtrade.worker import Worker -from tests.conftest_trades import (mock_trade_1, mock_trade_2, mock_trade_3, mock_trade_4, - mock_trade_5, mock_trade_6) +from tests.conftest_trades import (leverage_trade, mock_trade_1, mock_trade_2, mock_trade_3, + mock_trade_4, mock_trade_5, mock_trade_6, short_trade) from tests.conftest_trades_usdt import (mock_trade_usdt_1, mock_trade_usdt_2, mock_trade_usdt_3, mock_trade_usdt_4, mock_trade_usdt_5, mock_trade_usdt_6, mock_trade_usdt_7) @@ -35,6 +36,9 @@ logging.getLogger('').setLevel(logging.INFO) # Do not mask numpy errors as warnings that no one read, raise the exсeption np.seterr(all='raise') +CURRENT_TEST_STRATEGY = 'StrategyTestV3' +TRADE_SIDES = ('long', 'short') + def pytest_addoption(parser): parser.addoption('--longrun', action='store_true', dest="longrun", @@ -88,21 +92,40 @@ def patched_configuration_load_config_file(mocker, config) -> None: ) -def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> None: +def patch_exchange( + mocker, + api_mock=None, + id='binance', + mock_markets=True, + mock_supported_modes=True +) -> None: mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id)) mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title())) mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) + if mock_markets: if isinstance(mock_markets, bool): mock_markets = get_markets() mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=mock_markets)) + if mock_supported_modes: + mocker.patch( + f'freqtrade.exchange.{id.capitalize()}._supported_trading_mode_margin_pairs', + PropertyMock(return_value=[ + (TradingMode.MARGIN, MarginMode.CROSS), + (TradingMode.MARGIN, MarginMode.ISOLATED), + (TradingMode.FUTURES, MarginMode.CROSS), + (TradingMode.FUTURES, MarginMode.ISOLATED) + ]) + ) + if api_mock: mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) else: @@ -112,8 +135,8 @@ def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> No def get_patched_exchange(mocker, config, api_mock=None, id='binance', - mock_markets=True) -> Exchange: - patch_exchange(mocker, api_mock, id, mock_markets) + mock_markets=True, mock_supported_modes=True) -> Exchange: + patch_exchange(mocker, api_mock, id, mock_markets, mock_supported_modes) config['exchange']['name'] = id try: exchange = ExchangeResolver.load_exchange(id, config) @@ -159,7 +182,6 @@ def patch_freqtradebot(mocker, config) -> None: :return: None """ mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - init_db(config['db_url']) patch_exchange(mocker) mocker.patch('freqtrade.freqtradebot.RPCManager._init', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock()) @@ -189,17 +211,79 @@ def get_patched_worker(mocker, config) -> Worker: return Worker(args=None, config=config) -def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False, None, None)) -> None: +def patch_get_signal( + freqtrade: FreqtradeBot, + enter_long=True, + exit_long=False, + enter_short=False, + exit_short=False, + enter_tag: Optional[str] = None, + exit_tag: Optional[str] = None, +) -> None: """ :param mocker: mocker to patch IStrategy class - :param value: which value IStrategy.get_signal() must return :return: None """ - freqtrade.strategy.get_signal = lambda e, s, x: value + # returns (Signal-direction, signaname) + def patched_get_entry_signal(*args, **kwargs): + direction = None + if enter_long and not any([exit_long, enter_short]): + direction = SignalDirection.LONG + if enter_short and not any([exit_short, enter_long]): + direction = SignalDirection.SHORT + + return direction, enter_tag + + freqtrade.strategy.get_entry_signal = patched_get_entry_signal + + def patched_get_exit_signal(pair, timeframe, dataframe, is_short): + if is_short: + return enter_short, exit_short, exit_tag + else: + return enter_long, exit_long, exit_tag + + # returns (enter, exit) + freqtrade.strategy.get_exit_signal = patched_get_exit_signal + freqtrade.exchange.refresh_latest_ohlcv = lambda p: None -def create_mock_trades(fee, use_db: bool = True): +def create_mock_trades(fee, is_short: Optional[bool] = False, use_db: bool = True): + """ + Create some fake trades ... + :param is_short: Optional bool, None creates a mix of long and short trades. + """ + def add_trade(trade): + if use_db: + Trade.query.session.add(trade) + else: + LocalTrade.add_bt_trade(trade) + is_short1 = is_short if is_short is not None else True + is_short2 = is_short if is_short is not None else False + # Simulate dry_run entries + trade = mock_trade_1(fee, is_short1) + add_trade(trade) + + trade = mock_trade_2(fee, is_short1) + add_trade(trade) + + trade = mock_trade_3(fee, is_short2) + add_trade(trade) + + trade = mock_trade_4(fee, is_short2) + add_trade(trade) + + trade = mock_trade_5(fee, is_short2) + add_trade(trade) + + trade = mock_trade_6(fee, is_short1) + add_trade(trade) + + if use_db: + Trade.commit() + + +def create_mock_trades_with_leverage(fee, use_db: bool = True): """ Create some fake trades ... """ @@ -213,26 +297,32 @@ def create_mock_trades(fee, use_db: bool = True): LocalTrade.add_bt_trade(trade) # Simulate dry_run entries - trade = mock_trade_1(fee) + trade = mock_trade_1(fee, False) add_trade(trade) - trade = mock_trade_2(fee) + trade = mock_trade_2(fee, False) add_trade(trade) - trade = mock_trade_3(fee) + trade = mock_trade_3(fee, False) add_trade(trade) - trade = mock_trade_4(fee) + trade = mock_trade_4(fee, False) add_trade(trade) - trade = mock_trade_5(fee) + trade = mock_trade_5(fee, False) add_trade(trade) - trade = mock_trade_6(fee) + trade = mock_trade_6(fee, False) + add_trade(trade) + + trade = short_trade(fee) + add_trade(trade) + + trade = leverage_trade(fee) add_trade(trade) if use_db: - Trade.commit() + Trade.query.session.flush() def create_mock_trades_usdt(fee, use_db: bool = True): @@ -326,11 +416,11 @@ def get_default_conf(testdatadir): "dry_run_wallet": 1000, "stoploss": -0.10, "unfilledtimeout": { - "buy": 10, - "sell": 30 + "entry": 10, + "exit": 30 }, - "bid_strategy": { - "ask_last_balance": 0.0, + "entry_pricing": { + "price_last_balance": 0.0, "use_order_book": False, "order_book_top": 1, "check_depth_of_market": { @@ -338,7 +428,7 @@ def get_default_conf(testdatadir): "bids_to_ask_delta": 1 } }, - "ask_strategy": { + "exit_pricing": { "use_order_book": False, "order_book_top": 1, }, @@ -373,10 +463,11 @@ def get_default_conf(testdatadir): "user_data_dir": Path("user_data"), "verbosity": 3, "strategy_path": str(Path(__file__).parent / "strategy" / "strats"), - "strategy": "StrategyTestV2", + "strategy": CURRENT_TEST_STRATEGY, "disableparamexport": True, "internals": {}, "export": "none", + "candle_type_def": CandleType.SPOT, } return configuration @@ -488,24 +579,35 @@ def get_markets(): 'base': 'ETH', 'quote': 'BTC', 'active': True, + 'spot': True, + 'swap': False, + 'linear': None, + 'type': 'spot', 'precision': { 'price': 8, 'amount': 8, 'cost': 8, }, 'lot': 0.00000001, + 'contractSize': None, 'limits': { 'amount': { 'min': 0.01, - 'max': 1000, + 'max': 100000000, + }, + 'price': { + 'min': None, + 'max': 500000, }, - 'price': 500000, 'cost': { 'min': 0.0001, 'max': 500000, }, + 'leverage': { + 'min': 1.0, + 'max': 2.0 + } }, - 'info': {}, }, 'TKN/BTC': { 'id': 'tknbtc', @@ -514,24 +616,35 @@ def get_markets(): 'quote': 'BTC', # According to ccxt, markets without active item set are also active # 'active': True, + 'spot': True, + 'swap': False, + 'linear': None, + 'type': 'spot', 'precision': { 'price': 8, 'amount': 8, 'cost': 8, }, 'lot': 0.00000001, + 'contractSize': None, 'limits': { 'amount': { 'min': 0.01, - 'max': 1000, + 'max': 100000000, + }, + 'price': { + 'min': None, + 'max': 500000, }, - 'price': 500000, 'cost': { 'min': 0.0001, 'max': 500000, }, + 'leverage': { + 'min': 1.0, + 'max': 5.0 + } }, - 'info': {}, }, 'BLK/BTC': { 'id': 'blkbtc', @@ -539,24 +652,35 @@ def get_markets(): 'base': 'BLK', 'quote': 'BTC', 'active': True, + 'spot': True, + 'swap': False, + 'linear': None, + 'type': 'spot', 'precision': { 'price': 8, 'amount': 8, 'cost': 8, }, 'lot': 0.00000001, + 'contractSize': None, 'limits': { 'amount': { 'min': 0.01, 'max': 1000, }, - 'price': 500000, + 'price': { + 'min': None, + 'max': 500000, + }, 'cost': { 'min': 0.0001, 'max': 500000, }, + 'leverage': { + 'min': 1.0, + 'max': 3.0 + }, }, - 'info': {}, }, 'LTC/BTC': { 'id': 'ltcbtc', @@ -564,22 +688,34 @@ def get_markets(): 'base': 'LTC', 'quote': 'BTC', 'active': True, + 'spot': True, + 'swap': False, + 'linear': None, + 'type': 'spot', 'precision': { 'price': 8, 'amount': 8, 'cost': 8, }, 'lot': 0.00000001, + 'contractSize': None, 'limits': { 'amount': { 'min': 0.01, - 'max': 1000, + 'max': 100000000, + }, + 'price': { + 'min': None, + 'max': 500000, }, - 'price': 500000, 'cost': { 'min': 0.0001, 'max': 500000, }, + 'leverage': { + 'min': None, + 'max': None + }, }, 'info': {}, }, @@ -589,22 +725,34 @@ def get_markets(): 'base': 'XRP', 'quote': 'BTC', 'active': True, + 'spot': True, + 'swap': False, + 'linear': None, + 'type': 'spot', 'precision': { 'price': 8, 'amount': 8, 'cost': 8, }, 'lot': 0.00000001, + 'contractSize': None, 'limits': { 'amount': { 'min': 0.01, - 'max': 1000, + 'max': 100000000, + }, + 'price': { + 'min': None, + 'max': 500000, }, - 'price': 500000, 'cost': { 'min': 0.0001, 'max': 500000, }, + 'leverage': { + 'min': None, + 'max': None, + }, }, 'info': {}, }, @@ -614,22 +762,34 @@ def get_markets(): 'base': 'NEO', 'quote': 'BTC', 'active': True, + 'spot': True, + 'swap': False, + 'linear': None, + 'type': 'spot', 'precision': { 'price': 8, 'amount': 8, 'cost': 8, }, 'lot': 0.00000001, + 'contractSize': None, 'limits': { 'amount': { 'min': 0.01, - 'max': 1000, + 'max': 100000000, + }, + 'price': { + 'min': None, + 'max': 500000, }, - 'price': 500000, 'cost': { 'min': 0.0001, 'max': 500000, }, + 'leverage': { + 'min': None, + 'max': None, + }, }, 'info': {}, }, @@ -639,6 +799,11 @@ def get_markets(): 'base': 'BTT', 'quote': 'BTC', 'active': False, + 'spot': True, + 'swap': False, + 'linear': None, + 'type': 'spot', + 'contractSize': None, 'precision': { 'base': 8, 'quote': 8, @@ -657,7 +822,11 @@ def get_markets(): 'cost': { 'min': 0.0001, 'max': None - } + }, + 'leverage': { + 'min': None, + 'max': None, + }, }, 'info': {}, }, @@ -666,22 +835,52 @@ def get_markets(): 'symbol': 'ETH/USDT', 'base': 'ETH', 'quote': 'USDT', + 'settle': None, + 'baseId': 'ETH', + 'quoteId': 'USDT', + 'settleId': None, + 'type': 'spot', + 'spot': True, + 'margin': True, + 'swap': True, + 'future': True, + 'option': False, + 'active': True, + 'contract': None, + 'linear': None, + 'inverse': None, + 'taker': 0.0006, + 'maker': 0.0002, + 'contractSize': None, + 'expiry': None, + 'expiryDateTime': None, + 'strike': None, + 'optionType': None, 'precision': { 'amount': 8, - 'price': 8 + 'price': 8, }, 'limits': { + 'leverage': { + 'min': 1, + 'max': 100, + }, 'amount': { 'min': 0.02214286, - 'max': None + 'max': None, }, 'price': { 'min': 1e-08, - 'max': None - } + 'max': None, + }, + 'cost': { + 'min': None, + 'max': None, + }, + }, + 'info': { + 'maintenance_rate': '0.005', }, - 'active': True, - 'info': {}, }, 'LTC/USDT': { 'id': 'USDT-LTC', @@ -689,6 +888,16 @@ def get_markets(): 'base': 'LTC', 'quote': 'USDT', 'active': False, + 'spot': True, + 'future': True, + 'swap': True, + 'margin': True, + 'linear': None, + 'inverse': False, + 'type': 'spot', + 'contractSize': None, + 'taker': 0.0006, + 'maker': 0.0002, 'precision': { 'amount': 8, 'price': 8 @@ -701,7 +910,15 @@ def get_markets(): 'price': { 'min': 1e-08, 'max': None - } + }, + 'leverage': { + 'min': None, + 'max': None, + }, + 'cost': { + 'min': None, + 'max': None, + }, }, 'info': {}, }, @@ -711,18 +928,28 @@ def get_markets(): 'base': 'XRP', 'quote': 'USDT', 'active': True, + 'spot': True, + 'swap': False, + 'linear': None, + 'type': 'spot', + 'taker': 0.0006, + 'maker': 0.0002, 'precision': { 'price': 8, 'amount': 8, 'cost': 8, }, 'lot': 0.00000001, + 'contractSize': None, 'limits': { 'amount': { 'min': 0.01, 'max': 1000, }, - 'price': 500000, + 'price': { + 'min': None, + 'max': 500000, + }, 'cost': { 'min': 0.0001, 'max': 500000, @@ -735,19 +962,48 @@ def get_markets(): 'symbol': 'NEO/USDT', 'base': 'NEO', 'quote': 'USDT', + 'settle': '', + 'baseId': 'NEO', + 'quoteId': 'USDT', + 'settleId': '', + 'type': 'spot', + 'spot': True, + 'margin': True, + 'swap': False, + 'futures': False, + 'option': False, 'active': True, + 'contract': False, + 'linear': None, + 'inverse': None, + 'taker': 0.0006, + 'maker': 0.0002, + 'contractSize': None, + 'expiry': None, + 'expiryDatetime': None, + 'strike': None, + 'optionType': None, + 'tierBased': None, + 'percentage': None, + 'lot': 0.00000001, 'precision': { 'price': 8, 'amount': 8, 'cost': 8, }, - 'lot': 0.00000001, 'limits': { + "leverage": { + 'min': 1, + 'max': 10 + }, 'amount': { 'min': 0.01, 'max': 1000, }, - 'price': 500000, + 'price': { + 'min': None, + 'max': 500000, + }, 'cost': { 'min': 0.0001, 'max': 500000, @@ -761,6 +1017,13 @@ def get_markets(): 'base': 'TKN', 'quote': 'USDT', 'active': True, + 'spot': True, + 'swap': False, + 'linear': None, + 'type': 'spot', + 'contractSize': None, + 'taker': 0.0006, + 'maker': 0.0002, 'precision': { 'price': 8, 'amount': 8, @@ -770,13 +1033,20 @@ def get_markets(): 'limits': { 'amount': { 'min': 0.01, - 'max': 1000, + 'max': 100000000000, + }, + 'price': { + 'min': None, + 'max': 500000 }, - 'price': 500000, 'cost': { 'min': 0.0001, 'max': 500000, }, + 'leverage': { + 'min': None, + 'max': None, + }, }, 'info': {}, }, @@ -786,6 +1056,11 @@ def get_markets(): 'base': 'LTC', 'quote': 'USD', 'active': True, + 'spot': True, + 'swap': False, + 'linear': None, + 'type': 'spot', + 'contractSize': None, 'precision': { 'amount': 8, 'price': 8 @@ -798,7 +1073,15 @@ def get_markets(): 'price': { 'min': 1e-08, 'max': None - } + }, + 'leverage': { + 'min': None, + 'max': None, + }, + 'cost': { + 'min': None, + 'max': None, + }, }, 'info': {}, }, @@ -808,11 +1091,22 @@ def get_markets(): 'base': 'LTC', 'quote': 'USDT', 'active': True, + 'spot': False, + 'type': 'swap', + 'contractSize': 0.01, + 'swap': False, + 'linear': False, + 'taker': 0.0006, + 'maker': 0.0002, 'precision': { 'amount': 8, 'price': 8 }, 'limits': { + 'leverage': { + 'min': None, + 'max': None, + }, 'amount': { 'min': 0.06646786, 'max': None @@ -820,7 +1114,11 @@ def get_markets(): 'price': { 'min': 1e-08, 'max': None - } + }, + 'cost': { + 'min': None, + 'max': None, + }, }, 'info': {}, }, @@ -830,6 +1128,11 @@ def get_markets(): 'base': 'LTC', 'quote': 'ETH', 'active': True, + 'spot': True, + 'swap': False, + 'linear': None, + 'type': 'spot', + 'contractSize': None, 'precision': { 'base': 8, 'quote': 8, @@ -837,6 +1140,10 @@ def get_markets(): 'price': 5 }, 'limits': { + 'leverage': { + 'min': None, + 'max': None, + }, 'amount': { 'min': 0.001, 'max': 10000000.0 @@ -850,6 +1157,238 @@ def get_markets(): 'max': None } }, + 'info': { + } + }, + 'ETH/USDT:USDT': { + 'id': 'ETH_USDT', + 'symbol': 'ETH/USDT:USDT', + 'base': 'ETH', + 'quote': 'USDT', + 'settle': 'USDT', + 'baseId': 'ETH', + 'quoteId': 'USDT', + 'settleId': 'USDT', + 'type': 'swap', + 'spot': False, + 'margin': False, + 'swap': True, + 'future': True, # Binance mode ... + 'option': False, + 'contract': True, + 'linear': True, + 'inverse': False, + 'tierBased': False, + 'percentage': True, + 'taker': 0.0006, + 'maker': 0.0002, + 'contractSize': 10, + 'active': True, + 'expiry': None, + 'expiryDatetime': None, + 'strike': None, + 'optionType': None, + 'limits': { + 'leverage': { + 'min': 1, + 'max': 100 + }, + 'amount': { + 'min': 1, + 'max': 300000 + }, + 'price': { + 'min': None, + 'max': None, + }, + 'cost': { + 'min': None, + 'max': None, + } + }, + 'precision': { + 'price': 0.05, + 'amount': 1 + }, + 'info': {} + }, + 'ADA/USDT:USDT': { + 'limits': { + 'leverage': { + 'min': 1, + 'max': 20, + }, + 'amount': { + 'min': 1, + 'max': 1000000, + }, + 'price': { + 'min': 0.52981, + 'max': 1.58943, + }, + 'cost': { + 'min': None, + 'max': None, + } + }, + 'precision': { + 'amount': 1, + 'price': 0.00001 + }, + 'tierBased': True, + 'percentage': True, + 'taker': 0.0000075, + 'maker': -0.0000025, + 'feeSide': 'get', + 'tiers': { + 'maker': [ + [0, 0.002], [1.5, 0.00185], + [3, 0.00175], [6, 0.00165], + [12.5, 0.00155], [25, 0.00145], + [75, 0.00135], [200, 0.00125], + [500, 0.00115], [1250, 0.00105], + [2500, 0.00095], [3000, 0.00085], + [6000, 0.00075], [11000, 0.00065], + [20000, 0.00055], [40000, 0.00055], + [75000, 0.00055] + ], + 'taker': [ + [0, 0.002], [1.5, 0.00195], + [3, 0.00185], [6, 0.00175], + [12.5, 0.00165], [25, 0.00155], + [75, 0.00145], [200, 0.00135], + [500, 0.00125], [1250, 0.00115], + [2500, 0.00105], [3000, 0.00095], + [6000, 0.00085], [11000, 0.00075], + [20000, 0.00065], [40000, 0.00065], + [75000, 0.00065] + ] + }, + 'id': 'ADA_USDT', + 'symbol': 'ADA/USDT:USDT', + 'base': 'ADA', + 'quote': 'USDT', + 'settle': 'USDT', + 'baseId': 'ADA', + 'quoteId': 'USDT', + 'settleId': 'usdt', + 'type': 'swap', + 'spot': False, + 'margin': False, + 'swap': True, + 'future': True, # Binance mode ... + 'option': False, + 'active': True, + 'contract': True, + 'linear': True, + 'inverse': False, + 'contractSize': 0.01, + 'expiry': None, + 'expiryDatetime': None, + 'strike': None, + 'optionType': None, + 'info': {} + }, + 'SOL/BUSD:BUSD': { + 'limits': { + 'leverage': {'min': None, 'max': None}, + 'amount': {'min': 1, 'max': 1000000}, + 'price': {'min': 0.04, 'max': 100000}, + 'cost': {'min': 5, 'max': None}, + 'market': {'min': 1, 'max': 1500} + }, + 'precision': {'amount': 0, 'price': 2, 'base': 8, 'quote': 8}, + 'tierBased': False, + 'percentage': True, + 'taker': 0.0004, + 'maker': 0.0002, + 'feeSide': 'get', + 'id': 'SOLBUSD', + 'lowercaseId': 'solbusd', + 'symbol': 'SOL/BUSD', + 'base': 'SOL', + 'quote': 'BUSD', + 'settle': 'BUSD', + 'baseId': 'SOL', + 'quoteId': 'BUSD', + 'settleId': 'BUSD', + 'type': 'future', + 'spot': False, + 'margin': False, + 'future': True, + 'delivery': False, + 'option': False, + 'active': True, + 'contract': True, + 'linear': True, + 'inverse': False, + 'contractSize': 1, + 'expiry': None, + 'expiryDatetime': None, + 'strike': None, + 'optionType': None, + 'info': { + 'symbol': 'SOLBUSD', + 'pair': 'SOLBUSD', + 'contractType': 'PERPETUAL', + 'deliveryDate': '4133404800000', + 'onboardDate': '1630566000000', + 'status': 'TRADING', + 'maintMarginPercent': '2.5000', + 'requiredMarginPercent': '5.0000', + 'baseAsset': 'SOL', + 'quoteAsset': 'BUSD', + 'marginAsset': 'BUSD', + 'pricePrecision': '4', + 'quantityPrecision': '0', + 'baseAssetPrecision': '8', + 'quotePrecision': '8', + 'underlyingType': 'COIN', + 'underlyingSubType': [], + 'settlePlan': '0', + 'triggerProtect': '0.0500', + 'liquidationFee': '0.005000', + 'marketTakeBound': '0.05', + 'filters': [ + { + 'minPrice': '0.0400', + 'maxPrice': '100000', + 'filterType': 'PRICE_FILTER', + 'tickSize': '0.0100' + }, + { + 'stepSize': '1', + 'filterType': 'LOT_SIZE', + 'maxQty': '1000000', + 'minQty': '1' + }, + { + 'stepSize': '1', + 'filterType': 'MARKET_LOT_SIZE', + 'maxQty': '1500', + 'minQty': '1' + }, + {'limit': '200', 'filterType': 'MAX_NUM_ORDERS'}, + {'limit': '10', 'filterType': 'MAX_NUM_ALGO_ORDERS'}, + {'notional': '5', 'filterType': 'MIN_NOTIONAL'}, + { + 'multiplierDown': '0.9500', + 'multiplierUp': '1.0500', + 'multiplierDecimal': '4', + 'filterType': 'PERCENT_PRICE' + } + ], + 'orderTypes': [ + 'LIMIT', + 'MARKET', + 'STOP', + 'STOP_MARKET', + 'TAKE_PROFIT', + 'TAKE_PROFIT_MARKET', + 'TRAILING_STOP_MARKET' + ], + 'timeInForce': ['GTC', 'IOC', 'FOK', 'GTX'] + } }, } @@ -860,7 +1399,9 @@ def markets_static(): # market list. Do not modify this list without a good reason! Do not modify market parameters # of listed pairs in get_markets() without a good reason either! static_markets = ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', - 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC'] + 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC', + 'ADA/USDT:USDT', 'ETH/USDT:USDT', + ] all_markets = get_markets() return {m: all_markets[m] for m in static_markets} @@ -878,6 +1419,8 @@ def shitcoinmarkets(markets_static): 'base': 'HOT', 'quote': 'BTC', 'active': True, + 'spot': True, + 'type': 'spot', 'precision': { 'base': 8, 'quote': 8, @@ -906,6 +1449,8 @@ def shitcoinmarkets(markets_static): 'base': 'FUEL', 'quote': 'BTC', 'active': True, + 'spot': True, + 'type': 'spot', 'precision': { 'base': 8, 'quote': 8, @@ -940,6 +1485,22 @@ def shitcoinmarkets(markets_static): "price": 4 }, "limits": { + 'leverage': { + 'min': None, + 'max': None, + }, + 'amount': { + 'min': None, + 'max': None, + }, + 'price': { + 'min': None, + 'max': None, + }, + 'cost': { + 'min': None, + 'max': None, + }, }, "id": "NANOUSDT", "symbol": "NANO/USDT", @@ -965,6 +1526,22 @@ def shitcoinmarkets(markets_static): "price": 4 }, "limits": { + 'leverage': { + 'min': None, + 'max': None, + }, + 'amount': { + 'min': None, + 'max': None, + }, + 'price': { + 'min': None, + 'max': None, + }, + 'cost': { + 'min': None, + 'max': None, + }, }, "id": "ADAHALFUSDT", "symbol": "ADAHALF/USDT", @@ -990,6 +1567,22 @@ def shitcoinmarkets(markets_static): "price": 4 }, "limits": { + 'leverage': { + 'min': None, + 'max': None, + }, + 'amount': { + 'min': None, + 'max': None, + }, + 'price': { + 'min': None, + 'max': None, + }, + 'cost': { + 'min': None, + 'max': None, + }, }, "id": "ADADOUBLEUSDT", "symbol": "ADADOUBLE/USDT", @@ -2032,7 +2625,7 @@ def open_trade(): @pytest.fixture(scope="function") def open_trade_usdt(): - return Trade( + trade = Trade( pair='ADA/USDT', open_rate=2.0, exchange='binance', @@ -2044,6 +2637,26 @@ def open_trade_usdt(): open_date=arrow.utcnow().shift(minutes=-601).datetime, is_open=True ) + trade.orders = [ + Order( + ft_order_side='buy', + ft_pair=trade.pair, + ft_is_open=False, + order_id='123456789', + status="closed", + symbol=trade.pair, + order_type="market", + side="buy", + price=trade.open_rate, + average=trade.open_rate, + filled=trade.amount, + remaining=0, + cost=trade.open_rate * trade.amount, + order_date=trade.open_date, + order_filled_date=trade.open_date, + ) + ] + return trade @pytest.fixture @@ -2224,6 +2837,7 @@ def limit_sell_order_usdt_open(): 'timestamp': arrow.utcnow().int_timestamp * 1000, 'price': 2.20, 'amount': 30.0, + 'cost': 66.0, 'filled': 0.0, 'remaining': 30.0, 'status': 'open' @@ -2279,7 +2893,7 @@ def market_buy_order_usdt_doublefee(market_buy_order_usdt): 'amount': 25.0, 'cost': 50.25, 'fee': {'cost': 0.00025125, 'currency': 'BNB'} - }, { + }, { 'timestamp': None, 'datetime': None, 'symbol': 'ETH/USDT', @@ -2292,7 +2906,7 @@ def market_buy_order_usdt_doublefee(market_buy_order_usdt): 'amount': 5, 'cost': 10, 'fee': {'cost': 0.0100306, 'currency': 'USDT'} - }] + }] return order @@ -2311,3 +2925,590 @@ def market_sell_order_usdt(): 'remaining': 0.0, 'status': 'closed' } + + +@pytest.fixture(scope='function') +def limit_order(limit_buy_order_usdt, limit_sell_order_usdt): + return { + 'buy': limit_buy_order_usdt, + 'sell': limit_sell_order_usdt + } + + +@pytest.fixture(scope='function') +def market_order(market_buy_order_usdt, market_sell_order_usdt): + return { + 'buy': market_buy_order_usdt, + 'sell': market_sell_order_usdt + } + + +@pytest.fixture(scope='function') +def limit_order_open(limit_buy_order_usdt_open, limit_sell_order_usdt_open): + return { + 'buy': limit_buy_order_usdt_open, + 'sell': limit_sell_order_usdt_open + } + + +@pytest.fixture(scope='function') +def mark_ohlcv(): + return [ + [1630454400000, 2.77, 2.77, 2.73, 2.73, 0], + [1630458000000, 2.73, 2.76, 2.72, 2.74, 0], + [1630461600000, 2.74, 2.76, 2.74, 2.76, 0], + [1630465200000, 2.76, 2.76, 2.74, 2.76, 0], + [1630468800000, 2.76, 2.77, 2.75, 2.77, 0], + [1630472400000, 2.77, 2.79, 2.75, 2.78, 0], + [1630476000000, 2.78, 2.80, 2.77, 2.77, 0], + [1630479600000, 2.78, 2.79, 2.77, 2.77, 0], + [1630483200000, 2.77, 2.79, 2.77, 2.78, 0], + [1630486800000, 2.77, 2.84, 2.77, 2.84, 0], + [1630490400000, 2.84, 2.85, 2.81, 2.81, 0], + [1630494000000, 2.81, 2.83, 2.81, 2.81, 0], + [1630497600000, 2.81, 2.84, 2.81, 2.82, 0], + [1630501200000, 2.82, 2.83, 2.81, 2.81, 0], + ] + + +@pytest.fixture(scope='function') +def funding_rate_history_hourly(): + return [ + { + "symbol": "ADA/USDT", + "fundingRate": -0.000008, + "timestamp": 1630454400000, + "datetime": "2021-09-01T00:00:00.000Z" + }, + { + "symbol": "ADA/USDT", + "fundingRate": -0.000004, + "timestamp": 1630458000000, + "datetime": "2021-09-01T01:00:00.000Z" + }, + { + "symbol": "ADA/USDT", + "fundingRate": 0.000012, + "timestamp": 1630461600000, + "datetime": "2021-09-01T02:00:00.000Z" + }, + { + "symbol": "ADA/USDT", + "fundingRate": -0.000003, + "timestamp": 1630465200000, + "datetime": "2021-09-01T03:00:00.000Z" + }, + { + "symbol": "ADA/USDT", + "fundingRate": -0.000007, + "timestamp": 1630468800000, + "datetime": "2021-09-01T04:00:00.000Z" + }, + { + "symbol": "ADA/USDT", + "fundingRate": 0.000003, + "timestamp": 1630472400000, + "datetime": "2021-09-01T05:00:00.000Z" + }, + { + "symbol": "ADA/USDT", + "fundingRate": 0.000019, + "timestamp": 1630476000000, + "datetime": "2021-09-01T06:00:00.000Z" + }, + { + "symbol": "ADA/USDT", + "fundingRate": 0.000003, + "timestamp": 1630479600000, + "datetime": "2021-09-01T07:00:00.000Z" + }, + { + "symbol": "ADA/USDT", + "fundingRate": -0.000003, + "timestamp": 1630483200000, + "datetime": "2021-09-01T08:00:00.000Z" + }, + { + "symbol": "ADA/USDT", + "fundingRate": 0, + "timestamp": 1630486800000, + "datetime": "2021-09-01T09:00:00.000Z" + }, + { + "symbol": "ADA/USDT", + "fundingRate": 0.000013, + "timestamp": 1630490400000, + "datetime": "2021-09-01T10:00:00.000Z" + }, + { + "symbol": "ADA/USDT", + "fundingRate": 0.000077, + "timestamp": 1630494000000, + "datetime": "2021-09-01T11:00:00.000Z" + }, + { + "symbol": "ADA/USDT", + "fundingRate": 0.000072, + "timestamp": 1630497600000, + "datetime": "2021-09-01T12:00:00.000Z" + }, + { + "symbol": "ADA/USDT", + "fundingRate": 0.000097, + "timestamp": 1630501200000, + "datetime": "2021-09-01T13:00:00.000Z" + }, + ] + + +@pytest.fixture(scope='function') +def funding_rate_history_octohourly(): + return [ + { + "symbol": "ADA/USDT", + "fundingRate": -0.000008, + "timestamp": 1630454400000, + "datetime": "2021-09-01T00:00:00.000Z" + }, + { + "symbol": "ADA/USDT", + "fundingRate": -0.000003, + "timestamp": 1630483200000, + "datetime": "2021-09-01T08:00:00.000Z" + } + ] + + +@pytest.fixture(scope='function') +def leverage_tiers(): + return { + "1000SHIB/USDT": [ + { + 'min': 0, + 'max': 50000, + 'mmr': 0.01, + 'lev': 50, + 'maintAmt': 0.0 + }, + { + 'min': 50000, + 'max': 150000, + 'mmr': 0.025, + 'lev': 20, + 'maintAmt': 750.0 + }, + { + 'min': 150000, + 'max': 250000, + 'mmr': 0.05, + 'lev': 10, + 'maintAmt': 4500.0 + }, + { + 'min': 250000, + 'max': 500000, + 'mmr': 0.1, + 'lev': 5, + 'maintAmt': 17000.0 + }, + { + 'min': 500000, + 'max': 1000000, + 'mmr': 0.125, + 'lev': 4, + 'maintAmt': 29500.0 + }, + { + 'min': 1000000, + 'max': 2000000, + 'mmr': 0.25, + 'lev': 2, + 'maintAmt': 154500.0 + }, + { + 'min': 2000000, + 'max': 30000000, + 'mmr': 0.5, + 'lev': 1, + 'maintAmt': 654500.0 + }, + ], + "1INCH/USDT": [ + { + 'min': 0, + 'max': 5000, + 'mmr': 0.012, + 'lev': 50, + 'maintAmt': 0.0 + }, + { + 'min': 5000, + 'max': 25000, + 'mmr': 0.025, + 'lev': 20, + 'maintAmt': 65.0 + }, + { + 'min': 25000, + 'max': 100000, + 'mmr': 0.05, + 'lev': 10, + 'maintAmt': 690.0 + }, + { + 'min': 100000, + 'max': 250000, + 'mmr': 0.1, + 'lev': 5, + 'maintAmt': 5690.0 + }, + { + 'min': 250000, + 'max': 1000000, + 'mmr': 0.125, + 'lev': 2, + 'maintAmt': 11940.0 + }, + { + 'min': 1000000, + 'max': 100000000, + 'mmr': 0.5, + 'lev': 1, + 'maintAmt': 386940.0 + }, + ], + "AAVE/USDT": [ + { + 'min': 0, + 'max': 50000, + 'mmr': 0.01, + 'lev': 50, + 'maintAmt': 0.0 + }, + { + 'min': 50000, + 'max': 250000, + 'mmr': 0.02, + 'lev': 25, + 'maintAmt': 500.0 + }, + { + 'min': 250000, + 'max': 1000000, + 'mmr': 0.05, + 'lev': 10, + 'maintAmt': 8000.0 + }, + { + 'min': 1000000, + 'max': 2000000, + 'mmr': 0.1, + 'lev': 5, + 'maintAmt': 58000.0 + }, + { + 'min': 2000000, + 'max': 5000000, + 'mmr': 0.125, + 'lev': 4, + 'maintAmt': 108000.0 + }, + { + 'min': 5000000, + 'max': 10000000, + 'mmr': 0.1665, + 'lev': 3, + 'maintAmt': 315500.0 + }, + { + 'min': 10000000, + 'max': 20000000, + 'mmr': 0.25, + 'lev': 2, + 'maintAmt': 1150500.0 + }, + { + "min": 20000000, + "max": 50000000, + "mmr": 0.5, + "lev": 1, + "maintAmt": 6150500.0 + } + ], + "ADA/BUSD": [ + { + "min": 0, + "max": 100000, + "mmr": 0.025, + "lev": 20, + "maintAmt": 0.0 + }, + { + "min": 100000, + "max": 500000, + "mmr": 0.05, + "lev": 10, + "maintAmt": 2500.0 + }, + { + "min": 500000, + "max": 1000000, + "mmr": 0.1, + "lev": 5, + "maintAmt": 27500.0 + }, + { + "min": 1000000, + "max": 2000000, + "mmr": 0.15, + "lev": 3, + "maintAmt": 77500.0 + }, + { + "min": 2000000, + "max": 5000000, + "mmr": 0.25, + "lev": 2, + "maintAmt": 277500.0 + }, + { + "min": 5000000, + "max": 30000000, + "mmr": 0.5, + "lev": 1, + "maintAmt": 1527500.0 + }, + ], + 'BNB/BUSD': [ + { + "min": 0, # stake(before leverage) = 0 + "max": 100000, # max stake(before leverage) = 5000 + "mmr": 0.025, + "lev": 20, + "maintAmt": 0.0 + }, + { + "min": 100000, # stake = 10000.0 + "max": 500000, # max_stake = 50000.0 + "mmr": 0.05, + "lev": 10, + "maintAmt": 2500.0 + }, + { + "min": 500000, # stake = 100000.0 + "max": 1000000, # max_stake = 200000.0 + "mmr": 0.1, + "lev": 5, + "maintAmt": 27500.0 + }, + { + "min": 1000000, # stake = 333333.3333333333 + "max": 2000000, # max_stake = 666666.6666666666 + "mmr": 0.15, + "lev": 3, + "maintAmt": 77500.0 + }, + { + "min": 2000000, # stake = 1000000.0 + "max": 5000000, # max_stake = 2500000.0 + "mmr": 0.25, + "lev": 2, + "maintAmt": 277500.0 + }, + { + "min": 5000000, # stake = 5000000.0 + "max": 30000000, # max_stake = 30000000.0 + "mmr": 0.5, + "lev": 1, + "maintAmt": 1527500.0 + } + ], + 'BNB/USDT': [ + { + "min": 0, # stake = 0.0 + "max": 10000, # max_stake = 133.33333333333334 + "mmr": 0.0065, + "lev": 75, + "maintAmt": 0.0 + }, + { + "min": 10000, # stake = 200.0 + "max": 50000, # max_stake = 1000.0 + "mmr": 0.01, + "lev": 50, + "maintAmt": 35.0 + }, + { + "min": 50000, # stake = 2000.0 + "max": 250000, # max_stake = 10000.0 + "mmr": 0.02, + "lev": 25, + "maintAmt": 535.0 + }, + { + "min": 250000, # stake = 25000.0 + "max": 1000000, # max_stake = 100000.0 + "mmr": 0.05, + "lev": 10, + "maintAmt": 8035.0 + }, + { + "min": 1000000, # stake = 200000.0 + "max": 2000000, # max_stake = 400000.0 + "mmr": 0.1, + "lev": 5, + "maintAmt": 58035.0 + }, + { + "min": 2000000, # stake = 500000.0 + "max": 5000000, # max_stake = 1250000.0 + "mmr": 0.125, + "lev": 4, + "maintAmt": 108035.0 + }, + { + "min": 5000000, # stake = 1666666.6666666667 + "max": 10000000, # max_stake = 3333333.3333333335 + "mmr": 0.15, + "lev": 3, + "maintAmt": 233035.0 + }, + { + "min": 10000000, # stake = 5000000.0 + "max": 20000000, # max_stake = 10000000.0 + "mmr": 0.25, + "lev": 2, + "maintAmt": 1233035.0 + }, + { + "min": 20000000, # stake = 20000000.0 + "max": 50000000, # max_stake = 50000000.0 + "mmr": 0.5, + "lev": 1, + "maintAmt": 6233035.0 + }, + ], + 'BTC/USDT': [ + { + "min": 0, # stake = 0.0 + "max": 50000, # max_stake = 400.0 + "mmr": 0.004, + "lev": 125, + "maintAmt": 0.0 + }, + { + "min": 50000, # stake = 500.0 + "max": 250000, # max_stake = 2500.0 + "mmr": 0.005, + "lev": 100, + "maintAmt": 50.0 + }, + { + "min": 250000, # stake = 5000.0 + "max": 1000000, # max_stake = 20000.0 + "mmr": 0.01, + "lev": 50, + "maintAmt": 1300.0 + }, + { + "min": 1000000, # stake = 50000.0 + "max": 7500000, # max_stake = 375000.0 + "mmr": 0.025, + "lev": 20, + "maintAmt": 16300.0 + }, + { + "min": 7500000, # stake = 750000.0 + "max": 40000000, # max_stake = 4000000.0 + "mmr": 0.05, + "lev": 10, + "maintAmt": 203800.0 + }, + { + "min": 40000000, # stake = 8000000.0 + "max": 100000000, # max_stake = 20000000.0 + "mmr": 0.1, + "lev": 5, + "maintAmt": 2203800.0 + }, + { + "min": 100000000, # stake = 25000000.0 + "max": 200000000, # max_stake = 50000000.0 + "mmr": 0.125, + "lev": 4, + "maintAmt": 4703800.0 + }, + { + "min": 200000000, # stake = 66666666.666666664 + "max": 400000000, # max_stake = 133333333.33333333 + "mmr": 0.15, + "lev": 3, + "maintAmt": 9703800.0 + }, + { + "min": 400000000, # stake = 200000000.0 + "max": 600000000, # max_stake = 300000000.0 + "mmr": 0.25, + "lev": 2, + "maintAmt": 4.97038E7 + }, + { + "min": 600000000, # stake = 600000000.0 + "max": 1000000000, # max_stake = 1000000000.0 + "mmr": 0.5, + "lev": 1, + "maintAmt": 1.997038E8 + }, + ], + "ZEC/USDT": [ + { + 'min': 0, + 'max': 50000, + 'mmr': 0.01, + 'lev': 50, + 'maintAmt': 0.0 + }, + { + 'min': 50000, + 'max': 150000, + 'mmr': 0.025, + 'lev': 20, + 'maintAmt': 750.0 + }, + { + 'min': 150000, + 'max': 250000, + 'mmr': 0.05, + 'lev': 10, + 'maintAmt': 4500.0 + }, + { + 'min': 250000, + 'max': 500000, + 'mmr': 0.1, + 'lev': 5, + 'maintAmt': 17000.0 + }, + { + 'min': 500000, + 'max': 1000000, + 'mmr': 0.125, + 'lev': 4, + 'maintAmt': 29500.0 + }, + { + 'min': 1000000, + 'max': 2000000, + 'mmr': 0.25, + 'lev': 2, + 'maintAmt': 154500.0 + }, + { + 'min': 2000000, + 'max': 30000000, + 'mmr': 0.5, + 'lev': 1, + 'maintAmt': 654500.0 + }, + ] + } diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index 70a2a99a2..3e3ba9495 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -6,12 +6,24 @@ from freqtrade.persistence.models import Order, Trade MOCK_TRADE_COUNT = 6 -def mock_order_1(): +def enter_side(is_short: bool): + return "sell" if is_short else "buy" + + +def exit_side(is_short: bool): + return "buy" if is_short else "sell" + + +def direc(is_short: bool): + return "short" if is_short else "long" + + +def mock_order_1(is_short: bool): return { - 'id': '1234', + 'id': f'1234_{direc(is_short)}', 'symbol': 'ETH/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.123, 'average': 0.123, @@ -21,7 +33,7 @@ def mock_order_1(): } -def mock_trade_1(fee): +def mock_trade_1(fee, is_short: bool): trade = Trade( pair='ETH/BTC', stake_amount=0.001, @@ -33,21 +45,22 @@ def mock_trade_1(fee): open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=17), open_rate=0.123, exchange='binance', - open_order_id='dry_run_buy_12345', - strategy='StrategyTestV2', + open_order_id=f'dry_run_buy_{direc(is_short)}_12345', + strategy='StrategyTestV3', timeframe=5, + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_1(), 'ETH/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_1(is_short), 'ETH/BTC', enter_side(is_short)) trade.orders.append(o) return trade -def mock_order_2(): +def mock_order_2(is_short: bool): return { - 'id': '1235', + 'id': f'1235_{direc(is_short)}', 'symbol': 'ETC/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.123, 'amount': 123.0, @@ -56,12 +69,12 @@ def mock_order_2(): } -def mock_order_2_sell(): +def mock_order_2_sell(is_short: bool): return { - 'id': '12366', + 'id': f'12366_{direc(is_short)}', 'symbol': 'ETC/BTC', 'status': 'closed', - 'side': 'sell', + 'side': exit_side(is_short), 'type': 'limit', 'price': 0.128, 'amount': 123.0, @@ -70,7 +83,7 @@ def mock_order_2_sell(): } -def mock_trade_2(fee): +def mock_trade_2(fee, is_short: bool): """ Closed trade... """ @@ -83,31 +96,32 @@ def mock_trade_2(fee): fee_close=fee.return_value, open_rate=0.123, close_rate=0.128, - close_profit=0.005, - close_profit_abs=0.000584127, + close_profit=-0.005 if is_short else 0.005, + close_profit_abs=-0.005584127 if is_short else 0.000584127, exchange='binance', is_open=False, - open_order_id='dry_run_sell_12345', - strategy='StrategyTestV2', + open_order_id=f'dry_run_sell_{direc(is_short)}_12345', + strategy='StrategyTestV3', timeframe=5, - buy_tag='TEST1', + enter_tag='TEST1', sell_reason='sell_signal', open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2), + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_2(), 'ETC/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_2(is_short), 'ETC/BTC', enter_side(is_short)) trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_2_sell(), 'ETC/BTC', 'sell') + o = Order.parse_from_ccxt_object(mock_order_2_sell(is_short), 'ETC/BTC', exit_side(is_short)) trade.orders.append(o) return trade -def mock_order_3(): +def mock_order_3(is_short: bool): return { - 'id': '41231a12a', + 'id': f'41231a12a_{direc(is_short)}', 'symbol': 'XRP/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.05, 'amount': 123.0, @@ -116,12 +130,12 @@ def mock_order_3(): } -def mock_order_3_sell(): +def mock_order_3_sell(is_short: bool): return { - 'id': '41231a666a', + 'id': f'41231a666a_{direc(is_short)}', 'symbol': 'XRP/BTC', 'status': 'closed', - 'side': 'sell', + 'side': exit_side(is_short), 'type': 'stop_loss_limit', 'price': 0.06, 'average': 0.06, @@ -131,7 +145,7 @@ def mock_order_3_sell(): } -def mock_trade_3(fee): +def mock_trade_3(fee, is_short: bool): """ Closed trade """ @@ -144,29 +158,30 @@ def mock_trade_3(fee): fee_close=fee.return_value, open_rate=0.05, close_rate=0.06, - close_profit=0.01, - close_profit_abs=0.000155, + close_profit=-0.01 if is_short else 0.01, + close_profit_abs=-0.001155 if is_short else 0.000155, exchange='binance', is_open=False, - strategy='StrategyTestV2', + strategy='StrategyTestV3', timeframe=5, sell_reason='roi', open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), close_date=datetime.now(tz=timezone.utc), + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_3(), 'XRP/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_3(is_short), 'XRP/BTC', enter_side(is_short)) trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_3_sell(), 'XRP/BTC', 'sell') + o = Order.parse_from_ccxt_object(mock_order_3_sell(is_short), 'XRP/BTC', exit_side(is_short)) trade.orders.append(o) return trade -def mock_order_4(): +def mock_order_4(is_short: bool): return { - 'id': 'prod_buy_12345', + 'id': f'prod_buy_{direc(is_short)}_12345', 'symbol': 'ETC/BTC', 'status': 'open', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.123, 'amount': 123.0, @@ -175,7 +190,7 @@ def mock_order_4(): } -def mock_trade_4(fee): +def mock_trade_4(fee, is_short: bool): """ Simulate prod entry """ @@ -190,21 +205,22 @@ def mock_trade_4(fee): is_open=True, open_rate=0.123, exchange='binance', - open_order_id='prod_buy_12345', - strategy='StrategyTestV2', + open_order_id=f'prod_buy_{direc(is_short)}_12345', + strategy='StrategyTestV3', timeframe=5, + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_4(), 'ETC/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_4(is_short), 'ETC/BTC', enter_side(is_short)) trade.orders.append(o) return trade -def mock_order_5(): +def mock_order_5(is_short: bool): return { - 'id': 'prod_buy_3455', + 'id': f'prod_buy_{direc(is_short)}_3455', 'symbol': 'XRP/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.123, 'amount': 123.0, @@ -213,12 +229,12 @@ def mock_order_5(): } -def mock_order_5_stoploss(): +def mock_order_5_stoploss(is_short: bool): return { - 'id': 'prod_stoploss_3455', + 'id': f'prod_stoploss_{direc(is_short)}_3455', 'symbol': 'XRP/BTC', 'status': 'open', - 'side': 'sell', + 'side': exit_side(is_short), 'type': 'stop_loss_limit', 'price': 0.123, 'amount': 123.0, @@ -227,7 +243,7 @@ def mock_order_5_stoploss(): } -def mock_trade_5(fee): +def mock_trade_5(fee, is_short: bool): """ Simulate prod entry with stoploss """ @@ -243,23 +259,24 @@ def mock_trade_5(fee): open_rate=0.123, exchange='binance', strategy='SampleStrategy', - buy_tag='TEST1', - stoploss_order_id='prod_stoploss_3455', + enter_tag='TEST1', + stoploss_order_id=f'prod_stoploss_{direc(is_short)}_3455', timeframe=5, + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_5(), 'XRP/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_5(is_short), 'XRP/BTC', enter_side(is_short)) trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_5_stoploss(), 'XRP/BTC', 'stoploss') + o = Order.parse_from_ccxt_object(mock_order_5_stoploss(is_short), 'XRP/BTC', 'stoploss') trade.orders.append(o) return trade -def mock_order_6(): +def mock_order_6(is_short: bool): return { - 'id': 'prod_buy_6', + 'id': f'prod_buy_{direc(is_short)}_6', 'symbol': 'LTC/BTC', 'status': 'closed', - 'side': 'buy', + 'side': enter_side(is_short), 'type': 'limit', 'price': 0.15, 'amount': 2.0, @@ -268,23 +285,23 @@ def mock_order_6(): } -def mock_order_6_sell(): +def mock_order_6_sell(is_short: bool): return { - 'id': 'prod_sell_6', + 'id': f'prod_sell_{direc(is_short)}_6', 'symbol': 'LTC/BTC', 'status': 'open', - 'side': 'sell', + 'side': exit_side(is_short), 'type': 'limit', - 'price': 0.20, + 'price': 0.15 if is_short else 0.20, 'amount': 2.0, 'filled': 0.0, 'remaining': 2.0, } -def mock_trade_6(fee): +def mock_trade_6(fee, is_short: bool): """ - Simulate prod entry with open sell order + Simulate prod entry with open exit order """ trade = Trade( pair='LTC/BTC', @@ -298,12 +315,188 @@ def mock_trade_6(fee): open_rate=0.15, exchange='binance', strategy='SampleStrategy', - buy_tag='TEST2', - open_order_id="prod_sell_6", + enter_tag='TEST2', + open_order_id=f"prod_sell_{direc(is_short)}_6", timeframe=5, + is_short=is_short ) - o = Order.parse_from_ccxt_object(mock_order_6(), 'LTC/BTC', 'buy') + o = Order.parse_from_ccxt_object(mock_order_6(is_short), 'LTC/BTC', enter_side(is_short)) trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_6_sell(), 'LTC/BTC', 'sell') + o = Order.parse_from_ccxt_object(mock_order_6_sell(is_short), 'LTC/BTC', exit_side(is_short)) + trade.orders.append(o) + return trade + + +def short_order(): + return { + 'id': '1236', + 'symbol': 'ETC/BTC', + 'status': 'closed', + 'side': 'sell', + 'type': 'limit', + 'price': 0.123, + 'amount': 123.0, + 'filled': 123.0, + 'remaining': 0.0, + } + + +def exit_short_order(): + return { + 'id': '12367', + 'symbol': 'ETC/BTC', + 'status': 'closed', + 'side': 'buy', + 'type': 'limit', + 'price': 0.128, + 'amount': 123.0, + 'filled': 123.0, + 'remaining': 0.0, + } + + +def short_trade(fee): + """ + 10 minute short limit trade on binance + + Short trade + fee: 0.25% base + interest_rate: 0.05% per day + open_rate: 0.123 base + close_rate: 0.128 base + amount: 123.0 crypto + stake_amount: 15.129 base + borrowed: 123.0 crypto + time-periods: 10 minutes(rounds up to 1/24 time-period of 1 day) + interest: borrowed * interest_rate * time-periods + = 123.0 * 0.0005 * 1/24 = 0.0025625 crypto + open_value: (amount * open_rate) - (amount * open_rate * fee) + = (123 * 0.123) - (123 * 0.123 * 0.0025) + = 15.091177499999999 + amount_closed: amount + interest = 123 + 0.0025625 = 123.0025625 + close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + = (123.0025625 * 0.128) + (123.0025625 * 0.128 * 0.0025) + = 15.78368882 + total_profit = open_value - close_value + = 15.091177499999999 - 15.78368882 + = -0.6925113200000013 + total_profit_percentage = total_profit / stake_amount + = -0.6925113200000013 / 15.129 + = -0.04577376693766946 + + """ + trade = Trade( + pair='ETC/BTC', + stake_amount=15.129, + amount=123.0, + amount_requested=123.0, + fee_open=fee.return_value, + fee_close=fee.return_value, + open_rate=0.123, + # close_rate=0.128, + # close_profit=-0.04577376693766946, + # close_profit_abs=-0.6925113200000013, + exchange='binance', + is_open=True, + open_order_id='dry_run_exit_short_12345', + strategy='DefaultStrategy', + timeframe=5, + sell_reason='sell_signal', + open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), + # close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2), + is_short=True + ) + o = Order.parse_from_ccxt_object(short_order(), 'ETC/BTC', 'sell') + trade.orders.append(o) + o = Order.parse_from_ccxt_object(exit_short_order(), 'ETC/BTC', 'sell') + trade.orders.append(o) + return trade + + +def leverage_order(): + return { + 'id': '1237', + 'symbol': 'DOGE/BTC', + 'status': 'closed', + 'side': 'buy', + 'type': 'limit', + 'price': 0.123, + 'amount': 123.0, + 'filled': 123.0, + 'remaining': 0.0, + 'leverage': 5.0 + } + + +def leverage_order_sell(): + return { + 'id': '12368', + 'symbol': 'DOGE/BTC', + 'status': 'closed', + 'side': 'sell', + 'type': 'limit', + 'price': 0.128, + 'amount': 123.0, + 'filled': 123.0, + 'remaining': 0.0, + 'leverage': 5.0 + } + + +def leverage_trade(fee): + """ + 5 hour short limit trade on kraken + + Short trade + fee: 0.25% base + interest_rate: 0.05% per day + open_rate: 0.123 base + close_rate: 0.128 base + amount: 615 crypto + stake_amount: 15.129 base + borrowed: 60.516 base + leverage: 5 + hours: 5 + interest: borrowed * interest_rate * ceil(1 + hours/4) + = 60.516 * 0.0005 * ceil(1 + 5/4) = 0.090774 base + open_value: (amount * open_rate) + (amount * open_rate * fee) + = (615.0 * 0.123) + (615.0 * 0.123 * 0.0025) + = 75.83411249999999 + + close_value: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest + = (615.0 * 0.128) - (615.0 * 0.128 * 0.0025) - 0.090774 + = 78.432426 + total_profit = close_value - open_value + = 78.432426 - 75.83411249999999 + = 2.5983135000000175 + total_profit_percentage = ((close_value/open_value)-1) * leverage + = ((78.432426/75.83411249999999)-1) * 5 + = 0.1713156134055116 + """ + trade = Trade( + pair='DOGE/BTC', + stake_amount=15.129, + amount=615.0, + leverage=5.0, + amount_requested=615.0, + fee_open=fee.return_value, + fee_close=fee.return_value, + open_rate=0.123, + close_rate=0.128, + close_profit=0.1713156134055116, + close_profit_abs=2.5983135000000175, + exchange='kraken', + is_open=False, + open_order_id='dry_run_leverage_buy_12368', + strategy='DefaultStrategy', + timeframe=5, + sell_reason='sell_signal', + open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=300), + close_date=datetime.now(tz=timezone.utc), + interest_rate=0.0005 + ) + o = Order.parse_from_ccxt_object(leverage_order(), 'DOGE/BTC', 'sell') + trade.orders.append(o) + o = Order.parse_from_ccxt_object(leverage_order_sell(), 'DOGE/BTC', 'sell') trade.orders.append(o) return trade diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 341645854..f4275edd9 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -17,7 +17,7 @@ from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelis load_trades_from_db) from freqtrade.data.history import load_data, load_pair_history from freqtrade.exceptions import OperationalException -from tests.conftest import create_mock_trades +from tests.conftest import CURRENT_TEST_STRATEGY, create_mock_trades from tests.conftest_trades import MOCK_TRADE_COUNT @@ -108,7 +108,8 @@ def test_load_backtest_data_multi(testdatadir): for strategy in ('StrategyTestV2', 'TestStrategy'): bt_data = load_backtest_data(filename, strategy=strategy) assert isinstance(bt_data, DataFrame) - assert set(bt_data.columns) == set(BT_DATA_COLUMNS + ['close_timestamp', 'open_timestamp']) + assert set(bt_data.columns) == set( + BT_DATA_COLUMNS + ['close_timestamp', 'open_timestamp']) assert len(bt_data) == 179 # Test loading from string (must yield same result) @@ -123,9 +124,10 @@ def test_load_backtest_data_multi(testdatadir): @pytest.mark.usefixtures("init_persistence") -def test_load_trades_from_db(default_conf, fee, mocker): +@pytest.mark.parametrize('is_short', [False, True]) +def test_load_trades_from_db(default_conf, fee, is_short, mocker): - create_mock_trades(fee) + create_mock_trades(fee, is_short) # remove init so it does not init again init_mock = mocker.patch('freqtrade.data.btanalysis.init_db', MagicMock()) @@ -140,7 +142,7 @@ def test_load_trades_from_db(default_conf, fee, mocker): for col in BT_DATA_COLUMNS: if col not in ['index', 'open_at_end']: assert col in trades.columns - trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='StrategyTestV2') + trades = load_trades_from_db(db_url=default_conf['db_url'], strategy=CURRENT_TEST_STRATEGY) assert len(trades) == 4 trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy') assert len(trades) == 0 @@ -198,7 +200,7 @@ def test_load_trades(default_conf, mocker): db_url=default_conf.get('db_url'), exportfilename=default_conf.get('exportfilename'), no_trades=False, - strategy="StrategyTestV2", + strategy=CURRENT_TEST_STRATEGY, ) assert db_mock.call_count == 1 diff --git a/tests/data/test_converter.py b/tests/data/test_converter.py index 6c95a9f18..c6b0059a2 100644 --- a/tests/data/test_converter.py +++ b/tests/data/test_converter.py @@ -12,6 +12,8 @@ from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_forma trades_to_ohlcv, trim_dataframe) from freqtrade.data.history import (get_timerange, load_data, load_pair_history, validate_backtest_data) +from freqtrade.data.history.idatahandler import IDataHandler +from freqtrade.enums import CandleType from tests.conftest import log_has, log_has_re from tests.data.test_history import _clean_test_file @@ -75,7 +77,8 @@ def test_ohlcv_fill_up_missing_data(testdatadir, caplog): def test_ohlcv_fill_up_missing_data2(caplog): timeframe = '5m' - ticks = [[ + ticks = [ + [ 1511686200000, # 8:50:00 8.794e-05, # open 8.948e-05, # high @@ -106,7 +109,7 @@ def test_ohlcv_fill_up_missing_data2(caplog): 8.895e-05, 8.817e-05, 123551 - ] + ] ] # Generate test-data without filling missing @@ -133,7 +136,8 @@ def test_ohlcv_fill_up_missing_data2(caplog): def test_ohlcv_drop_incomplete(caplog): timeframe = '1d' - ticks = [[ + ticks = [ + [ 1559750400000, # 2019-06-04 8.794e-05, # open 8.948e-05, # high @@ -164,7 +168,7 @@ def test_ohlcv_drop_incomplete(caplog): 8.895e-05, 8.817e-05, 123551 - ] + ] ] caplog.set_level(logging.DEBUG) data = ohlcv_to_dataframe(ticks, timeframe, pair="UNITTEST/BTC", @@ -287,42 +291,56 @@ def test_convert_trades_format(default_conf, testdatadir, tmpdir): file['new'].unlink() -def test_convert_ohlcv_format(default_conf, testdatadir, tmpdir): +@pytest.mark.parametrize('file_base,candletype', [ + (['XRP_ETH-5m', 'XRP_ETH-1m'], CandleType.SPOT), + (['UNITTEST_USDT-1h-mark', 'XRP_USDT-1h-mark'], CandleType.MARK), + (['XRP_USDT-1h-futures'], CandleType.FUTURES), +]) +def test_convert_ohlcv_format(default_conf, testdatadir, tmpdir, file_base, candletype): tmpdir1 = Path(tmpdir) + prependix = '' if candletype == CandleType.SPOT else 'futures/' + files_orig = [] + files_temp = [] + files_new = [] + for file in file_base: + file_orig = testdatadir / f"{prependix}{file}.json" + file_temp = tmpdir1 / f"{prependix}{file}.json" + file_new = tmpdir1 / f"{prependix}{file}.json.gz" + IDataHandler.create_dir_if_needed(file_temp) + copyfile(file_orig, file_temp) - file1_orig = testdatadir / "XRP_ETH-5m.json" - file1 = tmpdir1 / "XRP_ETH-5m.json" - file1_new = tmpdir1 / "XRP_ETH-5m.json.gz" - file2_orig = testdatadir / "XRP_ETH-1m.json" - file2 = tmpdir1 / "XRP_ETH-1m.json" - file2_new = tmpdir1 / "XRP_ETH-1m.json.gz" - - copyfile(file1_orig, file1) - copyfile(file2_orig, file2) + files_orig.append(file_orig) + files_temp.append(file_temp) + files_new.append(file_new) default_conf['datadir'] = tmpdir1 - default_conf['pairs'] = ['XRP_ETH'] - default_conf['timeframes'] = ['1m', '5m'] + default_conf['pairs'] = ['XRP_ETH', 'XRP_USDT', 'UNITTEST_USDT'] + default_conf['timeframes'] = ['1m', '5m', '1h'] - assert not file1_new.exists() - assert not file2_new.exists() + assert not file_new.exists() - convert_ohlcv_format(default_conf, convert_from='json', - convert_to='jsongz', erase=False) - - assert file1_new.exists() - assert file2_new.exists() - assert file1.exists() - assert file2.exists() + convert_ohlcv_format( + default_conf, + convert_from='json', + convert_to='jsongz', + erase=False, + candle_type=candletype + ) + for file in (files_temp + files_new): + assert file.exists() # Remove original files - file1.unlink() - file2.unlink() + for file in (files_temp): + file.unlink() # Convert back - convert_ohlcv_format(default_conf, convert_from='jsongz', - convert_to='json', erase=True) - - assert file1.exists() - assert file2.exists() - assert not file1_new.exists() - assert not file2_new.exists() + convert_ohlcv_format( + default_conf, + convert_from='jsongz', + convert_to='json', + erase=True, + candle_type=candletype + ) + for file in (files_temp): + assert file.exists() + for file in (files_new): + assert not file.exists() diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index 0f42068c1..93f82de5d 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -5,40 +5,49 @@ import pytest from pandas import DataFrame from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import RunMode +from freqtrade.enums import CandleType, RunMode from freqtrade.exceptions import ExchangeError, OperationalException from freqtrade.plugins.pairlistmanager import PairListManager from tests.conftest import get_patched_exchange -def test_ohlcv(mocker, default_conf, ohlcv_history): +@pytest.mark.parametrize('candle_type', [ + 'mark', + '', +]) +def test_dp_ohlcv(mocker, default_conf, ohlcv_history, candle_type): default_conf["runmode"] = RunMode.DRY_RUN timeframe = default_conf["timeframe"] exchange = get_patched_exchange(mocker, default_conf) - exchange._klines[("XRP/BTC", timeframe)] = ohlcv_history - exchange._klines[("UNITTEST/BTC", timeframe)] = ohlcv_history + candletype = CandleType.from_string(candle_type) + exchange._klines[("XRP/BTC", timeframe, candletype)] = ohlcv_history + exchange._klines[("UNITTEST/BTC", timeframe, candletype)] = ohlcv_history dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.DRY_RUN - assert ohlcv_history.equals(dp.ohlcv("UNITTEST/BTC", timeframe)) - assert isinstance(dp.ohlcv("UNITTEST/BTC", timeframe), DataFrame) - assert dp.ohlcv("UNITTEST/BTC", timeframe) is not ohlcv_history - assert dp.ohlcv("UNITTEST/BTC", timeframe, copy=False) is ohlcv_history - assert not dp.ohlcv("UNITTEST/BTC", timeframe).empty - assert dp.ohlcv("NONESENSE/AAA", timeframe).empty + assert ohlcv_history.equals(dp.ohlcv("UNITTEST/BTC", timeframe, candle_type=candletype)) + assert isinstance(dp.ohlcv("UNITTEST/BTC", timeframe, candle_type=candletype), DataFrame) + assert dp.ohlcv("UNITTEST/BTC", timeframe, candle_type=candletype) is not ohlcv_history + assert dp.ohlcv("UNITTEST/BTC", timeframe, copy=False, candle_type=candletype) is ohlcv_history + assert not dp.ohlcv("UNITTEST/BTC", timeframe, candle_type=candletype).empty + assert dp.ohlcv("NONESENSE/AAA", timeframe, candle_type=candletype).empty # Test with and without parameter - assert dp.ohlcv("UNITTEST/BTC", timeframe).equals(dp.ohlcv("UNITTEST/BTC")) + assert dp.ohlcv( + "UNITTEST/BTC", + timeframe, + candle_type=candletype + ).equals(dp.ohlcv("UNITTEST/BTC", candle_type=candle_type)) default_conf["runmode"] = RunMode.LIVE dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.LIVE - assert isinstance(dp.ohlcv("UNITTEST/BTC", timeframe), DataFrame) + assert isinstance(dp.ohlcv("UNITTEST/BTC", timeframe, candle_type=candle_type), DataFrame) default_conf["runmode"] = RunMode.BACKTEST dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.BACKTEST - assert dp.ohlcv("UNITTEST/BTC", timeframe).empty + assert dp.ohlcv("UNITTEST/BTC", timeframe, candle_type=candle_type).empty def test_historic_ohlcv(mocker, default_conf, ohlcv_history): @@ -77,37 +86,50 @@ def test_historic_ohlcv_dataformat(mocker, default_conf, ohlcv_history): jsonloadmock.assert_not_called() -def test_get_pair_dataframe(mocker, default_conf, ohlcv_history): +@pytest.mark.parametrize('candle_type', [ + 'mark', + 'futures', + '', +]) +def test_get_pair_dataframe(mocker, default_conf, ohlcv_history, candle_type): default_conf["runmode"] = RunMode.DRY_RUN timeframe = default_conf["timeframe"] exchange = get_patched_exchange(mocker, default_conf) - exchange._klines[("XRP/BTC", timeframe)] = ohlcv_history - exchange._klines[("UNITTEST/BTC", timeframe)] = ohlcv_history + candletype = CandleType.from_string(candle_type) + exchange._klines[("XRP/BTC", timeframe, candletype)] = ohlcv_history + exchange._klines[("UNITTEST/BTC", timeframe, candletype)] = ohlcv_history dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.DRY_RUN - assert ohlcv_history.equals(dp.get_pair_dataframe("UNITTEST/BTC", timeframe)) - assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", timeframe), DataFrame) - assert dp.get_pair_dataframe("UNITTEST/BTC", timeframe) is not ohlcv_history - assert not dp.get_pair_dataframe("UNITTEST/BTC", timeframe).empty - assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe).empty + assert ohlcv_history.equals(dp.get_pair_dataframe( + "UNITTEST/BTC", timeframe, candle_type=candle_type)) + assert ohlcv_history.equals(dp.get_pair_dataframe( + "UNITTEST/BTC", timeframe, candle_type=candletype)) + assert isinstance(dp.get_pair_dataframe( + "UNITTEST/BTC", timeframe, candle_type=candle_type), DataFrame) + assert dp.get_pair_dataframe("UNITTEST/BTC", timeframe, + candle_type=candle_type) is not ohlcv_history + assert not dp.get_pair_dataframe("UNITTEST/BTC", timeframe, candle_type=candle_type).empty + assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe, candle_type=candle_type).empty # Test with and without parameter - assert dp.get_pair_dataframe("UNITTEST/BTC", timeframe)\ - .equals(dp.get_pair_dataframe("UNITTEST/BTC")) + assert dp.get_pair_dataframe("UNITTEST/BTC", timeframe, candle_type=candle_type)\ + .equals(dp.get_pair_dataframe("UNITTEST/BTC", candle_type=candle_type)) default_conf["runmode"] = RunMode.LIVE dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.LIVE - assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", timeframe), DataFrame) - assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe).empty + assert isinstance(dp.get_pair_dataframe( + "UNITTEST/BTC", timeframe, candle_type=candle_type), DataFrame) + assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe, candle_type=candle_type).empty historymock = MagicMock(return_value=ohlcv_history) mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock) default_conf["runmode"] = RunMode.BACKTEST dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.BACKTEST - assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", timeframe), DataFrame) + assert isinstance(dp.get_pair_dataframe( + "UNITTEST/BTC", timeframe, candle_type=candle_type), DataFrame) # assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe).empty @@ -230,8 +252,8 @@ def test_get_analyzed_dataframe(mocker, default_conf, ohlcv_history): exchange = get_patched_exchange(mocker, default_conf) dp = DataProvider(default_conf, exchange) - dp._set_cached_df("XRP/BTC", timeframe, ohlcv_history) - dp._set_cached_df("UNITTEST/BTC", timeframe, ohlcv_history) + dp._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT) + dp._set_cached_df("UNITTEST/BTC", timeframe, ohlcv_history, CandleType.SPOT) assert dp.runmode == RunMode.DRY_RUN dataframe, time = dp.get_analyzed_dataframe("UNITTEST/BTC", timeframe) @@ -276,7 +298,7 @@ def test_no_exchange_mode(default_conf): dp.refresh([()]) with pytest.raises(OperationalException, match=message): - dp.ohlcv('XRP/USDT', '5m') + dp.ohlcv('XRP/USDT', '5m', '') with pytest.raises(OperationalException, match=message): dp.market('XRP/USDT') diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 627e29444..0585fa0d4 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -1,6 +1,7 @@ # pragma pylint: disable=missing-docstring, protected-access, C0103 import json +import re import uuid from pathlib import Path from shutil import copyfile @@ -23,10 +24,12 @@ from freqtrade.data.history.history_utils import (_download_pair_history, _downl validate_backtest_data) from freqtrade.data.history.idatahandler import IDataHandler, get_datahandler, get_datahandlerclass from freqtrade.data.history.jsondatahandler import JsonDataHandler, JsonGzDataHandler +from freqtrade.enums import CandleType, TradingMode from freqtrade.exchange import timeframe_to_minutes from freqtrade.misc import file_dump_json from freqtrade.resolvers import StrategyResolver -from tests.conftest import get_patched_exchange, log_has, log_has_re, patch_exchange +from tests.conftest import (CURRENT_TEST_STRATEGY, get_patched_exchange, log_has, log_has_re, + patch_exchange) # Change this if modifying UNITTEST/BTC testdatafile @@ -78,7 +81,7 @@ def test_load_data_7min_timeframe(mocker, caplog, default_conf, testdatadir) -> assert isinstance(ld, DataFrame) assert ld.empty assert log_has( - 'No history data for pair: "UNITTEST/BTC", timeframe: 7m. ' + 'No history for UNITTEST/BTC, spot, 7m found. ' 'Use `freqtrade download-data` to download the data', caplog ) @@ -94,6 +97,17 @@ def test_load_data_1min_timeframe(ohlcv_history, mocker, caplog, testdatadir) -> ) +def test_load_data_mark(ohlcv_history, mocker, caplog, testdatadir) -> None: + mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history) + file = testdatadir / 'futures/UNITTEST_USDT-1h-mark.json' + load_data(datadir=testdatadir, timeframe='1h', pairs=['UNITTEST/BTC'], candle_type='mark') + assert file.is_file() + assert not log_has( + 'Download history data for pair: "UNITTEST/USDT", interval: 1m ' + 'and store in None.', caplog + ) + + def test_load_data_startup_candles(mocker, caplog, default_conf, testdatadir) -> None: ltfmock = mocker.patch( 'freqtrade.data.history.jsondatahandler.JsonDataHandler._ohlcv_load', @@ -109,8 +123,9 @@ def test_load_data_startup_candles(mocker, caplog, default_conf, testdatadir) -> assert ltfmock.call_args_list[0][1]['timerange'].startts == timerange.startts - 20 * 60 +@pytest.mark.parametrize('candle_type', ['mark', '']) def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog, - default_conf, tmpdir) -> None: + default_conf, tmpdir, candle_type) -> None: """ Test load_pair_history() with 1 min timeframe """ @@ -120,21 +135,22 @@ def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog, file = tmpdir1 / 'MEME_BTC-1m.json' # do not download a new pair if refresh_pairs isn't set - load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC') + load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC', candle_type=candle_type) assert not file.is_file() assert log_has( - 'No history data for pair: "MEME/BTC", timeframe: 1m. ' - 'Use `freqtrade download-data` to download the data', caplog + f"No history for MEME/BTC, {candle_type}, 1m found. " + "Use `freqtrade download-data` to download the data", caplog ) # download a new pair if refresh_pairs is set refresh_data(datadir=tmpdir1, timeframe='1m', pairs=['MEME/BTC'], - exchange=exchange) - load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC') + exchange=exchange, candle_type=CandleType.SPOT + ) + load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC', candle_type=candle_type) assert file.is_file() assert log_has_re( - r'Download history data for pair: "MEME/BTC" \(0/1\), timeframe: 1m ' - r'and store in .*', caplog + r'Download history data for pair: "MEME/BTC" \(0/1\), timeframe: 1m, ' + r'candle type: spot and store in .*', caplog ) @@ -142,19 +158,31 @@ def test_testdata_path(testdatadir) -> None: assert str(Path('tests') / 'testdata') in str(testdatadir) -@pytest.mark.parametrize("pair,expected_result", [ - ("ETH/BTC", 'freqtrade/hello/world/ETH_BTC-5m.json'), - ("Fabric Token/ETH", 'freqtrade/hello/world/Fabric_Token_ETH-5m.json'), - ("ETHH20", 'freqtrade/hello/world/ETHH20-5m.json'), - (".XBTBON2H", 'freqtrade/hello/world/_XBTBON2H-5m.json'), - ("ETHUSD.d", 'freqtrade/hello/world/ETHUSD_d-5m.json'), - ("ACC_OLD/BTC", 'freqtrade/hello/world/ACC_OLD_BTC-5m.json'), +@pytest.mark.parametrize("pair,expected_result,candle_type", [ + ("ETH/BTC", 'freqtrade/hello/world/ETH_BTC-5m.json', ""), + ("Fabric Token/ETH", 'freqtrade/hello/world/Fabric_Token_ETH-5m.json', ""), + ("ETHH20", 'freqtrade/hello/world/ETHH20-5m.json', ""), + (".XBTBON2H", 'freqtrade/hello/world/_XBTBON2H-5m.json', ""), + ("ETHUSD.d", 'freqtrade/hello/world/ETHUSD_d-5m.json', ""), + ("ACC_OLD/BTC", 'freqtrade/hello/world/ACC_OLD_BTC-5m.json', ""), + ("ETH/BTC", 'freqtrade/hello/world/futures/ETH_BTC-5m-mark.json', "mark"), + ("ACC_OLD/BTC", 'freqtrade/hello/world/futures/ACC_OLD_BTC-5m-index.json', "index"), ]) -def test_json_pair_data_filename(pair, expected_result): - fn = JsonDataHandler._pair_data_filename(Path('freqtrade/hello/world'), pair, '5m') +def test_json_pair_data_filename(pair, expected_result, candle_type): + fn = JsonDataHandler._pair_data_filename( + Path('freqtrade/hello/world'), + pair, + '5m', + CandleType.from_string(candle_type) + ) assert isinstance(fn, Path) assert fn == Path(expected_result) - fn = JsonGzDataHandler._pair_data_filename(Path('freqtrade/hello/world'), pair, '5m') + fn = JsonGzDataHandler._pair_data_filename( + Path('freqtrade/hello/world'), + pair, + '5m', + candle_type=CandleType.from_string(candle_type) + ) assert isinstance(fn, Path) assert fn == Path(expected_result + '.gz') @@ -195,14 +223,16 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None: # timeframe starts earlier than the cached data # should fully update data timerange = TimeRange('date', None, test_data[0][0] / 1000 - 1, 0) - data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler) + data, start_ts = _load_cached_data_for_updating( + 'UNITTEST/BTC', '1m', timerange, data_handler, CandleType.SPOT) assert data.empty assert start_ts == test_data[0][0] - 1000 # timeframe starts in the center of the cached data # should return the cached data w/o the last item timerange = TimeRange('date', None, test_data[0][0] / 1000 + 1, 0) - data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler) + data, start_ts = _load_cached_data_for_updating( + 'UNITTEST/BTC', '1m', timerange, data_handler, CandleType.SPOT) assert_frame_equal(data, test_data_df.iloc[:-1]) assert test_data[-2][0] <= start_ts < test_data[-1][0] @@ -210,42 +240,59 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None: # timeframe starts after the cached data # should return the cached data w/o the last item timerange = TimeRange('date', None, test_data[-1][0] / 1000 + 100, 0) - data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler) + data, start_ts = _load_cached_data_for_updating( + 'UNITTEST/BTC', '1m', timerange, data_handler, CandleType.SPOT) assert_frame_equal(data, test_data_df.iloc[:-1]) assert test_data[-2][0] <= start_ts < test_data[-1][0] # no datafile exist # should return timestamp start time timerange = TimeRange('date', None, now_ts - 10000, 0) - data, start_ts = _load_cached_data_for_updating('NONEXIST/BTC', '1m', timerange, data_handler) + data, start_ts = _load_cached_data_for_updating( + 'NONEXIST/BTC', '1m', timerange, data_handler, CandleType.SPOT) assert data.empty assert start_ts == (now_ts - 10000) * 1000 # no datafile exist, no timeframe is set # should return an empty array and None - data, start_ts = _load_cached_data_for_updating('NONEXIST/BTC', '1m', None, data_handler) + data, start_ts = _load_cached_data_for_updating( + 'NONEXIST/BTC', '1m', None, data_handler, CandleType.SPOT) assert data.empty assert start_ts is None -def test_download_pair_history(ohlcv_history_list, mocker, default_conf, tmpdir) -> None: +@pytest.mark.parametrize('candle_type,subdir,file_tail', [ + ('mark', 'futures/', '-mark'), + ('spot', '', ''), +]) +def test_download_pair_history( + ohlcv_history_list, + mocker, + default_conf, + tmpdir, + candle_type, + subdir, + file_tail +) -> None: mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history_list) exchange = get_patched_exchange(mocker, default_conf) tmpdir1 = Path(tmpdir) - file1_1 = tmpdir1 / 'MEME_BTC-1m.json' - file1_5 = tmpdir1 / 'MEME_BTC-5m.json' - file2_1 = tmpdir1 / 'CFI_BTC-1m.json' - file2_5 = tmpdir1 / 'CFI_BTC-5m.json' + file1_1 = tmpdir1 / f'{subdir}MEME_BTC-1m{file_tail}.json' + file1_5 = tmpdir1 / f'{subdir}MEME_BTC-5m{file_tail}.json' + file2_1 = tmpdir1 / f'{subdir}CFI_BTC-1m{file_tail}.json' + file2_5 = tmpdir1 / f'{subdir}CFI_BTC-5m{file_tail}.json' assert not file1_1.is_file() assert not file2_1.is_file() assert _download_pair_history(datadir=tmpdir1, exchange=exchange, pair='MEME/BTC', - timeframe='1m') + timeframe='1m', + candle_type=candle_type) assert _download_pair_history(datadir=tmpdir1, exchange=exchange, pair='CFI/BTC', - timeframe='1m') + timeframe='1m', + candle_type=candle_type) assert not exchange._pairs_last_refresh_time assert file1_1.is_file() assert file2_1.is_file() @@ -259,10 +306,12 @@ def test_download_pair_history(ohlcv_history_list, mocker, default_conf, tmpdir) assert _download_pair_history(datadir=tmpdir1, exchange=exchange, pair='MEME/BTC', - timeframe='5m') + timeframe='5m', + candle_type=candle_type) assert _download_pair_history(datadir=tmpdir1, exchange=exchange, pair='CFI/BTC', - timeframe='5m') + timeframe='5m', + candle_type=candle_type) assert not exchange._pairs_last_refresh_time assert file1_5.is_file() assert file2_5.is_file() @@ -279,10 +328,12 @@ def test_download_pair_history2(mocker, default_conf, testdatadir) -> None: mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=tick) exchange = get_patched_exchange(mocker, default_conf) _download_pair_history(datadir=testdatadir, exchange=exchange, pair="UNITTEST/BTC", - timeframe='1m') + timeframe='1m', candle_type='spot') _download_pair_history(datadir=testdatadir, exchange=exchange, pair="UNITTEST/BTC", - timeframe='3m') - assert json_dump_mock.call_count == 2 + timeframe='3m', candle_type='spot') + _download_pair_history(datadir=testdatadir, exchange=exchange, pair="UNITTEST/USDT", + timeframe='1h', candle_type='mark') + assert json_dump_mock.call_count == 3 def test_download_backtesting_data_exception(mocker, caplog, default_conf, tmpdir) -> None: @@ -293,7 +344,7 @@ def test_download_backtesting_data_exception(mocker, caplog, default_conf, tmpdi assert not _download_pair_history(datadir=tmpdir1, exchange=exchange, pair='MEME/BTC', - timeframe='1m') + timeframe='1m', candle_type='spot') assert log_has('Failed to download history data for pair: "MEME/BTC", timeframe: 1m.', caplog) @@ -310,8 +361,8 @@ def test_load_partial_missing(testdatadir, caplog) -> None: td = ((end - start).total_seconds() // 60 // 5) + 1 assert td != len(data['UNITTEST/BTC']) start_real = data['UNITTEST/BTC'].iloc[0, 0] - assert log_has(f'Missing data at start for pair ' - f'UNITTEST/BTC at 5m, data starts at {start_real.strftime("%Y-%m-%d %H:%M:%S")}', + assert log_has(f'UNITTEST/BTC, spot, 5m, ' + f'data starts at {start_real.strftime("%Y-%m-%d %H:%M:%S")}', caplog) # Make sure we start fresh - test missing data at end caplog.clear() @@ -325,8 +376,8 @@ def test_load_partial_missing(testdatadir, caplog) -> None: # Shift endtime with +5 - as last candle is dropped (partial candle) end_real = arrow.get(data['UNITTEST/BTC'].iloc[-1, 0]).shift(minutes=5) - assert log_has(f'Missing data at end for pair ' - f'UNITTEST/BTC at 5m, data ends at {end_real.strftime("%Y-%m-%d %H:%M:%S")}', + assert log_has(f'UNITTEST/BTC, spot, 5m, ' + f'data ends at {end_real.strftime("%Y-%m-%d %H:%M:%S")}', caplog) @@ -344,7 +395,8 @@ def test_init_with_refresh(default_conf, mocker) -> None: datadir=Path(''), pairs=[], timeframe=default_conf['timeframe'], - exchange=exchange + exchange=exchange, + candle_type=CandleType.SPOT ) assert {} == load_data( datadir=Path(''), @@ -380,7 +432,7 @@ def test_file_dump_json_tofile(testdatadir) -> None: def test_get_timerange(default_conf, mocker, testdatadir) -> None: patch_exchange(mocker) - default_conf.update({'strategy': 'StrategyTestV2'}) + default_conf.update({'strategy': CURRENT_TEST_STRATEGY}) strategy = StrategyResolver.load_strategy(default_conf) data = strategy.advise_all_indicators( @@ -398,7 +450,7 @@ def test_get_timerange(default_conf, mocker, testdatadir) -> None: def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None: patch_exchange(mocker) - default_conf.update({'strategy': 'StrategyTestV2'}) + default_conf.update({'strategy': CURRENT_TEST_STRATEGY}) strategy = StrategyResolver.load_strategy(default_conf) data = strategy.advise_all_indicators( @@ -422,7 +474,7 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None: patch_exchange(mocker) - default_conf.update({'strategy': 'StrategyTestV2'}) + default_conf.update({'strategy': CURRENT_TEST_STRATEGY}) strategy = StrategyResolver.load_strategy(default_conf) timerange = TimeRange('index', 'index', 200, 250) @@ -442,7 +494,13 @@ def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> No assert len(caplog.record_tuples) == 0 -def test_refresh_backtest_ohlcv_data(mocker, default_conf, markets, caplog, testdatadir): +@pytest.mark.parametrize('trademode,callcount', [ + ('spot', 4), + ('margin', 4), + ('futures', 8), # Called 8 times - 4 normal, 2 funding and 2 mark/index calls +]) +def test_refresh_backtest_ohlcv_data( + mocker, default_conf, markets, caplog, testdatadir, trademode, callcount): dl_mock = mocker.patch('freqtrade.data.history.history_utils._download_pair_history', MagicMock()) mocker.patch( @@ -455,10 +513,11 @@ def test_refresh_backtest_ohlcv_data(mocker, default_conf, markets, caplog, test timerange = TimeRange.parse_timerange("20190101-20190102") refresh_backtest_ohlcv_data(exchange=ex, pairs=["ETH/BTC", "XRP/BTC"], timeframes=["1m", "5m"], datadir=testdatadir, - timerange=timerange, erase=True + timerange=timerange, erase=True, + trading_mode=trademode ) - assert dl_mock.call_count == 4 + assert dl_mock.call_count == callcount assert dl_mock.call_args[1]['timerange'].starttype == 'date' assert log_has("Downloading pair ETH/BTC, interval 1m.", caplog) @@ -476,7 +535,8 @@ def test_download_data_no_markets(mocker, default_conf, caplog, testdatadir): unav_pairs = refresh_backtest_ohlcv_data(exchange=ex, pairs=["BTT/BTC", "LTC/USDT"], timeframes=["1m", "5m"], datadir=testdatadir, - timerange=timerange, erase=False + timerange=timerange, erase=False, + trading_mode='spot' ) assert dl_mock.call_count == 0 @@ -604,33 +664,101 @@ def test_convert_trades_to_ohlcv(testdatadir, tmpdir, caplog): def test_datahandler_ohlcv_get_pairs(testdatadir): - pairs = JsonDataHandler.ohlcv_get_pairs(testdatadir, '5m') + pairs = JsonDataHandler.ohlcv_get_pairs(testdatadir, '5m', candle_type=CandleType.SPOT) # Convert to set to avoid failures due to sorting assert set(pairs) == {'UNITTEST/BTC', 'XLM/BTC', 'ETH/BTC', 'TRX/BTC', 'LTC/BTC', 'XMR/BTC', 'ZEC/BTC', 'ADA/BTC', 'ETC/BTC', 'NXT/BTC', 'DASH/BTC', 'XRP/ETH'} - pairs = JsonGzDataHandler.ohlcv_get_pairs(testdatadir, '8m') + pairs = JsonGzDataHandler.ohlcv_get_pairs(testdatadir, '8m', candle_type=CandleType.SPOT) assert set(pairs) == {'UNITTEST/BTC'} - pairs = HDF5DataHandler.ohlcv_get_pairs(testdatadir, '5m') + pairs = HDF5DataHandler.ohlcv_get_pairs(testdatadir, '5m', candle_type=CandleType.SPOT) assert set(pairs) == {'UNITTEST/BTC'} + pairs = JsonDataHandler.ohlcv_get_pairs(testdatadir, '1h', candle_type=CandleType.MARK) + assert set(pairs) == {'UNITTEST/USDT', 'XRP/USDT'} + + pairs = JsonGzDataHandler.ohlcv_get_pairs(testdatadir, '1h', candle_type=CandleType.FUTURES) + assert set(pairs) == {'XRP/USDT'} + + pairs = HDF5DataHandler.ohlcv_get_pairs(testdatadir, '1h', candle_type=CandleType.MARK) + assert set(pairs) == {'UNITTEST/USDT:USDT'} + + +@pytest.mark.parametrize('filename,pair,timeframe,candletype', [ + ('XMR_BTC-5m.json', 'XMR_BTC', '5m', ''), + ('XMR_USDT-1h.h5', 'XMR_USDT', '1h', ''), + ('BTC-PERP-1h.h5', 'BTC-PERP', '1h', ''), + ('BTC_USDT-2h.jsongz', 'BTC_USDT', '2h', ''), + ('BTC_USDT-2h-mark.jsongz', 'BTC_USDT', '2h', 'mark'), + ('XMR_USDT-1h-mark.h5', 'XMR_USDT', '1h', 'mark'), + ('XMR_USDT-1h-random.h5', 'XMR_USDT', '1h', 'random'), + ('BTC-PERP-1h-index.h5', 'BTC-PERP', '1h', 'index'), + ('XMR_USDT_USDT-1h-mark.h5', 'XMR_USDT_USDT', '1h', 'mark'), +]) +def test_datahandler_ohlcv_regex(filename, pair, timeframe, candletype): + regex = JsonDataHandler._OHLCV_REGEX + + match = re.search(regex, filename) + assert len(match.groups()) > 1 + assert match[1] == pair + assert match[2] == timeframe + assert match[3] == candletype + + +@pytest.mark.parametrize('input,expected', [ + ('XMR_USDT', 'XMR/USDT'), + ('BTC_USDT', 'BTC/USDT'), + ('USDT_BUSD', 'USDT/BUSD'), + ('BTC_USDT_USDT', 'BTC/USDT:USDT'), # Futures + ('XRP_USDT_USDT', 'XRP/USDT:USDT'), # futures + ('BTC-PERP', 'BTC-PERP'), + ('BTC-PERP_USDT', 'BTC-PERP:USDT'), # potential FTX case + ('UNITTEST_USDT', 'UNITTEST/USDT'), +]) +def test_rebuild_pair_from_filename(input, expected): + + assert IDataHandler.rebuild_pair_from_filename(input) == expected + def test_datahandler_ohlcv_get_available_data(testdatadir): - paircombs = JsonDataHandler.ohlcv_get_available_data(testdatadir) + paircombs = JsonDataHandler.ohlcv_get_available_data(testdatadir, TradingMode.SPOT) # Convert to set to avoid failures due to sorting - assert set(paircombs) == {('UNITTEST/BTC', '5m'), ('ETH/BTC', '5m'), ('XLM/BTC', '5m'), - ('TRX/BTC', '5m'), ('LTC/BTC', '5m'), ('XMR/BTC', '5m'), - ('ZEC/BTC', '5m'), ('UNITTEST/BTC', '1m'), ('ADA/BTC', '5m'), - ('ETC/BTC', '5m'), ('NXT/BTC', '5m'), ('DASH/BTC', '5m'), - ('XRP/ETH', '1m'), ('XRP/ETH', '5m'), ('UNITTEST/BTC', '30m'), - ('UNITTEST/BTC', '8m'), ('NOPAIR/XXX', '4m')} + assert set(paircombs) == { + ('UNITTEST/BTC', '5m', CandleType.SPOT), + ('ETH/BTC', '5m', CandleType.SPOT), + ('XLM/BTC', '5m', CandleType.SPOT), + ('TRX/BTC', '5m', CandleType.SPOT), + ('LTC/BTC', '5m', CandleType.SPOT), + ('XMR/BTC', '5m', CandleType.SPOT), + ('ZEC/BTC', '5m', CandleType.SPOT), + ('UNITTEST/BTC', '1m', CandleType.SPOT), + ('ADA/BTC', '5m', CandleType.SPOT), + ('ETC/BTC', '5m', CandleType.SPOT), + ('NXT/BTC', '5m', CandleType.SPOT), + ('DASH/BTC', '5m', CandleType.SPOT), + ('XRP/ETH', '1m', CandleType.SPOT), + ('XRP/ETH', '5m', CandleType.SPOT), + ('UNITTEST/BTC', '30m', CandleType.SPOT), + ('UNITTEST/BTC', '8m', CandleType.SPOT), + ('NOPAIR/XXX', '4m', CandleType.SPOT), + } - paircombs = JsonGzDataHandler.ohlcv_get_available_data(testdatadir) - assert set(paircombs) == {('UNITTEST/BTC', '8m')} - paircombs = HDF5DataHandler.ohlcv_get_available_data(testdatadir) - assert set(paircombs) == {('UNITTEST/BTC', '5m')} + paircombs = JsonDataHandler.ohlcv_get_available_data(testdatadir, TradingMode.FUTURES) + # Convert to set to avoid failures due to sorting + assert set(paircombs) == { + ('UNITTEST/USDT', '1h', 'mark'), + ('XRP/USDT', '1h', 'futures'), + ('XRP/USDT', '1h', 'mark'), + ('XRP/USDT', '8h', 'mark'), + ('XRP/USDT', '8h', 'funding_rate'), + } + + paircombs = JsonGzDataHandler.ohlcv_get_available_data(testdatadir, TradingMode.SPOT) + assert set(paircombs) == {('UNITTEST/BTC', '8m', CandleType.SPOT)} + paircombs = HDF5DataHandler.ohlcv_get_available_data(testdatadir, TradingMode.SPOT) + assert set(paircombs) == {('UNITTEST/BTC', '5m', CandleType.SPOT)} def test_jsondatahandler_trades_get_pairs(testdatadir): @@ -643,21 +771,29 @@ def test_jsondatahandler_ohlcv_purge(mocker, testdatadir): mocker.patch.object(Path, "exists", MagicMock(return_value=False)) unlinkmock = mocker.patch.object(Path, "unlink", MagicMock()) dh = JsonGzDataHandler(testdatadir) - assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m') + assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', '') + assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', candle_type='mark') assert unlinkmock.call_count == 0 mocker.patch.object(Path, "exists", MagicMock(return_value=True)) - assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m') - assert unlinkmock.call_count == 1 + assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', '') + assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', candle_type='mark') + assert unlinkmock.call_count == 2 def test_jsondatahandler_ohlcv_load(testdatadir, caplog): dh = JsonDataHandler(testdatadir) - df = dh.ohlcv_load('XRP/ETH', '5m') + df = dh.ohlcv_load('XRP/ETH', '5m', 'spot') assert len(df) == 711 + df_mark = dh.ohlcv_load('UNITTEST/USDT', '1h', candle_type="mark") + assert len(df_mark) == 99 + + df_no_mark = dh.ohlcv_load('UNITTEST/USDT', '1h', 'spot') + assert len(df_no_mark) == 0 + # Failure case (empty array) - df1 = dh.ohlcv_load('NOPAIR/XXX', '4m') + df1 = dh.ohlcv_load('NOPAIR/XXX', '4m', 'spot') assert len(df1) == 0 assert log_has("Could not load data for NOPAIR/XXX.", caplog) assert df.columns.equals(df1.columns) @@ -690,7 +826,9 @@ def test_jsondatahandler_trades_purge(mocker, testdatadir): def test_datahandler_ohlcv_append(datahandler, testdatadir, ): dh = get_datahandler(testdatadir, datahandler) with pytest.raises(NotImplementedError): - dh.ohlcv_append('UNITTEST/ETH', '5m', DataFrame()) + dh.ohlcv_append('UNITTEST/ETH', '5m', DataFrame(), CandleType.SPOT) + with pytest.raises(NotImplementedError): + dh.ohlcv_append('UNITTEST/ETH', '5m', DataFrame(), CandleType.MARK) @pytest.mark.parametrize('datahandler', AVAILABLE_DATAHANDLERS) @@ -772,35 +910,52 @@ def test_hdf5datahandler_trades_purge(mocker, testdatadir): assert unlinkmock.call_count == 1 -def test_hdf5datahandler_ohlcv_load_and_resave(testdatadir, tmpdir): +@pytest.mark.parametrize('pair,timeframe,candle_type,candle_append,startdt,enddt', [ + # Data goes from 2018-01-10 - 2018-01-30 + ('UNITTEST/BTC', '5m', 'spot', '', '2018-01-15', '2018-01-19'), + # Mark data goes from to 2021-11-15 2021-11-19 + ('UNITTEST/USDT:USDT', '1h', 'mark', '-mark', '2021-11-16', '2021-11-18'), +]) +def test_hdf5datahandler_ohlcv_load_and_resave( + testdatadir, + tmpdir, + pair, + timeframe, + candle_type, + candle_append, + startdt, enddt +): tmpdir1 = Path(tmpdir) + tmpdir2 = tmpdir1 + if candle_type not in ('', 'spot'): + tmpdir2 = tmpdir1 / 'futures' + tmpdir2.mkdir() dh = HDF5DataHandler(testdatadir) - ohlcv = dh.ohlcv_load('UNITTEST/BTC', '5m') + ohlcv = dh._ohlcv_load(pair, timeframe, None, candle_type=candle_type) assert isinstance(ohlcv, DataFrame) assert len(ohlcv) > 0 - file = tmpdir1 / 'UNITTEST_NEW-5m.h5' + file = tmpdir2 / f"UNITTEST_NEW-{timeframe}{candle_append}.h5" assert not file.is_file() dh1 = HDF5DataHandler(tmpdir1) - dh1.ohlcv_store('UNITTEST/NEW', '5m', ohlcv) + dh1.ohlcv_store('UNITTEST/NEW', timeframe, ohlcv, candle_type=candle_type) assert file.is_file() - assert not ohlcv[ohlcv['date'] < '2018-01-15'].empty + assert not ohlcv[ohlcv['date'] < startdt].empty - # Data gores from 2018-01-10 - 2018-01-30 - timerange = TimeRange.parse_timerange('20180115-20180119') + timerange = TimeRange.parse_timerange(f"{startdt.replace('-', '')}-{enddt.replace('-', '')}") # Call private function to ensure timerange is filtered in hdf5 - ohlcv = dh._ohlcv_load('UNITTEST/BTC', '5m', timerange) - ohlcv1 = dh1._ohlcv_load('UNITTEST/NEW', '5m', timerange) + ohlcv = dh._ohlcv_load(pair, timeframe, timerange, candle_type=candle_type) + ohlcv1 = dh1._ohlcv_load('UNITTEST/NEW', timeframe, timerange, candle_type=candle_type) assert len(ohlcv) == len(ohlcv1) assert ohlcv.equals(ohlcv1) - assert ohlcv[ohlcv['date'] < '2018-01-15'].empty - assert ohlcv[ohlcv['date'] > '2018-01-19'].empty + assert ohlcv[ohlcv['date'] < startdt].empty + assert ohlcv[ohlcv['date'] > enddt].empty # Try loading inexisting file - ohlcv = dh.ohlcv_load('UNITTEST/NONEXIST', '5m') + ohlcv = dh.ohlcv_load('UNITTEST/NONEXIST', timeframe, candle_type=candle_type) assert ohlcv.empty @@ -808,12 +963,14 @@ def test_hdf5datahandler_ohlcv_purge(mocker, testdatadir): mocker.patch.object(Path, "exists", MagicMock(return_value=False)) unlinkmock = mocker.patch.object(Path, "unlink", MagicMock()) dh = HDF5DataHandler(testdatadir) - assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m') + assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', '') + assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', candle_type='mark') assert unlinkmock.call_count == 0 mocker.patch.object(Path, "exists", MagicMock(return_value=True)) - assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m') - assert unlinkmock.call_count == 1 + assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', '') + assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', candle_type='mark') + assert unlinkmock.call_count == 2 def test_gethandlerclass(): diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index 7bdc940df..4ac27adc0 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -12,7 +12,7 @@ from pandas import DataFrame, to_datetime from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.edge import Edge, PairInfo -from freqtrade.enums import SellType +from freqtrade.enums import ExitType from freqtrade.exceptions import OperationalException from tests.conftest import get_patched_freqtradebot, log_has from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, @@ -95,8 +95,8 @@ tc1 = BTContainer(data=[ [6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell ], stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00, - trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=2), - BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=4, close_tick=6)] + trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=2), + BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=4, close_tick=6)] ) # 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss @@ -107,7 +107,7 @@ tc2 = BTContainer(data=[ [2, 5000, 5025, 4975, 4987, 6172, 0, 0], ], stop_loss=-0.01, roi={"0": float('inf')}, profit_perc=-0.01, - trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)] + trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)] ) # 4) Entered, sl 3 %, candle drops 4%, recovers to 1 % = > Trade closed, 3 % loss @@ -118,7 +118,7 @@ tc3 = BTContainer(data=[ [2, 5000, 5025, 4975, 4987, 6172, 0, 0], ], stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03, - trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)] + trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)] ) # 5) Stoploss and sell are hit. should sell on stoploss @@ -129,7 +129,7 @@ tc4 = BTContainer(data=[ [2, 5000, 5025, 4975, 4987, 6172, 0, 0], ], stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03, - trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)] + trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)] ) TESTS = [ diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index d88ae9b1d..c3950e459 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -1,24 +1,29 @@ from datetime import datetime, timezone from random import randint -from unittest.mock import MagicMock +from unittest.mock import MagicMock, PropertyMock import ccxt import pytest +from freqtrade.enums import MarginMode, TradingMode from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException from tests.conftest import get_mock_coro, get_patched_exchange, log_has_re from tests.exchange.test_exchange import ccxt_exceptionhandlers -@pytest.mark.parametrize('limitratio,expected', [ - (None, 220 * 0.99), - (0.99, 220 * 0.99), - (0.98, 220 * 0.98), +@pytest.mark.parametrize('trademode', [TradingMode.FUTURES, TradingMode.SPOT]) +@pytest.mark.parametrize('limitratio,expected,side', [ + (None, 220 * 0.99, "sell"), + (0.99, 220 * 0.99, "sell"), + (0.98, 220 * 0.98, "sell"), + (None, 220 * 1.01, "buy"), + (0.99, 220 * 1.01, "buy"), + (0.98, 220 * 1.02, "buy"), ]) -def test_stoploss_order_binance(default_conf, mocker, limitratio, expected): +def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side, trademode): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) - order_type = 'stop_loss_limit' + order_type = 'stop_loss_limit' if trademode == TradingMode.SPOT else 'stop' api_mock.create_order = MagicMock(return_value={ 'id': order_id, @@ -27,45 +32,78 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected): } }) default_conf['dry_run'] = False + default_conf['margin_mode'] = MarginMode.ISOLATED + default_conf['trading_mode'] = trademode mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') with pytest.raises(OperationalException): - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, - order_types={'stoploss_on_exchange_limit_ratio': 1.05}) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=190, + side=side, + order_types={'stoploss_on_exchange_limit_ratio': 1.05}, + leverage=1.0 + ) api_mock.create_order.reset_mock() order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio} - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types=order_types, + side=side, + leverage=1.0 + ) assert 'id' in order assert 'info' in order assert order['id'] == order_id assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' assert api_mock.create_order.call_args_list[0][1]['type'] == order_type - assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' + assert api_mock.create_order.call_args_list[0][1]['side'] == side assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 # Price should be 1% below stopprice assert api_mock.create_order.call_args_list[0][1]['price'] == expected - assert api_mock.create_order.call_args_list[0][1]['params'] == {'stopPrice': 220} + if trademode == TradingMode.SPOT: + params_dict = {'stopPrice': 220} + else: + params_dict = {'stopPrice': 220, 'reduceOnly': True} + assert api_mock.create_order.call_args_list[0][1]['params'] == params_dict # test exception handling with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0) with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, + side=side, leverage=1.0) def test_stoploss_order_dry_run_binance(default_conf, mocker): @@ -78,12 +116,25 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') with pytest.raises(OperationalException): - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, - order_types={'stoploss_on_exchange_limit_ratio': 1.05}) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=190, + side="sell", + order_types={'stoploss_on_exchange_limit_ratio': 1.05}, + leverage=1.0 + ) api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side="sell", + leverage=1.0 + ) assert 'id' in order assert 'info' in order @@ -94,22 +145,383 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker): assert order['amount'] == 1 -def test_stoploss_adjust_binance(mocker, default_conf): +@pytest.mark.parametrize('sl1,sl2,sl3,side', [ + (1501, 1499, 1501, "sell"), + (1499, 1501, 1499, "buy") +]) +def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side): exchange = get_patched_exchange(mocker, default_conf, id='binance') order = { 'type': 'stop_loss_limit', 'price': 1500, 'info': {'stopPrice': 1500}, } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) + assert exchange.stoploss_adjust(sl1, order, side=side) + assert not exchange.stoploss_adjust(sl2, order, side=side) # Test with invalid order case order['type'] = 'stop_loss' - assert not exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(sl3, order, side=side) + + +def test_fill_leverage_tiers_binance(default_conf, mocker): + api_mock = MagicMock() + api_mock.fetch_leverage_tiers = MagicMock(return_value={ + 'ADA/BUSD': [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 100000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "0", + "maintMarginRatio": "0.025", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 100000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.05", + "cum": "2500.0" + } + }, + { + "tier": 3, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "3", + "initialLeverage": "5", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.1", + "cum": "27500.0" + } + }, + { + "tier": 4, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3, + "info": { + "bracket": "4", + "initialLeverage": "3", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.15", + "cum": "77500.0" + } + }, + { + "tier": 5, + "notionalFloor": 2000000, + "notionalCap": 5000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "277500.0" + } + }, + { + "tier": 6, + "notionalFloor": 5000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.5", + "cum": "1527500.0" + } + } + ], + "ZEC/USDT": [ + { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 50000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "0", + "maintMarginRatio": "0.01", + "cum": "0.0" + } + }, + { + "tier": 2, + "notionalFloor": 50000, + "notionalCap": 150000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "750.0" + } + }, + { + "tier": 3, + "notionalFloor": 150000, + "notionalCap": 250000, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4500.0" + } + }, + { + "tier": 4, + "notionalFloor": 250000, + "notionalCap": 500000, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17000.0" + } + }, + { + "tier": 5, + "notionalFloor": 500000, + "notionalCap": 1000000, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29500.0" + } + }, + { + "tier": 6, + "notionalFloor": 1000000, + "notionalCap": 2000000, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154500.0" + } + }, + { + "tier": 7, + "notionalFloor": 2000000, + "notionalCap": 30000000, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.5", + "cum": "654500.0" + } + } + ], + }) + default_conf['dry_run'] = False + default_conf['trading_mode'] = TradingMode.FUTURES + default_conf['margin_mode'] = MarginMode.ISOLATED + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") + exchange.fill_leverage_tiers() + + assert exchange._leverage_tiers == { + 'ADA/BUSD': [ + { + "min": 0, + "max": 100000, + "mmr": 0.025, + "lev": 20, + "maintAmt": 0.0 + }, + { + "min": 100000, + "max": 500000, + "mmr": 0.05, + "lev": 10, + "maintAmt": 2500.0 + }, + { + "min": 500000, + "max": 1000000, + "mmr": 0.1, + "lev": 5, + "maintAmt": 27500.0 + }, + { + "min": 1000000, + "max": 2000000, + "mmr": 0.15, + "lev": 3, + "maintAmt": 77500.0 + }, + { + "min": 2000000, + "max": 5000000, + "mmr": 0.25, + "lev": 2, + "maintAmt": 277500.0 + }, + { + "min": 5000000, + "max": 30000000, + "mmr": 0.5, + "lev": 1, + "maintAmt": 1527500.0 + } + ], + "ZEC/USDT": [ + { + 'min': 0, + 'max': 50000, + 'mmr': 0.01, + 'lev': 50, + 'maintAmt': 0.0 + }, + { + 'min': 50000, + 'max': 150000, + 'mmr': 0.025, + 'lev': 20, + 'maintAmt': 750.0 + }, + { + 'min': 150000, + 'max': 250000, + 'mmr': 0.05, + 'lev': 10, + 'maintAmt': 4500.0 + }, + { + 'min': 250000, + 'max': 500000, + 'mmr': 0.1, + 'lev': 5, + 'maintAmt': 17000.0 + }, + { + 'min': 500000, + 'max': 1000000, + 'mmr': 0.125, + 'lev': 4, + 'maintAmt': 29500.0 + }, + { + 'min': 1000000, + 'max': 2000000, + 'mmr': 0.25, + 'lev': 2, + 'maintAmt': 154500.0 + }, + { + 'min': 2000000, + 'max': 30000000, + 'mmr': 0.5, + 'lev': 1, + 'maintAmt': 654500.0 + }, + ] + } + + api_mock = MagicMock() + api_mock.load_leverage_tiers = MagicMock() + type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True}) + + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + "binance", + "fill_leverage_tiers", + "fetch_leverage_tiers", + ) + + +def test_fill_leverage_tiers_binance_dryrun(default_conf, mocker, leverage_tiers): + api_mock = MagicMock() + default_conf['trading_mode'] = TradingMode.FUTURES + default_conf['margin_mode'] = MarginMode.ISOLATED + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") + exchange.fill_leverage_tiers() + + leverage_tiers = leverage_tiers + + for key, value in leverage_tiers.items(): + assert exchange._leverage_tiers[key] == value + + +def test__set_leverage_binance(mocker, default_conf): + + api_mock = MagicMock() + api_mock.set_leverage = MagicMock() + type(api_mock).has = PropertyMock(return_value={'setLeverage': True}) + default_conf['dry_run'] = False + exchange = get_patched_exchange(mocker, default_conf, id="binance") + exchange._set_leverage(3.0, trading_mode=TradingMode.MARGIN) + + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + "binance", + "_set_leverage", + "set_leverage", + pair="XRP/USDT", + leverage=5.0, + trading_mode=TradingMode.FUTURES + ) @pytest.mark.asyncio -async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog): +@pytest.mark.parametrize('candle_type', ['mark', '']) +async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog, candle_type): ohlcv = [ [ int((datetime.now(timezone.utc).timestamp() - 1000) * 1000), @@ -126,18 +538,55 @@ async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog): exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) pair = 'ETH/BTC' - respair, restf, res = await exchange._async_get_historic_ohlcv( - pair, "5m", 1500000000000, is_new_pair=False) + respair, restf, restype, res = await exchange._async_get_historic_ohlcv( + pair, "5m", 1500000000000, is_new_pair=False, candle_type=candle_type) assert respair == pair assert restf == '5m' + assert restype == candle_type # Call with very old timestamp - causes tons of requests assert exchange._api_async.fetch_ohlcv.call_count > 400 # assert res == ohlcv exchange._api_async.fetch_ohlcv.reset_mock() - _, _, res = await exchange._async_get_historic_ohlcv( - pair, "5m", 1500000000000, is_new_pair=True) + _, _, _, res = await exchange._async_get_historic_ohlcv( + pair, "5m", 1500000000000, is_new_pair=True, candle_type=candle_type) # Called twice - one "init" call - and one to get the actual data. assert exchange._api_async.fetch_ohlcv.call_count == 2 assert res == ohlcv assert log_has_re(r"Candle-data for ETH/BTC available starting with .*", caplog) + + +@pytest.mark.parametrize("trading_mode,margin_mode,config", [ + ("spot", "", {}), + ("margin", "cross", {"options": {"defaultType": "margin"}}), + ("futures", "isolated", {"options": {"defaultType": "future"}}), +]) +def test__ccxt_config(default_conf, mocker, trading_mode, margin_mode, config): + default_conf['trading_mode'] = trading_mode + default_conf['margin_mode'] = margin_mode + exchange = get_patched_exchange(mocker, default_conf, id="binance") + assert exchange._ccxt_config == config + + +@pytest.mark.parametrize('pair,nominal_value,mm_ratio,amt', [ + ("BNB/BUSD", 0.0, 0.025, 0), + ("BNB/USDT", 100.0, 0.0065, 0), + ("BTC/USDT", 170.30, 0.004, 0), + ("BNB/BUSD", 999999.9, 0.1, 27500.0), + ("BNB/USDT", 5000000.0, 0.15, 233035.0), + ("BTC/USDT", 600000000, 0.5, 1.997038E8), +]) +def test_get_maintenance_ratio_and_amt_binance( + default_conf, + mocker, + leverage_tiers, + pair, + nominal_value, + mm_ratio, + amt, +): + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + exchange = get_patched_exchange(mocker, default_conf, id="binance") + exchange._leverage_tiers = leverage_tiers + (result_ratio, result_amt) = exchange.get_maintenance_ratio_and_amt(pair, nominal_value) + assert (round(result_ratio, 8), round(result_amt, 8)) == (mm_ratio, amt) diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index 877d53fe7..14e45c8b0 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -5,14 +5,16 @@ However, these tests should give a good idea to determine if a new exchange is suitable to run with freqtrade. """ +from copy import deepcopy from datetime import datetime, timedelta, timezone from pathlib import Path import pytest +from freqtrade.enums import CandleType from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date from freqtrade.resolvers.exchange_resolver import ExchangeResolver -from tests.conftest import get_default_conf +from tests.conftest import get_default_conf_usdt # Exchanges that should be tested @@ -22,65 +24,91 @@ EXCHANGES = { 'stake_currency': 'USDT', 'hasQuoteVolume': False, 'timeframe': '1h', + 'leverage_tiers_public': False, + 'leverage_in_spot_market': False, }, 'binance': { 'pair': 'BTC/USDT', 'stake_currency': 'USDT', 'hasQuoteVolume': True, 'timeframe': '5m', + 'futures': True, + 'leverage_tiers_public': False, + 'leverage_in_spot_market': False, }, 'kraken': { 'pair': 'BTC/USDT', 'stake_currency': 'USDT', 'hasQuoteVolume': True, 'timeframe': '5m', + 'leverage_tiers_public': False, + 'leverage_in_spot_market': True, }, 'ftx': { - 'pair': 'BTC/USDT', - 'stake_currency': 'USDT', + 'pair': 'BTC/USD', + 'stake_currency': 'USD', 'hasQuoteVolume': True, 'timeframe': '5m', + 'futures_pair': 'BTC/USD:USD', + 'futures': False, + 'leverage_tiers_public': False, # TODO: Set to True once implemented on CCXT + 'leverage_in_spot_market': True, }, 'kucoin': { 'pair': 'BTC/USDT', 'stake_currency': 'USDT', 'hasQuoteVolume': True, 'timeframe': '5m', + 'leverage_tiers_public': False, + 'leverage_in_spot_market': True, }, 'gateio': { 'pair': 'BTC/USDT', 'stake_currency': 'USDT', 'hasQuoteVolume': True, 'timeframe': '5m', + 'futures': True, + 'futures_pair': 'BTC/USDT:USDT', + 'leverage_tiers_public': True, + 'leverage_in_spot_market': True, }, 'okx': { 'pair': 'BTC/USDT', 'stake_currency': 'USDT', 'hasQuoteVolume': True, 'timeframe': '5m', + 'futures_pair': 'BTC/USDT:USDT', + 'futures': True, + 'leverage_tiers_public': True, + 'leverage_in_spot_market': True, }, 'huobi': { 'pair': 'BTC/USDT', 'stake_currency': 'USDT', 'hasQuoteVolume': True, 'timeframe': '5m', + 'futures': False, }, 'bitvavo': { 'pair': 'BTC/EUR', 'stake_currency': 'EUR', 'hasQuoteVolume': True, 'timeframe': '5m', + 'leverage_tiers_public': False, + 'leverage_in_spot_market': False, }, } @pytest.fixture(scope="class") def exchange_conf(): - config = get_default_conf((Path(__file__).parent / "testdata").resolve()) + config = get_default_conf_usdt((Path(__file__).parent / "testdata").resolve()) config['exchange']['pair_whitelist'] = [] config['exchange']['key'] = '' config['exchange']['secret'] = '' config['dry_run'] = False + config['entry_pricing']['use_order_book'] = True + config['exit_pricing']['use_order_book'] = True return config @@ -93,6 +121,25 @@ def exchange(request, exchange_conf): yield exchange, request.param +@pytest.fixture(params=EXCHANGES, scope="class") +def exchange_futures(request, exchange_conf, class_mocker): + if not EXCHANGES[request.param].get('futures') is True: + yield None, request.param + else: + exchange_conf = deepcopy(exchange_conf) + exchange_conf['exchange']['name'] = request.param + exchange_conf['trading_mode'] = 'futures' + exchange_conf['margin_mode'] = 'isolated' + exchange_conf['stake_currency'] = EXCHANGES[request.param]['stake_currency'] + + class_mocker.patch( + 'freqtrade.exchange.binance.Binance.fill_leverage_tiers') + class_mocker.patch('freqtrade.exchange.exchange.Exchange.fetch_trading_fees') + exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True) + + yield exchange, request.param + + @pytest.mark.longrun class TestCCXTExchange(): @@ -102,6 +149,20 @@ class TestCCXTExchange(): markets = exchange.markets assert pair in markets assert isinstance(markets[pair], dict) + assert exchange.market_is_spot(markets[pair]) + + def test_load_markets_futures(self, exchange_futures): + exchange, exchangename = exchange_futures + if not exchange: + # exchange_futures only returns values for supported exchanges + return + pair = EXCHANGES[exchangename]['pair'] + pair = EXCHANGES[exchangename].get('futures_pair', pair) + markets = exchange.markets + assert pair in markets + assert isinstance(markets[pair], dict) + + assert exchange.market_is_future(markets[pair]) def test_ccxt_fetch_tickers(self, exchange): exchange, exchangename = exchange @@ -161,7 +222,9 @@ class TestCCXTExchange(): exchange, exchangename = exchange pair = EXCHANGES[exchangename]['pair'] timeframe = EXCHANGES[exchangename]['timeframe'] - pair_tf = (pair, timeframe) + + pair_tf = (pair, timeframe, CandleType.SPOT) + ohlcv = exchange.refresh_latest_ohlcv([pair_tf]) assert isinstance(ohlcv, dict) assert len(ohlcv[pair_tf]) == len(exchange.klines(pair_tf)) @@ -172,6 +235,82 @@ class TestCCXTExchange(): now = datetime.now(timezone.utc) - timedelta(minutes=(timeframe_to_minutes(timeframe) * 2)) assert exchange.klines(pair_tf).iloc[-1]['date'] >= timeframe_to_prev_date(timeframe, now) + def test_ccxt_fetch_funding_rate_history(self, exchange_futures): + exchange, exchangename = exchange_futures + if not exchange: + # exchange_futures only returns values for supported exchanges + return + + pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair']) + since = int((datetime.now(timezone.utc) - timedelta(days=5)).timestamp() * 1000) + timeframe_ff = exchange._ft_has.get('funding_fee_timeframe', + exchange._ft_has['mark_ohlcv_timeframe']) + pair_tf = (pair, timeframe_ff, CandleType.FUNDING_RATE) + + funding_ohlcv = exchange.refresh_latest_ohlcv( + [pair_tf], + since_ms=since, + drop_incomplete=False) + + assert isinstance(funding_ohlcv, dict) + rate = funding_ohlcv[pair_tf] + + this_hour = timeframe_to_prev_date(timeframe_ff) + hour1 = timeframe_to_prev_date(timeframe_ff, this_hour - timedelta(minutes=1)) + hour2 = timeframe_to_prev_date(timeframe_ff, hour1 - timedelta(minutes=1)) + hour3 = timeframe_to_prev_date(timeframe_ff, hour2 - timedelta(minutes=1)) + val0 = rate[rate['date'] == this_hour].iloc[0]['open'] + val1 = rate[rate['date'] == hour1].iloc[0]['open'] + val2 = rate[rate['date'] == hour2].iloc[0]['open'] + val3 = rate[rate['date'] == hour3].iloc[0]['open'] + + # Test For last 4 hours + # Avoids random test-failure when funding-fees are 0 for a few hours. + assert val0 != 0.0 or val1 != 0.0 or val2 != 0.0 or val3 != 0.0 + # We expect funding rates to be different from 0.0 - or moving around. + assert ( + rate['open'].max() != 0.0 or rate['open'].min() != 0.0 or + (rate['open'].min() != rate['open'].max()) + ) + + def test_ccxt_fetch_mark_price_history(self, exchange_futures): + exchange, exchangename = exchange_futures + if not exchange: + # exchange_futures only returns values for supported exchanges + return + pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair']) + since = int((datetime.now(timezone.utc) - timedelta(days=5)).timestamp() * 1000) + pair_tf = (pair, '1h', CandleType.MARK) + + mark_ohlcv = exchange.refresh_latest_ohlcv( + [pair_tf], + since_ms=since, + drop_incomplete=False) + + assert isinstance(mark_ohlcv, dict) + expected_tf = '1h' + mark_candles = mark_ohlcv[pair_tf] + + this_hour = timeframe_to_prev_date(expected_tf) + prev_hour = timeframe_to_prev_date(expected_tf, this_hour - timedelta(minutes=1)) + + assert mark_candles[mark_candles['date'] == prev_hour].iloc[0]['open'] != 0.0 + assert mark_candles[mark_candles['date'] == this_hour].iloc[0]['open'] != 0.0 + + def test_ccxt__calculate_funding_fees(self, exchange_futures): + exchange, exchangename = exchange_futures + if not exchange: + # exchange_futures only returns values for supported exchanges + return + pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair']) + since = datetime.now(timezone.utc) - timedelta(days=5) + + funding_fee = exchange._fetch_and_calculate_funding_fees( + pair, 20, is_short=False, open_date=since) + + assert isinstance(funding_fee, float) + # assert funding_fee > 0 + # TODO: tests fetch_trades (?) def test_ccxt_get_fee(self, exchange): @@ -182,3 +321,110 @@ class TestCCXTExchange(): assert 0 < exchange.get_fee(pair, 'limit', 'sell') < threshold assert 0 < exchange.get_fee(pair, 'market', 'buy') < threshold assert 0 < exchange.get_fee(pair, 'market', 'sell') < threshold + + def test_ccxt_get_max_leverage_spot(self, exchange): + spot, spot_name = exchange + if spot: + leverage_in_market_spot = EXCHANGES[spot_name].get('leverage_in_spot_market') + if leverage_in_market_spot: + spot_pair = EXCHANGES[spot_name].get('pair', EXCHANGES[spot_name]['pair']) + spot_leverage = spot.get_max_leverage(spot_pair, 20) + assert (isinstance(spot_leverage, float) or isinstance(spot_leverage, int)) + assert spot_leverage >= 1.0 + + def test_ccxt_get_max_leverage_futures(self, exchange_futures): + futures, futures_name = exchange_futures + if futures: + leverage_tiers_public = EXCHANGES[futures_name].get('leverage_tiers_public') + if leverage_tiers_public: + futures_pair = EXCHANGES[futures_name].get( + 'futures_pair', + EXCHANGES[futures_name]['pair'] + ) + futures_leverage = futures.get_max_leverage(futures_pair, 20) + assert (isinstance(futures_leverage, float) or isinstance(futures_leverage, int)) + assert futures_leverage >= 1.0 + + def test_ccxt__get_contract_size(self, exchange_futures): + futures, futures_name = exchange_futures + if futures: + futures_pair = EXCHANGES[futures_name].get( + 'futures_pair', + EXCHANGES[futures_name]['pair'] + ) + contract_size = futures._get_contract_size(futures_pair) + assert (isinstance(contract_size, float) or isinstance(contract_size, int)) + assert contract_size >= 0.0 + + def test_ccxt_load_leverage_tiers(self, exchange_futures): + futures, futures_name = exchange_futures + if futures and EXCHANGES[futures_name].get('leverage_tiers_public'): + leverage_tiers = futures.load_leverage_tiers() + futures_pair = EXCHANGES[futures_name].get( + 'futures_pair', + EXCHANGES[futures_name]['pair'] + ) + assert (isinstance(leverage_tiers, dict)) + assert futures_pair in leverage_tiers + pair_tiers = leverage_tiers[futures_pair] + assert len(pair_tiers) > 0 + oldLeverage = float('inf') + oldMaintenanceMarginRate = oldNotionalFloor = oldNotionalCap = -1 + for tier in pair_tiers: + for key in [ + 'maintenanceMarginRate', + 'notionalFloor', + 'notionalCap', + 'maxLeverage' + ]: + assert key in tier + assert tier[key] >= 0.0 + assert tier['notionalCap'] > tier['notionalFloor'] + assert tier['maxLeverage'] <= oldLeverage + assert tier['maintenanceMarginRate'] >= oldMaintenanceMarginRate + assert tier['notionalFloor'] > oldNotionalFloor + assert tier['notionalCap'] > oldNotionalCap + oldLeverage = tier['maxLeverage'] + oldMaintenanceMarginRate = tier['maintenanceMarginRate'] + oldNotionalFloor = tier['notionalFloor'] + oldNotionalCap = tier['notionalCap'] + + def test_ccxt_dry_run_liquidation_price(self, exchange_futures): + futures, futures_name = exchange_futures + if futures and EXCHANGES[futures_name].get('leverage_tiers_public'): + + futures_pair = EXCHANGES[futures_name].get( + 'futures_pair', + EXCHANGES[futures_name]['pair'] + ) + + liquidation_price = futures.dry_run_liquidation_price( + futures_pair, + 40000, + False, + 100, + 100, + ) + assert (isinstance(liquidation_price, float)) + assert liquidation_price >= 0.0 + + liquidation_price = futures.dry_run_liquidation_price( + futures_pair, + 40000, + False, + 100, + 100, + ) + assert (isinstance(liquidation_price, float)) + assert liquidation_price >= 0.0 + + def test_ccxt_get_max_pair_stake_amount(self, exchange_futures): + futures, futures_name = exchange_futures + if futures: + futures_pair = EXCHANGES[futures_name].get( + 'futures_pair', + EXCHANGES[futures_name]['pair'] + ) + max_stake_amount = futures.get_max_pair_stake_amount(futures_pair, 40000) + assert (isinstance(max_stake_amount, float)) + assert max_stake_amount >= 0.0 diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index b76cb23e6..14d5e6472 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -11,6 +11,7 @@ import ccxt import pytest from pandas import DataFrame +from freqtrade.enums import CandleType, MarginMode, TradingMode from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException, OperationalException, PricingError, TemporaryError) from freqtrade.exchange import Binance, Bittrex, Exchange, Kraken @@ -24,7 +25,7 @@ from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has # Make sure to always keep one exchange here which is NOT subclassed!! -EXCHANGES = ['bittrex', 'binance', 'kraken', 'ftx'] +EXCHANGES = ['bittrex', 'binance', 'kraken', 'ftx', 'gateio'] def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, @@ -131,6 +132,7 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog): assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog) assert ex._api.headers == {'hello': 'world'} + assert ex._ccxt_config == {} Exchange._headers = {} @@ -165,6 +167,7 @@ def test_exchange_resolver(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') exchange = ExchangeResolver.load_exchange('zaif', default_conf) assert isinstance(exchange, Exchange) @@ -222,27 +225,46 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog): ex.validate_order_time_in_force(tif2) -@pytest.mark.parametrize("amount,precision_mode,precision,expected", [ - (2.34559, 2, 4, 2.3455), - (2.34559, 2, 5, 2.34559), - (2.34559, 2, 3, 2.345), - (2.9999, 2, 3, 2.999), - (2.9909, 2, 3, 2.990), +@pytest.mark.parametrize("amount,precision_mode,precision,contract_size,expected,trading_mode", [ + (2.34559, 2, 4, 1, 2.3455, 'spot'), + (2.34559, 2, 5, 1, 2.34559, 'spot'), + (2.34559, 2, 3, 1, 2.345, 'spot'), + (2.9999, 2, 3, 1, 2.999, 'spot'), + (2.9909, 2, 3, 1, 2.990, 'spot'), # Tests for Tick-size - (2.34559, 4, 0.0001, 2.3455), - (2.34559, 4, 0.00001, 2.34559), - (2.34559, 4, 0.001, 2.345), - (2.9999, 4, 0.001, 2.999), - (2.9909, 4, 0.001, 2.990), - (2.9909, 4, 0.005, 2.990), - (2.9999, 4, 0.005, 2.995), + (2.34559, 4, 0.0001, 1, 2.3455, 'spot'), + (2.34559, 4, 0.00001, 1, 2.34559, 'spot'), + (2.34559, 4, 0.001, 1, 2.345, 'spot'), + (2.9999, 4, 0.001, 1, 2.999, 'spot'), + (2.9909, 4, 0.001, 1, 2.990, 'spot'), + (2.9909, 4, 0.005, 0.01, 2.99, 'futures'), + (2.9999, 4, 0.005, 10, 2.995, 'futures'), ]) -def test_amount_to_precision(default_conf, mocker, amount, precision_mode, precision, expected): +def test_amount_to_precision( + default_conf, + mocker, + amount, + precision_mode, + precision, + contract_size, + expected, + trading_mode +): """ Test rounds down """ - markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': precision}}}) + markets = PropertyMock(return_value={ + 'ETH/BTC': { + 'contractSize': contract_size, + 'precision': { + 'amount': precision + } + } + }) + + default_conf['trading_mode'] = trading_mode + default_conf['margin_mode'] = 'isolated' exchange = get_patched_exchange(mocker, default_conf, id="binance") # digits counting mode @@ -321,7 +343,7 @@ def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precisio assert pytest.approx(exchange.price_get_one_pip(pair, price)) == expected -def test_get_min_pair_stake_amount(mocker, default_conf) -> None: +def test__get_stake_amount_limit(mocker, default_conf) -> None: exchange = get_patched_exchange(mocker, default_conf, id="binance") stoploss = -0.05 @@ -335,23 +357,10 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: with pytest.raises(ValueError, match=r'.*get market information.*'): exchange.get_min_pair_stake_amount('BNB/BTC', 1, stoploss) - # no 'limits' section - result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) - assert result is None - - # empty 'limits' section - markets["ETH/BTC"]["limits"] = {} - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) - result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) - assert result is None - - # no cost Min + # no cost/amount Min markets["ETH/BTC"]["limits"] = { - 'cost': {"min": None}, - 'amount': {} + 'cost': {'min': None, 'max': None}, + 'amount': {'min': None, 'max': None}, } mocker.patch( 'freqtrade.exchange.Exchange.markets', @@ -359,85 +368,131 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) assert result is None + result = exchange.get_max_pair_stake_amount('ETH/BTC', 1) + assert result == float('inf') - # no amount Min + # min/max cost is set markets["ETH/BTC"]["limits"] = { - 'cost': {}, - 'amount': {"min": None} + 'cost': {'min': 2, 'max': 10000}, + 'amount': {'min': None, 'max': None}, } mocker.patch( 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) ) + # min result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) - assert result is None - - # empty 'cost'/'amount' section - markets["ETH/BTC"]["limits"] = { - 'cost': {}, - 'amount': {} - } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) - result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) - assert result is None - - # min cost is set - markets["ETH/BTC"]["limits"] = { - 'cost': {'min': 2}, - 'amount': {} - } - mocker.patch( - 'freqtrade.exchange.Exchange.markets', - PropertyMock(return_value=markets) - ) - result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) - assert isclose(result, 2 * (1+0.05) / (1-abs(stoploss))) + expected_result = 2 * (1+0.05) / (1-abs(stoploss)) + assert isclose(result, expected_result) + # With Leverage + result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss, 3.0) + assert isclose(result, expected_result/3) + # max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) + assert result == 10000 # min amount is set markets["ETH/BTC"]["limits"] = { - 'cost': {}, - 'amount': {'min': 2} + 'cost': {'min': None, 'max': None}, + 'amount': {'min': 2, 'max': 10000}, } mocker.patch( 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) - assert isclose(result, 2 * 2 * (1+0.05) / (1-abs(stoploss))) + expected_result = 2 * 2 * (1+0.05) / (1-abs(stoploss)) + assert isclose(result, expected_result) + # With Leverage + result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0) + assert isclose(result, expected_result/5) + # max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) + assert result == 20000 # min amount and cost are set (cost is minimal) markets["ETH/BTC"]["limits"] = { - 'cost': {'min': 2}, - 'amount': {'min': 2} + 'cost': {'min': 2, 'max': None}, + 'amount': {'min': 2, 'max': None}, } mocker.patch( 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) - assert isclose(result, max(2, 2 * 2) * (1+0.05) / (1-abs(stoploss))) + expected_result = max(2, 2 * 2) * (1+0.05) / (1-abs(stoploss)) + assert isclose(result, expected_result) + # With Leverage + result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10) + assert isclose(result, expected_result/10) # min amount and cost are set (amount is minial) markets["ETH/BTC"]["limits"] = { - 'cost': {'min': 8}, - 'amount': {'min': 2} + 'cost': {'min': 8, 'max': 10000}, + 'amount': {'min': 2, 'max': 500}, } mocker.patch( 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) - assert isclose(result, max(8, 2 * 2) * (1+0.05) / (1-abs(stoploss))) + expected_result = max(8, 2 * 2) * (1+0.05) / (1-abs(stoploss)) + assert isclose(result, expected_result) + # With Leverage + result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0) + assert isclose(result, expected_result/7.0) + # Max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) + assert result == 1000 result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4) - assert isclose(result, max(8, 2 * 2) * 1.5) + expected_result = max(8, 2 * 2) * 1.5 + assert isclose(result, expected_result) + # With Leverage + result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0) + assert isclose(result, expected_result/8.0) + # Max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) + assert result == 1000 # Really big stoploss result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1) - assert isclose(result, max(8, 2 * 2) * 1.5) + expected_result = max(8, 2 * 2) * 1.5 + assert isclose(result, expected_result) + # With Leverage + result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) + assert isclose(result, expected_result/12) + # Max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) + assert result == 1000 + + markets["ETH/BTC"]["contractSize"] = '0.01' + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + exchange = get_patched_exchange(mocker, default_conf, id="binance") + mocker.patch( + 'freqtrade.exchange.Exchange.markets', + PropertyMock(return_value=markets) + ) + + # Contract size 0.01 + result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1) + assert isclose(result, expected_result * 0.01) + # Max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) + assert result == 10 + + markets["ETH/BTC"]["contractSize"] = '10' + mocker.patch( + 'freqtrade.exchange.Exchange.markets', + PropertyMock(return_value=markets) + ) + # With Leverage, Contract size 10 + result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) + assert isclose(result, (expected_result/12) * 10.0) + # Max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) + assert result == 10000 def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: @@ -445,20 +500,34 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: stoploss = -0.05 markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} - # Real Binance data + # ~Real Binance data markets["ETH/BTC"]["limits"] = { - 'cost': {'min': 0.0001}, - 'amount': {'min': 0.001} + 'cost': {'min': 0.0001, 'max': 4000}, + 'amount': {'min': 0.001, 'max': 10000}, } mocker.patch( 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss) - assert round(result, 8) == round( - max(0.0001, 0.001 * 0.020405) * (1+0.05) / (1-abs(stoploss)), - 8 - ) + expected_result = max(0.0001, 0.001 * 0.020405) * (1+0.05) / (1-abs(stoploss)) + assert round(result, 8) == round(expected_result, 8) + # Max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 2.0) + assert result == 4000 + + # Leverage + result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0) + assert round(result, 8) == round(expected_result/3, 8) + + # Contract_size + markets["ETH/BTC"]["contractSize"] = 0.1 + result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0) + assert round(result, 8) == round((expected_result/3), 8) + + # Max + result = exchange.get_max_pair_stake_amount('ETH/BTC', 12.0) + assert result == 4000 def test_set_sandbox(default_conf, mocker): @@ -502,6 +571,7 @@ def test__load_async_markets(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange._load_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') exchange = Exchange(default_conf) exchange._api_async.load_markets = get_mock_coro(None) exchange._load_async_markets() @@ -523,6 +593,7 @@ def test__load_markets(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') Exchange(default_conf) assert log_has('Unable to initialize markets.', caplog) @@ -591,6 +662,7 @@ def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange._load_async_markets') + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') Exchange(default_conf) @@ -621,7 +693,7 @@ def test_validate_stakecurrency_error(default_conf, mocker, caplog): def test_get_quote_currencies(default_conf, mocker): ex = get_patched_exchange(mocker, default_conf) - assert set(ex.get_quote_currencies()) == set(['USD', 'ETH', 'BTC', 'USDT']) + assert set(ex.get_quote_currencies()) == set(['USD', 'ETH', 'BTC', 'USDT', 'BUSD']) @pytest.mark.parametrize('pair,expected', [ @@ -663,6 +735,7 @@ def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs d mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') Exchange(default_conf) @@ -689,6 +762,7 @@ def test_validate_pairs_exception(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') mocker.patch('freqtrade.exchange.Exchange._load_async_markets') with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available on Binance'): @@ -709,6 +783,7 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange._load_async_markets') + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') Exchange(default_conf) @@ -728,6 +803,7 @@ def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') Exchange(default_conf) @@ -744,6 +820,7 @@ def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, ca mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') Exchange(default_conf) assert type(api_mock).load_markets.call_count == 1 @@ -784,6 +861,7 @@ def test_validate_timeframes(default_conf, mocker, timeframe): mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') Exchange(default_conf) @@ -868,10 +946,49 @@ def test_validate_timeframes_not_in_config(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') Exchange(default_conf) -def test_validate_order_types(default_conf, mocker): +def test_validate_pricing(default_conf, mocker): + api_mock = MagicMock() + has = { + 'fetchL2OrderBook': True, + 'fetchTicker': True, + } + type(api_mock).has = PropertyMock(return_value=has) + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_margin_mode') + 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', 'Binance') + ExchangeResolver.load_exchange('binance', default_conf) + has.update({'fetchTicker': False}) + with pytest.raises(OperationalException, match="Ticker pricing not available for .*"): + ExchangeResolver.load_exchange('binance', default_conf) + + has.update({'fetchTicker': True}) + + default_conf['exit_pricing']['use_order_book'] = True + ExchangeResolver.load_exchange('binance', default_conf) + has.update({'fetchL2OrderBook': False}) + + with pytest.raises(OperationalException, match="Orderbook not available for .*"): + ExchangeResolver.load_exchange('binance', default_conf) + + has.update({'fetchL2OrderBook': True}) + + # Binance has no tickers on futures + default_conf['trading_mode'] = TradingMode.FUTURES + default_conf['margin_mode'] = MarginMode.ISOLATED + + with pytest.raises(OperationalException, match="Ticker pricing not available for .*"): + ExchangeResolver.load_exchange('binance', default_conf) + + +def test_validate_ordertypes(default_conf, mocker): api_mock = MagicMock() type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True}) @@ -880,11 +997,12 @@ def test_validate_order_types(default_conf, mocker): 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.validate_pricing') mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') default_conf['order_types'] = { - 'buy': 'limit', - 'sell': 'limit', + 'entry': 'limit', + 'exit': 'limit', 'stoploss': 'market', 'stoploss_on_exchange': False } @@ -894,8 +1012,8 @@ def test_validate_order_types(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) default_conf['order_types'] = { - 'buy': 'limit', - 'sell': 'limit', + 'entry': 'limit', + 'exit': 'limit', 'stoploss': 'market', 'stoploss_on_exchange': False } @@ -904,8 +1022,8 @@ def test_validate_order_types(default_conf, mocker): Exchange(default_conf) default_conf['order_types'] = { - 'buy': 'limit', - 'sell': 'limit', + 'entry': 'limit', + 'exit': 'limit', 'stoploss': 'limit', 'stoploss_on_exchange': True } @@ -920,6 +1038,7 @@ def test_validate_order_types_not_in_config(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') conf = copy.deepcopy(default_conf) @@ -934,6 +1053,7 @@ def test_validate_required_startup_candles(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_pairs') + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') default_conf['startup_candle_count'] = 20 @@ -972,22 +1092,33 @@ def test_exchange_has(default_conf, mocker): assert not exchange.exchange_has("deadbeef") -@pytest.mark.parametrize("side", [ - ("buy"), - ("sell") +@pytest.mark.parametrize("side,leverage", [ + ("buy", 1), + ("buy", 5), + ("sell", 1.0), + ("sell", 5.0), ]) @pytest.mark.parametrize("exchange_name", EXCHANGES) -def test_create_dry_run_order(default_conf, mocker, side, exchange_name): +def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverage): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) order = exchange.create_dry_run_order( - pair='ETH/BTC', ordertype='limit', side=side, amount=1, rate=200) + pair='ETH/BTC', + ordertype='limit', + side=side, + amount=1, + rate=200, + leverage=leverage + ) assert 'id' in order assert f'dry_run_{side}_' in order["id"] assert order["side"] == side assert order["type"] == "limit" assert order["symbol"] == "ETH/BTC" + assert order["amount"] == 1 + assert order["leverage"] == leverage + assert order["cost"] == 1 * 200 / leverage @pytest.mark.parametrize("side,startprice,endprice", [ @@ -1005,7 +1136,13 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, startprice, ) order = exchange.create_dry_run_order( - pair='LTC/USDT', ordertype='limit', side=side, amount=1, rate=startprice) + pair='LTC/USDT', + ordertype='limit', + side=side, + amount=1, + rate=startprice, + leverage=1.0 + ) assert order_book_l2_usd.call_count == 1 assert 'id' in order assert f'dry_run_{side}_' in order["id"] @@ -1060,7 +1197,13 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou ) order = exchange.create_dry_run_order( - pair='LTC/USDT', ordertype='market', side=side, amount=amount, rate=rate) + pair='LTC/USDT', + ordertype='market', + side=side, + amount=amount, + rate=rate, + leverage=1.0 + ) assert 'id' in order assert f'dry_run_{side}_' in order["id"] assert order["side"] == side @@ -1071,10 +1214,7 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou assert round(order["average"], 4) == round(endprice, 4) -@pytest.mark.parametrize("side", [ - ("buy"), - ("sell") -]) +@pytest.mark.parametrize("side", ["buy", "sell"]) @pytest.mark.parametrize("ordertype,rate,marketprice", [ ("market", None, None), ("market", 200, True), @@ -1090,24 +1230,63 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, 'id': order_id, 'info': { 'foo': 'bar' - } + }, + 'symbol': 'XLTCUSDT', + 'amount': 1 }) default_conf['dry_run'] = False + default_conf['margin_mode'] = 'isolated' mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange._set_leverage = MagicMock() + exchange.set_margin_mode = MagicMock() order = exchange.create_order( - pair='ETH/BTC', ordertype=ordertype, side=side, amount=1, rate=rate) + pair='XLTCUSDT', + ordertype=ordertype, + side=side, + amount=1, + rate=rate, + leverage=1.0 + ) assert 'id' in order assert 'info' in order assert order['id'] == order_id - assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' + assert order['amount'] == 1 + assert api_mock.create_order.call_args[0][0] == 'XLTCUSDT' assert api_mock.create_order.call_args[0][1] == ordertype assert api_mock.create_order.call_args[0][2] == side assert api_mock.create_order.call_args[0][3] == 1 assert api_mock.create_order.call_args[0][4] is rate + assert exchange._set_leverage.call_count == 0 + assert exchange.set_margin_mode.call_count == 0 + + api_mock.create_order = MagicMock(return_value={ + 'id': order_id, + 'info': { + 'foo': 'bar' + }, + 'symbol': 'ADA/USDT:USDT', + 'amount': 1 + }) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange.trading_mode = TradingMode.FUTURES + exchange._set_leverage = MagicMock() + exchange.set_margin_mode = MagicMock() + order = exchange.create_order( + pair='ADA/USDT:USDT', + ordertype=ordertype, + side=side, + amount=1, + rate=200, + leverage=3.0 + ) + + assert exchange._set_leverage.call_count == 1 + assert exchange.set_margin_mode.call_count == 1 + assert order['amount'] == 0.01 def test_buy_dry_run(default_conf, mocker): @@ -1115,7 +1294,8 @@ def test_buy_dry_run(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf) order = exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy", - amount=1, rate=200, time_in_force='gtc') + amount=1, rate=200, leverage=1.0, + time_in_force='gtc') assert 'id' in order assert 'dry_run_buy_' in order['id'] @@ -1129,6 +1309,7 @@ def test_buy_prod(default_conf, mocker, exchange_name): api_mock.options = {} api_mock.create_order = MagicMock(return_value={ 'id': order_id, + 'symbol': 'ETH/BTC', 'info': { 'foo': 'bar' } @@ -1139,7 +1320,8 @@ def test_buy_prod(default_conf, mocker, exchange_name): exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -1158,7 +1340,9 @@ def test_buy_prod(default_conf, mocker, exchange_name): side="buy", amount=1, rate=200, - time_in_force=time_in_force) + leverage=1.0, + time_in_force=time_in_force + ) assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' assert api_mock.create_order.call_args[0][1] == order_type assert api_mock.create_order.call_args[0][2] == 'buy' @@ -1170,31 +1354,36 @@ def test_buy_prod(default_conf, mocker, exchange_name): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("Not enough funds")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.create_order(pair='ETH/BTC', ordertype='market', side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("Network disconnect")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) with pytest.raises(OperationalException): api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("Unknown error")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) @pytest.mark.parametrize("exchange_name", EXCHANGES) @@ -1204,6 +1393,7 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): api_mock.options = {} api_mock.create_order = MagicMock(return_value={ 'id': order_id, + 'symbol': 'ETH/BTC', 'info': { 'foo': 'bar' } @@ -1217,7 +1407,8 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): time_in_force = 'ioc' order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -1234,7 +1425,8 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): time_in_force = 'ioc' order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -1253,7 +1445,7 @@ def test_sell_dry_run(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf) order = exchange.create_order(pair='ETH/BTC', ordertype='limit', - side="sell", amount=1, rate=200) + side="sell", amount=1, rate=200, leverage=1.0) assert 'id' in order assert 'dry_run_sell_' in order['id'] @@ -1266,6 +1458,7 @@ def test_sell_prod(default_conf, mocker, exchange_name): api_mock.options = {} api_mock.create_order = MagicMock(return_value={ 'id': order_id, + 'symbol': 'ETH/BTC', 'info': { 'foo': 'bar' } @@ -1277,7 +1470,7 @@ def test_sell_prod(default_conf, mocker, exchange_name): exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, - side="sell", amount=1, rate=200) + side="sell", amount=1, rate=200, leverage=1.0) assert 'id' in order assert 'info' in order @@ -1291,7 +1484,8 @@ def test_sell_prod(default_conf, mocker, exchange_name): api_mock.create_order.reset_mock() order_type = 'limit' order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, - side="sell", amount=1, rate=200) + side="sell", amount=1, rate=200, + leverage=1.0) assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' assert api_mock.create_order.call_args[0][1] == order_type assert api_mock.create_order.call_args[0][2] == 'sell' @@ -1302,28 +1496,33 @@ def test_sell_prod(default_conf, mocker, exchange_name): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200, + leverage=1.0) with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype='limit', side="sell", amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype='limit', side="sell", amount=1, rate=200, + leverage=1.0) # Market orders don't require price, so the behaviour is slightly different with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype='market', side="sell", amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype='market', side="sell", amount=1, rate=200, + leverage=1.0) with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No Connection")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200, + leverage=1.0) with pytest.raises(OperationalException): api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200, + leverage=1.0) @pytest.mark.parametrize("exchange_name", EXCHANGES) @@ -1332,6 +1531,7 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6)) api_mock.create_order = MagicMock(return_value={ 'id': order_id, + 'symbol': 'ETH/BTC', 'info': { 'foo': 'bar' } @@ -1346,7 +1546,8 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): time_in_force = 'ioc' order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -1362,7 +1563,8 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): order_type = 'market' time_in_force = 'ioc' order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -1401,6 +1603,136 @@ def test_get_balances_prod(default_conf, mocker, exchange_name): "get_balances", "fetch_balance") +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_fetch_positions(default_conf, mocker, exchange_name): + mocker.patch('freqtrade.exchange.Exchange.validate_trading_mode_and_margin_mode') + api_mock = MagicMock() + api_mock.fetch_positions = MagicMock(return_value=[ + {'symbol': 'ETH/USDT:USDT', 'leverage': 5}, + {'symbol': 'XRP/USDT:USDT', 'leverage': 5}, + ]) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + assert exchange.fetch_positions() == [] + default_conf['dry_run'] = False + default_conf['trading_mode'] = 'futures' + + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + res = exchange.fetch_positions() + assert len(res) == 2 + + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, + "fetch_positions", "fetch_positions") + + +def test_fetch_trading_fees(default_conf, mocker): + api_mock = MagicMock() + tick = { + '1INCH/USDT:USDT': { + 'info': {'user_id': '', + 'taker_fee': '0.0018', + 'maker_fee': '0.0018', + 'gt_discount': False, + 'gt_taker_fee': '0', + 'gt_maker_fee': '0', + 'loan_fee': '0.18', + 'point_type': '1', + 'futures_taker_fee': '0.0005', + 'futures_maker_fee': '0'}, + 'symbol': '1INCH/USDT:USDT', + 'maker': 0.0, + 'taker': 0.0005}, + 'ETH/USDT:USDT': { + 'info': {'user_id': '', + 'taker_fee': '0.0018', + 'maker_fee': '0.0018', + 'gt_discount': False, + 'gt_taker_fee': '0', + 'gt_maker_fee': '0', + 'loan_fee': '0.18', + 'point_type': '1', + 'futures_taker_fee': '0.0005', + 'futures_maker_fee': '0'}, + 'symbol': 'ETH/USDT:USDT', + 'maker': 0.0, + 'taker': 0.0005} + } + exchange_name = 'gateio' + default_conf['dry_run'] = False + default_conf['trading_mode'] = TradingMode.FUTURES + default_conf['margin_mode'] = MarginMode.ISOLATED + api_mock.fetch_trading_fees = MagicMock(return_value=tick) + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + + assert '1INCH/USDT:USDT' in exchange._trading_fees + assert 'ETH/USDT:USDT' in exchange._trading_fees + assert api_mock.fetch_trading_fees.call_count == 1 + + api_mock.fetch_trading_fees.reset_mock() + + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, + "fetch_trading_fees", "fetch_trading_fees") + + api_mock.fetch_trading_fees = MagicMock(return_value={}) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange.fetch_trading_fees() + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + assert exchange.fetch_trading_fees() == {} + + +def test_fetch_bids_asks(default_conf, mocker): + api_mock = MagicMock() + tick = {'ETH/BTC': { + 'symbol': 'ETH/BTC', + 'bid': 0.5, + 'ask': 1, + 'last': 42, + }, 'BCH/BTC': { + 'symbol': 'BCH/BTC', + 'bid': 0.6, + 'ask': 0.5, + 'last': 41, + } + } + exchange_name = 'binance' + api_mock.fetch_bids_asks = MagicMock(return_value=tick) + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + # retrieve original ticker + bidsasks = exchange.fetch_bids_asks() + + assert 'ETH/BTC' in bidsasks + assert 'BCH/BTC' in bidsasks + assert bidsasks['ETH/BTC']['bid'] == 0.5 + assert bidsasks['ETH/BTC']['ask'] == 1 + assert bidsasks['BCH/BTC']['bid'] == 0.6 + assert bidsasks['BCH/BTC']['ask'] == 0.5 + assert api_mock.fetch_bids_asks.call_count == 1 + + api_mock.fetch_bids_asks.reset_mock() + + # Cached ticker should not call api again + tickers2 = exchange.fetch_bids_asks(cached=True) + assert tickers2 == bidsasks + assert api_mock.fetch_bids_asks.call_count == 0 + tickers2 = exchange.fetch_bids_asks(cached=False) + assert api_mock.fetch_bids_asks.call_count == 1 + + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, + "fetch_bids_asks", "fetch_bids_asks") + + with pytest.raises(OperationalException): + api_mock.fetch_bids_asks = MagicMock(side_effect=ccxt.NotSupported("DeadBeef")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange.fetch_bids_asks() + + api_mock.fetch_bids_asks = MagicMock(return_value={}) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange.fetch_bids_asks() + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + assert exchange.fetch_bids_asks() == {} + + @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_tickers(default_conf, mocker, exchange_name): api_mock = MagicMock() @@ -1417,6 +1749,7 @@ def test_get_tickers(default_conf, mocker, exchange_name): } } api_mock.fetch_tickers = MagicMock(return_value=tick) + api_mock.fetch_bids_asks = MagicMock(return_value={}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # retrieve original ticker tickers = exchange.get_tickers() @@ -1428,6 +1761,7 @@ def test_get_tickers(default_conf, mocker, exchange_name): assert tickers['BCH/BTC']['bid'] == 0.6 assert tickers['BCH/BTC']['ask'] == 0.5 assert api_mock.fetch_tickers.call_count == 1 + assert api_mock.fetch_bids_asks.call_count == 0 api_mock.fetch_tickers.reset_mock() @@ -1435,8 +1769,10 @@ def test_get_tickers(default_conf, mocker, exchange_name): tickers2 = exchange.get_tickers(cached=True) assert tickers2 == tickers assert api_mock.fetch_tickers.call_count == 0 + assert api_mock.fetch_bids_asks.call_count == 0 tickers2 = exchange.get_tickers(cached=False) assert api_mock.fetch_tickers.call_count == 1 + assert api_mock.fetch_bids_asks.call_count == 0 ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, "get_tickers", "fetch_tickers") @@ -1450,6 +1786,17 @@ def test_get_tickers(default_conf, mocker, exchange_name): exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_tickers() + api_mock.fetch_tickers.reset_mock() + api_mock.fetch_bids_asks.reset_mock() + default_conf['trading_mode'] = TradingMode.FUTURES + default_conf['margin_mode'] = MarginMode.ISOLATED + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + + exchange.get_tickers() + assert api_mock.fetch_tickers.call_count == 1 + assert api_mock.fetch_bids_asks.call_count == (1 if exchange_name == 'binance' else 0) + @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_fetch_ticker(default_conf, mocker, exchange_name): @@ -1500,7 +1847,8 @@ def test_fetch_ticker(default_conf, mocker, exchange_name): @pytest.mark.parametrize("exchange_name", EXCHANGES) -def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): +@pytest.mark.parametrize('candle_type', ['mark', '']) +def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_type): exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) ohlcv = [ [ @@ -1514,15 +1862,19 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): ] pair = 'ETH/BTC' - async def mock_candle_hist(pair, timeframe, since_ms): - return pair, timeframe, ohlcv + async def mock_candle_hist(pair, timeframe, candle_type, since_ms): + return pair, timeframe, candle_type, ohlcv exchange._async_get_candle_history = Mock(wraps=mock_candle_hist) # one_call calculation * 1.8 should do 2 calls since = 5 * 60 * exchange.ohlcv_candle_limit('5m') * 1.8 - ret = exchange.get_historic_ohlcv(pair, "5m", int(( - arrow.utcnow().int_timestamp - since) * 1000)) + ret = exchange.get_historic_ohlcv( + pair, + "5m", + int((arrow.utcnow().int_timestamp - since) * 1000), + candle_type=candle_type + ) assert exchange._async_get_candle_history.call_count == 2 # Returns twice the above OHLCV data @@ -1535,13 +1887,18 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): raise TimeoutError() exchange._async_get_candle_history = MagicMock(side_effect=mock_get_candle_hist_error) - ret = exchange.get_historic_ohlcv(pair, "5m", int( - (arrow.utcnow().int_timestamp - since) * 1000)) + ret = exchange.get_historic_ohlcv( + pair, + "5m", + int((arrow.utcnow().int_timestamp - since) * 1000), + candle_type=candle_type + ) assert log_has_re(r"Async code raised an exception: .*", caplog) @pytest.mark.parametrize("exchange_name", EXCHANGES) -def test_get_historic_ohlcv_as_df(default_conf, mocker, exchange_name): +@pytest.mark.parametrize('candle_type', ['mark', '']) +def test_get_historic_ohlcv_as_df(default_conf, mocker, exchange_name, candle_type): exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) ohlcv = [ [ @@ -1571,15 +1928,19 @@ def test_get_historic_ohlcv_as_df(default_conf, mocker, exchange_name): ] pair = 'ETH/BTC' - async def mock_candle_hist(pair, timeframe, since_ms): - return pair, timeframe, ohlcv + async def mock_candle_hist(pair, timeframe, candle_type, since_ms): + return pair, timeframe, candle_type, ohlcv exchange._async_get_candle_history = Mock(wraps=mock_candle_hist) # one_call calculation * 1.8 should do 2 calls since = 5 * 60 * exchange.ohlcv_candle_limit('5m') * 1.8 - ret = exchange.get_historic_ohlcv_as_df(pair, "5m", int(( - arrow.utcnow().int_timestamp - since) * 1000)) + ret = exchange.get_historic_ohlcv_as_df( + pair, + "5m", + int((arrow.utcnow().int_timestamp - since) * 1000), + candle_type=candle_type + ) assert exchange._async_get_candle_history.call_count == 2 # Returns twice the above OHLCV data @@ -1593,7 +1954,8 @@ def test_get_historic_ohlcv_as_df(default_conf, mocker, exchange_name): @pytest.mark.asyncio @pytest.mark.parametrize("exchange_name", EXCHANGES) -async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): +@pytest.mark.parametrize('candle_type', [CandleType.MARK, CandleType.SPOT]) +async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_type): ohlcv = [ [ int((datetime.now(timezone.utc).timestamp() - 1000) * 1000), @@ -1609,8 +1971,8 @@ async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_ exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) pair = 'ETH/USDT' - respair, restf, res = await exchange._async_get_historic_ohlcv( - pair, "5m", 1500000000000, is_new_pair=False) + respair, restf, _, res = await exchange._async_get_historic_ohlcv( + pair, "5m", 1500000000000, candle_type=candle_type, is_new_pair=False) assert respair == pair assert restf == '5m' # Call with very old timestamp - causes tons of requests @@ -1618,7 +1980,8 @@ async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_ assert res[0] == ohlcv[0] -def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: +@pytest.mark.parametrize('candle_type', [CandleType.FUTURES, CandleType.MARK, CandleType.SPOT]) +def test_refresh_latest_ohlcv(mocker, default_conf, caplog, candle_type) -> None: ohlcv = [ [ (arrow.utcnow().int_timestamp - 1) * 1000, # unix timestamp ms @@ -1642,7 +2005,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: exchange = get_patched_exchange(mocker, default_conf) exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) - pairs = [('IOTA/ETH', '5m'), ('XRP/ETH', '5m')] + pairs = [('IOTA/ETH', '5m', candle_type), ('XRP/ETH', '5m', candle_type)] # empty dicts assert not exchange._klines res = exchange.refresh_latest_ohlcv(pairs, cache=False) @@ -1673,32 +2036,38 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: assert exchange.klines(pair, copy=False) is exchange.klines(pair, copy=False) # test caching - res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')]) + res = exchange.refresh_latest_ohlcv( + [('IOTA/ETH', '5m', candle_type), ('XRP/ETH', '5m', candle_type)]) assert len(res) == len(pairs) assert exchange._api_async.fetch_ohlcv.call_count == 0 exchange.required_candle_call_count = 1 - assert log_has(f"Using cached candle (OHLCV) data for pair {pairs[0][0]}, " - f"timeframe {pairs[0][1]} ...", + assert log_has(f"Using cached candle (OHLCV) data for {pairs[0][0]}, " + f"{pairs[0][1]}, {candle_type} ...", caplog) - res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m'), ('XRP/ETH', '1d')], - cache=False) + pairlist = [ + ('IOTA/ETH', '5m', candle_type), + ('XRP/ETH', '5m', candle_type), + ('XRP/ETH', '1d', candle_type)] + res = exchange.refresh_latest_ohlcv(pairlist, cache=False) assert len(res) == 3 assert exchange._api_async.fetch_ohlcv.call_count == 3 # Test the same again, should NOT return from cache! exchange._api_async.fetch_ohlcv.reset_mock() - res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m'), ('XRP/ETH', '1d')], - cache=False) + res = exchange.refresh_latest_ohlcv(pairlist, cache=False) assert len(res) == 3 assert exchange._api_async.fetch_ohlcv.call_count == 3 exchange._api_async.fetch_ohlcv.reset_mock() caplog.clear() # Call with invalid timeframe - res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '3m')], cache=False) - assert not res - assert len(res) == 0 - assert log_has_re(r'Cannot download \(IOTA\/ETH, 3m\).*', caplog) + res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '3m', candle_type)], cache=False) + if candle_type != CandleType.MARK: + assert not res + assert len(res) == 0 + assert log_has_re(r'Cannot download \(IOTA\/ETH, 3m\).*', caplog) + else: + assert len(res) == 1 @pytest.mark.asyncio @@ -1721,33 +2090,35 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) pair = 'ETH/BTC' - res = await exchange._async_get_candle_history(pair, "5m") + res = await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT) assert type(res) is tuple - assert len(res) == 3 + assert len(res) == 4 assert res[0] == pair assert res[1] == "5m" - assert res[2] == ohlcv + assert res[2] == CandleType.SPOT + assert res[3] == ohlcv assert exchange._api_async.fetch_ohlcv.call_count == 1 assert not log_has(f"Using cached candle (OHLCV) data for {pair} ...", caplog) # exchange = Exchange(default_conf) await async_ccxt_exception(mocker, default_conf, MagicMock(), "_async_get_candle_history", "fetch_ohlcv", - pair='ABCD/BTC', timeframe=default_conf['timeframe']) + pair='ABCD/BTC', timeframe=default_conf['timeframe'], + candle_type=CandleType.SPOT) api_mock = MagicMock() with pytest.raises(OperationalException, match=r'Could not fetch historical candle \(OHLCV\) data.*'): api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError("Unknown error")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - await exchange._async_get_candle_history(pair, "5m", + await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT, (arrow.utcnow().int_timestamp - 2000) * 1000) with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching ' r'historical candle \(OHLCV\) data\..*'): api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - await exchange._async_get_candle_history(pair, "5m", + await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT, (arrow.utcnow().int_timestamp - 2000) * 1000) @@ -1801,12 +2172,13 @@ async def test__async_get_candle_history_empty(default_conf, mocker, caplog): exchange = Exchange(default_conf) pair = 'ETH/BTC' - res = await exchange._async_get_candle_history(pair, "5m") + res = await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT) assert type(res) is tuple - assert len(res) == 3 + assert len(res) == 4 assert res[0] == pair assert res[1] == "5m" - assert res[2] == ohlcv + assert res[2] == CandleType.SPOT + assert res[3] == ohlcv assert exchange._api_async.fetch_ohlcv.call_count == 1 @@ -1823,7 +2195,7 @@ def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog): # Monkey-patch async function with empty result exchange._api_async.fetch_ohlcv = MagicMock(side_effect=mock_get_candle_hist) - pairs = [("ETH/BTC", "5m"), ("XRP/BTC", "5m")] + pairs = [("ETH/BTC", "5m", ''), ("XRP/BTC", "5m", '')] res = exchange.refresh_latest_ohlcv(pairs) assert exchange._klines assert exchange._api_async.fetch_ohlcv.call_count == 2 @@ -1904,6 +2276,7 @@ def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name): @pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", [ + ('other', 20, 19, 10, 0.0, 20), # Full ask side ('ask', 20, 19, 10, 0.0, 20), # Full ask side ('ask', 20, 19, 10, 1.0, 10), # Full last side ('ask', 20, 19, 10, 0.5, 15), # Between ask and last @@ -1911,45 +2284,46 @@ 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', 20, 19, 10, None, 20), # price_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 ('ask', 4, 5, None, 0, 4), # last not available - uses ask + ('same', 21, 20, 10, 0.0, 20), # Full bid side ('bid', 21, 20, 10, 0.0, 20), # Full bid side ('bid', 21, 20, 10, 1.0, 10), # Full last side ('bid', 21, 20, 10, 0.5, 15), # Between bid and last ('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', 21, 20, 10, None, 20), # price_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 ('bid', 6, 5, None, 1, 5), # last not available - uses bid ('bid', 6, 5, None, 0, 5), # last not available - uses bid ]) -def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid, - last, last_ab, expected) -> None: +def test_get_entry_rate(mocker, default_conf, caplog, side, ask, bid, + last, last_ab, expected) -> None: caplog.set_level(logging.DEBUG) if last_ab is None: - del default_conf['bid_strategy']['ask_last_balance'] + del default_conf['entry_pricing']['price_last_balance'] else: - default_conf['bid_strategy']['ask_last_balance'] = last_ab - default_conf['bid_strategy']['price_side'] = side + default_conf['entry_pricing']['price_last_balance'] = last_ab + default_conf['entry_pricing']['price_side'] = side exchange = get_patched_exchange(mocker, default_conf) mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': ask, 'last': last, 'bid': bid}) - assert exchange.get_rate('ETH/BTC', refresh=True, side="buy") == expected - assert not log_has("Using cached buy rate for ETH/BTC.", caplog) + assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=True) == expected + assert not log_has("Using cached entry rate for ETH/BTC.", caplog) - assert exchange.get_rate('ETH/BTC', refresh=False, side="buy") == expected - assert log_has("Using cached buy rate for ETH/BTC.", caplog) + assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=False) == expected + assert log_has("Using cached entry rate for ETH/BTC.", caplog) # Running a 2nd time with Refresh on! caplog.clear() - assert exchange.get_rate('ETH/BTC', refresh=True, side="buy") == expected - assert not log_has("Using cached buy rate for ETH/BTC.", caplog) + assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=True) == expected + assert not log_has("Using cached entry rate for ETH/BTC.", caplog) @pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', [ @@ -1973,112 +2347,120 @@ def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid, ('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, +def test_get_exit_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['exit_pricing']['price_side'] = side if last_ab is not None: - default_conf['ask_strategy']['bid_last_balance'] = last_ab + default_conf['exit_pricing']['price_last_balance'] = last_ab mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': ask, 'bid': bid, 'last': last}) pair = "ETH/BTC" # Test regular mode exchange = get_patched_exchange(mocker, default_conf) - rate = exchange.get_rate(pair, refresh=True, side="sell") - assert not log_has("Using cached sell rate for ETH/BTC.", caplog) + rate = exchange.get_rate(pair, side="exit", is_short=False, refresh=True) + assert not log_has("Using cached exit rate for ETH/BTC.", caplog) assert isinstance(rate, float) assert rate == expected # Use caching - rate = exchange.get_rate(pair, refresh=False, side="sell") + rate = exchange.get_rate(pair, side="exit", is_short=False, refresh=False) assert rate == expected - assert log_has("Using cached sell rate for ETH/BTC.", caplog) + assert log_has("Using cached exit rate for ETH/BTC.", caplog) -@pytest.mark.parametrize("entry,side,ask,bid,last,last_ab,expected", [ - ('buy', 'ask', None, 4, 4, 0, 4), # ask not available - ('buy', 'ask', None, None, 4, 0, 4), # ask not available - ('buy', 'bid', 6, None, 4, 0, 5), # bid not available - ('buy', 'bid', None, None, 4, 0, 5), # No rate available - ('sell', 'ask', None, 4, 4, 0, 4), # ask not available - ('sell', 'ask', None, None, 4, 0, 4), # ask not available - ('sell', 'bid', 6, None, 4, 0, 5), # bid not available - ('sell', 'bid', None, None, 4, 0, 5), # bid not available +@pytest.mark.parametrize("entry,is_short,side,ask,bid,last,last_ab,expected", [ + ('entry', False, 'ask', None, 4, 4, 0, 4), # ask not available + ('entry', False, 'ask', None, None, 4, 0, 4), # ask not available + ('entry', False, 'bid', 6, None, 4, 0, 5), # bid not available + ('entry', False, 'bid', None, None, 4, 0, 5), # No rate available + ('exit', False, 'ask', None, 4, 4, 0, 4), # ask not available + ('exit', False, 'ask', None, None, 4, 0, 4), # ask not available + ('exit', False, 'bid', 6, None, 4, 0, 5), # bid not available + ('exit', False, 'bid', None, None, 4, 0, 5), # bid not available ]) -def test_get_ticker_rate_error(mocker, entry, default_conf, caplog, side, ask, bid, +def test_get_ticker_rate_error(mocker, entry, default_conf, caplog, side, is_short, ask, bid, last, last_ab, expected) -> None: caplog.set_level(logging.DEBUG) - default_conf['bid_strategy']['ask_last_balance'] = last_ab - default_conf['bid_strategy']['price_side'] = side - default_conf['ask_strategy']['price_side'] = side - default_conf['ask_strategy']['ask_last_balance'] = last_ab + default_conf['entry_pricing']['price_last_balance'] = last_ab + default_conf['entry_pricing']['price_side'] = side + default_conf['exit_pricing']['price_side'] = side + default_conf['exit_pricing']['price_last_balance'] = last_ab exchange = get_patched_exchange(mocker, default_conf) mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': ask, 'last': last, 'bid': bid}) with pytest.raises(PricingError): - exchange.get_rate('ETH/BTC', refresh=True, side=entry) + exchange.get_rate('ETH/BTC', refresh=True, side=entry, is_short=is_short) -@pytest.mark.parametrize('side,expected', [ - ('bid', 0.043936), # Value from order_book_l2 fiture - bids side - ('ask', 0.043949), # Value from order_book_l2 fiture - asks side +@pytest.mark.parametrize('is_short,side,expected', [ + (False, 'bid', 0.043936), # Value from order_book_l2 fitxure - bids side + (False, 'ask', 0.043949), # Value from order_book_l2 fitxure - asks side + (False, 'other', 0.043936), # Value from order_book_l2 fitxure - bids side + (False, 'same', 0.043949), # Value from order_book_l2 fitxure - asks side + (True, 'bid', 0.043936), # Value from order_book_l2 fitxure - bids side + (True, 'ask', 0.043949), # Value from order_book_l2 fitxure - asks side + (True, 'other', 0.043949), # Value from order_book_l2 fitxure - asks side + (True, 'same', 0.043936), # Value from order_book_l2 fitxure - bids side ]) -def test_get_sell_rate_orderbook(default_conf, mocker, caplog, side, expected, order_book_l2): +def test_get_exit_rate_orderbook( + default_conf, mocker, caplog, is_short, side, expected, order_book_l2): caplog.set_level(logging.DEBUG) # Test orderbook mode - default_conf['ask_strategy']['price_side'] = side - default_conf['ask_strategy']['use_order_book'] = True - default_conf['ask_strategy']['order_book_top'] = 1 + default_conf['exit_pricing']['price_side'] = side + default_conf['exit_pricing']['use_order_book'] = True + default_conf['exit_pricing']['order_book_top'] = 1 pair = "ETH/BTC" mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2) exchange = get_patched_exchange(mocker, default_conf) - rate = exchange.get_rate(pair, refresh=True, side="sell") - assert not log_has("Using cached sell rate for ETH/BTC.", caplog) + rate = exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) + assert not log_has("Using cached exit rate for ETH/BTC.", caplog) assert isinstance(rate, float) assert rate == expected - rate = exchange.get_rate(pair, refresh=False, side="sell") + rate = exchange.get_rate(pair, refresh=False, side="exit", is_short=is_short) assert rate == expected - assert log_has("Using cached sell rate for ETH/BTC.", caplog) + assert log_has("Using cached exit rate for ETH/BTC.", caplog) -def test_get_sell_rate_orderbook_exception(default_conf, mocker, caplog): +def test_get_exit_rate_orderbook_exception(default_conf, mocker, caplog): # Test orderbook mode - default_conf['ask_strategy']['price_side'] = 'ask' - default_conf['ask_strategy']['use_order_book'] = True - default_conf['ask_strategy']['order_book_top'] = 1 + default_conf['exit_pricing']['price_side'] = 'ask' + default_conf['exit_pricing']['use_order_book'] = True + default_conf['exit_pricing']['order_book_top'] = 1 pair = "ETH/BTC" # Test What happens if the exchange returns an empty orderbook. mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', return_value={'bids': [[]], 'asks': [[]]}) exchange = get_patched_exchange(mocker, default_conf) with pytest.raises(PricingError): - exchange.get_rate(pair, refresh=True, side="sell") - assert log_has_re(r"Sell Price at location 1 from orderbook could not be determined\..*", + exchange.get_rate(pair, refresh=True, side="exit", is_short=False) + assert log_has_re(r"Exit Price at location 1 from orderbook could not be determined\..*", caplog) -def test_get_sell_rate_exception(default_conf, mocker, caplog): +@pytest.mark.parametrize('is_short', [True, False]) +def test_get_exit_rate_exception(default_conf, mocker, is_short): # Ticker on one side can be empty in certain circumstances. - default_conf['ask_strategy']['price_side'] = 'ask' + default_conf['exit_pricing']['price_side'] = 'ask' pair = "ETH/BTC" mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': None, 'bid': 0.12, 'last': None}) exchange = get_patched_exchange(mocker, default_conf) - with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."): - exchange.get_rate(pair, refresh=True, side="sell") + with pytest.raises(PricingError, match=r"Exit-Rate for ETH/BTC was empty."): + exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) - exchange._config['ask_strategy']['price_side'] = 'bid' - assert exchange.get_rate(pair, refresh=True, side="sell") == 0.12 + exchange._config['exit_pricing']['price_side'] = 'bid' + assert exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) == 0.12 # Reverse sides mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': 0.13, 'bid': None, 'last': None}) - with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."): - exchange.get_rate(pair, refresh=True, side="sell") + with pytest.raises(PricingError, match=r"Exit-Rate for ETH/BTC was empty."): + exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) - exchange._config['ask_strategy']['price_side'] = 'ask' - assert exchange.get_rate(pair, refresh=True, side="sell") == 0.13 + exchange._config['exit_pricing']['price_side'] = 'ask' + assert exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) == 0.13 @pytest.mark.parametrize("exchange_name", EXCHANGES) @@ -2105,9 +2487,10 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) sort_mock = mocker.patch('freqtrade.exchange.exchange.sorted', MagicMock(side_effect=sort_data)) # Test the OHLCV data sort - res = await exchange._async_get_candle_history('ETH/BTC', default_conf['timeframe']) + res = await exchange._async_get_candle_history( + 'ETH/BTC', default_conf['timeframe'], CandleType.SPOT) assert res[0] == 'ETH/BTC' - res_ohlcv = res[2] + res_ohlcv = res[3] assert sort_mock.call_count == 1 assert res_ohlcv[0][0] == 1527830400000 @@ -2142,10 +2525,11 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na # Reset sort mock sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data)) # Test the OHLCV data sort - res = await exchange._async_get_candle_history('ETH/BTC', default_conf['timeframe']) + res = await exchange._async_get_candle_history( + 'ETH/BTC', default_conf['timeframe'], CandleType.SPOT) assert res[0] == 'ETH/BTC' assert res[1] == default_conf['timeframe'] - res_ohlcv = res[2] + res_ohlcv = res[3] # Sorted not called again - data is already in order assert sort_mock.call_count == 0 assert res_ohlcv[0][0] == 1527827700000 @@ -2167,7 +2551,6 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na @pytest.mark.parametrize("exchange_name", EXCHANGES) async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name, fetch_trades_result): - caplog.set_level(logging.DEBUG) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) # Monkey-patch async function @@ -2211,6 +2594,43 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name, await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000) +@pytest.mark.asyncio +@pytest.mark.parametrize("exchange_name", EXCHANGES) +async def test__async_fetch_trades_contract_size(default_conf, mocker, caplog, exchange_name, + fetch_trades_result): + caplog.set_level(logging.DEBUG) + default_conf['margin_mode'] = 'isolated' + default_conf['trading_mode'] = 'futures' + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + # Monkey-patch async function + exchange._api_async.fetch_trades = get_mock_coro([ + {'info': {'a': 126181333, + 'p': '0.01952600', + 'q': '0.01200000', + 'f': 138604158, + 'l': 138604158, + 'T': 1565798399872, + 'm': True, + 'M': True}, + 'timestamp': 1565798399872, + 'datetime': '2019-08-14T15:59:59.872Z', + 'symbol': 'ETH/USDT:USDT', + 'id': '126181383', + 'order': None, + 'type': None, + 'takerOrMaker': None, + 'side': 'sell', + 'price': 2.0, + 'amount': 30.0, + 'cost': 60.0, + 'fee': None}] + ) + + pair = 'ETH/USDT:USDT' + res = await exchange._async_fetch_trades(pair, since=None, params=None) + assert res[0][5] == 300 + + @pytest.mark.asyncio @pytest.mark.parametrize("exchange_name", EXCHANGES) async def test__async_get_trade_history_id(default_conf, mocker, exchange_name, @@ -2363,7 +2783,15 @@ def test_cancel_order_dry_run(default_conf, mocker, exchange_name): assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {} assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {} - order = exchange.create_order('ETH/BTC', 'limit', "buy", 5, 0.55, 'gtc') + order = exchange.create_order( + pair='ETH/BTC', + ordertype='limit', + side='buy', + amount=5, + rate=0.55, + time_in_force='gtc', + leverage=1.0, + ) cancel_order = exchange.cancel_order(order_id=order['id'], pair='ETH/BTC') assert order['id'] == cancel_order['id'] @@ -2478,37 +2906,36 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name): default_conf['dry_run'] = False mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', return_value={'for': 123}) mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order', return_value={'for': 123}) + mocker.patch('freqtrade.exchange.Gateio.fetch_stoploss_order', return_value={'for': 123}) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', - return_value={'fee': {}, 'status': 'canceled', 'amount': 1234}) - mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order', - return_value={'fee': {}, 'status': 'canceled', 'amount': 1234}) + res = {'fee': {}, 'status': 'canceled', 'amount': 1234} + mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', return_value=res) + mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order', return_value=res) + mocker.patch('freqtrade.exchange.Gateio.cancel_stoploss_order', return_value=res) co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) - assert co == {'fee': {}, 'status': 'canceled', 'amount': 1234} + assert co == res - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', - return_value='canceled') - mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order', - return_value='canceled') + mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', return_value='canceled') + mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order', return_value='canceled') + mocker.patch('freqtrade.exchange.Gateio.cancel_stoploss_order', return_value='canceled') # Fall back to fetch_stoploss_order co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co == {'for': 123} - mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', - side_effect=InvalidOrderException("")) - mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order', - side_effect=InvalidOrderException("")) - + exc = InvalidOrderException("") + mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', side_effect=exc) + mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order', side_effect=exc) + mocker.patch('freqtrade.exchange.Gateio.fetch_stoploss_order', side_effect=exc) co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co['amount'] == 555 assert co == {'fee': {}, 'status': 'canceled', 'amount': 555, 'info': {}} with pytest.raises(InvalidOrderException): - mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', - side_effect=InvalidOrderException("Did not find order")) - mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order', - side_effect=InvalidOrderException("Did not find order")) + exc = InvalidOrderException("Did not find order") + mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', side_effect=exc) + mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order', side_effect=exc) + mocker.patch('freqtrade.exchange.Gateio.cancel_stoploss_order', side_effect=exc) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123) @@ -2519,6 +2946,8 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog): default_conf['exchange']['log_responses'] = True order = MagicMock() order.myid = 123 + order.symbol = 'TKN/BTC' + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange._dry_run_open_orders['X'] = order assert exchange.fetch_order('X', 'TKN/BTC').myid == 123 @@ -2528,10 +2957,15 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog): default_conf['dry_run'] = False api_mock = MagicMock() - api_mock.fetch_order = MagicMock(return_value=456) + api_mock.fetch_order = MagicMock(return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - assert exchange.fetch_order('X', 'TKN/BTC') == 456 - assert log_has("API fetch_order: 456", caplog) + assert exchange.fetch_order( + 'X', 'TKN/BTC') == {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'} + assert log_has( + ("API fetch_order: {\'id\': \'123\', \'amount\': 2, \'symbol\': \'TKN/BTC\'}" + ), + caplog + ) with pytest.raises(InvalidOrderException): api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) @@ -2575,9 +3009,9 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name): default_conf['dry_run'] = False api_mock = MagicMock() - api_mock.fetch_order = MagicMock(return_value=456) + api_mock.fetch_order = MagicMock(return_value={'id': '123', 'symbol': 'TKN/BTC'}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - assert exchange.fetch_stoploss_order('X', 'TKN/BTC') == 456 + assert exchange.fetch_stoploss_order('X', 'TKN/BTC') == {'id': '123', 'symbol': 'TKN/BTC'} with pytest.raises(InvalidOrderException): api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) @@ -2624,12 +3058,17 @@ def test_name(default_conf, mocker, exchange_name): assert exchange.id == exchange_name +@pytest.mark.parametrize("trading_mode,amount", [ + ('spot', 0.2340606), + ('futures', 2.340606), +]) @pytest.mark.parametrize("exchange_name", EXCHANGES) -def test_get_trades_for_order(default_conf, mocker, exchange_name): - +def test_get_trades_for_order(default_conf, mocker, exchange_name, trading_mode, amount): order_id = 'ABCD-ABCD' since = datetime(2018, 5, 5, 0, 0, 0) default_conf["dry_run"] = False + default_conf["trading_mode"] = trading_mode + default_conf["margin_mode"] = 'isolated' mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) api_mock = MagicMock() @@ -2646,22 +3085,24 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name): 'id': 'ABCD-ABCD'}, 'timestamp': 1519860024438, 'datetime': '2018-02-28T23:20:24.438Z', - 'symbol': 'LTC/BTC', + 'symbol': 'ETH/USDT:USDT', 'type': 'limit', 'side': 'buy', 'price': 165.0, 'amount': 0.2340606, 'fee': {'cost': 0.06179, 'currency': 'BTC'} }]) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - orders = exchange.get_trades_for_order(order_id, 'LTC/BTC', since) + orders = exchange.get_trades_for_order(order_id, 'ETH/USDT:USDT', since) assert len(orders) == 1 assert orders[0]['price'] == 165 + assert isclose(orders[0]['amount'], amount) assert api_mock.fetch_my_trades.call_count == 1 # since argument should be assert isinstance(api_mock.fetch_my_trades.call_args[0][1], int) - assert api_mock.fetch_my_trades.call_args[0][0] == 'LTC/BTC' + assert api_mock.fetch_my_trades.call_args[0][0] == 'ETH/USDT:USDT' # Same test twice, hardcoded number and doing the same calculation assert api_mock.fetch_my_trades.call_args[0][1] == 1525478395000 assert api_mock.fetch_my_trades.call_args[0][1] == int(since.replace( @@ -2669,10 +3110,10 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name): ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, 'get_trades_for_order', 'fetch_my_trades', - order_id=order_id, pair='LTC/BTC', since=since) + order_id=order_id, pair='ETH/USDT:USDT', since=since) mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False)) - assert exchange.get_trades_for_order(order_id, 'LTC/BTC', since) == [] + assert exchange.get_trades_for_order(order_id, 'ETH/USDT:USDT', since) == [] @pytest.mark.parametrize("exchange_name", EXCHANGES) @@ -2703,10 +3144,17 @@ def test_get_fee(default_conf, mocker, exchange_name): def test_stoploss_order_unsupported_exchange(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, id='bittrex') with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side="sell", + leverage=1.0 + ) with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): - exchange.stoploss_adjust(1, {}) + exchange.stoploss_adjust(1, {}, side="sell") def test_merge_ft_has_dict(default_conf, mocker): @@ -2715,7 +3163,8 @@ def test_merge_ft_has_dict(default_conf, mocker): _load_async_markets=MagicMock(), validate_pairs=MagicMock(), validate_timeframes=MagicMock(), - validate_stakecurrency=MagicMock() + validate_stakecurrency=MagicMock(), + validate_pricing=MagicMock(), ) ex = Exchange(default_conf) assert ex._ft_has == Exchange._ft_has_default @@ -2749,6 +3198,7 @@ def test_get_valid_pair_combination(default_conf, mocker, markets): _load_async_markets=MagicMock(), validate_pairs=MagicMock(), validate_timeframes=MagicMock(), + validate_pricing=MagicMock(), markets=PropertyMock(return_value=markets)) ex = Exchange(default_conf) @@ -2759,7 +3209,8 @@ def test_get_valid_pair_combination(default_conf, mocker, markets): @pytest.mark.parametrize( - "base_currencies, quote_currencies, pairs_only, active_only, expected_keys", [ + "base_currencies,quote_currencies,tradable_only,active_only,spot_only," + "futures_only,expected_keys,test_comment", [ # Testing markets (in conftest.py): # 'BLK/BTC': 'active': True # 'BTT/BTC': 'active': True @@ -2773,58 +3224,81 @@ def test_get_valid_pair_combination(default_conf, mocker, markets): # 'TKN/BTC': 'active' not set # 'XLTCUSDT': 'active': True, not a pair # 'XRP/BTC': 'active': False - # all markets - ([], [], False, False, + ([], [], False, False, False, False, + ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', + 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC', 'ADA/USDT:USDT', + 'ETH/USDT:USDT'], + 'all markets'), + ([], [], False, False, True, False, ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', - 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']), - # active markets - ([], [], False, True, + 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC'], + 'all markets, only spot pairs'), + ([], [], False, True, False, False, ['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC', - 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']), - # all pairs - ([], [], True, False, + 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC', 'ADA/USDT:USDT', 'ETH/USDT:USDT'], + 'active markets'), + ([], [], True, False, False, False, ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', - 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']), - # active pairs - ([], [], True, True, + 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC'], + 'all pairs'), + ([], [], True, True, False, False, ['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC', - 'TKN/BTC', 'XRP/BTC']), - # all markets, base=ETH, LTC - (['ETH', 'LTC'], [], False, False, - ['ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']), - # all markets, base=LTC - (['LTC'], [], False, False, - ['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']), - # all markets, quote=USDT - ([], ['USDT'], False, False, - ['ETH/USDT', 'LTC/USDT', 'XLTCUSDT']), - # all markets, quote=USDT, USD - ([], ['USDT', 'USD'], False, False, - ['ETH/USDT', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']), - # all markets, base=LTC, quote=USDT - (['LTC'], ['USDT'], False, False, - ['LTC/USDT', 'XLTCUSDT']), - # all pairs, base=LTC, quote=USDT - (['LTC'], ['USDT'], True, False, - ['LTC/USDT']), - # all markets, base=LTC, quote=USDT, NONEXISTENT - (['LTC'], ['USDT', 'NONEXISTENT'], False, False, - ['LTC/USDT', 'XLTCUSDT']), - # all markets, base=LTC, quote=NONEXISTENT - (['LTC'], ['NONEXISTENT'], False, False, - []), + 'TKN/BTC', 'XRP/BTC'], + 'active pairs'), + (['ETH', 'LTC'], [], False, False, False, False, + ['ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT', + 'ETH/USDT:USDT'], + 'all markets, base=ETH, LTC'), + (['LTC'], [], False, False, False, False, + ['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT'], + 'all markets, base=LTC'), + (['LTC'], [], False, False, True, False, + ['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT'], + 'spot markets, base=LTC'), + ([], ['USDT'], False, False, False, False, + ['ETH/USDT', 'LTC/USDT', 'XLTCUSDT', 'ADA/USDT:USDT', 'ETH/USDT:USDT'], + 'all markets, quote=USDT'), + ([], ['USDT'], False, False, False, True, + ['ADA/USDT:USDT', 'ETH/USDT:USDT'], + 'Futures markets, quote=USDT'), + ([], ['USDT', 'USD'], False, False, False, False, + ['ETH/USDT', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT', 'ADA/USDT:USDT', 'ETH/USDT:USDT'], + 'all markets, quote=USDT, USD'), + ([], ['USDT', 'USD'], False, False, True, False, + ['ETH/USDT', 'LTC/USD', 'LTC/USDT'], + 'spot markets, quote=USDT, USD'), + (['LTC'], ['USDT'], False, False, False, False, + ['LTC/USDT', 'XLTCUSDT'], + 'all markets, base=LTC, quote=USDT'), + (['LTC'], ['USDT'], True, False, False, False, + ['LTC/USDT'], + 'all pairs, base=LTC, quote=USDT'), + (['LTC'], ['USDT', 'NONEXISTENT'], False, False, False, False, + ['LTC/USDT', 'XLTCUSDT'], + 'all markets, base=LTC, quote=USDT, NONEXISTENT'), + (['LTC'], ['NONEXISTENT'], False, False, False, False, + [], + 'all markets, base=LTC, quote=NONEXISTENT'), ]) def test_get_markets(default_conf, mocker, markets_static, - base_currencies, quote_currencies, pairs_only, active_only, - expected_keys): + base_currencies, quote_currencies, tradable_only, active_only, + spot_only, futures_only, expected_keys, + test_comment # Here for debugging purposes (Not used within method) + ): mocker.patch.multiple('freqtrade.exchange.Exchange', _init_ccxt=MagicMock(return_value=MagicMock()), _load_async_markets=MagicMock(), validate_pairs=MagicMock(), validate_timeframes=MagicMock(), + validate_pricing=MagicMock(), markets=PropertyMock(return_value=markets_static)) ex = Exchange(default_conf) - pairs = ex.get_markets(base_currencies, quote_currencies, pairs_only, active_only) + pairs = ex.get_markets(base_currencies, + quote_currencies, + tradable_only=tradable_only, + spot_only=spot_only, + futures_only=futures_only, + active_only=active_only) assert sorted(pairs.keys()) == sorted(expected_keys) @@ -2895,6 +3369,8 @@ def test_timeframe_to_prev_date(): # Does not round time = datetime(2019, 8, 12, 13, 20, 0, tzinfo=timezone.utc) assert timeframe_to_prev_date('5m', time) == time + time = datetime(2019, 8, 12, 13, 0, 0, tzinfo=timezone.utc) + assert timeframe_to_prev_date('1h', time) == time def test_timeframe_to_next_date(): @@ -2925,39 +3401,64 @@ def test_timeframe_to_next_date(): assert timeframe_to_next_date("5m", date) == date + timedelta(minutes=5) -@pytest.mark.parametrize("market_symbol,base,quote,exchange,add_dict,expected_result", [ - ("BTC/USDT", 'BTC', 'USDT', "binance", {}, True), - ("USDT/BTC", 'USDT', 'BTC', "binance", {}, True), - ("USDT/BTC", 'BTC', 'USDT', "binance", {}, False), # Reversed currencies - ("BTCUSDT", 'BTC', 'USDT', "binance", {}, False), # No seperating / - ("BTCUSDT", None, "USDT", "binance", {}, False), # - ("USDT/BTC", "BTC", None, "binance", {}, False), - ("BTCUSDT", "BTC", None, "binance", {}, False), - ("BTC/USDT", "BTC", "USDT", "binance", {}, True), - ("BTC/USDT", "USDT", "BTC", "binance", {}, False), # reversed currencies - ("BTC/USDT", "BTC", "USD", "binance", {}, False), # Wrong quote currency - ("BTC/", "BTC", 'UNK', "binance", {}, False), - ("/USDT", 'UNK', 'USDT', "binance", {}, False), - ("BTC/EUR", 'BTC', 'EUR', "kraken", {"darkpool": False}, True), - ("EUR/BTC", 'EUR', 'BTC', "kraken", {"darkpool": False}, True), - ("EUR/BTC", 'BTC', 'EUR', "kraken", {"darkpool": False}, False), # Reversed currencies - ("BTC/EUR", 'BTC', 'USD', "kraken", {"darkpool": False}, False), # wrong quote currency - ("BTC/EUR", 'BTC', 'EUR', "kraken", {"darkpool": True}, False), # no darkpools - ("BTC/EUR.d", 'BTC', 'EUR', "kraken", {"darkpool": True}, False), # no darkpools - ("BTC/USD", 'BTC', 'USD', "ftx", {'spot': True}, True), - ("USD/BTC", 'USD', 'BTC', "ftx", {'spot': True}, True), - ("BTC/USD", 'BTC', 'USDT', "ftx", {'spot': True}, False), # Wrong quote currency - ("BTC/USD", 'USD', 'BTC', "ftx", {'spot': True}, False), # Reversed currencies - ("BTC/USD", 'BTC', 'USD', "ftx", {'spot': False}, False), # Can only trade spot markets - ("BTC-PERP", 'BTC', 'USD', "ftx", {'spot': False}, False), # Can only trade spot markets -]) -def test_market_is_tradable(mocker, default_conf, market_symbol, base, - quote, add_dict, exchange, expected_result) -> None: +@pytest.mark.parametrize( + "market_symbol,base,quote,exchange,spot,margin,futures,trademode,add_dict,expected_result", + [ + ("BTC/USDT", 'BTC', 'USDT', "binance", True, False, False, 'spot', {}, True), + ("USDT/BTC", 'USDT', 'BTC', "binance", True, False, False, 'spot', {}, True), + # No seperating / + ("BTCUSDT", 'BTC', 'USDT', "binance", True, False, False, 'spot', {}, True), + ("BTCUSDT", None, "USDT", "binance", True, False, False, 'spot', {}, False), + ("USDT/BTC", "BTC", None, "binance", True, False, False, 'spot', {}, False), + ("BTCUSDT", "BTC", None, "binance", True, False, False, 'spot', {}, False), + ("BTC/USDT", "BTC", "USDT", "binance", True, False, False, 'spot', {}, True), + # Futures mode, spot pair + ("BTC/USDT", "BTC", "USDT", "binance", True, False, False, 'futures', {}, False), + ("BTC/USDT", "BTC", "USDT", "binance", True, False, False, 'margin', {}, False), + ("BTC/USDT", "BTC", "USDT", "binance", True, True, True, 'margin', {}, True), + ("BTC/USDT", "BTC", "USDT", "binance", False, True, False, 'margin', {}, True), + # Futures mode, futures pair + ("BTC/USDT", "BTC", "USDT", "binance", False, False, True, 'futures', {}, True), + # Futures market + ("BTC/UNK", "BTC", 'UNK', "binance", False, False, True, 'spot', {}, False), + ("BTC/EUR", 'BTC', 'EUR', "kraken", True, False, False, 'spot', {"darkpool": False}, True), + ("EUR/BTC", 'EUR', 'BTC', "kraken", True, False, False, 'spot', {"darkpool": False}, True), + # no darkpools + ("BTC/EUR", 'BTC', 'EUR', "kraken", True, False, False, 'spot', + {"darkpool": True}, False), + # no darkpools + ("BTC/EUR.d", 'BTC', 'EUR', "kraken", True, False, False, 'spot', + {"darkpool": True}, False), + ("BTC/USD", 'BTC', 'USD', "ftx", True, False, False, 'spot', {}, True), + ("USD/BTC", 'USD', 'BTC', "ftx", True, False, False, 'spot', {}, True), + # Can only trade spot markets + ("BTC/USD", 'BTC', 'USD', "ftx", False, False, True, 'spot', {}, False), + ("BTC/USD", 'BTC', 'USD', "ftx", False, False, True, 'futures', {}, True), + # Can only trade spot markets + ("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'spot', {}, False), + ("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'margin', {}, False), + ("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'futures', {}, True), + + ("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'spot', {}, False), + ("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'margin', {}, False), + ("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'futures', {}, True), + ]) +def test_market_is_tradable( + mocker, default_conf, market_symbol, base, + quote, spot, margin, futures, trademode, add_dict, exchange, expected_result +) -> None: + default_conf['trading_mode'] = trademode + mocker.patch('freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_margin_mode') ex = get_patched_exchange(mocker, default_conf, id=exchange) market = { 'symbol': market_symbol, 'base': base, 'quote': quote, + 'spot': spot, + 'future': futures, + 'swap': futures, + 'margin': margin, + 'linear': True, **(add_dict), } assert ex.market_is_tradable(market) == expected_result @@ -3061,7 +3562,1366 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_r (3, 5, 5), (4, 5, 2), (5, 5, 1), - ]) 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), + (20.0, 5.0, 4.0), + (100.0, 100.0, 1.0) +]) +def test_get_stake_amount_considering_leverage( + exchange, + stake_amount, + leverage, + min_stake_with_lev, + mocker, + default_conf +): + exchange = get_patched_exchange(mocker, default_conf, id=exchange) + assert exchange._get_stake_amount_considering_leverage( + stake_amount, leverage) == min_stake_with_lev + + +@pytest.mark.parametrize("exchange_name,trading_mode", [ + ("binance", TradingMode.FUTURES), + ("ftx", TradingMode.MARGIN), + ("ftx", TradingMode.FUTURES) +]) +def test__set_leverage(mocker, default_conf, exchange_name, trading_mode): + + api_mock = MagicMock() + api_mock.set_leverage = MagicMock() + type(api_mock).has = PropertyMock(return_value={'setLeverage': True}) + default_conf['dry_run'] = False + + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + exchange_name, + "_set_leverage", + "set_leverage", + pair="XRP/USDT", + leverage=5.0, + trading_mode=trading_mode + ) + + +@pytest.mark.parametrize("margin_mode", [ + (MarginMode.CROSS), + (MarginMode.ISOLATED) +]) +def test_set_margin_mode(mocker, default_conf, margin_mode): + + api_mock = MagicMock() + api_mock.set_margin_mode = MagicMock() + type(api_mock).has = PropertyMock(return_value={'setMarginMode': True}) + default_conf['dry_run'] = False + + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + "binance", + "set_margin_mode", + "set_margin_mode", + pair="XRP/USDT", + margin_mode=margin_mode + ) + + +@pytest.mark.parametrize("exchange_name, trading_mode, margin_mode, exception_thrown", [ + ("binance", TradingMode.SPOT, None, False), + ("binance", TradingMode.MARGIN, MarginMode.ISOLATED, True), + ("kraken", TradingMode.SPOT, None, False), + ("kraken", TradingMode.MARGIN, MarginMode.ISOLATED, True), + ("kraken", TradingMode.FUTURES, MarginMode.ISOLATED, True), + ("ftx", TradingMode.SPOT, None, False), + ("ftx", TradingMode.MARGIN, MarginMode.ISOLATED, True), + ("ftx", TradingMode.FUTURES, MarginMode.ISOLATED, True), + ("bittrex", TradingMode.SPOT, None, False), + ("bittrex", TradingMode.MARGIN, MarginMode.CROSS, True), + ("bittrex", TradingMode.MARGIN, MarginMode.ISOLATED, True), + ("bittrex", TradingMode.FUTURES, MarginMode.CROSS, True), + ("bittrex", TradingMode.FUTURES, MarginMode.ISOLATED, True), + ("gateio", TradingMode.MARGIN, MarginMode.ISOLATED, True), + ("okx", TradingMode.SPOT, None, False), + ("okx", TradingMode.MARGIN, MarginMode.CROSS, True), + ("okx", TradingMode.MARGIN, MarginMode.ISOLATED, True), + ("okx", TradingMode.FUTURES, MarginMode.CROSS, True), + + ("binance", TradingMode.FUTURES, MarginMode.ISOLATED, False), + ("gateio", TradingMode.FUTURES, MarginMode.ISOLATED, False), + ("okx", TradingMode.FUTURES, MarginMode.ISOLATED, False), + + # * Remove once implemented + ("binance", TradingMode.MARGIN, MarginMode.CROSS, True), + ("binance", TradingMode.FUTURES, MarginMode.CROSS, True), + ("kraken", TradingMode.MARGIN, MarginMode.CROSS, True), + ("kraken", TradingMode.FUTURES, MarginMode.CROSS, True), + ("ftx", TradingMode.MARGIN, MarginMode.CROSS, True), + ("ftx", TradingMode.FUTURES, MarginMode.CROSS, True), + ("gateio", TradingMode.MARGIN, MarginMode.CROSS, True), + ("gateio", TradingMode.FUTURES, MarginMode.CROSS, True), + + # * Uncomment once implemented + # ("binance", TradingMode.MARGIN, MarginMode.CROSS, False), + # ("binance", TradingMode.FUTURES, MarginMode.CROSS, False), + # ("kraken", TradingMode.MARGIN, MarginMode.CROSS, False), + # ("kraken", TradingMode.FUTURES, MarginMode.CROSS, False), + # ("ftx", TradingMode.MARGIN, MarginMode.CROSS, False), + # ("ftx", TradingMode.FUTURES, MarginMode.CROSS, False), + # ("gateio", TradingMode.MARGIN, MarginMode.CROSS, False), + # ("gateio", TradingMode.FUTURES, MarginMode.CROSS, False), +]) +def test_validate_trading_mode_and_margin_mode( + default_conf, + mocker, + exchange_name, + trading_mode, + margin_mode, + exception_thrown +): + exchange = get_patched_exchange( + mocker, default_conf, id=exchange_name, mock_supported_modes=False) + if (exception_thrown): + with pytest.raises(OperationalException): + exchange.validate_trading_mode_and_margin_mode(trading_mode, margin_mode) + else: + exchange.validate_trading_mode_and_margin_mode(trading_mode, margin_mode) + + +@pytest.mark.parametrize("exchange_name,trading_mode,ccxt_config", [ + ("binance", "spot", {}), + ("binance", "margin", {"options": {"defaultType": "margin"}}), + ("binance", "futures", {"options": {"defaultType": "future"}}), + ("bibox", "spot", {"has": {"fetchCurrencies": False}}), + ("bibox", "margin", {"has": {"fetchCurrencies": False}, "options": {"defaultType": "margin"}}), + ("bibox", "futures", {"has": {"fetchCurrencies": False}, "options": {"defaultType": "swap"}}), + ("bybit", "futures", {"options": {"defaultType": "linear"}}), + ("ftx", "futures", {"options": {"defaultType": "swap"}}), + ("gateio", "futures", {"options": {"defaultType": "swap"}}), + ("hitbtc", "futures", {"options": {"defaultType": "swap"}}), + ("kraken", "futures", {"options": {"defaultType": "swap"}}), + ("kucoin", "futures", {"options": {"defaultType": "swap"}}), + ("okx", "futures", {"options": {"defaultType": "swap"}}), +]) +def test__ccxt_config( + default_conf, + mocker, + exchange_name, + trading_mode, + ccxt_config +): + default_conf['trading_mode'] = trading_mode + default_conf['margin_mode'] = 'isolated' + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + assert exchange._ccxt_config == ccxt_config + + +@pytest.mark.parametrize('pair,nominal_value,max_lev', [ + ("ETH/BTC", 0.0, 2.0), + ("TKN/BTC", 100.0, 5.0), + ("BLK/BTC", 173.31, 3.0), + ("LTC/BTC", 0.0, 1.0), + ("TKN/USDT", 210.30, 1.0), +]) +def test_get_max_leverage_from_margin(default_conf, mocker, pair, nominal_value, max_lev): + default_conf['trading_mode'] = 'margin' + default_conf['margin_mode'] = 'isolated' + api_mock = MagicMock() + type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': False}) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="gateio") + assert exchange.get_max_leverage(pair, nominal_value) == max_lev + + +@pytest.mark.parametrize( + 'size,funding_rate,mark_price,time_in_ratio,funding_fee,kraken_fee', [ + (10, 0.0001, 2.0, 1.0, 0.002, 0.002), + (10, 0.0002, 2.0, 0.01, 0.004, 0.00004), + (10, 0.0002, 2.5, None, 0.005, None), + ]) +def test_calculate_funding_fees( + default_conf, + mocker, + size, + funding_rate, + mark_price, + funding_fee, + kraken_fee, + time_in_ratio +): + exchange = get_patched_exchange(mocker, default_conf) + kraken = get_patched_exchange(mocker, default_conf, id="kraken") + prior_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc) - timedelta(hours=1)) + trade_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc)) + funding_rates = DataFrame([ + {'date': prior_date, 'open': funding_rate}, # Line not used. + {'date': trade_date, 'open': funding_rate}, + ]) + mark_rates = DataFrame([ + {'date': prior_date, 'open': mark_price}, + {'date': trade_date, 'open': mark_price}, + ]) + df = exchange.combine_funding_and_mark(funding_rates, mark_rates) + + assert exchange.calculate_funding_fees( + df, + amount=size, + is_short=True, + open_date=trade_date, + close_date=trade_date, + time_in_ratio=time_in_ratio, + ) == funding_fee + + if (kraken_fee is None): + with pytest.raises(OperationalException): + kraken.calculate_funding_fees( + df, + amount=size, + is_short=True, + open_date=trade_date, + close_date=trade_date, + time_in_ratio=time_in_ratio, + ) + + else: + assert kraken.calculate_funding_fees( + df, + amount=size, + is_short=True, + open_date=trade_date, + close_date=trade_date, + time_in_ratio=time_in_ratio, + ) == kraken_fee + + +def test_get_or_calculate_liquidation_price(mocker, default_conf): + + api_mock = MagicMock() + positions = [ + { + 'info': {}, + 'symbol': 'NEAR/USDT:USDT', + 'timestamp': 1642164737148, + 'datetime': '2022-01-14T12:52:17.148Z', + 'initialMargin': 1.51072, + 'initialMarginPercentage': 0.1, + 'maintenanceMargin': 0.38916147, + 'maintenanceMarginPercentage': 0.025, + 'entryPrice': 18.884, + 'notional': 15.1072, + 'leverage': 9.97, + 'unrealizedPnl': 0.0048, + 'contracts': 8, + 'contractSize': 0.1, + 'marginRatio': None, + 'liquidationPrice': 17.47, + 'markPrice': 18.89, + 'margin_mode': 1.52549075, + 'marginType': 'isolated', + 'side': 'buy', + 'percentage': 0.003177292946409658 + } + ] + api_mock.fetch_positions = MagicMock(return_value=positions) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + exchange_has=MagicMock(return_value=True), + ) + default_conf['dry_run'] = False + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + default_conf['liquidation_buffer'] = 0.0 + + exchange = get_patched_exchange(mocker, default_conf, api_mock) + liq_price = exchange.get_or_calculate_liquidation_price( + pair='NEAR/USDT:USDT', + open_rate=18.884, + is_short=False, + position=0.8, + wallet_balance=0.8, + ) + assert liq_price == 17.47 + + default_conf['liquidation_buffer'] = 0.05 + exchange = get_patched_exchange(mocker, default_conf, api_mock) + liq_price = exchange.get_or_calculate_liquidation_price( + pair='NEAR/USDT:USDT', + open_rate=18.884, + is_short=False, + position=0.8, + wallet_balance=0.8, + ) + assert liq_price == 17.540699999999998 + + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + "binance", + "get_or_calculate_liquidation_price", + "fetch_positions", + pair="XRP/USDT", + open_rate=0.0, + is_short=False, + position=0.0, + wallet_balance=0.0, + ) + + +@pytest.mark.parametrize('exchange,rate_start,rate_end,d1,d2,amount,expected_fees', [ + ('binance', 0, 2, "2021-09-01 01:00:00", "2021-09-01 04:00:00", 30.0, 0.0), + ('binance', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.00091409999), + ('binance', 0, 2, "2021-09-01 00:00:15", "2021-09-01 08:00:00", 30.0, -0.0002493), + ('binance', 1, 2, "2021-09-01 01:00:14", "2021-09-01 08:00:00", 30.0, -0.0002493), + ('binance', 1, 2, "2021-09-01 00:00:16", "2021-09-01 08:00:00", 30.0, -0.0002493), + ('binance', 0, 1, "2021-09-01 00:00:00", "2021-09-01 07:59:59", 30.0, -0.00066479999), + ('binance', 0, 2, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, -0.00091409999), + ('binance', 0, 2, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0002493), + # TODO: Uncoment once _calculate_funding_fees can pas time_in_ratio to exchange._get_funding_fee + # ('kraken', "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0014937), + # ('kraken', "2021-09-01 00:00:15", "2021-09-01 08:00:00", 30.0, -0.0008289), + # ('kraken', "2021-09-01 01:00:14", "2021-09-01 08:00:00", 30.0, -0.0008289), + # ('kraken', "2021-09-01 00:00:00", "2021-09-01 07:59:59", 30.0, -0.0012443999999999999), + # ('kraken', "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, 0.0045759), + # ('kraken', "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0008289), + ('ftx', 0, 2, "2021-09-01 00:10:00", "2021-09-01 00:30:00", 30.0, 0.0), + ('ftx', 0, 9, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, 0.0010008), + ('ftx', 0, 13, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, 0.0146691), + ('ftx', 0, 9, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 50.0, 0.001668), + ('ftx', 1, 9, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, 0.0019932), + ('gateio', 0, 2, "2021-09-01 00:10:00", "2021-09-01 04:00:00", 30.0, 0.0), + ('gateio', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0009140999), + ('gateio', 0, 2, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, -0.0009140999), + ('gateio', 1, 2, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0002493), + ('binance', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 50.0, -0.0015235), + # TODO: Uncoment once _calculate_funding_fees can pas time_in_ratio to exchange._get_funding_fee + # ('kraken', "2021-09-01 00:00:00", "2021-09-01 08:00:00", 50.0, -0.0024895), +]) +def test__fetch_and_calculate_funding_fees( + mocker, + default_conf, + funding_rate_history_hourly, + funding_rate_history_octohourly, + rate_start, + rate_end, + mark_ohlcv, + exchange, + d1, + d2, + amount, + expected_fees +): + """ + nominal_value = mark_price * size + funding_fee = nominal_value * funding_rate + size: 30 + time: 0, mark: 2.77, nominal_value: 83.1, fundRate: -0.000008, fundFee: -0.0006648 + time: 1, mark: 2.73, nominal_value: 81.9, fundRate: -0.000004, fundFee: -0.0003276 + time: 2, mark: 2.74, nominal_value: 82.2, fundRate: 0.000012, fundFee: 0.0009864 + time: 3, mark: 2.76, nominal_value: 82.8, fundRate: -0.000003, fundFee: -0.0002484 + time: 4, mark: 2.76, nominal_value: 82.8, fundRate: -0.000007, fundFee: -0.0005796 + time: 5, mark: 2.77, nominal_value: 83.1, fundRate: 0.000003, fundFee: 0.0002493 + time: 6, mark: 2.78, nominal_value: 83.4, fundRate: 0.000019, fundFee: 0.0015846 + time: 7, mark: 2.78, nominal_value: 83.4, fundRate: 0.000003, fundFee: 0.0002502 + time: 8, mark: 2.77, nominal_value: 83.1, fundRate: -0.000003, fundFee: -0.0002493 + time: 9, mark: 2.77, nominal_value: 83.1, fundRate: 0, fundFee: 0.0 + time: 10, mark: 2.84, nominal_value: 85.2, fundRate: 0.000013, fundFee: 0.0011076 + time: 11, mark: 2.81, nominal_value: 84.3, fundRate: 0.000077, fundFee: 0.0064911 + time: 12, mark: 2.81, nominal_value: 84.3, fundRate: 0.000072, fundFee: 0.0060696 + time: 13, mark: 2.82, nominal_value: 84.6, fundRate: 0.000097, fundFee: 0.0082062 + + size: 50 + time: 0, mark: 2.77, nominal_value: 138.5, fundRate: -0.000008, fundFee: -0.001108 + time: 1, mark: 2.73, nominal_value: 136.5, fundRate: -0.000004, fundFee: -0.000546 + time: 2, mark: 2.74, nominal_value: 137.0, fundRate: 0.000012, fundFee: 0.001644 + time: 3, mark: 2.76, nominal_value: 138.0, fundRate: -0.000003, fundFee: -0.000414 + time: 4, mark: 2.76, nominal_value: 138.0, fundRate: -0.000007, fundFee: -0.000966 + time: 5, mark: 2.77, nominal_value: 138.5, fundRate: 0.000003, fundFee: 0.0004155 + time: 6, mark: 2.78, nominal_value: 139.0, fundRate: 0.000019, fundFee: 0.002641 + time: 7, mark: 2.78, nominal_value: 139.0, fundRate: 0.000003, fundFee: 0.000417 + time: 8, mark: 2.77, nominal_value: 138.5, fundRate: -0.000003, fundFee: -0.0004155 + time: 9, mark: 2.77, nominal_value: 138.5, fundRate: 0, fundFee: 0.0 + time: 10, mark: 2.84, nominal_value: 142.0, fundRate: 0.000013, fundFee: 0.001846 + time: 11, mark: 2.81, nominal_value: 140.5, fundRate: 0.000077, fundFee: 0.0108185 + time: 12, mark: 2.81, nominal_value: 140.5, fundRate: 0.000072, fundFee: 0.010116 + time: 13, mark: 2.82, nominal_value: 141.0, fundRate: 0.000097, fundFee: 0.013677 + """ + d1 = datetime.strptime(f"{d1} +0000", '%Y-%m-%d %H:%M:%S %z') + d2 = datetime.strptime(f"{d2} +0000", '%Y-%m-%d %H:%M:%S %z') + funding_rate_history = { + 'binance': funding_rate_history_octohourly, + 'ftx': funding_rate_history_hourly, + 'gateio': funding_rate_history_octohourly, + }[exchange][rate_start:rate_end] + api_mock = MagicMock() + api_mock.fetch_funding_rate_history = get_mock_coro(return_value=funding_rate_history) + api_mock.fetch_ohlcv = get_mock_coro(return_value=mark_ohlcv) + type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) + type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True}) + + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) + mocker.patch('freqtrade.exchange.Exchange.timeframes', PropertyMock( + return_value=['1h', '4h', '8h'])) + funding_fees = exchange._fetch_and_calculate_funding_fees( + pair='ADA/USDT', amount=amount, is_short=True, open_date=d1, close_date=d2) + assert pytest.approx(funding_fees) == expected_fees + # Fees for Longs are inverted + funding_fees = exchange._fetch_and_calculate_funding_fees( + pair='ADA/USDT', amount=amount, is_short=False, open_date=d1, close_date=d2) + assert pytest.approx(funding_fees) == -expected_fees + + +@pytest.mark.parametrize('exchange,expected_fees', [ + ('binance', -0.0009140999999999999), + ('gateio', -0.0009140999999999999), +]) +def test__fetch_and_calculate_funding_fees_datetime_called( + mocker, + default_conf, + funding_rate_history_octohourly, + mark_ohlcv, + exchange, + time_machine, + expected_fees +): + api_mock = MagicMock() + api_mock.fetch_ohlcv = get_mock_coro(return_value=mark_ohlcv) + api_mock.fetch_funding_rate_history = get_mock_coro( + return_value=funding_rate_history_octohourly) + type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) + type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True}) + mocker.patch('freqtrade.exchange.Exchange.timeframes', PropertyMock(return_value=['4h', '8h'])) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) + d1 = datetime.strptime("2021-09-01 00:00:00 +0000", '%Y-%m-%d %H:%M:%S %z') + + time_machine.move_to("2021-09-01 08:00:00 +00:00") + funding_fees = exchange._fetch_and_calculate_funding_fees('ADA/USDT', 30.0, True, d1) + assert funding_fees == expected_fees + funding_fees = exchange._fetch_and_calculate_funding_fees('ADA/USDT', 30.0, False, d1) + assert funding_fees == 0 - expected_fees + + +@pytest.mark.parametrize('pair,expected_size,trading_mode', [ + ('XLTCUSDT', 1, 'spot'), + ('LTC/USD', 1, 'futures'), + ('XLTCUSDT', 0.01, 'futures'), + ('ETH/USDT:USDT', 10, 'futures') +]) +def test__get_contract_size(mocker, default_conf, pair, expected_size, trading_mode): + api_mock = MagicMock() + default_conf['trading_mode'] = trading_mode + default_conf['margin_mode'] = 'isolated' + exchange = get_patched_exchange(mocker, default_conf, api_mock) + mocker.patch('freqtrade.exchange.Exchange.markets', { + 'LTC/USD': { + 'symbol': 'LTC/USD', + 'contractSize': None, + }, + 'XLTCUSDT': { + 'symbol': 'XLTCUSDT', + 'contractSize': '0.01', + }, + 'ETH/USDT:USDT': { + 'symbol': 'ETH/USDT:USDT', + 'contractSize': '10', + } + }) + size = exchange._get_contract_size(pair) + assert expected_size == size + + +@pytest.mark.parametrize('pair,contract_size,trading_mode', [ + ('XLTCUSDT', 1, 'spot'), + ('LTC/USD', 1, 'futures'), + ('ADA/USDT:USDT', 0.01, 'futures'), + ('LTC/ETH', 1, 'futures'), + ('ETH/USDT:USDT', 10, 'futures'), +]) +def test__order_contracts_to_amount( + mocker, + default_conf, + markets, + pair, + contract_size, + trading_mode, +): + api_mock = MagicMock() + default_conf['trading_mode'] = trading_mode + default_conf['margin_mode'] = 'isolated' + mocker.patch('freqtrade.exchange.Exchange.markets', markets) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + + orders = [ + { + 'id': '123456320', + 'clientOrderId': '12345632018', + 'timestamp': 1640124992000, + 'datetime': 'Tue 21 Dec 2021 22:16:32 UTC', + 'lastTradeTimestamp': 1640124911000, + 'status': 'active', + 'symbol': pair, + 'type': 'limit', + 'timeInForce': 'gtc', + 'postOnly': None, + 'side': 'buy', + 'price': 2.0, + 'stopPrice': None, + 'average': None, + 'amount': 30.0, + 'cost': 60.0, + 'filled': None, + 'remaining': 30.0, + 'fee': 0.06, + 'fees': [{ + 'currency': 'USDT', + 'cost': 0.06, + }], + 'trades': None, + 'info': {}, + }, + { + 'id': '123456380', + 'clientOrderId': '12345638203', + 'timestamp': 1640124992000, + 'datetime': 'Tue 21 Dec 2021 22:16:32 UTC', + 'lastTradeTimestamp': 1640124911000, + 'status': 'active', + 'symbol': pair, + 'type': 'limit', + 'timeInForce': 'gtc', + 'postOnly': None, + 'side': 'sell', + 'price': 2.2, + 'stopPrice': None, + 'average': None, + 'amount': 40.0, + 'cost': 80.0, + 'filled': None, + 'remaining': 40.0, + 'fee': 0.08, + 'fees': [{ + 'currency': 'USDT', + 'cost': 0.08, + }], + 'trades': None, + 'info': {}, + }, + { + # Realistic stoploss order on gateio. + 'id': '123456380', + 'clientOrderId': '12345638203', + 'timestamp': None, + 'datetime': None, + 'lastTradeTimestamp': None, + 'status': None, + 'symbol': None, + 'type': None, + 'timeInForce': None, + 'postOnly': None, + 'side': None, + 'price': None, + 'stopPrice': None, + 'average': None, + 'amount': None, + 'cost': None, + 'filled': None, + 'remaining': None, + 'fee': None, + 'fees': [], + 'trades': None, + 'info': {}, + }, + ] + + order1 = exchange._order_contracts_to_amount(orders[0]) + order2 = exchange._order_contracts_to_amount(orders[1]) + exchange._order_contracts_to_amount(orders[2]) + assert order1['amount'] == 30.0 * contract_size + assert order2['amount'] == 40.0 * contract_size + + +@pytest.mark.parametrize('pair,contract_size,trading_mode', [ + ('XLTCUSDT', 1, 'spot'), + ('LTC/USD', 1, 'futures'), + ('ADA/USDT:USDT', 0.01, 'futures'), + ('LTC/ETH', 1, 'futures'), + ('ETH/USDT:USDT', 10, 'futures'), +]) +def test__trades_contracts_to_amount( + mocker, + default_conf, + markets, + pair, + contract_size, + trading_mode, +): + api_mock = MagicMock() + default_conf['trading_mode'] = trading_mode + default_conf['margin_mode'] = 'isolated' + mocker.patch('freqtrade.exchange.Exchange.markets', markets) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + + trades = [ + { + 'symbol': pair, + 'amount': 30.0, + }, + { + 'symbol': pair, + 'amount': 40.0, + } + ] + + new_amount_trades = exchange._trades_contracts_to_amount(trades) + assert new_amount_trades[0]['amount'] == 30.0 * contract_size + assert new_amount_trades[1]['amount'] == 40.0 * contract_size + + +@pytest.mark.parametrize('pair,param_amount,param_size', [ + ('ADA/USDT:USDT', 40, 4000), + ('LTC/ETH', 30, 30), + ('LTC/USD', 30, 30), + ('ETH/USDT:USDT', 10, 1), +]) +def test__amount_to_contracts( + mocker, + default_conf, + pair, + param_amount, + param_size +): + api_mock = MagicMock() + default_conf['trading_mode'] = 'spot' + default_conf['margin_mode'] = 'isolated' + exchange = get_patched_exchange(mocker, default_conf, api_mock) + mocker.patch('freqtrade.exchange.Exchange.markets', { + 'LTC/USD': { + 'symbol': 'LTC/USD', + 'contractSize': None, + }, + 'XLTCUSDT': { + 'symbol': 'XLTCUSDT', + 'contractSize': '0.01', + }, + 'LTC/ETH': { + 'symbol': 'LTC/ETH', + }, + 'ETH/USDT:USDT': { + 'symbol': 'ETH/USDT:USDT', + 'contractSize': '10', + } + }) + result_size = exchange._amount_to_contracts(pair, param_amount) + assert result_size == param_amount + result_amount = exchange._contracts_to_amount(pair, param_size) + assert result_amount == param_size + + default_conf['trading_mode'] = 'futures' + exchange = get_patched_exchange(mocker, default_conf, api_mock) + result_size = exchange._amount_to_contracts(pair, param_amount) + assert result_size == param_size + result_amount = exchange._contracts_to_amount(pair, param_size) + assert result_amount == param_amount + + +@pytest.mark.parametrize('exchange_name,open_rate,is_short,trading_mode,margin_mode', [ + # Bittrex + ('bittrex', 2.0, False, 'spot', None), + ('bittrex', 2.0, False, 'spot', 'cross'), + ('bittrex', 2.0, True, 'spot', 'isolated'), + # Binance + ('binance', 2.0, False, 'spot', None), + ('binance', 2.0, False, 'spot', 'cross'), + ('binance', 2.0, True, 'spot', 'isolated'), +]) +def test_liquidation_price_is_none( + mocker, + default_conf, + exchange_name, + open_rate, + is_short, + trading_mode, + margin_mode +): + default_conf['trading_mode'] = trading_mode + default_conf['margin_mode'] = margin_mode + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + assert exchange.get_or_calculate_liquidation_price( + pair='DOGE/USDT', + open_rate=open_rate, + is_short=is_short, + position=71200.81144, + wallet_balance=-56354.57, + mm_ex_1=0.10, + upnl_ex_1=0.0 + ) is None + + +@pytest.mark.parametrize( + 'exchange_name, is_short, trading_mode, margin_mode, wallet_balance, ' + 'mm_ex_1, upnl_ex_1, maintenance_amt, position, open_rate, ' + 'mm_ratio, expected', + [ + ("binance", False, 'futures', 'isolated', 1535443.01, 0.0, + 0.0, 135365.00, 3683.979, 1456.84, 0.10, 1114.78), + ("binance", False, 'futures', 'isolated', 1535443.01, 0.0, + 0.0, 16300.000, 109.488, 32481.980, 0.025, 18778.73), + ("binance", False, 'futures', 'cross', 1535443.01, 71200.81144, + -56354.57, 135365.00, 3683.979, 1456.84, 0.10, 1153.26), + ("binance", False, 'futures', 'cross', 1535443.01, 356512.508, + -448192.89, 16300.000, 109.488, 32481.980, 0.025, 26316.89) + ]) +def test_liquidation_price( + mocker, default_conf, exchange_name, open_rate, is_short, trading_mode, + margin_mode, wallet_balance, mm_ex_1, upnl_ex_1, maintenance_amt, position, mm_ratio, expected +): + default_conf['trading_mode'] = trading_mode + default_conf['margin_mode'] = margin_mode + default_conf['liquidation_buffer'] = 0.0 + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(mm_ratio, maintenance_amt)) + assert isclose(round(exchange.get_or_calculate_liquidation_price( + pair='DOGE/USDT', + open_rate=open_rate, + is_short=is_short, + wallet_balance=wallet_balance, + mm_ex_1=mm_ex_1, + upnl_ex_1=upnl_ex_1, + position=position, + ), 2), expected) + + +def test_get_max_pair_stake_amount( + mocker, + default_conf, +): + api_mock = MagicMock() + default_conf['margin_mode'] = 'isolated' + default_conf['trading_mode'] = 'futures' + exchange = get_patched_exchange(mocker, default_conf, api_mock) + markets = { + 'XRP/USDT:USDT': { + 'limits': { + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'cost': { + 'min': 5, + 'max': None + }, + }, + 'contractSize': None, + 'spot': False, + }, + 'LTC/USDT:USDT': { + 'limits': { + 'amount': { + 'min': 0.001, + 'max': None + }, + 'cost': { + 'min': 5, + 'max': None + }, + }, + 'contractSize': 0.01, + 'spot': False, + }, + 'ETH/USDT:USDT': { + 'limits': { + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'cost': { + 'min': 5, + 'max': 30000, + }, + }, + 'contractSize': 0.01, + 'spot': False, + }, + 'BTC/USDT': { + 'limits': { + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'cost': { + 'min': 5, + 'max': None + }, + }, + 'contractSize': 0.01, + 'spot': True, + }, + 'ADA/USDT': { + 'limits': { + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'cost': { + 'min': 5, + 'max': 500, + }, + }, + 'contractSize': 0.01, + 'spot': True, + }, + 'DOGE/USDT:USDT': { + 'limits': { + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'cost': { + 'min': 5, + 'max': 500 + }, + }, + 'contractSize': None, + 'spot': False, + }, + 'LUNA/USDT:USDT': { + 'limits': { + 'amount': { + 'min': 0.001, + 'max': 10000 + }, + 'cost': { + 'min': 5, + 'max': 500 + }, + }, + 'contractSize': 0.01, + 'spot': False, + }, + } + + mocker.patch('freqtrade.exchange.Exchange.markets', markets) + assert exchange.get_max_pair_stake_amount('XRP/USDT:USDT', 2.0) == 20000 + assert exchange.get_max_pair_stake_amount('XRP/USDT:USDT', 2.0, 5) == 4000 + assert exchange.get_max_pair_stake_amount('LTC/USDT:USDT', 2.0) == float('inf') + assert exchange.get_max_pair_stake_amount('ETH/USDT:USDT', 2.0) == 200 + assert exchange.get_max_pair_stake_amount('DOGE/USDT:USDT', 2.0) == 500 + assert exchange.get_max_pair_stake_amount('LUNA/USDT:USDT', 2.0) == 5.0 + + default_conf['trading_mode'] = 'spot' + exchange = get_patched_exchange(mocker, default_conf, api_mock) + mocker.patch('freqtrade.exchange.Exchange.markets', markets) + assert exchange.get_max_pair_stake_amount('BTC/USDT', 2.0) == 20000 + assert exchange.get_max_pair_stake_amount('ADA/USDT', 2.0) == 500 + + +@pytest.mark.parametrize('exchange_name', EXCHANGES) +def test_load_leverage_tiers(mocker, default_conf, leverage_tiers, exchange_name): + api_mock = MagicMock() + api_mock.fetch_leverage_tiers = MagicMock() + type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True}) + default_conf['dry_run'] = False + mocker.patch('freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_margin_mode') + + api_mock.fetch_leverage_tiers = MagicMock(return_value={ + 'ADA/USDT:USDT': [ + { + 'tier': 1, + 'notionalFloor': 0, + 'notionalCap': 500, + 'maintenanceMarginRate': 0.02, + 'maxLeverage': 75, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.013', + 'instId': '', + 'maxLever': '75', + 'maxSz': '500', + 'minSz': '0', + 'mmr': '0.01', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '1', + 'uly': 'ADA-USDT' + } + }, + ] + }) + + # SPOT + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + assert exchange.load_leverage_tiers() == {} + + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + + if exchange_name != 'binance': + # FUTURES has.fetchLeverageTiers == False + type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': False}) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + assert exchange.load_leverage_tiers() == {} + + # FUTURES regular + type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True}) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + assert exchange.load_leverage_tiers() == { + 'ADA/USDT:USDT': [ + { + 'tier': 1, + 'notionalFloor': 0, + 'notionalCap': 500, + 'maintenanceMarginRate': 0.02, + 'maxLeverage': 75, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.013', + 'instId': '', + 'maxLever': '75', + 'maxSz': '500', + 'minSz': '0', + 'mmr': '0.01', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '1', + 'uly': 'ADA-USDT' + } + }, + ] + } + + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + exchange_name, + "load_leverage_tiers", + "fetch_leverage_tiers", + ) + + +def test_parse_leverage_tier(mocker, default_conf): + exchange = get_patched_exchange(mocker, default_conf) + + tier = { + "tier": 1, + "notionalFloor": 0, + "notionalCap": 100000, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "0", + "maintMarginRatio": "0.025", + "cum": "0.0" + } + } + + assert exchange.parse_leverage_tier(tier) == { + "min": 0, + "max": 100000, + "mmr": 0.025, + "lev": 20, + "maintAmt": 0.0, + } + + tier2 = { + 'tier': 1, + 'notionalFloor': 0, + 'notionalCap': 2000, + 'maintenanceMarginRate': 0.01, + 'maxLeverage': 75, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.013', + 'instId': '', + 'maxLever': '75', + 'maxSz': '2000', + 'minSz': '0', + 'mmr': '0.01', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '1', + 'uly': 'SHIB-USDT' + } + } + + assert exchange.parse_leverage_tier(tier2) == { + 'min': 0, + 'max': 2000, + 'mmr': 0.01, + 'lev': 75, + "maintAmt": None, + } + + +def test_get_maintenance_ratio_and_amt_exceptions(mocker, default_conf, leverage_tiers): + api_mock = MagicMock() + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + + exchange._leverage_tiers = leverage_tiers + with pytest.raises( + OperationalException, + match='nominal value can not be lower than 0', + ): + exchange.get_maintenance_ratio_and_amt('1000SHIB/USDT', -1) + + exchange._leverage_tiers = {} + + with pytest.raises( + InvalidOrderException, + match="Maintenance margin rate for 1000SHIB/USDT is unavailable for", + ): + exchange.get_maintenance_ratio_and_amt('1000SHIB/USDT', 10000) + + +@pytest.mark.parametrize('pair,value,mmr,maintAmt', [ + ('ADA/BUSD', 500, 0.025, 0.0), + ('ADA/BUSD', 20000000, 0.5, 1527500.0), + ('ZEC/USDT', 500, 0.01, 0.0), + ('ZEC/USDT', 20000000, 0.5, 654500.0), +]) +def test_get_maintenance_ratio_and_amt( + mocker, + default_conf, + leverage_tiers, + pair, + value, + mmr, + maintAmt +): + api_mock = MagicMock() + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange._leverage_tiers = leverage_tiers + exchange.get_maintenance_ratio_and_amt(pair, value) == (mmr, maintAmt) + + +def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers): + + # Test Spot + exchange = get_patched_exchange(mocker, default_conf, id="binance") + assert exchange.get_max_leverage("BNB/USDT", 100.0) == 1.0 + + # Test Futures + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + exchange = get_patched_exchange(mocker, default_conf, id="binance") + + exchange._leverage_tiers = leverage_tiers + + assert exchange.get_max_leverage("BNB/BUSD", 1.0) == 20.0 + assert exchange.get_max_leverage("BNB/USDT", 100.0) == 75.0 + assert exchange.get_max_leverage("BTC/USDT", 170.30) == 125.0 + assert isclose(exchange.get_max_leverage("BNB/BUSD", 99999.9), 5.000005) + assert isclose(exchange.get_max_leverage("BNB/USDT", 1500), 33.333333333333333) + assert exchange.get_max_leverage("BTC/USDT", 300000000) == 2.0 + assert exchange.get_max_leverage("BTC/USDT", 600000000) == 1.0 # Last tier + + assert exchange.get_max_leverage("SPONGE/USDT", 200) == 1.0 # Pair not in leverage_tiers + assert exchange.get_max_leverage("BTC/USDT", 0.0) == 125.0 # No stake amount + with pytest.raises( + InvalidOrderException, + match=r'Amount 1000000000.01 too high for BTC/USDT' + ): + exchange.get_max_leverage("BTC/USDT", 1000000000.01) + + +@pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'ftx', 'gateio', 'okx']) +def test__get_params(mocker, default_conf, exchange_name): + api_mock = MagicMock() + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange._params = {'test': True} + + params1 = {'test': True} + params2 = { + 'test': True, + 'timeInForce': 'ioc', + 'reduceOnly': True, + } + + if exchange_name == 'kraken': + params2['leverage'] = 3.0 + + if exchange_name == 'okx': + params2['tdMode'] = 'isolated' + + assert exchange._get_params( + ordertype='market', + reduceOnly=False, + time_in_force='gtc', + leverage=1.0, + ) == params1 + + assert exchange._get_params( + ordertype='market', + reduceOnly=False, + time_in_force='ioc', + leverage=1.0, + ) == params1 + + assert exchange._get_params( + ordertype='limit', + reduceOnly=False, + time_in_force='gtc', + leverage=1.0, + ) == params1 + + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange._params = {'test': True} + + assert exchange._get_params( + ordertype='limit', + reduceOnly=True, + time_in_force='ioc', + leverage=3.0, + ) == params2 + + +@pytest.mark.parametrize('liquidation_buffer', [0.0, 0.05]) +@pytest.mark.parametrize( + "is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,expected_liq", [ + (False, 'spot', 'binance', '', 5.0, 10.0, 1.0, None), + (True, 'spot', 'binance', '', 5.0, 10.0, 1.0, None), + (False, 'spot', 'gateio', '', 5.0, 10.0, 1.0, None), + (True, 'spot', 'gateio', '', 5.0, 10.0, 1.0, None), + (False, 'spot', 'okx', '', 5.0, 10.0, 1.0, None), + (True, 'spot', 'okx', '', 5.0, 10.0, 1.0, None), + # Binance, short + (True, 'futures', 'binance', 'isolated', 5.0, 10.0, 1.0, 11.89108910891089), + (True, 'futures', 'binance', 'isolated', 3.0, 10.0, 1.0, 13.211221122079207), + (True, 'futures', 'binance', 'isolated', 5.0, 8.0, 1.0, 9.514851485148514), + (True, 'futures', 'binance', 'isolated', 5.0, 10.0, 0.6, 12.557755775577558), + # Binance, long + (False, 'futures', 'binance', 'isolated', 5, 10, 1.0, 8.070707070707071), + (False, 'futures', 'binance', 'isolated', 5, 8, 1.0, 6.454545454545454), + (False, 'futures', 'binance', 'isolated', 3, 10, 1.0, 6.717171717171718), + (False, 'futures', 'binance', 'isolated', 5, 10, 0.6, 7.39057239057239), + # Gateio/okx, short + (True, 'futures', 'gateio', 'isolated', 5, 10, 1.0, 11.87413417771621), + (True, 'futures', 'gateio', 'isolated', 5, 10, 2.0, 11.87413417771621), + (True, 'futures', 'gateio', 'isolated', 3, 10, 1.0, 13.476180850346978), + (True, 'futures', 'gateio', 'isolated', 5, 8, 1.0, 9.499307342172967), + # Gateio/okx, long + (False, 'futures', 'gateio', 'isolated', 5.0, 10.0, 1.0, 8.085708510208207), + (False, 'futures', 'gateio', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506), + # (True, 'futures', 'okx', 'isolated', 11.87413417771621), + # (False, 'futures', 'okx', 'isolated', 8.085708510208207), + ] +) +def test_get_liquidation_price( + mocker, + default_conf_usdt, + is_short, + trading_mode, + exchange_name, + margin_mode, + leverage, + open_rate, + amount, + expected_liq, + liquidation_buffer, +): + """ + position = 0.2 * 5 + wb: wallet balance (stake_amount if isolated) + cum_b: maintenance amount + side_1: -1 if is_short else 1 + ep1: entry price + mmr_b: maintenance margin ratio + + Binance, Short + leverage = 5, open_rate = 10, amount = 1.0 + ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position)) + ((2 + 0.01) - ((-1) * 1 * 10)) / ((1 * 0.01) - ((-1) * 1)) = 11.89108910891089 + leverage = 3, open_rate = 10, amount = 1.0 + ((3.3333333333 + 0.01) - ((-1) * 1.0 * 10)) / ((1.0 * 0.01) - ((-1) * 1.0)) = 13.2112211220 + leverage = 5, open_rate = 8, amount = 1.0 + ((1.6 + 0.01) - ((-1) * 1 * 8)) / ((1 * 0.01) - ((-1) * 1)) = 9.514851485148514 + leverage = 5, open_rate = 10, amount = 0.6 + ((1.6 + 0.01) - ((-1) * 0.6 * 10)) / ((0.6 * 0.01) - ((-1) * 0.6)) = 12.557755775577558 + + Binance, Long + leverage = 5, open_rate = 10, amount = 1.0 + ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position)) + ((2 + 0.01) - (1 * 1 * 10)) / ((1 * 0.01) - (1 * 1)) = 8.070707070707071 + leverage = 5, open_rate = 8, amount = 1.0 + ((1.6 + 0.01) - (1 * 1 * 8)) / ((1 * 0.01) - (1 * 1)) = 6.454545454545454 + leverage = 3, open_rate = 10, amount = 1.0 + ((2 + 0.01) - (1 * 0.6 * 10)) / ((0.6 * 0.01) - (1 * 0.6)) = 6.717171717171718 + leverage = 5, open_rate = 10, amount = 0.6 + ((1.6 + 0.01) - (1 * 0.6 * 10)) / ((0.6 * 0.01) - (1 * 0.6)) = 7.39057239057239 + + Gateio/Okx, Short + leverage = 5, open_rate = 10, amount = 1.0 + (open_rate + (wallet_balance / position)) / (1 + (mm_ratio + taker_fee_rate)) + (10 + (2 / 1.0)) / (1 + (0.01 + 0.0006)) = 11.87413417771621 + leverage = 5, open_rate = 10, amount = 2.0 + (10 + (4 / 2.0)) / (1 + (0.01 + 0.0006)) = 11.87413417771621 + leverage = 3, open_rate = 10, amount = 1.0 + (10 + (3.3333333333333 / 1.0)) / (1 - (0.01 + 0.0006)) = 13.476180850346978 + leverage = 5, open_rate = 8, amount = 1.0 + (8 + (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 9.499307342172967 + + Gateio/Okx, Long + leverage = 5, open_rate = 10, amount = 1.0 + (open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate)) + (10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207 + leverage = 5, open_rate = 10, amount = 2.0 + (10 - (4 / 2.0)) / (1 + (0.01 + 0.0006)) = 7.916089451810806 + leverage = 3, open_rate = 10, amount = 1.0 + (10 - (3.333333333333333333 / 1.0)) / (1 - (0.01 + 0.0006)) = 6.738090425173506 + leverage = 5, open_rate = 8, amount = 1.0 + (8 - (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 6.332871561448645 + """ + default_conf_usdt['liquidation_buffer'] = liquidation_buffer + default_conf_usdt['trading_mode'] = trading_mode + default_conf_usdt['exchange']['name'] = exchange_name + default_conf_usdt['margin_mode'] = margin_mode + mocker.patch('freqtrade.exchange.Gateio.validate_ordertypes') + exchange = get_patched_exchange(mocker, default_conf_usdt) + + exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01)) + exchange.name = exchange_name + # default_conf_usdt.update({ + # "dry_run": False, + # }) + liq = exchange.get_liquidation_price( + pair='ETH/USDT:USDT', + open_rate=open_rate, + amount=amount, + leverage=leverage, + is_short=is_short, + ) + if expected_liq is None: + assert liq is None + else: + buffer_amount = liquidation_buffer * abs(open_rate - expected_liq) + expected_liq = expected_liq - buffer_amount if is_short else expected_liq + buffer_amount + isclose(expected_liq, liq) + + +@pytest.mark.parametrize('contract_size,order_amount', [ + (10, 10), + (0.01, 10000), +]) +def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amount): + api_mock = MagicMock() + order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) + + api_mock.create_order = MagicMock(return_value={ + 'id': order_id, + 'info': { + 'foo': 'bar' + }, + 'amount': order_amount, + 'cost': order_amount, + 'filled': order_amount, + 'remaining': order_amount, + 'symbol': 'ETH/BTC', + }) + default_conf['dry_run'] = False + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange._get_contract_size = MagicMock(return_value=contract_size) + + api_mock.create_order.reset_mock() + order = exchange.stoploss( + pair='ETH/BTC', + amount=100, + stop_price=220, + order_types={}, + side='buy', + leverage=1.0 + ) + + assert api_mock.create_order.call_args_list[0][1]['amount'] == order_amount + assert order['amount'] == 100 + assert order['cost'] == 100 + assert order['filled'] == 100 + assert order['remaining'] == 100 diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index c2fb90c9d..0f16d4433 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -14,7 +14,11 @@ from .test_exchange import ccxt_exceptionhandlers STOPLOSS_ORDERTYPE = 'stop' -def test_stoploss_order_ftx(default_conf, mocker): +@pytest.mark.parametrize('order_price,exchangelimitratio,side', [ + (217.8, 1.05, "sell"), + (222.2, 0.95, "buy"), +]) +def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitratio, side): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) @@ -32,12 +36,18 @@ def test_stoploss_order_ftx(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') # stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, - order_types={'stoploss_on_exchange_limit_ratio': 1.05}) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=190, + side=side, + order_types={'stoploss_on_exchange_limit_ratio': exchangelimitratio}, + leverage=1.0 + ) assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE - assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' + assert api_mock.create_order.call_args_list[0][1]['side'] == side assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params'] assert 'stopPrice' in api_mock.create_order.call_args_list[0][1]['params'] @@ -47,51 +57,79 @@ def test_stoploss_order_ftx(default_conf, mocker): api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) assert 'id' in order assert 'info' in order assert order['id'] == order_id assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE - assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' + assert api_mock.create_order.call_args_list[0][1]['side'] == side assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params'] assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220 api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types={'stoploss': 'limit'}) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={'stoploss': 'limit'}, side=side, + leverage=1.0 + ) assert 'id' in order assert 'info' in order assert order['id'] == order_id assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE - assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' + assert api_mock.create_order.call_args_list[0][1]['side'] == side assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 assert 'orderPrice' in api_mock.create_order.call_args_list[0][1]['params'] - assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == 217.8 + assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == order_price assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220 # test exception handling with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, + side=side, leverage=1.0) -def test_stoploss_order_dry_run_ftx(default_conf, mocker): +@pytest.mark.parametrize('side', [("sell"), ("buy")]) +def test_stoploss_order_dry_run_ftx(default_conf, mocker, side): api_mock = MagicMock() default_conf['dry_run'] = True mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) @@ -101,7 +139,14 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker): api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) assert 'id' in order assert 'info' in order @@ -112,20 +157,24 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker): assert order['amount'] == 1 -def test_stoploss_adjust_ftx(mocker, default_conf): +@pytest.mark.parametrize('sl1,sl2,sl3,side', [ + (1501, 1499, 1501, "sell"), + (1499, 1501, 1499, "buy") +]) +def test_stoploss_adjust_ftx(mocker, default_conf, sl1, sl2, sl3, side): exchange = get_patched_exchange(mocker, default_conf, id='ftx') order = { 'type': STOPLOSS_ORDERTYPE, 'price': 1500, } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) + assert exchange.stoploss_adjust(sl1, order, side=side) + assert not exchange.stoploss_adjust(sl2, order, side=side) # Test with invalid order case ... order['type'] = 'stop_loss_limit' - assert not exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(sl3, order, side=side) -def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order): +def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order, limit_buy_order): default_conf['dry_run'] = True order = MagicMock() order.myid = 123 diff --git a/tests/exchange/test_gateio.py b/tests/exchange/test_gateio.py index ce356be8c..ad30a7d86 100644 --- a/tests/exchange/test_gateio.py +++ b/tests/exchange/test_gateio.py @@ -1,7 +1,9 @@ +from datetime import datetime, timezone from unittest.mock import MagicMock import pytest +from freqtrade.enums import MarginMode, TradingMode from freqtrade.exceptions import OperationalException from freqtrade.exchange import Gateio from freqtrade.resolvers.exchange_resolver import ExchangeResolver @@ -15,13 +17,14 @@ def test_validate_order_types_gateio(default_conf, mocker): 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.validate_pricing') 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', + 'entry': 'market', + 'exit': 'limit', 'stoploss': 'market', 'stoploss_on_exchange': False } @@ -57,11 +60,59 @@ def test_cancel_stoploss_order_gateio(default_conf, mocker): assert cancel_order_mock.call_args_list[0][1]['params'] == {'stop': True} -def test_stoploss_adjust_gateio(mocker, default_conf): +@pytest.mark.parametrize('sl1,sl2,sl3,side', [ + (1501, 1499, 1501, "sell"), + (1499, 1501, 1499, "buy") +]) +def test_stoploss_adjust_gateio(mocker, default_conf, sl1, sl2, sl3, side): exchange = get_patched_exchange(mocker, default_conf, id='gateio') order = { 'price': 1500, 'stopPrice': 1500, } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) + assert exchange.stoploss_adjust(sl1, order, side) + assert not exchange.stoploss_adjust(sl2, order, side) + + +@pytest.mark.parametrize('takerormaker,rate,cost', [ + ('taker', 0.0005, 0.0001554325), + ('maker', 0.0, 0.0), +]) +def test_fetch_my_trades_gateio(mocker, default_conf, takerormaker, rate, cost): + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + tick = {'ETH/USDT:USDT': { + 'info': {'user_id': '', + 'taker_fee': '0.0018', + 'maker_fee': '0.0018', + 'gt_discount': False, + 'gt_taker_fee': '0', + 'gt_maker_fee': '0', + 'loan_fee': '0.18', + 'point_type': '1', + 'futures_taker_fee': '0.0005', + 'futures_maker_fee': '0'}, + 'symbol': 'ETH/USDT:USDT', + 'maker': 0.0, + 'taker': 0.0005} + } + default_conf['dry_run'] = False + default_conf['trading_mode'] = TradingMode.FUTURES + default_conf['margin_mode'] = MarginMode.ISOLATED + + api_mock = MagicMock() + api_mock.fetch_my_trades = MagicMock(return_value=[{ + 'fee': {'cost': None}, + 'price': 3108.65, + 'cost': 0.310865, + 'order': '22255', + 'takerOrMaker': takerormaker, + 'amount': 1, # 1 contract + }]) + exchange = get_patched_exchange(mocker, default_conf, api_mock=api_mock, id='gateio') + exchange._trading_fees = tick + trades = exchange.get_trades_for_order('22255', 'ETH/USDT:USDT', datetime.now(timezone.utc)) + trade = trades[0] + assert trade['fee'] + assert trade['fee']['rate'] == rate + assert trade['fee']['currency'] == 'USDT' + assert trade['fee']['cost'] == cost diff --git a/tests/exchange/test_huobi.py b/tests/exchange/test_huobi.py index b39b5ab30..fc7c7cefb 100644 --- a/tests/exchange/test_huobi.py +++ b/tests/exchange/test_huobi.py @@ -9,12 +9,12 @@ from tests.conftest import get_patched_exchange from tests.exchange.test_exchange import ccxt_exceptionhandlers -@pytest.mark.parametrize('limitratio,expected', [ - (None, 220 * 0.99), - (0.99, 220 * 0.99), - (0.98, 220 * 0.98), +@pytest.mark.parametrize('limitratio,expected,side', [ + (None, 220 * 0.99, "sell"), + (0.99, 220 * 0.99, "sell"), + (0.98, 220 * 0.98, "sell"), ]) -def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected): +def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) order_type = 'stop-limit' @@ -33,11 +33,14 @@ def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected): with pytest.raises(OperationalException): order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, - order_types={'stoploss_on_exchange_limit_ratio': 1.05}) + order_types={'stoploss_on_exchange_limit_ratio': 1.05}, + side=side, + leverage=1.0) api_mock.create_order.reset_mock() order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio} - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types, + side=side, leverage=1.0) assert 'id' in order assert 'info' in order @@ -56,17 +59,20 @@ def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={}, side=side, leverage=1.0) with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={}, side=side, leverage=1.0) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "huobi", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, + side=side, leverage=1.0) def test_stoploss_order_dry_run_huobi(default_conf, mocker): @@ -80,11 +86,13 @@ def test_stoploss_order_dry_run_huobi(default_conf, mocker): with pytest.raises(OperationalException): order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, - order_types={'stoploss_on_exchange_limit_ratio': 1.05}) + order_types={'stoploss_on_exchange_limit_ratio': 1.05}, + side='sell', leverage=1.0) api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={}, side='sell', leverage=1.0) assert 'id' in order assert 'info' in order @@ -102,8 +110,8 @@ def test_stoploss_adjust_huobi(mocker, default_conf): 'price': 1500, 'stopPrice': '1500', } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) + assert exchange.stoploss_adjust(1501, order, 'sell') + assert not exchange.stoploss_adjust(1499, order, 'sell') # Test with invalid order case order['type'] = 'stop_loss' - assert not exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1501, order, 'sell') diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index eb79dfc10..02df60990 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -21,6 +21,7 @@ def test_buy_kraken_trading_agreement(default_conf, mocker): api_mock.options = {} api_mock.create_order = MagicMock(return_value={ 'id': order_id, + 'symbol': 'ETH/BTC', 'info': { 'foo': 'bar' } @@ -31,8 +32,15 @@ def test_buy_kraken_trading_agreement(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") - order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, time_in_force=time_in_force) + order = exchange.create_order( + pair='ETH/BTC', + ordertype=order_type, + side="buy", + amount=1, + rate=200, + leverage=1.0, + time_in_force=time_in_force + ) assert 'id' in order assert 'info' in order @@ -53,6 +61,7 @@ def test_sell_kraken_trading_agreement(default_conf, mocker): api_mock.options = {} api_mock.create_order = MagicMock(return_value={ 'id': order_id, + 'symbol': 'ETH/BTC', 'info': { 'foo': 'bar' } @@ -64,7 +73,7 @@ def test_sell_kraken_trading_agreement(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, - side="sell", amount=1, rate=200) + side="sell", amount=1, rate=200, leverage=1.0) assert 'id' in order assert 'info' in order @@ -166,7 +175,11 @@ def test_get_balances_prod(default_conf, mocker): @pytest.mark.parametrize('ordertype', ['market', 'limit']) -def test_stoploss_order_kraken(default_conf, mocker, ordertype): +@pytest.mark.parametrize('side,adjustedprice', [ + ("sell", 217.8), + ("buy", 222.2), +]) +def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedprice): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) @@ -183,10 +196,17 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types={'stoploss': ordertype, - 'stoploss_on_exchange_limit_ratio': 0.99 - }) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + side=side, + order_types={ + 'stoploss': ordertype, + 'stoploss_on_exchange_limit_ratio': 0.99 + }, + leverage=1.0 + ) assert 'id' in order assert 'info' in order @@ -195,12 +215,14 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype): if ordertype == 'limit': assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_LIMIT_ORDERTYPE assert api_mock.create_order.call_args_list[0][1]['params'] == { - 'trading_agreement': 'agree', 'price2': 217.8} + 'trading_agreement': 'agree', + 'price2': adjustedprice + } else: assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE assert api_mock.create_order.call_args_list[0][1]['params'] == { 'trading_agreement': 'agree'} - assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' + assert api_mock.create_order.call_args_list[0][1]['side'] == side assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 assert api_mock.create_order.call_args_list[0][1]['price'] == 220 @@ -208,20 +230,36 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, + side=side, leverage=1.0) -def test_stoploss_order_dry_run_kraken(default_conf, mocker): +@pytest.mark.parametrize('side', ['buy', 'sell']) +def test_stoploss_order_dry_run_kraken(default_conf, mocker, side): api_mock = MagicMock() default_conf['dry_run'] = True mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) @@ -231,7 +269,14 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker): api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss( + pair='ETH/BTC', + amount=1, + stop_price=220, + order_types={}, + side=side, + leverage=1.0 + ) assert 'id' in order assert 'info' in order @@ -242,14 +287,18 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker): assert order['amount'] == 1 -def test_stoploss_adjust_kraken(mocker, default_conf): +@pytest.mark.parametrize('sl1,sl2,sl3,side', [ + (1501, 1499, 1501, "sell"), + (1499, 1501, 1499, "buy") +]) +def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side): exchange = get_patched_exchange(mocker, default_conf, id='kraken') order = { 'type': STOPLOSS_ORDERTYPE, 'price': 1500, } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) + assert exchange.stoploss_adjust(sl1, order, side=side) + assert not exchange.stoploss_adjust(sl2, order, side=side) # Test with invalid order case ... order['type'] = 'stop_loss_limit' - assert not exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(sl3, order, side=side) diff --git a/tests/exchange/test_kucoin.py b/tests/exchange/test_kucoin.py index 87f9ae8d9..8af1e83a3 100644 --- a/tests/exchange/test_kucoin.py +++ b/tests/exchange/test_kucoin.py @@ -10,12 +10,12 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers @pytest.mark.parametrize('order_type', ['market', 'limit']) -@pytest.mark.parametrize('limitratio,expected', [ - (None, 220 * 0.99), - (0.99, 220 * 0.99), - (0.98, 220 * 0.98), +@pytest.mark.parametrize('limitratio,expected,side', [ + (None, 220 * 0.99, "sell"), + (0.99, 220 * 0.99, "sell"), + (0.98, 220 * 0.98, "sell"), ]) -def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, order_type): +def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side, order_type): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) @@ -35,13 +35,15 @@ def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, order order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, order_types={ 'stoploss': order_type, - 'stoploss_on_exchange_limit_ratio': 1.05}) + 'stoploss_on_exchange_limit_ratio': 1.05}, + side=side, leverage=1.0) api_mock.create_order.reset_mock() order_types = {'stoploss': order_type} if limitratio is not None: order_types.update({'stoploss_on_exchange_limit_ratio': limitratio}) - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types=order_types, side=side, leverage=1.0) assert 'id' in order assert 'info' in order @@ -65,17 +67,20 @@ def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, order with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={}, side=side, leverage=1.0) with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("kucoin Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={}, side=side, leverage=1.0) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kucoin", "stoploss", "create_order", retries=1, - pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + pair='ETH/BTC', amount=1, stop_price=220, order_types={}, + side=side, leverage=1.0) def test_stoploss_order_dry_run_kucoin(default_conf, mocker): @@ -90,11 +95,13 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker): with pytest.raises(OperationalException): order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, order_types={'stoploss': 'limit', - 'stoploss_on_exchange_limit_ratio': 1.05}) + 'stoploss_on_exchange_limit_ratio': 1.05}, + side='sell', leverage=1.0) api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={}, side='sell', leverage=1.0) assert 'id' in order assert 'info' in order @@ -113,8 +120,8 @@ def test_stoploss_adjust_kucoin(mocker, default_conf): 'stopPrice': 1500, 'info': {'stopPrice': 1500, 'stop': "limit"}, } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) + assert exchange.stoploss_adjust(1501, order, 'sell') + assert not exchange.stoploss_adjust(1499, order, 'sell') # Test with invalid order case order['info']['stop'] = None - assert not exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1501, order, 'sell') diff --git a/tests/exchange/test_okx.py b/tests/exchange/test_okx.py new file mode 100644 index 000000000..8ecdf6904 --- /dev/null +++ b/tests/exchange/test_okx.py @@ -0,0 +1,360 @@ +from unittest.mock import MagicMock, PropertyMock + +from freqtrade.enums import MarginMode, TradingMode +from tests.conftest import get_patched_exchange + + +def test_get_maintenance_ratio_and_amt_okx( + default_conf, + mocker, +): + api_mock = MagicMock() + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + default_conf['dry_run'] = False + mocker.patch.multiple( + 'freqtrade.exchange.Okx', + exchange_has=MagicMock(return_value=True), + load_leverage_tiers=MagicMock(return_value={ + 'ETH/USDT:USDT': [ + { + 'tier': 1, + 'notionalFloor': 0, + 'notionalCap': 2000, + 'maintenanceMarginRate': 0.01, + 'maxLeverage': 75, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.013', + 'instId': '', + 'maxLever': '75', + 'maxSz': '2000', + 'minSz': '0', + 'mmr': '0.01', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '1', + 'uly': 'ETH-USDT' + } + }, + { + 'tier': 2, + 'notionalFloor': 2001, + 'notionalCap': 4000, + 'maintenanceMarginRate': 0.015, + 'maxLeverage': 50, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.02', + 'instId': '', + 'maxLever': '50', + 'maxSz': '4000', + 'minSz': '2001', + 'mmr': '0.015', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '2', + 'uly': 'ETH-USDT' + } + }, + { + 'tier': 3, + 'notionalFloor': 4001, + 'notionalCap': 8000, + 'maintenanceMarginRate': 0.02, + 'maxLeverage': 20, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.05', + 'instId': '', + 'maxLever': '20', + 'maxSz': '8000', + 'minSz': '4001', + 'mmr': '0.02', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '3', + 'uly': 'ETH-USDT' + } + }, + ], + 'ADA/USDT:USDT': [ + { + 'tier': 1, + 'notionalFloor': 0, + 'notionalCap': 500, + 'maintenanceMarginRate': 0.02, + 'maxLeverage': 75, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.013', + 'instId': '', + 'maxLever': '75', + 'maxSz': '500', + 'minSz': '0', + 'mmr': '0.01', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '1', + 'uly': 'ADA-USDT' + } + }, + { + 'tier': 2, + 'notionalFloor': 501, + 'notionalCap': 1000, + 'maintenanceMarginRate': 0.025, + 'maxLeverage': 50, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.02', + 'instId': '', + 'maxLever': '50', + 'maxSz': '1000', + 'minSz': '501', + 'mmr': '0.015', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '2', + 'uly': 'ADA-USDT' + } + }, + { + 'tier': 3, + 'notionalFloor': 1001, + 'notionalCap': 2000, + 'maintenanceMarginRate': 0.03, + 'maxLeverage': 20, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.05', + 'instId': '', + 'maxLever': '20', + 'maxSz': '2000', + 'minSz': '1001', + 'mmr': '0.02', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '3', + 'uly': 'ADA-USDT' + } + }, + ] + }) + ) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") + assert exchange.get_maintenance_ratio_and_amt('ETH/USDT:USDT', 2000) == (0.01, None) + assert exchange.get_maintenance_ratio_and_amt('ETH/USDT:USDT', 2001) == (0.015, None) + assert exchange.get_maintenance_ratio_and_amt('ETH/USDT:USDT', 4001) == (0.02, None) + assert exchange.get_maintenance_ratio_and_amt('ETH/USDT:USDT', 8000) == (0.02, None) + + assert exchange.get_maintenance_ratio_and_amt('ADA/USDT:USDT', 1) == (0.02, None) + assert exchange.get_maintenance_ratio_and_amt('ADA/USDT:USDT', 2000) == (0.03, None) + + +def test_get_max_pair_stake_amount_okx(default_conf, mocker, leverage_tiers): + + exchange = get_patched_exchange(mocker, default_conf, id="okx") + assert exchange.get_max_pair_stake_amount('BNB/BUSD', 1.0) == float('inf') + + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + exchange = get_patched_exchange(mocker, default_conf, id="okx") + exchange._leverage_tiers = leverage_tiers + + assert exchange.get_max_pair_stake_amount('BNB/BUSD', 1.0) == 30000000 + assert exchange.get_max_pair_stake_amount('BNB/USDT', 1.0) == 50000000 + assert exchange.get_max_pair_stake_amount('BTC/USDT', 1.0) == 1000000000 + assert exchange.get_max_pair_stake_amount('BTC/USDT', 1.0, 10.0) == 100000000 + + assert exchange.get_max_pair_stake_amount('TTT/USDT', 1.0) == float('inf') # Not in tiers + + +def test_load_leverage_tiers_okx(default_conf, mocker, markets): + api_mock = MagicMock() + type(api_mock).has = PropertyMock(return_value={ + 'fetchLeverageTiers': False, + 'fetchMarketLeverageTiers': True, + }) + api_mock.fetch_market_leverage_tiers = MagicMock(side_effect=[ + [ + { + 'tier': 1, + 'notionalFloor': 0, + 'notionalCap': 500, + 'maintenanceMarginRate': 0.02, + 'maxLeverage': 75, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.013', + 'instId': '', + 'maxLever': '75', + 'maxSz': '500', + 'minSz': '0', + 'mmr': '0.01', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '1', + 'uly': 'ADA-USDT' + } + }, + { + 'tier': 2, + 'notionalFloor': 501, + 'notionalCap': 1000, + 'maintenanceMarginRate': 0.025, + 'maxLeverage': 50, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.02', + 'instId': '', + 'maxLever': '50', + 'maxSz': '1000', + 'minSz': '501', + 'mmr': '0.015', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '2', + 'uly': 'ADA-USDT' + } + }, + { + 'tier': 3, + 'notionalFloor': 1001, + 'notionalCap': 2000, + 'maintenanceMarginRate': 0.03, + 'maxLeverage': 20, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.05', + 'instId': '', + 'maxLever': '20', + 'maxSz': '2000', + 'minSz': '1001', + 'mmr': '0.02', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '3', + 'uly': 'ADA-USDT' + } + }, + ], + [ + { + 'tier': 1, + 'notionalFloor': 0, + 'notionalCap': 2000, + 'maintenanceMarginRate': 0.01, + 'maxLeverage': 75, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.013', + 'instId': '', + 'maxLever': '75', + 'maxSz': '2000', + 'minSz': '0', + 'mmr': '0.01', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '1', + 'uly': 'ETH-USDT' + } + }, + { + 'tier': 2, + 'notionalFloor': 2001, + 'notionalCap': 4000, + 'maintenanceMarginRate': 0.015, + 'maxLeverage': 50, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.02', + 'instId': '', + 'maxLever': '50', + 'maxSz': '4000', + 'minSz': '2001', + 'mmr': '0.015', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '2', + 'uly': 'ETH-USDT' + } + }, + { + 'tier': 3, + 'notionalFloor': 4001, + 'notionalCap': 8000, + 'maintenanceMarginRate': 0.02, + 'maxLeverage': 20, + 'info': { + 'baseMaxLoan': '', + 'imr': '0.05', + 'instId': '', + 'maxLever': '20', + 'maxSz': '8000', + 'minSz': '4001', + 'mmr': '0.02', + 'optMgnFactor': '0', + 'quoteMaxLoan': '', + 'tier': '3', + 'uly': 'ETH-USDT' + } + }, + ] + ]) + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + default_conf['stake_currency'] = 'USDT' + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") + exchange.trading_mode = TradingMode.FUTURES + exchange.margin_mode = MarginMode.ISOLATED + exchange.markets = markets + # Initialization of load_leverage_tiers happens as part of exchange init. + assert exchange._leverage_tiers == { + 'ADA/USDT:USDT': [ + { + 'min': 0, + 'max': 500, + 'mmr': 0.02, + 'lev': 75, + 'maintAmt': None + }, + { + 'min': 501, + 'max': 1000, + 'mmr': 0.025, + 'lev': 50, + 'maintAmt': None + }, + { + 'min': 1001, + 'max': 2000, + 'mmr': 0.03, + 'lev': 20, + 'maintAmt': None + }, + ], + 'ETH/USDT:USDT': [ + { + 'min': 0, + 'max': 2000, + 'mmr': 0.01, + 'lev': 75, + 'maintAmt': None + }, + { + 'min': 2001, + 'max': 4000, + 'mmr': 0.015, + 'lev': 50, + 'maintAmt': None + }, + { + 'min': 4001, + 'max': 8000, + 'mmr': 0.02, + 'lev': 20, + 'maintAmt': None + }, + ], + } diff --git a/tests/leverage/test_candletype.py b/tests/leverage/test_candletype.py new file mode 100644 index 000000000..ed7991d26 --- /dev/null +++ b/tests/leverage/test_candletype.py @@ -0,0 +1,27 @@ +import pytest + +from freqtrade.enums import CandleType + + +@pytest.mark.parametrize('input,expected', [ + ('', CandleType.SPOT), + ('spot', CandleType.SPOT), + (CandleType.SPOT, CandleType.SPOT), + (CandleType.FUTURES, CandleType.FUTURES), + (CandleType.INDEX, CandleType.INDEX), + (CandleType.MARK, CandleType.MARK), + ('futures', CandleType.FUTURES), + ('mark', CandleType.MARK), + ('premiumIndex', CandleType.PREMIUMINDEX), +]) +def test_CandleType_from_string(input, expected): + assert CandleType.from_string(input) == expected + + +@pytest.mark.parametrize('input,expected', [ + ('futures', CandleType.FUTURES), + ('spot', CandleType.SPOT), + ('margin', CandleType.SPOT), +]) +def test_CandleType_get_default(input, expected): + assert CandleType.get_default(input) == expected diff --git a/tests/leverage/test_interest.py b/tests/leverage/test_interest.py new file mode 100644 index 000000000..c7e787bdb --- /dev/null +++ b/tests/leverage/test_interest.py @@ -0,0 +1,38 @@ +from decimal import Decimal +from math import isclose + +import pytest + +from freqtrade.leverage import interest + + +ten_mins = Decimal(1/6) +five_hours = Decimal(5.0) +twentyfive_hours = Decimal(25.0) + + +@pytest.mark.parametrize('exchange,interest_rate,hours,expected', [ + ('binance', 0.0005, ten_mins, 0.00125), + ('binance', 0.00025, ten_mins, 0.000625), + ('binance', 0.00025, five_hours, 0.003125), + ('binance', 0.00025, twentyfive_hours, 0.015625), + # Kraken + ('kraken', 0.0005, ten_mins, 0.06), + ('kraken', 0.00025, ten_mins, 0.03), + ('kraken', 0.00025, five_hours, 0.045), + ('kraken', 0.00025, twentyfive_hours, 0.12), + # FTX + ('ftx', 0.0005, ten_mins, 0.00125), + ('ftx', 0.00025, ten_mins, 0.000625), + ('ftx', 0.00025, five_hours, 0.003125), + ('ftx', 0.00025, twentyfive_hours, 0.015625), +]) +def test_interest(exchange, interest_rate, hours, expected): + borrowed = Decimal(60.0) + + assert isclose(interest( + exchange_name=exchange, + borrowed=borrowed, + rate=Decimal(interest_rate), + hours=hours + ), expected) diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index ce6ea0f0c..ad14125b5 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -3,7 +3,7 @@ from typing import Dict, List, NamedTuple, Optional import arrow from pandas import DataFrame -from freqtrade.enums import SellType +from freqtrade.enums import ExitType from freqtrade.exchange import timeframe_to_minutes @@ -15,10 +15,11 @@ class BTrade(NamedTuple): """ Minimalistic Trade result used for functional backtesting """ - sell_reason: SellType + sell_reason: ExitType open_tick: int close_tick: int - buy_tag: Optional[str] = None + enter_tag: Optional[str] = None + is_short: bool = False class BTContainer(NamedTuple): @@ -38,6 +39,7 @@ class BTContainer(NamedTuple): use_custom_stoploss: bool = False custom_entry_price: Optional[float] = None custom_exit_price: Optional[float] = None + leverage: float = 1.0 def _get_frame_time_from_offset(offset): @@ -46,18 +48,18 @@ def _get_frame_time_from_offset(offset): def _build_backtest_dataframe(data): - columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell'] - columns = columns + ['buy_tag'] if len(data[0]) == 9 else columns + columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'enter_long', 'exit_long', + 'enter_short', 'exit_short'] + if len(data[0]) == 8: + # No short columns + data = [d + [0, 0] for d in data] + columns = columns + ['enter_tag'] if len(data[0]) == 11 else columns frame = DataFrame.from_records(data, columns=columns) frame['date'] = frame['date'].apply(_get_frame_time_from_offset) # Ensure floats are in place for column in ['open', 'high', 'low', 'close', 'volume']: frame[column] = frame[column].astype('float64') - if 'buy_tag' not in columns: - frame['buy_tag'] = None - if 'exit_tag' not in columns: - frame['exit_tag'] = None # Ensure all candles make kindof sense assert all(frame['low'] <= frame['close']) diff --git a/tests/optimize/conftest.py b/tests/optimize/conftest.py index 8c7fa3ac9..7b93773f0 100644 --- a/tests/optimize/conftest.py +++ b/tests/optimize/conftest.py @@ -5,7 +5,7 @@ from pathlib import Path import pandas as pd import pytest -from freqtrade.enums import RunMode, SellType +from freqtrade.enums import ExitType, RunMode from freqtrade.optimize.hyperopt import Hyperopt from tests.conftest import patch_exchange @@ -44,7 +44,7 @@ def hyperopt_results(): 'profit_abs': [-0.2, 0.4, -0.2, 0.6], 'trade_duration': [10, 30, 10, 10], 'amount': [0.1, 0.1, 0.1, 0.1], - 'sell_reason': [SellType.STOP_LOSS, SellType.ROI, SellType.STOP_LOSS, SellType.ROI], + 'sell_reason': [ExitType.STOP_LOSS, ExitType.ROI, ExitType.STOP_LOSS, ExitType.ROI], 'open_date': [ datetime(2019, 1, 1, 9, 15, 0), diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 977563eeb..c5aaab5e6 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -5,7 +5,7 @@ from unittest.mock import MagicMock import pytest from freqtrade.data.history import get_timerange -from freqtrade.enums import SellType +from freqtrade.enums import ExitType from freqtrade.optimize.backtesting import Backtesting from tests.conftest import patch_exchange from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, @@ -15,7 +15,7 @@ from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, # Test 0: Sell with signal sell in candle 3 # Test with Stop-loss at 1% tc0 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5012, 4986, 4986, 6172, 0, 0], # exit with stoploss hit @@ -23,13 +23,13 @@ tc0 = BTContainer(data=[ [4, 5010, 5011, 4977, 4995, 6172, 0, 0], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True, - trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] + trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)] ) # Test 1: Stop-Loss Triggered 1% loss # Test with Stop-loss at 1% tc1 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5012, 4600, 4600, 6172, 0, 0], # exit with stoploss hit @@ -37,14 +37,14 @@ tc1 = BTContainer(data=[ [4, 4977, 4995, 4977, 4995, 6172, 0, 0], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, - trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] + trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)] ) # Test 2: Minus 4% Low, minus 1% close # Test with Stop-Loss at 3% tc2 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5012, 4962, 4975, 6172, 0, 0], @@ -52,7 +52,7 @@ tc2 = BTContainer(data=[ [4, 4962, 4987, 4937, 4950, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.03, roi={"0": 1}, profit_perc=-0.03, - trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=3)] ) @@ -63,7 +63,7 @@ tc2 = BTContainer(data=[ # Trade-A: Stop-Loss Triggered 2% Loss # Trade-B: Stop-Loss Triggered 2% Loss tc3 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5012, 4800, 4975, 6172, 0, 0], # exit with stoploss hit @@ -72,8 +72,8 @@ tc3 = BTContainer(data=[ [5, 4962, 4987, 4000, 4000, 6172, 0, 0], # exit with stoploss hit [6, 4950, 4975, 4950, 4950, 6172, 0, 0]], stop_loss=-0.02, roi={"0": 1}, profit_perc=-0.04, - trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2), - BTrade(sell_reason=SellType.STOP_LOSS, open_tick=4, close_tick=5)] + trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2), + BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=4, close_tick=5)] ) # Test 4: Minus 3% / recovery +15% @@ -81,7 +81,7 @@ tc3 = BTContainer(data=[ # Test with Stop-loss at 2% ROI 6% # Stop-Loss Triggered 2% Loss tc4 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5750, 4850, 5750, 6172, 0, 0], # Exit with stoploss hit @@ -89,13 +89,13 @@ tc4 = BTContainer(data=[ [4, 4962, 4987, 4937, 4950, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.02, roi={"0": 0.06}, profit_perc=-0.02, - trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] + trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)] ) # Test 5: Drops 0.5% Closes +20%, ROI triggers 3% Gain # stop-loss: 1%, ROI: 3% tc5 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4980, 4987, 6172, 1, 0], [1, 5000, 5025, 4980, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5025, 4975, 4987, 6172, 0, 0], @@ -103,13 +103,13 @@ tc5 = BTContainer(data=[ [4, 4962, 4987, 4962, 4972, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 0.03}, profit_perc=0.03, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] ) # Test 6: Drops 3% / Recovers 6% Positive / Closes 1% positve, Stop-Loss triggers 2% Loss # stop-loss: 2% ROI: 5% tc6 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5300, 4850, 5050, 6172, 0, 0], # Exit with stoploss @@ -117,13 +117,13 @@ tc6 = BTContainer(data=[ [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.02, roi={"0": 0.05}, profit_perc=-0.02, - trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] + trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)] ) # Test 7: 6% Positive / 1% Negative / Close 1% Positve, ROI Triggers 3% Gain # stop-loss: 2% ROI: 3% tc7 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 4987, 5300, 4950, 5050, 6172, 0, 0], @@ -131,42 +131,42 @@ tc7 = BTContainer(data=[ [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.02, roi={"0": 0.03}, profit_perc=0.03, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2)] ) # Test 8: trailing_stop should raise so candle 3 causes a stoploss. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted in candle 2 tc8 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5050, 4950, 5000, 6172, 0, 0], [2, 5000, 5250, 4750, 4850, 6172, 0, 0], [3, 4850, 5050, 4650, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.055, trailing_stop=True, - trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] ) # Test 9: trailing_stop should raise - high and low in same candle. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted in candle 3 tc9 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5050, 4950, 5000, 6172, 0, 0], [2, 5000, 5050, 4950, 5000, 6172, 0, 0], [3, 5000, 5200, 4550, 4850, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.064, trailing_stop=True, - trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] ) # Test 10: trailing_stop should raise so candle 3 causes a stoploss # without applying trailing_stop_positive since stoploss_offset is at 10%. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2 tc10 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 5100, 5100, 6172, 0, 0], @@ -175,14 +175,14 @@ tc10 = BTContainer(data=[ stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.1, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.10, trailing_stop_positive=0.03, - trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=4)] + trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=4)] ) # Test 11: trailing_stop should raise so candle 3 causes a stoploss # applying a positive trailing stop of 3% since stop_positive_offset is reached. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2 tc11 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 5100, 5100, 6172, 0, 0], @@ -191,14 +191,14 @@ tc11 = BTContainer(data=[ stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, - trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] ) # Test 12: trailing_stop should raise in candle 2 and cause a stoploss in the same candle # applying a positive trailing stop of 3% since stop_positive_offset is reached. # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2 tc12 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 4650, 5100, 6172, 0, 0], @@ -207,55 +207,55 @@ tc12 = BTContainer(data=[ stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, - trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)] + trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)] ) # Test 13: Buy and sell ROI on same candle # stop-loss: 10% (should not apply), ROI: 1% tc13 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 4850, 5100, 6172, 0, 0], [3, 4850, 5050, 4750, 4750, 6172, 0, 0], [4, 4750, 4950, 4750, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=1)] ) # Test 14 - Buy and Stoploss on same candle # stop-loss: 5%, ROI: 10% (should not apply) tc14 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5100, 4600, 5100, 6172, 0, 0], [2, 5100, 5251, 4850, 5100, 6172, 0, 0], [3, 4850, 5050, 4750, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.05, roi={"0": 0.10}, profit_perc=-0.05, - trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)] + trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)] ) # Test 15 - Buy and ROI on same candle, followed by buy and Stoploss on next candle # stop-loss: 5%, ROI: 10% (should not apply) tc15 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5100, 4900, 5100, 6172, 1, 0], [2, 5100, 5251, 4650, 5100, 6172, 0, 0], [3, 4850, 5050, 4750, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.05, roi={"0": 0.01}, profit_perc=-0.04, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1), - BTrade(sell_reason=SellType.STOP_LOSS, open_tick=2, close_tick=2)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=1), + BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=2, close_tick=2)] ) # Test 16: Buy, hold for 65 min, then forcesell using roi=-1 # Causes negative profit even though sell-reason is ROI. # stop-loss: 10%, ROI: 10% (should not apply), -100% after 65 minutes (limits trade duration) tc16 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 4987, 5300, 4950, 5050, 6172, 0, 0], @@ -263,7 +263,7 @@ tc16 = BTContainer(data=[ [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "65": -1}, profit_perc=-0.012, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] ) # Test 17: Buy, hold for 120 mins, then forcesell using roi=-1 @@ -271,7 +271,7 @@ tc16 = BTContainer(data=[ # stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) # Uses open as sell-rate (special case) - since the roi-time is a multiple of the timeframe. tc17 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 4987, 5300, 4950, 5050, 6172, 0, 0], @@ -279,7 +279,7 @@ tc17 = BTContainer(data=[ [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "120": -1}, profit_perc=-0.004, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] ) @@ -287,7 +287,7 @@ tc17 = BTContainer(data=[ # stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) # uses open_rate as sell-price tc18 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 4987, 5300, 4950, 5200, 6172, 0, 0], @@ -295,14 +295,14 @@ tc18 = BTContainer(data=[ [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.04, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] ) # Test 19: Buy, hold for 119 mins, then drop ROI to 1%, causing a sell in candle 3. # stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) # uses calculated ROI (1%) as sell rate, otherwise identical to tc18 tc19 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 4987, 5300, 4950, 5200, 6172, 0, 0], @@ -310,14 +310,14 @@ tc19 = BTContainer(data=[ [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4550, 4975, 4550, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.01, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] ) # Test 20: Buy, hold for 119 mins, then drop ROI to 1%, causing a sell in candle 3. # stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) # uses calculated ROI (1%) as sell rate, otherwise identical to tc18 tc20 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 4987, 5300, 4950, 5200, 6172, 0, 0], @@ -325,7 +325,7 @@ tc20 = BTContainer(data=[ [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4925, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "119": 0.01}, profit_perc=0.01, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] ) # Test 21: trailing_stop ROI collision. @@ -333,7 +333,7 @@ tc20 = BTContainer(data=[ # which cannot happen in reality # stop-loss: 10%, ROI: 4%, Trailing stop adjusted at the sell candle tc21 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 4650, 5100, 6172, 0, 0], @@ -342,14 +342,14 @@ tc21 = BTContainer(data=[ stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2)] ) # Test 22: trailing_stop Raises in candle 2 - but ROI applies at the same time. # applying a positive trailing stop of 3% - ROI should apply before trailing stop. # stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2 tc22 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 5100, 5100, 6172, 0, 0], @@ -358,7 +358,24 @@ tc22 = BTContainer(data=[ stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2)] +) + + +# Test 22s: trailing_stop Raises in candle 2 - but ROI applies at the same time. +# applying a positive trailing stop of 3% - ROI should apply before trailing stop. +# stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2 +tc22s = BTContainer(data=[ + # D O H L C V EL XL ES Xs BT + [0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0], + [1, 5000, 5050, 4900, 4900, 6172, 0, 0, 0, 0], + [2, 4900, 4900, 4749, 4900, 6172, 0, 0, 0, 0], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0]], + stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True, + trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, + trailing_stop_positive=0.03, + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2, is_short=True)] ) # Test 23: trailing_stop Raises in candle 2 (does not trigger) @@ -368,7 +385,7 @@ tc22 = BTContainer(data=[ # Stoploss would trigger in this candle too, but it's no longer relevant. # stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2, ROI adjusted in candle 3 (causing the sell) tc23 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 5100, 5100, 6172, 0, 0], @@ -377,14 +394,14 @@ tc23 = BTContainer(data=[ stop_loss=-0.10, roi={"0": 0.1, "119": 0.03}, profit_perc=0.03, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] ) # Test 24: Sell with signal sell in candle 3 (stoploss also triggers on this candle) # Stoploss at 1%. # Stoploss wins over Sell-signal (because sell-signal is acted on in the next candle) tc24 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5012, 4986, 4986, 6172, 0, 0], @@ -392,14 +409,14 @@ tc24 = BTContainer(data=[ [4, 5010, 5010, 4977, 4995, 6172, 0, 0], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, use_sell_signal=True, - trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=3)] ) # Test 25: Sell with signal sell in candle 3 (stoploss also triggers on this candle) # Stoploss at 1%. # Sell-signal wins over stoploss tc25 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5012, 4986, 4986, 6172, 0, 0], @@ -407,14 +424,47 @@ tc25 = BTContainer(data=[ [4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True, - trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] + trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)] ) +# Test 25l: (copy of test25 with leverage) +# Sell with signal sell in candle 3 (stoploss also triggers on this candle) +# Stoploss at 1%. +# Sell-signal wins over stoploss +tc25l = BTContainer(data=[ + # D O H L C V EL XL ES Xs BT + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) + [2, 4987, 5012, 4986, 4986, 6172, 0, 0], + [3, 5010, 5010, 4986, 5010, 6172, 0, 1], + [4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on + [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], + stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_sell_signal=True, + leverage=5.0, + trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)] +) + +# Test 25s: (copy of test25 with leverage and as short) +# Sell with signal sell in candle 3 (stoploss also triggers on this candle) +# Stoploss at 1%. +# Sell-signal wins over stoploss +tc25s = BTContainer(data=[ + # D O H L C V EL XL ES Xs BT + [0, 5000, 5025, 4975, 4987, 6172, 0, 0, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0, 0, 0], # enter trade (signal on last candle) + [2, 4987, 5012, 4986, 4986, 6172, 0, 0, 0, 0], + [3, 5010, 5010, 4986, 5010, 6172, 0, 0, 0, 1], + [4, 4990, 5010, 4855, 4995, 6172, 0, 0, 0, 0], # Triggers stoploss + sellsignal acted on + [5, 4995, 4995, 4950, 4950, 6172, 0, 0, 0, 0]], + stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_sell_signal=True, + leverage=5.0, + trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4, is_short=True)] +) # Test 26: Sell with signal sell in candle 3 (ROI at signal candle) # Stoploss at 10% (irrelevant), ROI at 5% (will trigger) # Sell-signal wins over stoploss tc26 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5012, 4986, 4986, 6172, 0, 0], @@ -422,13 +472,13 @@ tc26 = BTContainer(data=[ [4, 5010, 5010, 4855, 4995, 6172, 0, 0], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] ) # Test 27: Sell with signal sell in candle 3 (ROI at signal candle) # Stoploss at 10% (irrelevant), ROI at 5% (will trigger) - Wins over Sell-signal tc27 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5012, 4986, 4986, 6172, 0, 0], @@ -436,7 +486,7 @@ tc27 = BTContainer(data=[ [4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.002, use_sell_signal=True, - trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] + trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)] ) # Test 28: trailing_stop should raise so candle 3 causes a stoploss @@ -444,7 +494,7 @@ tc27 = BTContainer(data=[ # therefore "open" will be used # stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2 tc28 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 5100, 5100, 6172, 0, 0], @@ -453,14 +503,33 @@ tc28 = BTContainer(data=[ stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.03, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_stop_positive=0.03, - trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] +) + +# Test 28s: trailing_stop should raise so candle 3 causes a stoploss +# Same case than tc11 - but candle 3 "gaps down" - the stoploss will be above the candle, +# therefore "open" will be used +# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2 +tc28s = BTContainer(data=[ + # D O H L C V EL XL ES Xs BT + [0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0], + [1, 5000, 5050, 4890, 4890, 6172, 0, 0, 0, 0], + [2, 4890, 4890, 4749, 4890, 6172, 0, 0, 0, 0], + [3, 5150, 5350, 4950, 4950, 6172, 0, 0, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0]], + stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.03, trailing_stop=True, + trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, + trailing_stop_positive=0.03, + trades=[ + BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3, is_short=True) + ] ) # Test 29: trailing_stop should be triggered by low of next candle, without adjusting stoploss using # high of stoploss candle. # stop-loss: 10%, ROI: 10% (should not apply) tc29 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5050, 5000, 5000, 6172, 0, 0], # enter trade (signal on last candle) [2, 4900, 5250, 4500, 5100, 6172, 0, 0], # Triggers trailing-stoploss @@ -468,13 +537,13 @@ tc29 = BTContainer(data=[ [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.02, trailing_stop=True, trailing_stop_positive=0.03, - trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)] + trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)] ) # Test 30: trailing_stop should be triggered immediately on trade open candle. # stop-loss: 10%, ROI: 10% (should not apply) tc30 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5500, 4900, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop [2, 4900, 5250, 4500, 5100, 6172, 0, 0], @@ -482,13 +551,13 @@ tc30 = BTContainer(data=[ [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True, trailing_stop_positive=0.01, - trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] + trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] ) # Test 31: trailing_stop should be triggered immediately on trade open candle. # stop-loss: 10%, ROI: 10% (should not apply) tc31 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5500, 4900, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop [2, 4900, 5250, 4500, 5100, 6172, 0, 0], @@ -497,47 +566,68 @@ tc31 = BTContainer(data=[ stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.01, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, trailing_stop_positive=0.01, - trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] + trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] ) # Test 32: trailing_stop should be triggered immediately on trade open candle. # stop-loss: 1%, ROI: 10% (should not apply) tc32 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5500, 4951, 5000, 6172, 0, 0], # enter trade (signal on last candle) and stop + [1, 5000, 5500, 4951, 5000, 6172, 0, 0], # enter trade and stop [2, 4900, 5250, 4500, 5100, 6172, 0, 0], [3, 5100, 5100, 4650, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, trailing_stop_positive=0.01, use_custom_stoploss=True, - trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] + trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] ) # Test 33: trailing_stop should be triggered immediately on trade open candle. # stop-loss: 1%, ROI: 10% (should not apply) tc33 = BTContainer(data=[ - # D O H L C V B S BT - [0, 5000, 5050, 4950, 5000, 6172, 1, 0, 'buy_signal_01'], - [1, 5000, 5500, 4951, 5000, 6172, 0, 0, None], # enter trade (signal on last candle) and stop - [2, 4900, 5250, 4500, 5100, 6172, 0, 0, None], - [3, 5100, 5100, 4650, 4750, 6172, 0, 0, None], - [4, 4750, 4950, 4350, 4750, 6172, 0, 0, None]], + # D O H L C V EL XL ES Xs BT + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, 0, 0, 'buy_signal_01'], + [1, 5000, 5500, 4951, 5000, 6172, 0, 0, 0, 0, None], # enter trade and stop + [2, 4900, 5250, 4500, 5100, 6172, 0, 0, 0, 0, None], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0, 0, 0, None], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0, None]], stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, trailing_stop_positive=0.01, use_custom_stoploss=True, trades=[BTrade( - sell_reason=SellType.TRAILING_STOP_LOSS, + sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1, - buy_tag='buy_signal_01' + enter_tag='buy_signal_01' + )] +) +# Test 33s: trailing_stop should be triggered immediately on trade open candle. +# copy of Test33 using shorts. +# stop-loss: 1%, ROI: 10% (should not apply) +tc33s = BTContainer(data=[ + # D O H L C V EL XL ES Xs BT + [0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0, 'short_signal_01'], + [1, 5000, 5049, 4500, 5000, 6172, 0, 0, 0, 0, None], # enter trade and stop + [2, 4900, 5250, 4500, 5100, 6172, 0, 0, 0, 0, None], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0, 0, 0, None], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0, None]], + stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True, + trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, + trailing_stop_positive=0.01, use_custom_stoploss=True, + trades=[BTrade( + sell_reason=ExitType.TRAILING_STOP_LOSS, + open_tick=1, + close_tick=1, + enter_tag='short_signal_01', + is_short=True, )] ) # Test 34: Custom-entry-price below all candles should timeout - so no trade happens. tc34 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5500, 4951, 5000, 6172, 0, 0], # timeout [2, 4900, 5250, 4500, 5100, 6172, 0, 0], @@ -549,7 +639,7 @@ tc34 = BTContainer(data=[ # Test 35: Custom-entry-price above all candles should have rate adjusted to "entry candle high" tc35 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5500, 4951, 5000, 6172, 0, 0], # Timeout [2, 4900, 5250, 4500, 5100, 6172, 0, 0], @@ -557,7 +647,21 @@ tc35 = BTContainer(data=[ [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, custom_entry_price=7200, trades=[ - BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1) + BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1) +]) + +# Test 35s: Custom-entry-price above all candles should have rate adjusted to "entry candle high" +tc35s = BTContainer(data=[ + # D O H L C V EL XL ES Xs BT + [0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0], + [1, 5000, 5500, 4951, 5000, 6172, 0, 0, 0, 0], # Timeout + [2, 4900, 5250, 4500, 5100, 6172, 0, 0, 0, 0], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0]], + stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, + custom_entry_price=4000, + trades=[ + BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1, is_short=True) ] ) @@ -566,7 +670,7 @@ tc35 = BTContainer(data=[ # below open, we treat this as cheating, and delay the sell by 1 candle. # details: https://github.com/freqtrade/freqtrade/issues/6261 tc36 = BTContainer(data=[ - # D O H L C V B S BT + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5500, 4951, 4999, 6172, 0, 0], # Enter and immediate ROI [2, 4900, 5250, 4500, 5100, 6172, 0, 0], @@ -574,14 +678,14 @@ tc36 = BTContainer(data=[ [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01, custom_entry_price=4952, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2)] ) # Test 37: Custom-entry-price around candle low # Would cause immediate ROI exit below close # details: https://github.com/freqtrade/freqtrade/issues/6261 tc37 = BTContainer(data=[ - # D O H L C V B S BT + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5400, 5500, 4951, 5100, 6172, 0, 0], # Enter and immediate ROI [2, 4900, 5250, 4500, 5100, 6172, 0, 0], @@ -589,13 +693,13 @@ tc37 = BTContainer(data=[ [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01, custom_entry_price=4952, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)] + trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=1)] ) # Test 38: Custom exit price below all candles # Price adjusted to candle Low. tc38 = BTContainer(data=[ - # D O H L C V B S BT + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5500, 4951, 5000, 6172, 0, 0], [2, 4900, 5250, 4900, 5100, 6172, 0, 1], # exit - but timeout @@ -604,22 +708,50 @@ tc38 = BTContainer(data=[ stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, use_sell_signal=True, custom_exit_price=4552, - trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=3)] + trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=3)] ) # Test 39: Custom exit price above all candles # causes sell signal timeout tc39 = BTContainer(data=[ - # D O H L C V B S BT + # D O H L C V EL XL ES Xs BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5500, 4951, 5000, 6172, 0, 0], - [2, 4900, 5250, 4900, 5100, 6172, 0, 1], # exit - but timeout + [2, 4950, 5250, 4900, 5100, 6172, 0, 1], # exit - entry timeout [3, 5100, 5100, 4950, 4950, 6172, 0, 0], [4, 5000, 5100, 4950, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0, use_sell_signal=True, custom_exit_price=6052, - trades=[BTrade(sell_reason=SellType.FORCE_SELL, open_tick=1, close_tick=4)] + trades=[BTrade(sell_reason=ExitType.FORCE_SELL, open_tick=1, close_tick=4)] +) + +# Test 39: Custom short exit price above below candles +# causes sell signal timeout +tc39a = BTContainer(data=[ + # D O H L C V EL XL ES Xs BT + [0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0], + [1, 5000, 5000, 4951, 5000, 6172, 0, 0, 0, 0], + [2, 4910, 5150, 4910, 5100, 6172, 0, 0, 0, 1], # exit - entry timeout + [3, 5100, 5100, 4950, 4950, 6172, 0, 0, 0, 0], + [4, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 0]], + stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0, + use_sell_signal=True, + custom_exit_price=4700, + trades=[BTrade(sell_reason=ExitType.FORCE_SELL, open_tick=1, close_tick=4, is_short=True)] +) + +# Test 40: Colliding long and short signal +tc40 = BTContainer(data=[ + # D O H L C V EL XL ES Xs BT + [0, 5000, 5050, 4950, 5000, 6172, 1, 0, 1, 0], + [1, 5000, 5500, 4951, 5000, 6172, 0, 0, 0, 0], + [2, 4900, 5250, 4900, 5100, 6172, 0, 0, 0, 0], + [3, 5100, 5100, 4950, 4950, 6172, 0, 0, 0, 0], + [4, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 0]], + stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0, + use_sell_signal=True, + trades=[] ) @@ -647,23 +779,31 @@ TESTS = [ tc20, tc21, tc22, + tc22s, tc23, tc24, tc25, + tc25l, + tc25s, tc26, tc27, tc28, + tc28s, tc29, tc30, tc31, tc32, tc33, + tc33s, tc34, tc35, + tc35s, tc36, tc37, tc38, tc39, + tc39a, + tc40, ] @@ -685,18 +825,23 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: mocker.patch("freqtrade.exchange.Exchange.get_fee", return_value=0.0) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch("freqtrade.exchange.Binance.get_max_leverage", return_value=100) patch_exchange(mocker) frame = _build_backtest_dataframe(data.data) backtesting = Backtesting(default_conf) + # TODO: Should we initialize this properly?? + backtesting._can_short = True backtesting._set_strategy(backtesting.strategylist[0]) backtesting.required_startup = 0 - backtesting.strategy.advise_buy = lambda a, m: frame - backtesting.strategy.advise_sell = lambda a, m: frame + backtesting.strategy.advise_entry = lambda a, m: frame + backtesting.strategy.advise_exit = lambda a, m: frame if data.custom_entry_price: backtesting.strategy.custom_entry_price = MagicMock(return_value=data.custom_entry_price) if data.custom_exit_price: backtesting.strategy.custom_exit_price = MagicMock(return_value=data.custom_exit_price) backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss + backtesting.strategy.leverage = lambda **kwargs: data.leverage caplog.set_level(logging.DEBUG) pair = "UNITTEST/BTC" @@ -715,8 +860,9 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: assert round(results["profit_ratio"].sum(), 3) == round(data.profit_perc, 3) for c, trade in enumerate(data.trades): - res = results.iloc[c] + res: BTrade = results.iloc[c] assert res.sell_reason == trade.sell_reason.value - assert res.buy_tag == trade.buy_tag + assert res.enter_tag == trade.enter_tag assert res.open_date == _get_frame_time_from_offset(trade.open_tick) assert res.close_date == _get_frame_time_from_offset(trade.close_tick) + assert res.is_short == trade.is_short diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 7a72747c0..736071af9 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -19,27 +19,27 @@ from freqtrade.data.btanalysis import BT_DATA_COLUMNS, evaluate_result_multi from freqtrade.data.converter import clean_ohlcv_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.data.history import get_timerange -from freqtrade.enums import RunMode, SellType +from freqtrade.enums import ExitType, RunMode from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange.exchange import timeframe_to_next_date from freqtrade.misc import get_strategy_run_id from freqtrade.optimize.backtesting import Backtesting from freqtrade.persistence import LocalTrade from freqtrade.resolvers import StrategyResolver -from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, +from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re, patch_exchange, patched_configuration_load_config_file) ORDER_TYPES = [ { - 'buy': 'limit', - 'sell': 'limit', + 'entry': 'limit', + 'exit': 'limit', 'stoploss': 'limit', 'stoploss_on_exchange': False }, { - 'buy': 'limit', - 'sell': 'limit', + 'entry': 'limit', + 'exit': 'limit', 'stoploss': 'limit', 'stoploss_on_exchange': True }] @@ -134,12 +134,14 @@ def _trend(signals, buy_value, sell_value): n = len(signals['low']) buy = np.zeros(n) sell = np.zeros(n) - for i in range(0, len(signals['buy'])): + for i in range(0, len(signals['date'])): if random.random() > 0.5: # Both buy and sell signals at same timeframe buy[i] = buy_value sell[i] = sell_value - signals['buy'] = buy - signals['sell'] = sell + signals['enter_long'] = buy + signals['exit_long'] = sell + signals['enter_short'] = 0 + signals['exit_short'] = 0 return signals @@ -154,8 +156,10 @@ def _trend_alternate(dataframe=None, metadata=None): buy[i] = 1 else: sell[i] = 1 - signals['buy'] = buy - signals['sell'] = sell + signals['enter_long'] = buy + signals['exit_long'] = sell + signals['enter_short'] = 0 + signals['exit_short'] = 0 return dataframe @@ -166,7 +170,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca args = [ 'backtesting', '--config', 'config.json', - '--strategy', 'StrategyTestV2', + '--strategy', CURRENT_TEST_STRATEGY, '--export', 'none' ] @@ -201,7 +205,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> args = [ 'backtesting', '--config', 'config.json', - '--strategy', 'StrategyTestV2', + '--strategy', CURRENT_TEST_STRATEGY, '--datadir', '/foo/bar', '--timeframe', '1m', '--enable-position-stacking', @@ -251,7 +255,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog) args = [ 'backtesting', '--config', 'config.json', - '--strategy', 'StrategyTestV2', + '--strategy', CURRENT_TEST_STRATEGY, '--stake-amount', '1', '--starting-balance', '2' ] @@ -262,7 +266,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog) args = [ 'backtesting', '--config', 'config.json', - '--strategy', 'StrategyTestV2', + '--strategy', CURRENT_TEST_STRATEGY, '--stake-amount', '1', '--starting-balance', '0.5' ] @@ -280,7 +284,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None: args = [ 'backtesting', '--config', 'config.json', - '--strategy', 'StrategyTestV2', + '--strategy', CURRENT_TEST_STRATEGY, ] pargs = get_args(args) start_backtesting(pargs) @@ -302,8 +306,8 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: assert backtesting.config == default_conf assert backtesting.timeframe == '5m' assert callable(backtesting.strategy.advise_all_indicators) - assert callable(backtesting.strategy.advise_buy) - assert callable(backtesting.strategy.advise_sell) + assert callable(backtesting.strategy.advise_entry) + assert callable(backtesting.strategy.advise_exit) assert isinstance(backtesting.strategy.dp, DataProvider) get_fee.assert_called() assert backtesting.fee == 0.5 @@ -313,7 +317,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: patch_exchange(mocker) del default_conf['timeframe'] - default_conf['strategy_list'] = ['StrategyTestV2', + default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY, 'HyperoptableStrategy'] mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) @@ -350,7 +354,6 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None: assert len(processed['UNITTEST/BTC']) == 102 # Load strategy to compare the result between Backtesting function and strategy are the same - default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) processed2 = strategy.advise_all_indicators(data) @@ -494,7 +497,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti Backtesting(default_conf) # Multiple strategies - default_conf['strategy_list'] = ['StrategyTestV2', 'TestStrategyLegacyV1'] + default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY, 'TestStrategyLegacyV1'] with pytest.raises(OperationalException, match='PrecisionFilter not allowed for backtesting multiple strategies.'): Backtesting(default_conf) @@ -504,6 +507,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) default_conf['stake_amount'] = 'unlimited' default_conf['max_open_trades'] = 2 @@ -520,7 +524,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: 0.0012, # High '', # Buy Signal Name ] - trade = backtesting._enter_trade(pair, row=row) + trade = backtesting._enter_trade(pair, row=row, direction='long') assert isinstance(trade, LocalTrade) assert trade.stake_amount == 495 @@ -528,35 +532,115 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: LocalTrade.trades_open.append(trade) LocalTrade.trades_open.append(trade) backtesting.wallets.update() - trade = backtesting._enter_trade(pair, row=row) + trade = backtesting._enter_trade(pair, row=row, direction='long') assert trade is None LocalTrade.trades_open.pop() - trade = backtesting._enter_trade(pair, row=row) + trade = backtesting._enter_trade(pair, row=row, direction='long') assert trade is not None backtesting.strategy.custom_stake_amount = lambda **kwargs: 123.5 backtesting.wallets.update() - trade = backtesting._enter_trade(pair, row=row) + trade = backtesting._enter_trade(pair, row=row, direction='long') assert trade assert trade.stake_amount == 123.5 # In case of error - use proposed stake backtesting.strategy.custom_stake_amount = lambda **kwargs: 20 / 0 - trade = backtesting._enter_trade(pair, row=row) + trade = backtesting._enter_trade(pair, row=row, direction='long') assert trade assert trade.stake_amount == 495 + assert trade.is_short is False + + trade = backtesting._enter_trade(pair, row=row, direction='short') + assert trade + assert trade.stake_amount == 495 + assert trade.is_short is True + + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=300.0) + trade = backtesting._enter_trade(pair, row=row, direction='long') + assert trade + assert trade.stake_amount == 300.0 + + +def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None: + default_conf_usdt['use_sell_signal'] = False + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) + mocker.patch("freqtrade.exchange.Exchange.get_max_leverage", return_value=100) + patch_exchange(mocker) + default_conf_usdt['stake_amount'] = 300 + default_conf_usdt['max_open_trades'] = 2 + default_conf_usdt['trading_mode'] = 'futures' + default_conf_usdt['margin_mode'] = 'isolated' + default_conf_usdt['stake_currency'] = 'USDT' + default_conf_usdt['exchange']['pair_whitelist'] = ['.*'] + backtesting = Backtesting(default_conf_usdt) + backtesting._set_strategy(backtesting.strategylist[0]) + pair = 'UNITTEST/USDT:USDT' + row = [ + pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0), + 0.001, # Open + 0.0012, # High + 0.00099, # Low + 0.0011, # Close + 1, # enter_long + 0, # exit_long + 1, # enter_short + 0, # exit_hsort + '', # Long Signal Name + '', # Short Signal Name + '', # Exit Signal Name + ] + + backtesting.strategy.leverage = MagicMock(return_value=5.0) + mocker.patch("freqtrade.exchange.Exchange.get_maintenance_ratio_and_amt", + return_value=(0.01, 0.01)) + + # leverage = 5 + # ep1(trade.open_rate) = 0.001 + # position(trade.amount) = 1500000 + # stake_amount = 300 -> wb = 300 / 5 = 60 + # mmr = 0.01 + # cum_b = 0.01 + # side_1: -1 if is_short else 1 + # liq_buffer = 0.05 + # + # Binance, Long + # liquidation_price + # = ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position)) + # = ((300 + 0.01) - (1 * 1500000 * 0.001)) / ((1500000 * 0.01) - (1 * 1500000)) + # = 0.0008080740740740741 + # freqtrade_liquidation_price = liq + (abs(open_rate - liq) * liq_buffer * side_1) + # = 0.0008080740740740741 + ((0.001 - 0.0008080740740740741) * 0.05 * 1) + # = 0.0008176703703703704 + + trade = backtesting._enter_trade(pair, row=row, direction='long') + assert pytest.approx(trade.liquidation_price) == 0.00081767037 + + # Binance, Short + # liquidation_price + # = ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position)) + # = ((300 + 0.01) - ((-1) * 1500000 * 0.001)) / ((1500000 * 0.01) - ((-1) * 1500000)) + # = 0.0011881254125412541 + # freqtrade_liquidation_price = liq + (abs(open_rate - liq) * liq_buffer * side_1) + # = 0.0011881254125412541 + (abs(0.001 - 0.0011881254125412541) * 0.05 * -1) + # = 0.0011787191419141915 + + trade = backtesting._enter_trade(pair, row=row, direction='short') + assert pytest.approx(trade.liquidation_price) == 0.0011787191 # Stake-amount too high! mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=600.0) - trade = backtesting._enter_trade(pair, row=row) + trade = backtesting._enter_trade(pair, row=row, direction='long') assert trade is None # Stake-amount throwing error mocker.patch("freqtrade.wallets.Wallets.get_trade_stake_amount", side_effect=DependencyException) - trade = backtesting._enter_trade(pair, row=row) + trade = backtesting._enter_trade(pair, row=row, direction='long') assert trade is None @@ -564,6 +648,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None: default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) default_conf['timeframe_detail'] = '1m' default_conf['max_open_trades'] = 2 @@ -572,63 +657,72 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None: pair = 'UNITTEST/BTC' row = [ pd.Timestamp(year=2020, month=1, day=1, hour=4, minute=55, tzinfo=timezone.utc), - 1, # Buy 200, # Open - 201, # Close - 0, # Sell - 195, # Low 201.5, # High - '', # Buy Signal Name + 195, # Low + 201, # Close + 1, # enter_long + 0, # exit_long + 0, # enter_short + 0, # exit_hsort + '', # Long Signal Name + '', # Short Signal Name '', # Exit Signal Name ] - trade = backtesting._enter_trade(pair, row=row) + trade = backtesting._enter_trade(pair, row=row, direction='long') assert isinstance(trade, LocalTrade) row_sell = [ pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc), - 0, # Buy 200, # Open - 201, # Close - 0, # Sell - 195, # Low 210.5, # High - '', # Buy Signal Name + 195, # Low + 201, # Close + 0, # enter_long + 0, # exit_long + 0, # enter_short + 0, # exit_short + '', # long Signal Name + '', # Short Signal Name '', # Exit Signal Name + ] row_detail = pd.DataFrame( [ [ pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc), - 1, 200, 199, 0, 197, 200.1, '', '', + 200, 200.1, 197, 199, 1, 0, 0, 0, '', '', '', ], [ pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=1, tzinfo=timezone.utc), - 0, 199, 199.5, 0, 199, 199.7, '', '', + 199, 199.7, 199, 199.5, 0, 0, 0, 0, '', '', '', ], [ pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=2, tzinfo=timezone.utc), - 0, 199.5, 200.5, 0, 199, 200.8, '', '', + 199.5, 200.8, 199, 200.9, 0, 0, 0, 0, '', '', '', ], [ pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=3, tzinfo=timezone.utc), - 0, 200.5, 210.5, 0, 193, 210.5, '', '', # ROI sell (?) + 200.5, 210.5, 193, 210.5, 0, 0, 0, 0, '', '', '', # ROI sell (?) ], [ pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=4, tzinfo=timezone.utc), - 0, 200, 199, 0, 193, 200.1, '', '', + 200, 200.1, 193, 199, 0, 0, 0, 0, '', '', '', ], - ], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag", "exit_tag"] + ], columns=['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long', + 'enter_short', 'exit_short', 'long_tag', 'short_tag', 'exit_tag'] ) # No data available. res = backtesting._get_sell_trade_entry(trade, row_sell) assert res is not None - assert res.sell_reason == SellType.ROI.value + assert res.sell_reason == ExitType.ROI.value assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc) # Enter new trade - trade = backtesting._enter_trade(pair, row=row) + trade = backtesting._enter_trade(pair, row=row, direction='long') assert isinstance(trade, LocalTrade) # Assign empty ... no result. backtesting.detail_data[pair] = pd.DataFrame( - [], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag", "exit_tag"]) + [], columns=['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long', + 'enter_short', 'exit_short', 'long_tag', 'short_tag', 'exit_tag']) res = backtesting._get_sell_trade_entry(trade, row) assert res is None @@ -638,7 +732,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None: res = backtesting._get_sell_trade_entry(trade, row_sell) assert res is not None - assert res.sell_reason == SellType.ROI.value + assert res.sell_reason == ExitType.ROI.value # Sell at minute 3 (not available above!) assert res.close_date_utc == datetime(2020, 1, 1, 5, 3, tzinfo=timezone.utc) sell_order = res.select_order('sell', True) @@ -649,6 +743,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -686,7 +781,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: 'trade_duration': [235, 40], 'profit_ratio': [0.0, 0.0], 'profit_abs': [0.0, 0.0], - 'sell_reason': [SellType.ROI.value, SellType.ROI.value], + 'sell_reason': [ExitType.ROI.value, ExitType.ROI.value], 'initial_stop_loss_abs': [0.0940005, 0.09272236], 'initial_stop_loss_ratio': [-0.1, -0.1], 'stop_loss_abs': [0.0940005, 0.09272236], @@ -694,7 +789,8 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: 'min_rate': [0.10370188, 0.10300000000000001], 'max_rate': [0.10501, 0.1038888], 'is_open': [False, False], - 'buy_tag': [None, None] + 'enter_tag': [None, None], + "is_short": [False, False], }) pd.testing.assert_frame_equal(results, expected) data_pair = processed[pair] @@ -714,6 +810,7 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -754,6 +851,7 @@ def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadi default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=100000) patch_exchange(mocker) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -770,7 +868,7 @@ def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadi dp = backtesting.strategy.dp df, _ = dp.get_analyzed_dataframe(pair, backtesting.strategy.timeframe) current_candle = df.iloc[-1].squeeze() - assert current_candle['buy'] == 1 + assert current_candle['enter_long'] == 1 candle_date = timeframe_to_next_date(backtesting.strategy.timeframe, current_candle['date']) assert candle_date == current_time @@ -802,6 +900,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad default_conf['enable_protections'] = True mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) tests = [ ['sine', 9], ['raise', 10], @@ -809,7 +908,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad ['sine', 9], ['raise', 10], ] - # While buy-signals are unrealistic, running backtesting + # While entry-signals are unrealistic, running backtesting # over and over again should not cause different results for [contour, numres] in tests: # Debug output for random test failure @@ -836,14 +935,15 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir, default_conf['enable_protections'] = True mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - # While buy-signals are unrealistic, running backtesting + # While entry-signals are unrealistic, running backtesting # over and over again should not cause different results assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == expected def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir): - # Override the default buy trend function in our StrategyTestV2 + # Override the default buy trend function in our StrategyTest def fun(dataframe=None, pair=None): buy_value = 1 sell_value = 1 @@ -852,14 +952,14 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir): backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) - backtesting.strategy.advise_buy = fun # Override - backtesting.strategy.advise_sell = fun # Override + backtesting.strategy.advise_entry = fun # Override + backtesting.strategy.advise_exit = fun # Override result = backtesting.backtest(**backtest_conf) assert result['results'].empty def test_backtest_only_sell(mocker, default_conf, testdatadir): - # Override the default buy trend function in our StrategyTestV2 + # Override the default buy trend function in our StrategyTest def fun(dataframe=None, pair=None): buy_value = 0 sell_value = 1 @@ -868,14 +968,15 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir): backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) - backtesting.strategy.advise_buy = fun # Override - backtesting.strategy.advise_sell = fun # Override + backtesting.strategy.advise_entry = fun # Override + backtesting.strategy.advise_exit = fun # Override result = backtesting.backtest(**backtest_conf) assert result['results'].empty def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC', datadir=testdatadir) @@ -883,8 +984,8 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): backtesting = Backtesting(default_conf) backtesting.required_startup = 0 backtesting._set_strategy(backtesting.strategylist[0]) - backtesting.strategy.advise_buy = _trend_alternate # Override - backtesting.strategy.advise_sell = _trend_alternate # Override + backtesting.strategy.advise_entry = _trend_alternate # Override + backtesting.strategy.advise_exit = _trend_alternate # Override result = backtesting.backtest(**backtest_conf) # 200 candles in backtest data # won't buy on first (shifted by 1) @@ -915,11 +1016,14 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) multi = 20 else: multi = 18 - dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0) - dataframe['sell'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0) + dataframe['enter_long'] = np.where(dataframe.index % multi == 0, 1, 0) + dataframe['exit_long'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0) + dataframe['enter_short'] = 0 + dataframe['exit_short'] = 0 return dataframe mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) patch_exchange(mocker) @@ -935,8 +1039,8 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) - backtesting.strategy.advise_buy = _trend_alternate_hold # Override - backtesting.strategy.advise_sell = _trend_alternate_hold # Override + backtesting.strategy.advise_entry = _trend_alternate_hold # Override + backtesting.strategy.advise_exit = _trend_alternate_hold # Override processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) @@ -959,8 +1063,9 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) offset = 1 if tres == 0 else 0 removed_candles = len(data[pair]) - offset - backtesting.strategy.startup_candle_count assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, '5m')[0]) == removed_candles - assert len(backtesting.dataprovider.get_analyzed_dataframe( - 'NXT/BTC', '5m')[0]) == len(data['NXT/BTC']) - 1 - backtesting.strategy.startup_candle_count + assert len( + backtesting.dataprovider.get_analyzed_dataframe('NXT/BTC', '5m')[0] + ) == len(data['NXT/BTC']) - 1 - backtesting.strategy.startup_candle_count backtest_conf = { 'processed': deepcopy(processed), @@ -986,7 +1091,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): args = [ 'backtesting', '--config', 'config.json', - '--strategy', 'StrategyTestV2', + '--strategy', CURRENT_TEST_STRATEGY, '--datadir', str(testdatadir), '--timeframe', '1m', '--timerange', '1510694220-1510700340', @@ -1059,7 +1164,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): '--enable-position-stacking', '--disable-max-market-positions', '--strategy-list', - 'StrategyTestV2', + CURRENT_TEST_STRATEGY, 'TestStrategyLegacyV1', ] args = get_args(args) @@ -1082,7 +1187,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): 'Backtesting with data from 2017-11-14 21:17:00 ' 'up to 2017-11-14 22:58:00 (0 days).', 'Parameter --enable-position-stacking detected ...', - 'Running backtesting for Strategy StrategyTestV2', + f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}', 'Running backtesting for Strategy TestStrategyLegacyV1', ] @@ -1112,7 +1217,9 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat 'stake_amount': [0.01, 0.01], 'open_rate': [0.104445, 0.10302485], 'close_rate': [0.104969, 0.103541], - 'sell_reason': [SellType.ROI, SellType.ROI] + "is_short": [False, False], + + 'sell_reason': [ExitType.ROI, ExitType.ROI] }) result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'], 'profit_ratio': [0.03, 0.01, 0.1], @@ -1129,7 +1236,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat 'stake_amount': [0.01, 0.01, 0.01], 'open_rate': [0.104445, 0.10302485, 0.122541], 'close_rate': [0.104969, 0.103541, 0.123541], - 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] + "is_short": [False, False, False], + 'sell_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS] }) backtestmock = MagicMock(side_effect=[ { @@ -1168,7 +1276,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat '--disable-max-market-positions', '--breakdown', 'day', '--strategy-list', - 'StrategyTestV2', + CURRENT_TEST_STRATEGY, 'TestStrategyLegacyV1', ] args = get_args(args) @@ -1185,7 +1293,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat 'Backtesting with data from 2017-11-14 21:17:00 ' 'up to 2017-11-14 22:58:00 (0 days).', 'Parameter --enable-position-stacking detected ...', - 'Running backtesting for Strategy StrategyTestV2', + f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}', 'Running backtesting for Strategy TestStrategyLegacyV1', ] @@ -1194,13 +1302,119 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat captured = capsys.readouterr() assert 'BACKTESTING REPORT' in captured.out - assert 'SELL REASON STATS' in captured.out + assert 'EXIT REASON STATS' in captured.out assert 'DAY BREAKDOWN' in captured.out assert 'LEFT OPEN TRADES REPORT' in captured.out assert '2017-11-14 21:17:00 -> 2017-11-14 22:58:00 | Max open trades : 1' in captured.out assert 'STRATEGY SUMMARY' in captured.out +@pytest.mark.filterwarnings("ignore:deprecated") +def test_backtest_start_nomock_futures(default_conf_usdt, mocker, + caplog, testdatadir, capsys): + # Tests detail-data loading + default_conf_usdt.update({ + "trading_mode": "futures", + "margin_mode": "isolated", + "use_sell_signal": True, + "sell_profit_only": False, + "sell_profit_offset": 0.0, + "ignore_roi_if_buy_signal": False, + "strategy": CURRENT_TEST_STRATEGY, + }) + patch_exchange(mocker) + result1 = pd.DataFrame({'pair': ['XRP/USDT', 'XRP/USDT'], + 'profit_ratio': [0.0, 0.0], + 'profit_abs': [0.0, 0.0], + 'open_date': pd.to_datetime(['2021-11-18 18:00:00', + '2021-11-18 03:00:00', ], utc=True + ), + 'close_date': pd.to_datetime(['2021-11-18 20:00:00', + '2021-11-18 05:00:00', ], utc=True), + 'trade_duration': [235, 40], + 'is_open': [False, False], + 'is_short': [False, False], + 'stake_amount': [0.01, 0.01], + 'open_rate': [0.104445, 0.10302485], + 'close_rate': [0.104969, 0.103541], + 'sell_reason': [ExitType.ROI, ExitType.ROI] + }) + result2 = pd.DataFrame({'pair': ['XRP/USDT', 'XRP/USDT', 'XRP/USDT'], + 'profit_ratio': [0.03, 0.01, 0.1], + 'profit_abs': [0.01, 0.02, 0.2], + 'open_date': pd.to_datetime(['2021-11-19 18:00:00', + '2021-11-19 03:00:00', + '2021-11-19 05:00:00'], utc=True + ), + 'close_date': pd.to_datetime(['2021-11-19 20:00:00', + '2021-11-19 05:00:00', + '2021-11-19 08:00:00'], utc=True), + 'trade_duration': [47, 40, 20], + 'is_open': [False, False, False], + 'is_short': [False, False, False], + 'stake_amount': [0.01, 0.01, 0.01], + 'open_rate': [0.104445, 0.10302485, 0.122541], + 'close_rate': [0.104969, 0.103541, 0.123541], + 'sell_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS] + }) + backtestmock = MagicMock(side_effect=[ + { + 'results': result1, + 'config': default_conf_usdt, + 'locks': [], + 'rejected_signals': 20, + 'timedout_entry_orders': 0, + 'timedout_exit_orders': 0, + 'final_balance': 1000, + }, + { + 'results': result2, + 'config': default_conf_usdt, + 'locks': [], + 'rejected_signals': 20, + 'timedout_entry_orders': 0, + 'timedout_exit_orders': 0, + 'final_balance': 1000, + } + ]) + mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', + PropertyMock(return_value=['XRP/USDT'])) + mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) + + patched_configuration_load_config_file(mocker, default_conf_usdt) + + args = [ + 'backtesting', + '--config', 'config.json', + '--datadir', str(testdatadir), + '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), + '--timeframe', '1h', + ] + args = get_args(args) + start_backtesting(args) + + # check the logs, that will contain the backtest result + exists = [ + 'Parameter -i/--timeframe detected ... Using timeframe: 1h ...', + f'Using data directory: {testdatadir} ...', + 'Loading data from 2021-11-17 01:00:00 ' + 'up to 2021-11-21 03:00:00 (4 days).', + 'Backtesting with data from 2021-11-17 21:00:00 ' + 'up to 2021-11-21 03:00:00 (3 days).', + 'XRP/USDT, funding_rate, 8h, data starts at 2021-11-18 00:00:00', + 'XRP/USDT, mark, 8h, data starts at 2021-11-18 00:00:00', + f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}', + ] + + for line in exists: + assert log_has(line, caplog) + + captured = capsys.readouterr() + assert 'BACKTESTING REPORT' in captured.out + assert 'EXIT REASON STATS' in captured.out + assert 'LEFT OPEN TRADES REPORT' in captured.out + + @pytest.mark.filterwarnings("ignore:deprecated") def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker, caplog, testdatadir, capsys): @@ -1222,10 +1436,11 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker, '2018-01-30 05:35:00', ], utc=True), 'trade_duration': [235, 40], 'is_open': [False, False], + 'is_short': [False, False], 'stake_amount': [0.01, 0.01], 'open_rate': [0.104445, 0.10302485], 'close_rate': [0.104969, 0.103541], - 'sell_reason': [SellType.ROI, SellType.ROI] + 'sell_reason': [ExitType.ROI, ExitType.ROI] }) result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'], 'profit_ratio': [0.03, 0.01, 0.1], @@ -1239,10 +1454,11 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker, '2018-01-30 08:30:00'], utc=True), 'trade_duration': [47, 40, 20], 'is_open': [False, False, False], + 'is_short': [False, False, False], 'stake_amount': [0.01, 0.01, 0.01], 'open_rate': [0.104445, 0.10302485, 0.122541], 'close_rate': [0.104969, 0.103541, 0.123541], - 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] + 'sell_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS] }) backtestmock = MagicMock(side_effect=[ { @@ -1278,7 +1494,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker, '--timeframe', '5m', '--timeframe-detail', '1m', '--strategy-list', - 'StrategyTestV2' + CURRENT_TEST_STRATEGY ] args = get_args(args) start_backtesting(args) @@ -1292,7 +1508,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker, 'up to 2019-10-13 11:10:00 (2 days).', 'Backtesting with data from 2019-10-11 01:40:00 ' 'up to 2019-10-13 11:10:00 (2 days).', - 'Running backtesting for Strategy StrategyTestV2', + f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}', ] for line in exists: @@ -1300,7 +1516,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker, captured = capsys.readouterr() assert 'BACKTESTING REPORT' in captured.out - assert 'SELL REASON STATS' in captured.out + assert 'EXIT REASON STATS' in captured.out assert 'LEFT OPEN TRADES REPORT' in captured.out diff --git a/tests/optimize/test_backtesting_adjust_position.py b/tests/optimize/test_backtesting_adjust_position.py index a7f953008..95847c660 100644 --- a/tests/optimize/test_backtesting_adjust_position.py +++ b/tests/optimize/test_backtesting_adjust_position.py @@ -8,7 +8,7 @@ from arrow import Arrow from freqtrade.configuration import TimeRange from freqtrade.data import history from freqtrade.data.history import get_timerange -from freqtrade.enums import SellType +from freqtrade.enums import ExitType from freqtrade.optimize.backtesting import Backtesting from tests.conftest import patch_exchange @@ -17,6 +17,7 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) patch_exchange(mocker) default_conf.update({ "stake_amount": 100.0, @@ -59,7 +60,7 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> 'trade_duration': [200, 40], 'profit_ratio': [0.0, 0.0], 'profit_abs': [0.0, 0.0], - 'sell_reason': [SellType.ROI.value, SellType.ROI.value], + 'sell_reason': [ExitType.ROI.value, ExitType.ROI.value], 'initial_stop_loss_abs': [0.0940005, 0.09272236], 'initial_stop_loss_ratio': [-0.1, -0.1], 'stop_loss_abs': [0.0940005, 0.09272236], @@ -67,7 +68,8 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> 'min_rate': [0.10370188, 0.10300000000000001], 'max_rate': [0.10481985, 0.1038888], 'is_open': [False, False], - 'buy_tag': [None, None], + 'enter_tag': [None, None], + 'is_short': [False, False], }) pd.testing.assert_frame_equal(results, expected) data_pair = processed[pair] diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index 466a5f1cd..f0f436a43 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -6,7 +6,8 @@ from unittest.mock import MagicMock from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge from freqtrade.enums import RunMode from freqtrade.optimize.edge_cli import EdgeCli -from tests.conftest import get_args, log_has, patch_exchange, patched_configuration_load_config_file +from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, patch_exchange, + patched_configuration_load_config_file) def test_setup_optimize_configuration_without_arguments(mocker, default_conf, caplog) -> None: @@ -15,7 +16,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca args = [ 'edge', '--config', 'config.json', - '--strategy', 'StrategyTestV2', + '--strategy', CURRENT_TEST_STRATEGY, ] config = setup_optimize_configuration(get_args(args), RunMode.EDGE) @@ -44,7 +45,7 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N args = [ 'edge', '--config', 'config.json', - '--strategy', 'StrategyTestV2', + '--strategy', CURRENT_TEST_STRATEGY, '--datadir', '/foo/bar', '--timeframe', '1m', '--timerange', ':100', @@ -78,7 +79,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None: args = [ 'edge', '--config', 'config.json', - '--strategy', 'StrategyTestV2', + '--strategy', CURRENT_TEST_STRATEGY, ] pargs = get_args(args) start_edge(pargs) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index cc551277a..3a6fe9293 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -10,7 +10,7 @@ from filelock import Timeout from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt from freqtrade.data.history import load_data -from freqtrade.enums import RunMode, SellType +from freqtrade.enums import ExitType, RunMode from freqtrade.exceptions import OperationalException from freqtrade.optimize.hyperopt import Hyperopt from freqtrade.optimize.hyperopt_auto import HyperOptAuto @@ -18,31 +18,31 @@ from freqtrade.optimize.hyperopt_tools import HyperoptTools from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.optimize.space import SKDecimal from freqtrade.strategy.hyper import IntParameter -from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, +from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re, patch_exchange, patched_configuration_load_config_file) def generate_result_metrics(): return { - 'trade_count': 1, - 'total_trades': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 0.01, - 'duration': 20.0, - 'wins': 1, - 'draws': 0, - 'losses': 0, - 'profit_mean': 0.01, - 'profit_total_abs': 0.001, - 'profit_total': 0.01, - 'holding_avg': timedelta(minutes=20), - 'max_drawdown': 0.001, - 'max_drawdown_abs': 0.001, - 'loss': 0.001, - 'is_initial_point': 0.001, - 'is_best': 1, - } + 'trade_count': 1, + 'total_trades': 1, + 'avg_profit': 0.1, + 'total_profit': 0.001, + 'profit': 0.01, + 'duration': 20.0, + 'wins': 1, + 'draws': 0, + 'losses': 0, + 'profit_mean': 0.01, + 'profit_total_abs': 0.001, + 'profit_total': 0.01, + 'holding_avg': timedelta(minutes=20), + 'max_drawdown': 0.001, + 'max_drawdown_abs': 0.001, + 'loss': 0.001, + 'is_initial_point': 0.001, + 'is_best': 1, + } def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None: @@ -144,7 +144,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None args = [ 'hyperopt', '--config', 'config.json', - '--strategy', 'StrategyTestV2', + '--strategy', CURRENT_TEST_STRATEGY, '--stake-amount', '1', '--starting-balance', '0.5' ] @@ -329,8 +329,8 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None: # Should be called for historical candle data assert dumper.call_count == 1 assert dumper2.call_count == 1 - assert hasattr(hyperopt.backtesting.strategy, "advise_sell") - assert hasattr(hyperopt.backtesting.strategy, "advise_buy") + assert hasattr(hyperopt.backtesting.strategy, "advise_exit") + assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt, "max_open_trades") assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] assert hasattr(hyperopt, "position_stacking") @@ -355,9 +355,10 @@ def test_hyperopt_format_results(hyperopt): "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], "trade_duration": [123, 34, 31, 14], "is_open": [False, False, False, True], + "is_short": [False, False, False, False], "stake_amount": [0.01, 0.01, 0.01, 0.01], - "sell_reason": [SellType.ROI, SellType.STOP_LOSS, - SellType.ROI, SellType.FORCE_SELL] + "sell_reason": [ExitType.ROI, ExitType.STOP_LOSS, + ExitType.ROI, ExitType.FORCE_SELL] }), 'config': hyperopt.config, 'locks': [], @@ -425,9 +426,10 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None: "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], "trade_duration": [123, 34, 31, 14], "is_open": [False, False, False, True], + "is_short": [False, False, False, False], "stake_amount": [0.01, 0.01, 0.01, 0.01], - "sell_reason": [SellType.ROI, SellType.STOP_LOSS, - SellType.ROI, SellType.FORCE_SELL] + "sell_reason": [ExitType.ROI, ExitType.STOP_LOSS, + ExitType.ROI, ExitType.FORCE_SELL] }), 'config': hyperopt_conf, 'locks': [], @@ -686,8 +688,8 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non assert dumper.call_count == 1 assert dumper2.call_count == 1 - assert hasattr(hyperopt.backtesting.strategy, "advise_sell") - assert hasattr(hyperopt.backtesting.strategy, "advise_buy") + assert hasattr(hyperopt.backtesting.strategy, "advise_exit") + assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt, "max_open_trades") assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] assert hasattr(hyperopt, "position_stacking") @@ -759,8 +761,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None: assert dumper.called assert dumper.call_count == 1 assert dumper2.call_count == 1 - assert hasattr(hyperopt.backtesting.strategy, "advise_sell") - assert hasattr(hyperopt.backtesting.strategy, "advise_buy") + assert hasattr(hyperopt.backtesting.strategy, "advise_exit") + assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt, "max_open_trades") assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] assert hasattr(hyperopt, "position_stacking") @@ -801,8 +803,8 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None: assert dumper.called assert dumper.call_count == 1 assert dumper2.call_count == 1 - assert hasattr(hyperopt.backtesting.strategy, "advise_sell") - assert hasattr(hyperopt.backtesting.strategy, "advise_buy") + assert hasattr(hyperopt.backtesting.strategy, "advise_exit") + assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt, "max_open_trades") assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] assert hasattr(hyperopt, "position_stacking") @@ -849,6 +851,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None: 'spaces': ['all'] }) hyperopt = Hyperopt(hyperopt_conf) + hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0) assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter) diff --git a/tests/optimize/test_hyperopt_tools.py b/tests/optimize/test_hyperopt_tools.py index 6cc0caf40..7d4fef3bd 100644 --- a/tests/optimize/test_hyperopt_tools.py +++ b/tests/optimize/test_hyperopt_tools.py @@ -10,7 +10,7 @@ import rapidjson from freqtrade.constants import FTHYPT_FILEVERSION from freqtrade.exceptions import OperationalException from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer -from tests.conftest import log_has, log_has_re +from tests.conftest import CURRENT_TEST_STRATEGY, log_has, log_has_re # Functions for recurrent object patching @@ -168,9 +168,9 @@ def test__pprint_dict(): def test_get_strategy_filename(default_conf): - x = HyperoptTools.get_strategy_filename(default_conf, 'StrategyTestV2') + x = HyperoptTools.get_strategy_filename(default_conf, 'StrategyTestV3') assert isinstance(x, Path) - assert x == Path(__file__).parents[1] / 'strategy/strats/strategy_test_v2.py' + assert x == Path(__file__).parents[1] / 'strategy/strats/strategy_test_v3.py' x = HyperoptTools.get_strategy_filename(default_conf, 'NonExistingStrategy') assert x is None @@ -178,7 +178,7 @@ def test_get_strategy_filename(default_conf): def test_export_params(tmpdir): - filename = Path(tmpdir) / "StrategyTestV2.json" + filename = Path(tmpdir) / f"{CURRENT_TEST_STRATEGY}.json" assert not filename.is_file() params = { "params_details": { @@ -206,13 +206,13 @@ def test_export_params(tmpdir): } } - HyperoptTools.export_params(params, "StrategyTestV2", filename) + HyperoptTools.export_params(params, CURRENT_TEST_STRATEGY, filename) assert filename.is_file() with filename.open('r') as f: content = rapidjson.load(f) - assert content['strategy_name'] == 'StrategyTestV2' + assert content['strategy_name'] == CURRENT_TEST_STRATEGY assert 'params' in content assert "buy" in content["params"] assert "sell" in content["params"] @@ -225,7 +225,7 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker): default_conf['disableparamexport'] = False export_mock = mocker.patch("freqtrade.optimize.hyperopt_tools.HyperoptTools.export_params") - filename = Path(tmpdir) / "StrategyTestV2.json" + filename = Path(tmpdir) / f"{CURRENT_TEST_STRATEGY}.json" assert not filename.is_file() params = { "params_details": { @@ -254,17 +254,17 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker): FTHYPT_FILEVERSION: 2, } - HyperoptTools.try_export_params(default_conf, "StrategyTestV222", params) + HyperoptTools.try_export_params(default_conf, "StrategyTestVXXX", params) assert log_has("Strategy not found, not exporting parameter file.", caplog) assert export_mock.call_count == 0 caplog.clear() - HyperoptTools.try_export_params(default_conf, "StrategyTestV2", params) + HyperoptTools.try_export_params(default_conf, CURRENT_TEST_STRATEGY, params) assert export_mock.call_count == 1 - assert export_mock.call_args_list[0][0][1] == 'StrategyTestV2' - assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v2.json' + assert export_mock.call_args_list[0][0][1] == CURRENT_TEST_STRATEGY + assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v3.json' def test_params_print(capsys): diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index c8768e236..3ff8d5870 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -12,7 +12,7 @@ from freqtrade.data import history from freqtrade.data.btanalysis import (get_latest_backtest_filename, load_backtest_data, load_backtest_stats) from freqtrade.edge import PairInfo -from freqtrade.enums import SellType +from freqtrade.enums import ExitType from freqtrade.optimize.optimize_reports import (_get_resample_from_period, generate_backtest_stats, generate_daily_stats, generate_edge_table, generate_pair_metrics, @@ -21,8 +21,9 @@ from freqtrade.optimize.optimize_reports import (_get_resample_from_period, gene generate_strategy_comparison, generate_trading_stats, show_sorted_pairlist, store_backtest_stats, text_table_bt_results, - text_table_sell_reason, text_table_strategy) + text_table_exit_reason, text_table_strategy) from freqtrade.resolvers.strategy_resolver import StrategyResolver +from tests.conftest import CURRENT_TEST_STRATEGY from tests.data.test_history import _backup_file, _clean_test_file @@ -54,7 +55,7 @@ def test_text_table_bt_results(): def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): - default_conf.update({'strategy': 'StrategyTestV2'}) + default_conf.update({'strategy': CURRENT_TEST_STRATEGY}) StrategyResolver.load_strategy(default_conf) results = {'DefStrat': { @@ -74,9 +75,10 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], "trade_duration": [123, 34, 31, 14], "is_open": [False, False, False, True], + "is_short": [False, False, False, False], "stake_amount": [0.01, 0.01, 0.01, 0.01], - "sell_reason": [SellType.ROI, SellType.STOP_LOSS, - SellType.ROI, SellType.FORCE_SELL] + "sell_reason": [ExitType.ROI, ExitType.STOP_LOSS, + ExitType.ROI, ExitType.FORCE_SELL] }), 'config': default_conf, 'locks': [], @@ -125,9 +127,10 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): "close_rate": [0.002546, 0.003014, 0.0032903, 0.003217], "trade_duration": [123, 34, 31, 14], "is_open": [False, False, False, True], + "is_short": [False, False, False, False], "stake_amount": [0.01, 0.01, 0.01, 0.01], - "sell_reason": [SellType.ROI, SellType.ROI, - SellType.STOP_LOSS, SellType.FORCE_SELL] + "sell_reason": [ExitType.ROI, ExitType.ROI, + ExitType.STOP_LOSS, ExitType.FORCE_SELL] }), 'config': default_conf, 'locks': [], @@ -273,12 +276,12 @@ def test_text_table_sell_reason(): 'wins': [2, 0, 0], 'draws': [0, 0, 0], 'losses': [0, 0, 1], - 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] + 'sell_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS] } ) result_str = ( - '| Sell Reason | Sells | Win Draws Loss Win% | Avg Profit % | Cum Profit % |' + '| Exit Reason | Exits | Win Draws Loss Win% | Avg Profit % | Cum Profit % |' ' Tot Profit BTC | Tot Profit % |\n' '|---------------+---------+--------------------------+----------------+----------------+' '------------------+----------------|\n' @@ -290,7 +293,7 @@ def test_text_table_sell_reason(): sell_reason_stats = generate_sell_reason_stats(max_open_trades=2, results=results) - assert text_table_sell_reason(sell_reason_stats=sell_reason_stats, + assert text_table_exit_reason(sell_reason_stats=sell_reason_stats, stake_currency='BTC') == result_str @@ -305,7 +308,7 @@ def test_generate_sell_reason_stats(): 'wins': [2, 0, 0], 'draws': [0, 0, 0], 'losses': [0, 0, 1], - 'sell_reason': [SellType.ROI.value, SellType.ROI.value, SellType.STOP_LOSS.value] + 'sell_reason': [ExitType.ROI.value, ExitType.ROI.value, ExitType.STOP_LOSS.value] } ) @@ -397,6 +400,6 @@ def test_show_sorted_pairlist(testdatadir, default_conf, capsys): show_sorted_pairlist(default_conf, bt_data) out, err = capsys.readouterr() - assert 'Pairs for Strategy StrategyTestV2: \n[' in out + assert 'Pairs for Strategy StrategyTestV3: \n[' in out assert 'TOTAL' not in out assert '"ETH/BTC", // ' in out diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 08ba892fe..d80f23c8a 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -9,7 +9,7 @@ import pytest import time_machine from freqtrade.constants import AVAILABLE_PAIRLISTS -from freqtrade.enums import RunMode +from freqtrade.enums import CandleType, RunMode from freqtrade.exceptions import OperationalException from freqtrade.persistence import Trade from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist @@ -491,11 +491,11 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t ohlcv_history_high_vola.loc[ohlcv_history_high_vola.index == 1, 'close'] = 0.00090 ohlcv_data = { - ('ETH/BTC', '1d'): ohlcv_history, - ('TKN/BTC', '1d'): ohlcv_history, - ('LTC/BTC', '1d'): pd.concat([ohlcv_history, ohlcv_history]), - ('XRP/BTC', '1d'): ohlcv_history, - ('HOT/BTC', '1d'): ohlcv_history_high_vola, + ('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('LTC/BTC', '1d', CandleType.SPOT): pd.concat([ohlcv_history, ohlcv_history]), + ('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('HOT/BTC', '1d', CandleType.SPOT): ohlcv_history_high_vola, } mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) @@ -587,10 +587,10 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", "lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}], "BTC", "binance", ['LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC', 'HOT/BTC']), - # expecting pairs from default tickers, because 1h candles are not available + # expecting pairs as input, because 1h candles are not available ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", "lookback_timeframe": "1h", "lookback_period": 2, "refresh_period": 3600}], - "BTC", "binance", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), + "BTC", "binance", ['ETH/BTC', 'LTC/BTC', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']), # ftx data is already in Quote currency, therefore won't require conversion ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", "lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}], @@ -619,11 +619,11 @@ def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, mocker.patch('freqtrade.exchange.ftx.Ftx.market_is_tradable', return_value=True) ohlcv_data = { - ('ETH/BTC', '1d'): ohlcv_history, - ('TKN/BTC', '1d'): ohlcv_history, - ('LTC/BTC', '1d'): ohlcv_history_medium_volume, - ('XRP/BTC', '1d'): ohlcv_history_high_vola, - ('HOT/BTC', '1d'): ohlcv_history_high_volume, + ('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history_medium_volume, + ('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history_high_vola, + ('HOT/BTC', '1d', CandleType.SPOT): ohlcv_history_high_volume, } mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) @@ -937,9 +937,9 @@ def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tick def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, ohlcv_history): with time_machine.travel("2021-09-01 05:00:00 +00:00") as t: ohlcv_data = { - ('ETH/BTC', '1d'): ohlcv_history, - ('TKN/BTC', '1d'): ohlcv_history, - ('LTC/BTC', '1d'): ohlcv_history, + ('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history, } mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -961,10 +961,10 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 2 ohlcv_data = { - ('ETH/BTC', '1d'): ohlcv_history, - ('TKN/BTC', '1d'): ohlcv_history, - ('LTC/BTC', '1d'): ohlcv_history, - ('XRP/BTC', '1d'): ohlcv_history.iloc[[0]], + ('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history.iloc[[0]], } mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data) freqtrade.pairlists.refresh_pairlist() @@ -982,10 +982,10 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o t.move_to("2021-09-03 01:00:00 +00:00") # Called once for XRP/BTC ohlcv_data = { - ('ETH/BTC', '1d'): ohlcv_history, - ('TKN/BTC', '1d'): ohlcv_history, - ('LTC/BTC', '1d'): ohlcv_history, - ('XRP/BTC', '1d'): ohlcv_history, + ('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history, } mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data) freqtrade.pairlists.refresh_pairlist() @@ -1046,12 +1046,12 @@ def test_rangestabilityfilter_caching(mocker, markets, default_conf, tickers, oh get_tickers=tickers ) ohlcv_data = { - ('ETH/BTC', '1d'): ohlcv_history, - ('TKN/BTC', '1d'): ohlcv_history, - ('LTC/BTC', '1d'): ohlcv_history, - ('XRP/BTC', '1d'): ohlcv_history, - ('HOT/BTC', '1d'): ohlcv_history, - ('BLK/BTC', '1d'): ohlcv_history, + ('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('HOT/BTC', '1d', CandleType.SPOT): ohlcv_history, + ('BLK/BTC', '1d', CandleType.SPOT): ohlcv_history, } mocker.patch.multiple( 'freqtrade.exchange.Exchange', diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index a3cb29c9d..69c42c93d 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -4,14 +4,14 @@ from datetime import datetime, timedelta import pytest from freqtrade import constants -from freqtrade.enums import SellType +from freqtrade.enums import ExitType from freqtrade.persistence import PairLocks, Trade from freqtrade.plugins.protectionmanager import ProtectionManager from tests.conftest import get_patched_freqtradebot, log_has_re def generate_mock_trade(pair: str, fee: float, is_open: bool, - sell_reason: str = SellType.SELL_SIGNAL, + sell_reason: str = ExitType.SELL_SIGNAL, min_ago_open: int = None, min_ago_close: int = None, profit_rate: float = 0.9 ): @@ -91,7 +91,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog): caplog.clear() Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=200, min_ago_close=30, )) @@ -100,12 +100,12 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog): caplog.clear() # This trade does not count, as it's closed too long ago Trade.query.session.add(generate_mock_trade( - 'BCH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + 'BCH/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=250, min_ago_close=100, )) Trade.query.session.add(generate_mock_trade( - 'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + 'ETH/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=240, min_ago_close=30, )) # 3 Trades closed - but the 2nd has been closed too long ago. @@ -114,7 +114,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog): caplog.clear() Trade.query.session.add(generate_mock_trade( - 'LTC/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + 'LTC/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=180, min_ago_close=30, )) @@ -148,7 +148,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair caplog.clear() Trade.query.session.add(generate_mock_trade( - pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + pair, fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=200, min_ago_close=30, profit_rate=0.9, )) @@ -158,12 +158,12 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair caplog.clear() # This trade does not count, as it's closed too long ago Trade.query.session.add(generate_mock_trade( - pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + pair, fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=250, min_ago_close=100, profit_rate=0.9, )) # Trade does not count for per pair stop as it's the wrong pair. Trade.query.session.add(generate_mock_trade( - 'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + 'ETH/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=240, min_ago_close=30, profit_rate=0.9, )) # 3 Trades closed - but the 2nd has been closed too long ago. @@ -178,7 +178,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair # 2nd Trade that counts with correct pair Trade.query.session.add(generate_mock_trade( - pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + pair, fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=180, min_ago_close=30, profit_rate=0.9, )) @@ -203,7 +203,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog): caplog.clear() Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=200, min_ago_close=30, )) @@ -213,7 +213,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog): assert not PairLocks.is_global_lock() Trade.query.session.add(generate_mock_trade( - 'ETH/BTC', fee.return_value, False, sell_reason=SellType.ROI.value, + 'ETH/BTC', fee.return_value, False, sell_reason=ExitType.ROI.value, min_ago_open=205, min_ago_close=35, )) @@ -242,7 +242,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog): caplog.clear() Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=800, min_ago_close=450, profit_rate=0.9, )) @@ -253,7 +253,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog): assert not PairLocks.is_global_lock() Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=200, min_ago_close=120, profit_rate=0.9, )) @@ -265,14 +265,14 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog): # Add positive trade Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value, + 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.ROI.value, min_ago_open=20, min_ago_close=10, profit_rate=1.15, )) assert not freqtrade.protections.stop_per_pair('XRP/BTC') assert not PairLocks.is_pair_locked('XRP/BTC') Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=110, min_ago_close=20, profit_rate=0.8, )) @@ -300,15 +300,15 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): caplog.clear() Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=1000, min_ago_close=900, profit_rate=1.1, )) Trade.query.session.add(generate_mock_trade( - 'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + 'ETH/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=1000, min_ago_close=900, profit_rate=1.1, )) Trade.query.session.add(generate_mock_trade( - 'NEO/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + 'NEO/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=1000, min_ago_close=900, profit_rate=1.1, )) # No losing trade yet ... so max_drawdown will raise exception @@ -316,7 +316,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): assert not freqtrade.protections.stop_per_pair('XRP/BTC') Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=500, min_ago_close=400, profit_rate=0.9, )) # Not locked with one trade @@ -326,7 +326,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): assert not PairLocks.is_global_lock() Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, min_ago_open=1200, min_ago_close=1100, profit_rate=0.5, )) @@ -339,7 +339,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): # Winning trade ... (should not lock, does not change drawdown!) Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value, + 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.ROI.value, min_ago_open=320, min_ago_close=410, profit_rate=1.5, )) assert not freqtrade.protections.global_stop() @@ -349,7 +349,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): # Add additional negative trade, causing a loss of > 15% Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value, + 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.ROI.value, min_ago_open=20, min_ago_close=10, profit_rate=0.8, )) assert not freqtrade.protections.stop_per_pair('XRP/BTC') diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 003b43ad2..580ee5c84 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -8,7 +8,7 @@ import pytest from numpy import isnan from freqtrade.edge import PairInfo -from freqtrade.enums import State +from freqtrade.enums import SignalDirection, State, TradingMode from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError from freqtrade.persistence import Trade from freqtrade.persistence.models import Order @@ -71,6 +71,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'max_rate': ANY, 'strategy': ANY, 'buy_tag': ANY, + 'enter_tag': ANY, 'timeframe': 5, 'open_order_id': ANY, 'close_date': None, @@ -109,13 +110,20 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stoploss_entry_dist_ratio': -0.10448878, 'open_order': None, 'exchange': 'binance', + 'leverage': 1.0, + 'interest_rate': 0.0, + 'liquidation_price': None, + 'is_short': False, + 'funding_fees': 0.0, + 'trading_mode': TradingMode.SPOT, 'orders': [{ 'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05, 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, 'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY, - 'remaining': ANY, 'status': ANY}], + 'remaining': ANY, 'status': ANY, 'ft_is_entry': True, + }], } mocker.patch('freqtrade.exchange.Exchange.get_rate', @@ -145,6 +153,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'max_rate': ANY, 'strategy': ANY, 'buy_tag': ANY, + 'enter_tag': ANY, 'timeframe': ANY, 'open_order_id': ANY, 'close_date': None, @@ -183,13 +192,20 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stoploss_entry_dist_ratio': -0.10448878, 'open_order': None, 'exchange': 'binance', + 'leverage': 1.0, + 'interest_rate': 0.0, + 'liquidation_price': None, + 'is_short': False, + 'funding_fees': 0.0, + 'trading_mode': TradingMode.SPOT, 'orders': [{ 'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05, 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, 'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY, - 'remaining': ANY, 'status': ANY}], + 'remaining': ANY, 'status': ANY, 'ft_is_entry': True, + }], } @@ -304,7 +320,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, rpc._rpc_daily_profit(0, stake_currency, fiat_display_currency) -def test_rpc_trade_history(mocker, default_conf, markets, fee): +@pytest.mark.parametrize('is_short', [True, False]) +def test_rpc_trade_history(mocker, default_conf, markets, fee, is_short): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -312,7 +329,7 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee): ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - create_mock_trades(fee) + create_mock_trades(fee, is_short) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() trades = rpc._rpc_trade_history(2) @@ -329,7 +346,8 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee): assert trades['trades'][0]['pair'] == 'XRP/BTC' -def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog): +@pytest.mark.parametrize('is_short', [True, False]) +def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) stoploss_mock = MagicMock() cancel_mock = MagicMock() @@ -342,7 +360,7 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog): freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot.strategy.order_types['stoploss_on_exchange'] = True - create_mock_trades(fee) + create_mock_trades(fee, is_short) rpc = RPC(freqtradebot) with pytest.raises(RPCException, match='invalid argument'): rpc._rpc_delete('200') @@ -584,6 +602,30 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): 'used': 5.0, } } + mock_pos = [ + { + "symbol": "ETH/USDT:USDT", + "timestamp": None, + "datetime": None, + "initialMargin": 0.0, + "initialMarginPercentage": None, + "maintenanceMargin": 0.0, + "maintenanceMarginPercentage": 0.005, + "entryPrice": 0.0, + "notional": 100.0, + "leverage": 5.0, + "unrealizedPnl": 0.0, + "contracts": 100.0, + "contractSize": 1, + "marginRatio": None, + "liquidationPrice": 0.0, + "markPrice": 2896.41, + "collateral": 20, + "marginType": "isolated", + "side": 'short', + "percentage": None + } + ] mocker.patch.multiple( 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', @@ -593,47 +635,77 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', + validate_trading_mode_and_margin_mode=MagicMock(), get_balances=MagicMock(return_value=mock_balance), + fetch_positions=MagicMock(return_value=mock_pos), get_tickers=tickers, get_valid_pair_combination=MagicMock( side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}") ) default_conf['dry_run'] = False + default_conf['trading_mode'] = 'futures' freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency']) - assert prec_satoshi(result['total'], 12.30909624) - assert prec_satoshi(result['value'], 184636.443606915) + assert prec_satoshi(result['total'], 30.30909624) + assert prec_satoshi(result['value'], 454636.44360691) assert tickers.call_count == 1 assert tickers.call_args_list[0][1]['cached'] is True assert 'USD' == result['symbol'] assert result['currencies'] == [ - {'currency': 'BTC', - 'free': 10.0, - 'balance': 12.0, - 'used': 2.0, - 'est_stake': 12.0, - 'stake': 'BTC', - }, - {'free': 1.0, - 'balance': 5.0, - 'currency': 'ETH', - 'est_stake': 0.30794, - 'used': 4.0, - 'stake': 'BTC', - }, - {'free': 5.0, - 'balance': 10.0, - 'currency': 'USDT', - 'est_stake': 0.0011562404610161968, - 'used': 5.0, - 'stake': 'BTC', - } + { + 'currency': 'BTC', + 'free': 10.0, + 'balance': 12.0, + 'used': 2.0, + 'est_stake': 10.0, # In futures mode, "free" is used here. + 'stake': 'BTC', + 'is_position': False, + 'leverage': 1.0, + 'position': 0.0, + 'side': 'long', + }, + { + 'free': 1.0, + 'balance': 5.0, + 'currency': 'ETH', + 'est_stake': 0.30794, + 'used': 4.0, + 'stake': 'BTC', + 'is_position': False, + 'leverage': 1.0, + 'position': 0.0, + 'side': 'long', + + }, + { + 'free': 5.0, + 'balance': 10.0, + 'currency': 'USDT', + 'est_stake': 0.0011562404610161968, + 'used': 5.0, + 'stake': 'BTC', + 'is_position': False, + 'leverage': 1.0, + 'position': 0.0, + 'side': 'long', + }, + { + 'free': 0.0, + 'balance': 0.0, + 'currency': 'ETH/USDT:USDT', + 'est_stake': 20, + 'used': 0, + 'stake': 'BTC', + 'is_position': True, + 'leverage': 5.0, + 'position': 1000.0, + 'side': 'short', + } ] - assert result['total'] == 12.309096240461017 def test_rpc_start(mocker, default_conf) -> None: @@ -697,7 +769,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None: assert freqtradebot.config['max_open_trades'] == 0 -def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: +def test_rpc_forceexit(default_conf, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) cancel_order_mock = MagicMock() @@ -724,29 +796,29 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: freqtradebot.state = State.STOPPED with pytest.raises(RPCException, match=r'.*trader is not running*'): - rpc._rpc_forcesell(None) + rpc._rpc_forceexit(None) freqtradebot.state = State.RUNNING with pytest.raises(RPCException, match=r'.*invalid argument*'): - rpc._rpc_forcesell(None) + rpc._rpc_forceexit(None) - msg = rpc._rpc_forcesell('all') + msg = rpc._rpc_forceexit('all') assert msg == {'result': 'Created sell orders for all open trades.'} freqtradebot.enter_positions() - msg = rpc._rpc_forcesell('all') + msg = rpc._rpc_forceexit('all') assert msg == {'result': 'Created sell orders for all open trades.'} freqtradebot.enter_positions() - msg = rpc._rpc_forcesell('2') + msg = rpc._rpc_forceexit('2') assert msg == {'result': 'Created sell order for trade 2.'} freqtradebot.state = State.STOPPED with pytest.raises(RPCException, match=r'.*trader is not running*'): - rpc._rpc_forcesell(None) + rpc._rpc_forceexit(None) with pytest.raises(RPCException, match=r'.*trader is not running*'): - rpc._rpc_forcesell('all') + rpc._rpc_forceexit('all') freqtradebot.state = State.RUNNING assert cancel_order_mock.call_count == 0 @@ -775,7 +847,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: ) # check that the trade is called, which is done by ensuring exchange.cancel_order is called # and trade amount is updated - rpc._rpc_forcesell('3') + rpc._rpc_forceexit('3') assert cancel_order_mock.call_count == 1 assert trade.amount == filled_amount @@ -803,7 +875,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: } ) # check that the trade is called, which is done by ensuring exchange.cancel_order is called - msg = rpc._rpc_forcesell('4') + msg = rpc._rpc_forceexit('4') assert msg == {'result': 'Created sell order for trade 4.'} assert cancel_order_mock.call_count == 2 assert trade.amount == amount @@ -820,7 +892,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: 'filled': 0.0 } ) - msg = rpc._rpc_forcesell('3') + msg = rpc._rpc_forceexit('3') assert msg == {'result': 'Created sell order for trade 3.'} # status quo, no exchange calls assert cancel_order_mock.call_count == 3 @@ -862,8 +934,8 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, assert prec_satoshi(res[0]['profit_pct'], 6.2) -def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, - limit_sell_order, mocker) -> None: +def test_enter_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, + limit_sell_order, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -891,23 +963,23 @@ def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, trade.close_date = datetime.utcnow() trade.is_open = False - res = rpc._rpc_buy_tag_performance(None) + res = rpc._rpc_enter_tag_performance(None) assert len(res) == 1 - assert res[0]['buy_tag'] == 'Other' + assert res[0]['enter_tag'] == 'Other' assert res[0]['count'] == 1 assert prec_satoshi(res[0]['profit_pct'], 6.2) - trade.buy_tag = "TEST_TAG" - res = rpc._rpc_buy_tag_performance(None) + trade.enter_tag = "TEST_TAG" + res = rpc._rpc_enter_tag_performance(None) assert len(res) == 1 - assert res[0]['buy_tag'] == 'TEST_TAG' + assert res[0]['enter_tag'] == 'TEST_TAG' assert res[0]['count'] == 1 assert prec_satoshi(res[0]['profit_pct'], 6.2) -def test_buy_tag_performance_handle_2(mocker, default_conf, markets, fee): +def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -918,21 +990,21 @@ def test_buy_tag_performance_handle_2(mocker, default_conf, markets, fee): create_mock_trades(fee) rpc = RPC(freqtradebot) - res = rpc._rpc_buy_tag_performance(None) + res = rpc._rpc_enter_tag_performance(None) assert len(res) == 2 - assert res[0]['buy_tag'] == 'TEST1' + assert res[0]['enter_tag'] == 'TEST1' assert res[0]['count'] == 1 assert prec_satoshi(res[0]['profit_pct'], 0.5) - assert res[1]['buy_tag'] == 'Other' + assert res[1]['enter_tag'] == 'Other' assert res[1]['count'] == 1 assert prec_satoshi(res[1]['profit_pct'], 1.0) # Test for a specific pair - res = rpc._rpc_buy_tag_performance('ETC/BTC') + res = rpc._rpc_enter_tag_performance('ETC/BTC') assert len(res) == 1 assert res[0]['count'] == 1 - assert res[0]['buy_tag'] == 'TEST1' + assert res[0]['enter_tag'] == 'TEST1' assert prec_satoshi(res[0]['profit_pct'], 0.5) @@ -1046,7 +1118,7 @@ def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, assert res[0]['count'] == 1 assert prec_satoshi(res[0]['profit_pct'], 6.2) - trade.buy_tag = "TESTBUY" + trade.enter_tag = "TESTBUY" trade.sell_reason = "TESTSELL" res = rpc._rpc_mix_tag_performance(None) @@ -1108,7 +1180,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None: assert counts["current"] == 1 -def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> None: +def test_rpc_forceentry(mocker, default_conf, ticker, fee, limit_buy_order_open) -> None: default_conf['forcebuy_enable'] = True mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) buy_mm = MagicMock(return_value=limit_buy_order_open) @@ -1124,16 +1196,16 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) pair = 'ETH/BTC' - trade = rpc._rpc_forcebuy(pair, None) + trade = rpc._rpc_force_entry(pair, None) assert isinstance(trade, Trade) assert trade.pair == pair assert trade.open_rate == ticker()['bid'] # Test buy duplicate with pytest.raises(RPCException, match=r'position for ETH/BTC already open - id: 1'): - rpc._rpc_forcebuy(pair, 0.0001) + rpc._rpc_force_entry(pair, 0.0001) pair = 'XRP/BTC' - trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit') + trade = rpc._rpc_force_entry(pair, 0.0001, order_type='limit') assert isinstance(trade, Trade) assert trade.pair == pair assert trade.open_rate == 0.0001 @@ -1141,11 +1213,11 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> # Test buy pair not with stakes with pytest.raises(RPCException, match=r'Wrong pair selected. Only pairs with stake-currency.*'): - rpc._rpc_forcebuy('LTC/ETH', 0.0001) + rpc._rpc_force_entry('LTC/ETH', 0.0001) # Test with defined stake_amount pair = 'LTC/BTC' - trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit', stake_amount=0.05) + trade = rpc._rpc_force_entry(pair, 0.0001, order_type='limit', stake_amount=0.05) assert trade.stake_amount == 0.05 assert trade.buy_tag == 'forceentry' @@ -1156,11 +1228,11 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) pair = 'TKN/BTC' - trade = rpc._rpc_forcebuy(pair, None) + trade = rpc._rpc_force_entry(pair, None) assert trade is None -def test_rpcforcebuy_stopped(mocker, default_conf) -> None: +def test_rpc_forceentry_stopped(mocker, default_conf) -> None: default_conf['forcebuy_enable'] = True default_conf['initial_state'] = 'stopped' mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) @@ -1170,18 +1242,30 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None: rpc = RPC(freqtradebot) pair = 'ETH/BTC' with pytest.raises(RPCException, match=r'trader is not running'): - rpc._rpc_forcebuy(pair, None) + rpc._rpc_force_entry(pair, None) -def test_rpcforcebuy_disabled(mocker, default_conf) -> None: +def test_rpc_forceentry_disabled(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) pair = 'ETH/BTC' - with pytest.raises(RPCException, match=r'Forcebuy not enabled.'): - rpc._rpc_forcebuy(pair, None) + with pytest.raises(RPCException, match=r'Forceentry not enabled.'): + rpc._rpc_force_entry(pair, None) + + +def test_rpc_forceentry_wrong_mode(mocker, default_conf) -> None: + default_conf['forcebuy_enable'] = True + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + patch_get_signal(freqtradebot) + rpc = RPC(freqtradebot) + pair = 'ETH/BTC' + with pytest.raises(RPCException, match="Can't go short on Spot markets."): + rpc._rpc_force_entry(pair, None, order_side=SignalDirection.SHORT) @pytest.mark.usefixtures("init_persistence") diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 84a18440e..167f644c6 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -17,7 +17,7 @@ from numpy import isnan from requests.auth import _basic_auth_str from freqtrade.__init__ import __version__ -from freqtrade.enums import RunMode, State +from freqtrade.enums import CandleType, RunMode, State, TradingMode from freqtrade.exceptions import DependencyException, ExchangeError, OperationalException from freqtrade.loggers import setup_logging, setup_logging_pre from freqtrade.persistence import PairLocks, Trade @@ -25,8 +25,8 @@ from freqtrade.rpc import RPC from freqtrade.rpc.api_server import ApiServer from freqtrade.rpc.api_server.api_auth import create_token, get_user_from_token from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer -from tests.conftest import (create_mock_trades, get_mock_coro, get_patched_freqtradebot, log_has, - log_has_re, patch_get_signal) +from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_mock_coro, + get_patched_freqtradebot, log_has, log_has_re, patch_get_signal) BASE_URI = "/api/v1" @@ -452,6 +452,10 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers): 'used': 0.0, 'est_stake': 12.0, 'stake': 'BTC', + 'is_position': False, + 'leverage': 1.0, + 'position': 0.0, + 'side': 'long', } assert 'starting_capital' in response assert 'starting_capital_fiat' in response @@ -459,7 +463,8 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers): assert 'starting_capital_ratio' in response -def test_api_count(botclient, mocker, ticker, fee, markets): +@pytest.mark.parametrize('is_short', [True, False]) +def test_api_count(botclient, mocker, ticker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( @@ -476,7 +481,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets): assert rc.json()["max"] == 1 # Create some test data - create_mock_trades(fee) + create_mock_trades(fee, is_short=is_short) rc = client_get(client, f"{BASE_URI}/count") assert_response(rc) assert rc.json()["current"] == 4 @@ -527,21 +532,23 @@ def test_api_show_config(botclient): rc = client_get(client, f"{BASE_URI}/show_config") assert_response(rc) - assert 'dry_run' in rc.json() - assert rc.json()['exchange'] == 'binance' - assert rc.json()['timeframe'] == '5m' - assert rc.json()['timeframe_ms'] == 300000 - assert rc.json()['timeframe_min'] == 5 - assert rc.json()['state'] == 'running' - assert rc.json()['bot_name'] == 'freqtrade' - assert rc.json()['strategy_version'] is None - assert not rc.json()['trailing_stop'] - assert 'bid_strategy' in rc.json() - assert 'ask_strategy' in rc.json() - assert 'unfilledtimeout' in rc.json() - assert 'version' in rc.json() - assert 'api_version' in rc.json() - assert 1.1 <= rc.json()['api_version'] <= 1.2 + response = rc.json() + assert 'dry_run' in response + assert response['exchange'] == 'binance' + assert response['timeframe'] == '5m' + assert response['timeframe_ms'] == 300000 + assert response['timeframe_min'] == 5 + assert response['state'] == 'running' + assert response['bot_name'] == 'freqtrade' + assert response['trading_mode'] == 'spot' + assert response['strategy_version'] is None + assert not response['trailing_stop'] + assert 'entry_pricing' in response + assert 'exit_pricing' in response + assert 'unfilledtimeout' in response + assert 'version' in response + assert 'api_version' in response + assert 2.1 <= response['api_version'] <= 2.2 def test_api_daily(botclient, mocker, ticker, fee, markets): @@ -562,7 +569,8 @@ def test_api_daily(botclient, mocker, ticker, fee, markets): assert rc.json()['data'][0]['date'] == str(datetime.utcnow().date()) -def test_api_trades(botclient, mocker, fee, markets): +@pytest.mark.parametrize('is_short', [True, False]) +def test_api_trades(botclient, mocker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( @@ -575,13 +583,15 @@ def test_api_trades(botclient, mocker, fee, markets): assert rc.json()['trades_count'] == 0 assert rc.json()['total_trades'] == 0 - create_mock_trades(fee) + create_mock_trades(fee, is_short=is_short) + Trade.query.session.flush() rc = client_get(client, f"{BASE_URI}/trades") assert_response(rc) assert len(rc.json()['trades']) == 2 assert rc.json()['trades_count'] == 2 assert rc.json()['total_trades'] == 2 + assert rc.json()['trades'][0]['is_short'] == is_short rc = client_get(client, f"{BASE_URI}/trades?limit=1") assert_response(rc) assert len(rc.json()['trades']) == 1 @@ -589,9 +599,10 @@ def test_api_trades(botclient, mocker, fee, markets): assert rc.json()['total_trades'] == 2 -def test_api_trade_single(botclient, mocker, fee, ticker, markets): +@pytest.mark.parametrize('is_short', [True, False]) +def test_api_trade_single(botclient, mocker, fee, ticker, markets, is_short): ftbot, client = botclient - patch_get_signal(ftbot) + patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) mocker.patch.multiple( 'freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), @@ -601,16 +612,19 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets): assert_response(rc, 404) assert rc.json()['detail'] == 'Trade not found.' - create_mock_trades(fee) + Trade.query.session.rollback() + create_mock_trades(fee, is_short=is_short) rc = client_get(client, f"{BASE_URI}/trade/3") assert_response(rc) assert rc.json()['trade_id'] == 3 + assert rc.json()['is_short'] == is_short -def test_api_delete_trade(botclient, mocker, fee, markets): +@pytest.mark.parametrize('is_short', [True, False]) +def test_api_delete_trade(botclient, mocker, fee, markets, is_short): ftbot, client = botclient - patch_get_signal(ftbot) + patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) stoploss_mock = MagicMock() cancel_mock = MagicMock() mocker.patch.multiple( @@ -619,11 +633,8 @@ def test_api_delete_trade(botclient, mocker, fee, markets): cancel_order=cancel_mock, cancel_stoploss_order=stoploss_mock, ) - rc = client_delete(client, f"{BASE_URI}/trades/1") - # Error - trade won't exist yet. - assert_response(rc, 502) - create_mock_trades(fee) + create_mock_trades(fee, is_short=is_short) ftbot.strategy.order_types['stoploss_on_exchange'] = True trades = Trade.query.all() @@ -650,6 +661,10 @@ def test_api_delete_trade(botclient, mocker, fee, markets): assert len(trades) - 2 == len(Trade.query.all()) assert stoploss_mock.call_count == 1 + rc = client_delete(client, f"{BASE_URI}/trades/502") + # Error - trade won't exist. + assert_response(rc, 502) + def test_api_logs(botclient): ftbot, client = botclient @@ -698,7 +713,48 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): assert rc.json() == {"error": "Error querying /api/v1/edge: Edge is not enabled."} -def test_api_profit(botclient, mocker, ticker, fee, markets): +@pytest.mark.parametrize('is_short,expected', [ + ( + True, + {'best_pair': 'ETC/BTC', 'best_rate': -0.5, 'best_pair_profit_ratio': -0.005, + 'profit_all_coin': 43.61269123, + 'profit_all_fiat': 538398.67323435, 'profit_all_percent_mean': 66.41, + 'profit_all_ratio_mean': 0.664109545, 'profit_all_percent_sum': 398.47, + 'profit_all_ratio_sum': 3.98465727, 'profit_all_percent': 4.36, + 'profit_all_ratio': 0.043612222872799825, 'profit_closed_coin': -0.00673913, + 'profit_closed_fiat': -83.19455985, 'profit_closed_ratio_mean': -0.0075, + 'profit_closed_percent_mean': -0.75, 'profit_closed_ratio_sum': -0.015, + 'profit_closed_percent_sum': -1.5, 'profit_closed_ratio': -6.739057628404269e-06, + 'profit_closed_percent': -0.0, 'winning_trades': 0, 'losing_trades': 2} + ), + ( + False, + {'best_pair': 'XRP/BTC', 'best_rate': 1.0, 'best_pair_profit_ratio': 0.01, + 'profit_all_coin': -44.0631579, + 'profit_all_fiat': -543959.6842755, 'profit_all_percent_mean': -66.41, + 'profit_all_ratio_mean': -0.6641100666666667, 'profit_all_percent_sum': -398.47, + 'profit_all_ratio_sum': -3.9846604, 'profit_all_percent': -4.41, + 'profit_all_ratio': -0.044063014216106644, 'profit_closed_coin': 0.00073913, + 'profit_closed_fiat': 9.124559849999999, 'profit_closed_ratio_mean': 0.0075, + 'profit_closed_percent_mean': 0.75, 'profit_closed_ratio_sum': 0.015, + 'profit_closed_percent_sum': 1.5, 'profit_closed_ratio': 7.391275897987988e-07, + 'profit_closed_percent': 0.0, 'winning_trades': 2, 'losing_trades': 0} + ), + ( + None, + {'best_pair': 'XRP/BTC', 'best_rate': 1.0, 'best_pair_profit_ratio': 0.01, + 'profit_all_coin': -14.43790415, + 'profit_all_fiat': -178235.92673175, 'profit_all_percent_mean': 0.08, + 'profit_all_ratio_mean': 0.000835751666666662, 'profit_all_percent_sum': 0.5, + 'profit_all_ratio_sum': 0.005014509999999972, 'profit_all_percent': -1.44, + 'profit_all_ratio': -0.014437768014451796, 'profit_closed_coin': -0.00542913, + 'profit_closed_fiat': -67.02260985, 'profit_closed_ratio_mean': 0.0025, + 'profit_closed_percent_mean': 0.25, 'profit_closed_ratio_sum': 0.005, + 'profit_closed_percent_sum': 0.5, 'profit_closed_ratio': -5.429078808526421e-06, + 'profit_closed_percent': -0.0, 'winning_trades': 1, 'losing_trades': 1} + ) +]) +def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( @@ -713,45 +769,48 @@ def test_api_profit(botclient, mocker, ticker, fee, markets): assert_response(rc, 200) assert rc.json()['trade_count'] == 0 - create_mock_trades(fee) + create_mock_trades(fee, is_short=is_short) # Simulate fulfilled LIMIT_BUY order for trade rc = client_get(client, f"{BASE_URI}/profit") assert_response(rc) - assert rc.json() == {'avg_duration': ANY, - 'best_pair': 'XRP/BTC', - 'best_rate': 1.0, - 'best_pair_profit_ratio': 0.01, - 'first_trade_date': ANY, - 'first_trade_timestamp': ANY, - 'latest_trade_date': '5 minutes ago', - 'latest_trade_timestamp': ANY, - 'profit_all_coin': -44.0631579, - 'profit_all_fiat': -543959.6842755, - 'profit_all_percent_mean': -66.41, - 'profit_all_ratio_mean': -0.6641100666666667, - 'profit_all_percent_sum': -398.47, - 'profit_all_ratio_sum': -3.9846604, - 'profit_all_percent': -4.41, - 'profit_all_ratio': -0.044063014216106644, - 'profit_closed_coin': 0.00073913, - 'profit_closed_fiat': 9.124559849999999, - 'profit_closed_ratio_mean': 0.0075, - 'profit_closed_percent_mean': 0.75, - 'profit_closed_ratio_sum': 0.015, - 'profit_closed_percent_sum': 1.5, - 'profit_closed_ratio': 7.391275897987988e-07, - 'profit_closed_percent': 0.0, - 'trade_count': 6, - 'closed_trade_count': 2, - 'winning_trades': 2, - 'losing_trades': 0, - } + # raise ValueError(rc.json()) + assert rc.json() == { + 'avg_duration': ANY, + 'best_pair': expected['best_pair'], + 'best_pair_profit_ratio': expected['best_pair_profit_ratio'], + 'best_rate': expected['best_rate'], + 'first_trade_date': ANY, + 'first_trade_timestamp': ANY, + 'latest_trade_date': '5 minutes ago', + 'latest_trade_timestamp': ANY, + 'profit_all_coin': expected['profit_all_coin'], + 'profit_all_fiat': expected['profit_all_fiat'], + 'profit_all_percent_mean': expected['profit_all_percent_mean'], + 'profit_all_ratio_mean': expected['profit_all_ratio_mean'], + 'profit_all_percent_sum': expected['profit_all_percent_sum'], + 'profit_all_ratio_sum': expected['profit_all_ratio_sum'], + 'profit_all_percent': expected['profit_all_percent'], + 'profit_all_ratio': expected['profit_all_ratio'], + 'profit_closed_coin': expected['profit_closed_coin'], + 'profit_closed_fiat': expected['profit_closed_fiat'], + 'profit_closed_ratio_mean': expected['profit_closed_ratio_mean'], + 'profit_closed_percent_mean': expected['profit_closed_percent_mean'], + 'profit_closed_ratio_sum': expected['profit_closed_ratio_sum'], + 'profit_closed_percent_sum': expected['profit_closed_percent_sum'], + 'profit_closed_ratio': expected['profit_closed_ratio'], + 'profit_closed_percent': expected['profit_closed_percent'], + 'trade_count': 6, + 'closed_trade_count': 2, + 'winning_trades': expected['winning_trades'], + 'losing_trades': expected['losing_trades'], + } -def test_api_stats(botclient, mocker, ticker, fee, markets,): +@pytest.mark.parametrize('is_short', [True, False]) +def test_api_stats(botclient, mocker, ticker, fee, markets, is_short): ftbot, client = botclient - patch_get_signal(ftbot) + patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=ticker), @@ -765,7 +824,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,): assert 'durations' in rc.json() assert 'sell_reasons' in rc.json() - create_mock_trades(fee) + create_mock_trades(fee, is_short=is_short) rc = client_get(client, f"{BASE_URI}/stats") assert_response(rc, 200) @@ -825,7 +884,12 @@ def test_api_performance(botclient, fee): 'profit_ratio': -0.05570419, 'profit_abs': -0.1150375}] -def test_api_status(botclient, mocker, ticker, fee, markets): +@pytest.mark.parametrize( + 'is_short,current_rate,open_order_id,open_trade_value', + [(True, 1.098e-05, 'dry_run_buy_short_12345', 15.0911775), + (False, 1.099e-05, 'dry_run_buy_long_12345', 15.1668225)]) +def test_api_status(botclient, mocker, ticker, fee, markets, is_short, + current_rate, open_order_id, open_trade_value): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( @@ -840,7 +904,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): rc = client_get(client, f"{BASE_URI}/status") assert_response(rc, 200) assert rc.json() == [] - create_mock_trades(fee) + create_mock_trades(fee, is_short=is_short) rc = client_get(client, f"{BASE_URI}/status") assert_response(rc) @@ -861,7 +925,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'profit_pct': ANY, 'profit_abs': ANY, 'profit_fiat': ANY, - 'current_rate': 1.099e-05, + 'current_rate': current_rate, 'open_date': ANY, 'open_timestamp': ANY, 'open_order': None, @@ -891,19 +955,24 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'fee_open_cost': None, 'fee_open_currency': None, 'is_open': True, + "is_short": is_short, 'max_rate': ANY, 'min_rate': ANY, - 'open_order_id': 'dry_run_buy_12345', + 'open_order_id': open_order_id, 'open_rate_requested': ANY, - 'open_trade_value': 15.1668225, + 'open_trade_value': open_trade_value, 'sell_reason': None, 'sell_order_status': None, - 'strategy': 'StrategyTestV2', + 'strategy': CURRENT_TEST_STRATEGY, 'buy_tag': None, + 'enter_tag': None, 'timeframe': 5, 'exchange': 'binance', + 'leverage': 1.0, + 'interest_rate': 0.0, + 'funding_fees': None, + 'trading_mode': ANY, 'orders': [ANY], - } mocker.patch('freqtrade.exchange.Exchange.get_rate', @@ -977,8 +1046,8 @@ def test_api_blacklist(botclient, mocker): "NOTHING/BTC": { "error_msg": "Pair NOTHING/BTC is not in the current blacklist." } - }, - } + }, + } rc = client_delete( client, f"{BASE_URI}/blacklist?pairs_to_delete=HOT/BTC&pairs_to_delete=ETH/BTC") @@ -1003,23 +1072,27 @@ def test_api_whitelist(botclient): } -def test_api_forcebuy(botclient, mocker, fee): +@pytest.mark.parametrize('endpoint', [ + 'forcebuy', + 'forceenter', +]) +def test_api_forceentry(botclient, mocker, fee, endpoint): ftbot, client = botclient - rc = client_post(client, f"{BASE_URI}/forcebuy", + rc = client_post(client, f"{BASE_URI}/{endpoint}", data='{"pair": "ETH/BTC"}') assert_response(rc, 502) - assert rc.json() == {"error": "Error querying /api/v1/forcebuy: Forcebuy not enabled."} + assert rc.json() == {"error": f"Error querying /api/v1/{endpoint}: Forceentry not enabled."} # enable forcebuy ftbot.config['forcebuy_enable'] = True fbuy_mock = MagicMock(return_value=None) - mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock) - rc = client_post(client, f"{BASE_URI}/forcebuy", + mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock) + rc = client_post(client, f"{BASE_URI}/{endpoint}", data='{"pair": "ETH/BTC"}') assert_response(rc) - assert rc.json() == {"status": "Error buying pair ETH/BTC."} + assert rc.json() == {"status": "Error entering long trade for pair ETH/BTC."} # Test creating trade fbuy_mock = MagicMock(return_value=Trade( @@ -1032,21 +1105,23 @@ def test_api_forcebuy(botclient, mocker, fee): open_order_id="123456", open_date=datetime.utcnow(), is_open=False, + is_short=False, fee_close=fee.return_value, fee_open=fee.return_value, close_rate=0.265441, id=22, timeframe=5, - strategy="StrategyTestV2" + strategy=CURRENT_TEST_STRATEGY, + trading_mode=TradingMode.SPOT )) - mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock) + mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock) - rc = client_post(client, f"{BASE_URI}/forcebuy", + rc = client_post(client, f"{BASE_URI}/{endpoint}", data='{"pair": "ETH/BTC"}') assert_response(rc) assert rc.json() == { - 'amount': 1, - 'amount_requested': 1, + 'amount': 1.0, + 'amount_requested': 1.0, 'trade_id': 22, 'close_date': None, 'close_timestamp': None, @@ -1080,6 +1155,7 @@ def test_api_forcebuy(botclient, mocker, fee): 'fee_open_cost': None, 'fee_open_currency': None, 'is_open': False, + 'is_short': False, 'max_rate': None, 'min_rate': None, 'open_order_id': '123456', @@ -1087,10 +1163,15 @@ def test_api_forcebuy(botclient, mocker, fee): 'open_trade_value': 0.24605460, 'sell_reason': None, 'sell_order_status': None, - 'strategy': 'StrategyTestV2', + 'strategy': CURRENT_TEST_STRATEGY, 'buy_tag': None, + 'enter_tag': None, 'timeframe': 5, 'exchange': 'binance', + 'leverage': None, + 'interest_rate': None, + 'funding_fees': None, + 'trading_mode': 'spot', 'orders': [], } @@ -1146,17 +1227,19 @@ def test_api_pair_candles(botclient, ohlcv_history): assert 'data_stop_ts' in rc.json() assert len(rc.json()['data']) == 0 ohlcv_history['sma'] = ohlcv_history['close'].rolling(2).mean() - ohlcv_history['buy'] = 0 - ohlcv_history.loc[1, 'buy'] = 1 - ohlcv_history['sell'] = 0 + ohlcv_history['enter_long'] = 0 + ohlcv_history.loc[1, 'enter_long'] = 1 + ohlcv_history['exit_long'] = 0 + ohlcv_history['enter_short'] = 0 + ohlcv_history['exit_short'] = 0 - ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history) + ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT) rc = client_get(client, f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}") assert_response(rc) assert 'strategy' in rc.json() - assert rc.json()['strategy'] == 'StrategyTestV2' + assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY assert 'columns' in rc.json() assert 'data_start_ts' in rc.json() assert 'data_start' in rc.json() @@ -1167,9 +1250,12 @@ def test_api_pair_candles(botclient, ohlcv_history): assert rc.json()['data_stop'] == '2017-11-26 09:00:00+00:00' assert rc.json()['data_stop_ts'] == 1511686800000 assert isinstance(rc.json()['columns'], list) - assert rc.json()['columns'] == ['date', 'open', 'high', - 'low', 'close', 'volume', 'sma', 'buy', 'sell', - '__date_ts', '_buy_signal_close', '_sell_signal_close'] + assert set(rc.json()['columns']) == { + 'date', 'open', 'high', 'low', 'close', 'volume', + 'sma', 'enter_long', 'exit_long', 'enter_short', 'exit_short', '__date_ts', + '_enter_long_signal_close', '_exit_long_signal_close', + '_enter_short_signal_close', '_exit_short_signal_close' + } assert 'pair' in rc.json() assert rc.json()['pair'] == 'XRP/BTC' @@ -1178,31 +1264,32 @@ def test_api_pair_candles(botclient, ohlcv_history): assert (rc.json()['data'] == [['2017-11-26 08:50:00', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869, - None, 0, 0, 1511686200000, None, None], + None, 0, 0, 0, 0, 1511686200000, None, None, None, None], ['2017-11-26 08:55:00', 8.88e-05, 8.942e-05, 8.88e-05, - 8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0, 1511686500000, 8.893e-05, - None], + 8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0, 0, 0, 1511686500000, 8.893e-05, + None, None, None], ['2017-11-26 09:00:00', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05, - 0.7039405, 8.885e-05, 0, 0, 1511686800000, None, None] + 0.7039405, 8.885e-05, 0, 0, 0, 0, 1511686800000, None, None, None, None] ]) - ohlcv_history['sell'] = ohlcv_history['sell'].astype('float64') - ohlcv_history.at[0, 'sell'] = float('inf') + ohlcv_history['exit_long'] = ohlcv_history['exit_long'].astype('float64') + ohlcv_history.at[0, 'exit_long'] = float('inf') ohlcv_history['date1'] = ohlcv_history['date'] ohlcv_history.at[0, 'date1'] = pd.NaT - ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history) + ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT) rc = client_get(client, f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}") assert_response(rc) assert (rc.json()['data'] == [['2017-11-26 08:50:00', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869, - None, 0, None, None, 1511686200000, None, None], + None, 0, None, 0, 0, None, 1511686200000, None, None, None, None], ['2017-11-26 08:55:00', 8.88e-05, 8.942e-05, 8.88e-05, - 8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0.0, '2017-11-26 08:55:00', - 1511686500000, 8.893e-05, None], + 8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0.0, 0, 0, '2017-11-26 08:55:00', + 1511686500000, 8.893e-05, None, None, None], ['2017-11-26 09:00:00', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05, - 0.7039405, 8.885e-05, 0, 0.0, '2017-11-26 09:00:00', 1511686800000, None, None] + 0.7039405, 8.885e-05, 0, 0.0, 0, 0, '2017-11-26 09:00:00', 1511686800000, + None, None, None, None] ]) @@ -1213,19 +1300,19 @@ def test_api_pair_history(botclient, ohlcv_history): # No pair rc = client_get(client, f"{BASE_URI}/pair_history?timeframe={timeframe}" - "&timerange=20180111-20180112&strategy=StrategyTestV2") + f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}") assert_response(rc, 422) # No Timeframe rc = client_get(client, f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC" - "&timerange=20180111-20180112&strategy=StrategyTestV2") + f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}") assert_response(rc, 422) # No timerange rc = client_get(client, f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" - "&strategy=StrategyTestV2") + f"&strategy={CURRENT_TEST_STRATEGY}") assert_response(rc, 422) # No strategy @@ -1237,14 +1324,14 @@ def test_api_pair_history(botclient, ohlcv_history): # Working rc = client_get(client, f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" - "&timerange=20180111-20180112&strategy=StrategyTestV2") + f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}") assert_response(rc, 200) assert rc.json()['length'] == 289 assert len(rc.json()['data']) == rc.json()['length'] assert 'columns' in rc.json() assert 'data' in rc.json() assert rc.json()['pair'] == 'UNITTEST/BTC' - assert rc.json()['strategy'] == 'StrategyTestV2' + assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00' assert rc.json()['data_start_ts'] == 1515628800000 assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00' @@ -1253,7 +1340,7 @@ def test_api_pair_history(botclient, ohlcv_history): # No data found rc = client_get(client, f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" - "&timerange=20200111-20200112&strategy=StrategyTestV2") + f"&timerange=20200111-20200112&strategy={CURRENT_TEST_STRATEGY}") assert_response(rc, 502) assert rc.json()['error'] == ("Error querying /api/v1/pair_history: " "No data for UNITTEST/BTC, 5m in 20200111-20200112 found.") @@ -1294,19 +1381,21 @@ def test_api_strategies(botclient): 'HyperoptableStrategy', 'InformativeDecoratorTest', 'StrategyTestV2', - 'TestStrategyLegacyV1' + 'StrategyTestV3', + 'StrategyTestV3Futures', + 'TestStrategyLegacyV1', ]} def test_api_strategy(botclient): ftbot, client = botclient - rc = client_get(client, f"{BASE_URI}/strategy/StrategyTestV2") + rc = client_get(client, f"{BASE_URI}/strategy/{CURRENT_TEST_STRATEGY}") assert_response(rc) - assert rc.json()['strategy'] == 'StrategyTestV2' + assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY - data = (Path(__file__).parents[1] / "strategy/strats/strategy_test_v2.py").read_text() + data = (Path(__file__).parents[1] / "strategy/strats/strategy_test_v3.py").read_text() assert rc.json()['code'] == data rc = client_get(client, f"{BASE_URI}/strategy/NoStrat") @@ -1338,6 +1427,20 @@ def test_list_available_pairs(botclient): assert rc.json()['pairs'] == ['XRP/ETH'] assert len(rc.json()['pair_interval']) == 1 + ftbot.config['trading_mode'] = 'futures' + rc = client_get( + client, f"{BASE_URI}/available_pairs?timeframe=1h") + assert_response(rc) + assert rc.json()['length'] == 1 + assert rc.json()['pairs'] == ['XRP/USDT'] + + rc = client_get( + client, f"{BASE_URI}/available_pairs?timeframe=1h&candletype=mark") + assert_response(rc) + assert rc.json()['length'] == 2 + assert rc.json()['pairs'] == ['UNITTEST/USDT', 'XRP/USDT'] + assert len(rc.json()['pair_interval']) == 2 + def test_sysinfo(botclient): ftbot, client = botclient @@ -1383,7 +1486,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): # start backtesting data = { - "strategy": "StrategyTestV2", + "strategy": CURRENT_TEST_STRATEGY, "timeframe": "5m", "timerange": "20180110-20180111", "max_open_trades": 3, diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index f53f48cc2..db93a6ec4 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -18,7 +18,7 @@ from telegram.error import BadRequest, NetworkError, TelegramError from freqtrade import __version__ from freqtrade.constants import CANCEL_REASON from freqtrade.edge import PairInfo -from freqtrade.enums import RPCMessageType, RunMode, SellType, State +from freqtrade.enums import ExitType, RPCMessageType, RunMode, SignalDirection, State from freqtrade.exceptions import OperationalException from freqtrade.freqtradebot import FreqtradeBot from freqtrade.loggers import setup_logging @@ -27,8 +27,8 @@ from freqtrade.persistence.models import Order from freqtrade.rpc import RPC from freqtrade.rpc.rpc import RPCException from freqtrade.rpc.telegram import Telegram, authorized_only -from tests.conftest import (create_mock_trades, get_patched_freqtradebot, log_has, log_has_re, - patch_exchange, patch_get_signal, patch_whitelist) +from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_patched_freqtradebot, + log_has, log_has_re, patch_exchange, patch_get_signal, patch_whitelist) class DummyCls(Telegram): @@ -94,8 +94,10 @@ def test_telegram_init(default_conf, mocker, caplog) -> None: assert start_polling.start_polling.call_count == 1 message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " - "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], " - "['delete'], ['performance'], ['buys'], ['sells'], ['mix_tags'], " + "['balance'], ['start'], ['stop'], " + "['forcesell', 'forceexit'], ['forcebuy', 'forcelong'], ['forceshort'], " + "['trades'], ['delete'], ['performance'], " + "['buys', 'entries'], ['sells'], ['mix_tags'], " "['stats'], ['daily'], ['weekly'], ['monthly'], " "['count'], ['locks'], ['unlock', 'delete_locks'], " "['reload_config', 'reload_conf'], ['show_config', 'show_conf'], " @@ -191,6 +193,7 @@ def test_telegram_status(default_conf, update, mocker) -> None: 'amount': 90.99181074, 'stake_amount': 90.99181074, 'buy_tag': None, + 'enter_tag': None, 'close_profit_ratio': None, 'profit': -0.0059, 'profit_ratio': -0.0059, @@ -203,6 +206,8 @@ def test_telegram_status(default_conf, update, mocker) -> None: 'stop_loss_ratio': -0.0001, 'open_order': '(limit buy rem=0.00000000)', 'is_open': True, + 'is_short': False, + 'filled_entry_orders': [], 'orders': [] }]), ) @@ -393,7 +398,8 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ') assert int(fields[0]) == 1 - assert 'ETH/BTC' in fields[1] + assert 'L' in fields[1] + assert 'ETH/BTC' in fields[2] assert msg_mock.call_count == 1 @@ -623,7 +629,7 @@ def test_weekly_wrong_input(default_conf, update, ticker, mocker) -> None: context.args = ["this week"] telegram._weekly(update=update, context=context) assert str('Weekly Profit over the last 8 weeks (starting from Monday):') \ - in msg_mock.call_args_list[0][0][0] + in msg_mock.call_args_list[0][0][0] def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee, @@ -809,8 +815,9 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0] +@pytest.mark.parametrize('is_short', [True, False]) def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee, - limit_buy_order, limit_sell_order, mocker) -> None: + limit_buy_order, limit_sell_order, mocker, is_short) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -826,7 +833,7 @@ def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee, msg_mock.reset_mock() # Create some test data - create_mock_trades(fee) + create_mock_trades(fee, is_short=is_short) telegram._stats(update=update, context=MagicMock()) assert msg_mock.call_count == 1 @@ -900,6 +907,10 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None 'balance': i, 'est_stake': 1, 'stake': 'BTC', + 'is_position': False, + 'leverage': 1.0, + 'position': 0.0, + 'side': 'long', }) mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={ 'currencies': balances, @@ -1024,7 +1035,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee, # /forcesell 1 context = MagicMock() context.args = ["1"] - telegram._forcesell(update=update, context=context) + telegram._forceexit(update=update, context=context) assert msg_mock.call_count == 4 last_msg = msg_mock.call_args_list[-2][0][0] @@ -1034,18 +1045,21 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee, 'exchange': 'Binance', 'pair': 'ETH/BTC', 'gain': 'profit', + 'leverage': 1.0, 'limit': 1.173e-05, 'amount': 91.07468123, 'order_type': 'limit', 'open_rate': 1.098e-05, 'current_rate': 1.173e-05, + 'direction': 'Long', 'profit_amount': 6.314e-05, 'profit_ratio': 0.0629778, 'stake_currency': 'BTC', 'base_currency': 'ETH', 'fiat_currency': 'USD', 'buy_tag': ANY, - 'sell_reason': SellType.FORCE_SELL.value, + 'enter_tag': ANY, + 'sell_reason': ExitType.FORCE_SELL.value, 'open_date': ANY, 'close_date': ANY, 'close_rate': ANY, @@ -1088,7 +1102,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee, # /forcesell 1 context = MagicMock() context.args = ["1"] - telegram._forcesell(update=update, context=context) + telegram._forceexit(update=update, context=context) assert msg_mock.call_count == 4 @@ -1099,18 +1113,21 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee, 'exchange': 'Binance', 'pair': 'ETH/BTC', 'gain': 'loss', + 'leverage': 1.0, 'limit': 1.043e-05, 'amount': 91.07468123, 'order_type': 'limit', 'open_rate': 1.098e-05, 'current_rate': 1.043e-05, + 'direction': 'Long', 'profit_amount': -5.497e-05, 'profit_ratio': -0.05482878, 'stake_currency': 'BTC', 'base_currency': 'ETH', 'fiat_currency': 'USD', 'buy_tag': ANY, - 'sell_reason': SellType.FORCE_SELL.value, + 'enter_tag': ANY, + 'sell_reason': ExitType.FORCE_SELL.value, 'open_date': ANY, 'close_date': ANY, 'close_rate': ANY, @@ -1143,7 +1160,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None # /forcesell all context = MagicMock() context.args = ["all"] - telegram._forcesell(update=update, context=context) + telegram._forceexit(update=update, context=context) # Called for each trade 2 times assert msg_mock.call_count == 8 @@ -1154,18 +1171,21 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None 'exchange': 'Binance', 'pair': 'ETH/BTC', 'gain': 'loss', + 'leverage': 1.0, 'limit': 1.099e-05, 'amount': 91.07468123, 'order_type': 'limit', 'open_rate': 1.098e-05, 'current_rate': 1.099e-05, + 'direction': 'Long', 'profit_amount': -4.09e-06, 'profit_ratio': -0.00408133, 'stake_currency': 'BTC', 'base_currency': 'ETH', 'fiat_currency': 'USD', 'buy_tag': ANY, - 'sell_reason': SellType.FORCE_SELL.value, + 'enter_tag': ANY, + 'sell_reason': ExitType.FORCE_SELL.value, 'open_date': ANY, 'close_date': ANY, 'close_rate': ANY, @@ -1184,7 +1204,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: # /forcesell 1 context = MagicMock() context.args = ["1"] - telegram._forcesell(update=update, context=context) + telegram._forceexit(update=update, context=context) assert msg_mock.call_count == 1 assert 'not running' in msg_mock.call_args_list[0][0][0] @@ -1193,7 +1213,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: freqtradebot.state = State.RUNNING context = MagicMock() context.args = [] - telegram._forcesell(update=update, context=context) + telegram._forceexit(update=update, context=context) assert msg_mock.call_count == 1 assert "You must specify a trade-id or 'all'." in msg_mock.call_args_list[0][0][0] @@ -1203,36 +1223,37 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: # /forcesell 123456 context = MagicMock() context.args = ["123456"] - telegram._forcesell(update=update, context=context) + telegram._forceexit(update=update, context=context) assert msg_mock.call_count == 1 assert 'invalid argument' in msg_mock.call_args_list[0][0][0] -def test_forcebuy_handle(default_conf, update, mocker) -> None: +def test_forceenter_handle(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) fbuy_mock = MagicMock(return_value=None) - mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock) + mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock) telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) - # /forcebuy ETH/BTC + # /forcelong ETH/BTC context = MagicMock() context.args = ["ETH/BTC"] - telegram._forcebuy(update=update, context=context) + telegram._forceenter(update=update, context=context, order_side=SignalDirection.LONG) assert fbuy_mock.call_count == 1 assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC' assert fbuy_mock.call_args_list[0][0][1] is None + assert fbuy_mock.call_args_list[0][1]['order_side'] == SignalDirection.LONG # Reset and retry with specified price fbuy_mock = MagicMock(return_value=None) - mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock) - # /forcebuy ETH/BTC 0.055 + mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock) + # /forcelong ETH/BTC 0.055 context = MagicMock() context.args = ["ETH/BTC", "0.055"] - telegram._forcebuy(update=update, context=context) + telegram._forceenter(update=update, context=context, order_side=SignalDirection.LONG) assert fbuy_mock.call_count == 1 assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC' @@ -1240,24 +1261,24 @@ def test_forcebuy_handle(default_conf, update, mocker) -> None: assert fbuy_mock.call_args_list[0][0][1] == 0.055 -def test_forcebuy_handle_exception(default_conf, update, mocker) -> None: +def test_forceenter_handle_exception(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) update.message.text = '/forcebuy ETH/Nonepair' - telegram._forcebuy(update=update, context=MagicMock()) + telegram._forceenter(update=update, context=MagicMock(), order_side=SignalDirection.LONG) assert msg_mock.call_count == 1 - assert msg_mock.call_args_list[0][0][0] == 'Forcebuy not enabled.' + assert msg_mock.call_args_list[0][0][0] == 'Forceentry not enabled.' -def test_forcebuy_no_pair(default_conf, update, mocker) -> None: +def test_forceenter_no_pair(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) fbuy_mock = MagicMock(return_value=None) - mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock) + mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) @@ -1265,7 +1286,7 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None: context = MagicMock() context.args = [] - telegram._forcebuy(update=update, context=context) + telegram._forceenter(update=update, context=context, order_side=SignalDirection.LONG) assert fbuy_mock.call_count == 0 assert msg_mock.call_count == 1 @@ -1276,8 +1297,8 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None: assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5 update = MagicMock() update.callback_query = MagicMock() - update.callback_query.data = 'XRP/USDT' - telegram._forcebuy_inline(update, None) + update.callback_query.data = 'XRP/USDT_||_long' + telegram._forceenter_inline(update, None) assert fbuy_mock.call_count == 1 @@ -1327,12 +1348,12 @@ def test_telegram_buy_tag_performance_handle(default_conf, update, ticker, fee, freqtradebot.enter_positions() trade = Trade.query.first() assert trade - trade.buy_tag = "TESTBUY" # Simulate fulfilled LIMIT_BUY order for trade oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') trade.update_trade(oobj) + trade.enter_tag = "TESTBUY" # Simulate fulfilled LIMIT_SELL order for trade oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') trade.update_trade(oobj) @@ -1340,19 +1361,19 @@ def test_telegram_buy_tag_performance_handle(default_conf, update, ticker, fee, trade.close_date = datetime.utcnow() trade.is_open = False context = MagicMock() - telegram._buy_tag_performance(update=update, context=context) + telegram._enter_tag_performance(update=update, context=context) assert msg_mock.call_count == 1 assert 'Buy Tag Performance' in msg_mock.call_args_list[0][0][0] assert 'TESTBUY\t0.00006217 BTC (6.20%) (1)' in msg_mock.call_args_list[0][0][0] context.args = [trade.pair] - telegram._buy_tag_performance(update=update, context=context) + telegram._enter_tag_performance(update=update, context=context) assert msg_mock.call_count == 2 msg_mock.reset_mock() - mocker.patch('freqtrade.rpc.rpc.RPC._rpc_buy_tag_performance', + mocker.patch('freqtrade.rpc.rpc.RPC._rpc_enter_tag_performance', side_effect=RPCException('Error')) - telegram._buy_tag_performance(update=update, context=MagicMock()) + telegram._enter_tag_performance(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert "Error" in msg_mock.call_args_list[0][0][0] @@ -1416,7 +1437,8 @@ def test_telegram_mix_tag_performance_handle(default_conf, update, ticker, fee, freqtradebot.enter_positions() trade = Trade.query.first() assert trade - trade.buy_tag = "TESTBUY" + + trade.enter_tag = "TESTBUY" trade.sell_reason = "TESTSELL" # Simulate fulfilled LIMIT_BUY order for trade @@ -1642,7 +1664,10 @@ def test_edge_enabled(edge_conf, update, mocker) -> None: assert 'Winrate' not in msg_mock.call_args_list[0][0][0] -def test_telegram_trades(mocker, update, default_conf, fee): +@pytest.mark.parametrize('is_short,regex_pattern', + [(True, r"just now[ ]*XRP\/BTC \(#3\) -1.00% \("), + (False, r"just now[ ]*XRP\/BTC \(#3\) 1.00% \(")]) +def test_telegram_trades(mocker, update, default_conf, fee, is_short, regex_pattern): telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) @@ -1660,7 +1685,7 @@ def test_telegram_trades(mocker, update, default_conf, fee): assert "
" not in msg_mock.call_args_list[0][0][0]
     msg_mock.reset_mock()
 
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short=is_short)
 
     context = MagicMock()
     context.args = [5]
@@ -1670,11 +1695,11 @@ def test_telegram_trades(mocker, update, default_conf, fee):
     assert "Profit (" in msg_mock.call_args_list[0][0][0]
     assert "Close Date" in msg_mock.call_args_list[0][0][0]
     assert "
" in msg_mock.call_args_list[0][0][0]
-    assert bool(re.search(r"just now[ ]*XRP\/BTC \(#3\)  1.00% \(",
-                msg_mock.call_args_list[0][0][0]))
+    assert bool(re.search(regex_pattern, msg_mock.call_args_list[0][0][0]))
 
 
-def test_telegram_delete_trade(mocker, update, default_conf, fee):
+@pytest.mark.parametrize('is_short', [True, False])
+def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short):
 
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
     context = MagicMock()
@@ -1684,7 +1709,7 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee):
     assert "Trade-id not set." in msg_mock.call_args_list[0][0][0]
 
     msg_mock.reset_mock()
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short=is_short)
 
     context = MagicMock()
     context.args = [1]
@@ -1729,7 +1754,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
     assert msg_mock.call_count == 1
     assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
     assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
-    assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0]
+    assert f'*Strategy:* `{CURRENT_TEST_STRATEGY}`' in msg_mock.call_args_list[0][0][0]
     assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
 
     msg_mock.reset_mock()
@@ -1738,18 +1763,25 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
     assert msg_mock.call_count == 1
     assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
     assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
-    assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0]
+    assert f'*Strategy:* `{CURRENT_TEST_STRATEGY}`' in msg_mock.call_args_list[0][0][0]
     assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
 
 
-def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
+@pytest.mark.parametrize('message_type,enter,enter_signal,leverage', [
+    (RPCMessageType.BUY, 'Long', 'long_signal_01', None),
+    (RPCMessageType.BUY, 'Long', 'long_signal_01', 1.0),
+    (RPCMessageType.BUY, 'Long', 'long_signal_01', 5.0),
+    (RPCMessageType.SHORT, 'Short', 'short_signal_01', 2.0)])
+def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type,
+                                   enter, enter_signal, leverage) -> None:
 
     msg = {
-        'type': RPCMessageType.BUY,
+        'type': message_type,
         'trade_id': 1,
-        'buy_tag': 'buy_signal_01',
+        'enter_tag': enter_signal,
         'exchange': 'Binance',
         'pair': 'ETH/BTC',
+        'leverage': leverage,
         'limit': 1.099e-05,
         'order_type': 'limit',
         'stake_amount': 0.01465333,
@@ -1763,13 +1795,17 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
     telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
 
     telegram.send_msg(msg)
-    assert msg_mock.call_args[0][0] \
-        == '\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' \
-           '*Buy Tag:* `buy_signal_01`\n' \
-           '*Amount:* `1333.33333333`\n' \
-           '*Open Rate:* `0.00001099`\n' \
-           '*Current Rate:* `0.00001099`\n' \
-           '*Total:* `(0.01465333 BTC, 180.895 USD)`'
+    leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
+
+    assert msg_mock.call_args[0][0] == (
+        f'\N{LARGE BLUE CIRCLE} *Binance:* {enter} ETH/BTC (#1)\n'
+        f'*Enter Tag:* `{enter_signal}`\n'
+        '*Amount:* `1333.33333333`\n'
+        f'{leverage_text}'
+        '*Open Rate:* `0.00001099`\n'
+        '*Current Rate:* `0.00001099`\n'
+        '*Total:* `(0.01465333 BTC, 180.895 USD)`'
+    )
 
     freqtradebot.config['telegram']['notification_settings'] = {'buy': 'off'}
     caplog.clear()
@@ -1787,20 +1823,23 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
     msg_mock.call_args_list[0][1]['disable_notification'] is True
 
 
-def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
+@pytest.mark.parametrize('message_type,enter_signal', [
+    (RPCMessageType.BUY_CANCEL, 'long_signal_01'),
+    (RPCMessageType.SHORT_CANCEL, 'short_signal_01')])
+def test_send_msg_buy_cancel_notification(default_conf, mocker, message_type, enter_signal) -> None:
 
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 
     telegram.send_msg({
-        'type': RPCMessageType.BUY_CANCEL,
-        'buy_tag': 'buy_signal_01',
+        'type': message_type,
+        'enter_tag': enter_signal,
         'trade_id': 1,
         'exchange': 'Binance',
         'pair': 'ETH/BTC',
         'reason': CANCEL_REASON['TIMEOUT']
     })
     assert (msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Binance:* '
-            'Cancelling open buy Order for ETH/BTC (#1). '
+            'Cancelling enter Order for ETH/BTC (#1). '
             'Reason: cancelled due to timeout.')
 
 
@@ -1832,17 +1871,24 @@ def test_send_msg_protection_notification(default_conf, mocker, time_machine) ->
             "*All pairs* will be locked until `2021-09-01 06:45:00`.")
 
 
-def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
+@pytest.mark.parametrize('message_type,entered,enter_signal,leverage', [
+    (RPCMessageType.BUY_FILL, 'Longed', 'long_signal_01', 1.0),
+    (RPCMessageType.BUY_FILL, 'Longed', 'long_signal_02', 2.0),
+    (RPCMessageType.SHORT_FILL, 'Shorted', 'short_signal_01', 2.0),
+])
+def test_send_msg_buy_fill_notification(default_conf, mocker, message_type, entered,
+                                        enter_signal, leverage) -> None:
 
     default_conf['telegram']['notification_settings']['buy_fill'] = 'on'
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 
     telegram.send_msg({
-        'type': RPCMessageType.BUY_FILL,
+        'type': message_type,
         'trade_id': 1,
-        'buy_tag': 'buy_signal_01',
+        'enter_tag': enter_signal,
         'exchange': 'Binance',
         'pair': 'ETH/BTC',
+        'leverage': leverage,
         'stake_amount': 0.01465333,
         # 'stake_amount_fiat': 0.0,
         'stake_currency': 'BTC',
@@ -1851,13 +1897,15 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
         'amount': 1333.3333333333335,
         'open_date': arrow.utcnow().shift(hours=-1)
     })
-
-    assert msg_mock.call_args[0][0] \
-        == '\N{CHECK MARK} *Binance:* Bought ETH/BTC (#1)\n' \
-           '*Buy Tag:* `buy_signal_01`\n' \
-           '*Amount:* `1333.33333333`\n' \
-           '*Open Rate:* `0.00001099`\n' \
-           '*Total:* `(0.01465333 BTC, 180.895 USD)`'
+    leverage_text = f'*Leverage:* `{leverage}`\n' if leverage != 1.0 else ''
+    assert msg_mock.call_args[0][0] == (
+        f'\N{CHECK MARK} *Binance:* {entered} ETH/BTC (#1)\n'
+        f'*Enter Tag:* `{enter_signal}`\n'
+        '*Amount:* `1333.33333333`\n'
+        f"{leverage_text}"
+        '*Open Rate:* `0.00001099`\n'
+        '*Total:* `(0.01465333 BTC, 180.895 USD)`'
+    )
 
 
 def test_send_msg_sell_notification(default_conf, mocker) -> None:
@@ -1871,6 +1919,8 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
         'trade_id': 1,
         'exchange': 'Binance',
         'pair': 'KEY/ETH',
+        'leverage': 1.0,
+        'direction': 'Long',
         'gain': 'loss',
         'limit': 3.201e-05,
         'amount': 1333.3333333333335,
@@ -1881,22 +1931,23 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
         'profit_ratio': -0.57405275,
         'stake_currency': 'ETH',
         'fiat_currency': 'USD',
-        'buy_tag': 'buy_signal1',
-        'sell_reason': SellType.STOP_LOSS.value,
+        'enter_tag': 'buy_signal1',
+        'sell_reason': ExitType.STOP_LOSS.value,
         'open_date': arrow.utcnow().shift(hours=-1),
         'close_date': arrow.utcnow(),
     })
-    assert msg_mock.call_args[0][0] \
-        == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
-            '*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
-            '*Buy Tag:* `buy_signal1`\n'
-            '*Sell Reason:* `stop_loss`\n'
-            '*Duration:* `1:00:00 (60.0 min)`\n'
-            '*Amount:* `1333.33333333`\n'
-            '*Open Rate:* `0.00007500`\n'
-            '*Current Rate:* `0.00003201`\n'
-            '*Close Rate:* `0.00003201`'
-            )
+    assert msg_mock.call_args[0][0] == (
+        '\N{WARNING SIGN} *Binance:* Exiting KEY/ETH (#1)\n'
+        '*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
+        '*Enter Tag:* `buy_signal1`\n'
+        '*Exit Reason:* `stop_loss`\n'
+        '*Duration:* `1:00:00 (60.0 min)`\n'
+        '*Direction:* `Long`\n'
+        '*Amount:* `1333.33333333`\n'
+        '*Open Rate:* `0.00007500`\n'
+        '*Current Rate:* `0.00003201`\n'
+        '*Close Rate:* `0.00003201`'
+    )
 
     msg_mock.reset_mock()
     telegram.send_msg({
@@ -1904,6 +1955,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
         'trade_id': 1,
         'exchange': 'Binance',
         'pair': 'KEY/ETH',
+        'direction': 'Long',
         'gain': 'loss',
         'limit': 3.201e-05,
         'amount': 1333.3333333333335,
@@ -1913,22 +1965,23 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
         'profit_amount': -0.05746268,
         'profit_ratio': -0.57405275,
         'stake_currency': 'ETH',
-        'buy_tag': 'buy_signal1',
-        'sell_reason': SellType.STOP_LOSS.value,
+        'enter_tag': 'buy_signal1',
+        'sell_reason': ExitType.STOP_LOSS.value,
         'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
         'close_date': arrow.utcnow(),
     })
-    assert msg_mock.call_args[0][0] \
-        == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
-            '*Unrealized Profit:* `-57.41%`\n'
-            '*Buy Tag:* `buy_signal1`\n'
-            '*Sell Reason:* `stop_loss`\n'
-            '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
-            '*Amount:* `1333.33333333`\n'
-            '*Open Rate:* `0.00007500`\n'
-            '*Current Rate:* `0.00003201`\n'
-            '*Close Rate:* `0.00003201`'
-            )
+    assert msg_mock.call_args[0][0] == (
+        '\N{WARNING SIGN} *Binance:* Exiting KEY/ETH (#1)\n'
+        '*Unrealized Profit:* `-57.41%`\n'
+        '*Enter Tag:* `buy_signal1`\n'
+        '*Exit Reason:* `stop_loss`\n'
+        '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
+        '*Direction:* `Long`\n'
+        '*Amount:* `1333.33333333`\n'
+        '*Open Rate:* `0.00007500`\n'
+        '*Current Rate:* `0.00003201`\n'
+        '*Close Rate:* `0.00003201`'
+    )
     # Reset singleton function to avoid random breaks
     telegram._rpc._fiat_converter.convert_amount = old_convamount
 
@@ -1946,9 +1999,9 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
         'pair': 'KEY/ETH',
         'reason': 'Cancelled on exchange'
     })
-    assert msg_mock.call_args[0][0] \
-        == ('\N{WARNING SIGN} *Binance:* Cancelling open sell Order for KEY/ETH (#1).'
-            ' Reason: Cancelled on exchange.')
+    assert msg_mock.call_args[0][0] == (
+        '\N{WARNING SIGN} *Binance:* Cancelling exit Order for KEY/ETH (#1).'
+        ' Reason: Cancelled on exchange.')
 
     msg_mock.reset_mock()
     telegram.send_msg({
@@ -1958,14 +2011,19 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
         'pair': 'KEY/ETH',
         'reason': 'timeout'
     })
-    assert msg_mock.call_args[0][0] \
-        == ('\N{WARNING SIGN} *Binance:* Cancelling open sell Order for KEY/ETH (#1).'
-            ' Reason: timeout.')
+    assert msg_mock.call_args[0][0] == (
+        '\N{WARNING SIGN} *Binance:* Cancelling exit Order for KEY/ETH (#1). Reason: timeout.')
     # Reset singleton function to avoid random breaks
     telegram._rpc._fiat_converter.convert_amount = old_convamount
 
 
-def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
+@pytest.mark.parametrize('direction,enter_signal,leverage', [
+    ('Long', 'long_signal_01', None),
+    ('Long', 'long_signal_01', 1.0),
+    ('Long', 'long_signal_01', 5.0),
+    ('Short', 'short_signal_01', 2.0)])
+def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
+                                         enter_signal, leverage) -> None:
 
     default_conf['telegram']['notification_settings']['sell_fill'] = 'on'
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -1975,6 +2033,8 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
         'trade_id': 1,
         'exchange': 'Binance',
         'pair': 'KEY/ETH',
+        'leverage': leverage,
+        'direction': direction,
         'gain': 'loss',
         'limit': 3.201e-05,
         'amount': 1333.3333333333335,
@@ -1984,21 +2044,25 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
         'profit_amount': -0.05746268,
         'profit_ratio': -0.57405275,
         'stake_currency': 'ETH',
-        'buy_tag': 'buy_signal1',
-        'sell_reason': SellType.STOP_LOSS.value,
+        'enter_tag': enter_signal,
+        'sell_reason': ExitType.STOP_LOSS.value,
         'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
         'close_date': arrow.utcnow(),
     })
-    assert msg_mock.call_args[0][0] \
-        == ('\N{WARNING SIGN} *Binance:* Sold KEY/ETH (#1)\n'
-            '*Profit:* `-57.41%`\n'
-            '*Buy Tag:* `buy_signal1`\n'
-            '*Sell Reason:* `stop_loss`\n'
-            '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
-            '*Amount:* `1333.33333333`\n'
-            '*Open Rate:* `0.00007500`\n'
-            '*Close Rate:* `0.00003201`'
-            )
+
+    leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
+    assert msg_mock.call_args[0][0] == (
+        '\N{WARNING SIGN} *Binance:* Exited KEY/ETH (#1)\n'
+        '*Profit:* `-57.41%`\n'
+        f'*Enter Tag:* `{enter_signal}`\n'
+        '*Exit Reason:* `stop_loss`\n'
+        '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
+        f"*Direction:* `{direction}`\n"
+        f"{leverage_text}"
+        '*Amount:* `1333.33333333`\n'
+        '*Open Rate:* `0.00007500`\n'
+        '*Close Rate:* `0.00003201`'
+    )
 
 
 def test_send_msg_status_notification(default_conf, mocker) -> None:
@@ -2037,16 +2101,22 @@ def test_send_msg_unknown_type(default_conf, mocker) -> None:
         })
 
 
-def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
+@pytest.mark.parametrize('message_type,enter,enter_signal,leverage', [
+    (RPCMessageType.BUY, 'Long', 'long_signal_01', None),
+    (RPCMessageType.BUY, 'Long', 'long_signal_01', 2.0),
+    (RPCMessageType.SHORT, 'Short', 'short_signal_01', 2.0)])
+def test_send_msg_buy_notification_no_fiat(
+        default_conf, mocker, message_type, enter, enter_signal, leverage) -> None:
     del default_conf['fiat_display_currency']
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 
     telegram.send_msg({
-        'type': RPCMessageType.BUY,
-        'buy_tag': 'buy_signal_01',
+        'type': message_type,
+        'enter_tag': enter_signal,
         'trade_id': 1,
         'exchange': 'Binance',
         'pair': 'ETH/BTC',
+        'leverage': leverage,
         'limit': 1.099e-05,
         'order_type': 'limit',
         'stake_amount': 0.01465333,
@@ -2057,15 +2127,27 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
         'amount': 1333.3333333333335,
         'open_date': arrow.utcnow().shift(hours=-1)
     })
-    assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n'
-                                        '*Buy Tag:* `buy_signal_01`\n'
-                                        '*Amount:* `1333.33333333`\n'
-                                        '*Open Rate:* `0.00001099`\n'
-                                        '*Current Rate:* `0.00001099`\n'
-                                        '*Total:* `(0.01465333 BTC)`')
+
+    leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
+    assert msg_mock.call_args[0][0] == (
+        f'\N{LARGE BLUE CIRCLE} *Binance:* {enter} ETH/BTC (#1)\n'
+        f'*Enter Tag:* `{enter_signal}`\n'
+        '*Amount:* `1333.33333333`\n'
+        f'{leverage_text}'
+        '*Open Rate:* `0.00001099`\n'
+        '*Current Rate:* `0.00001099`\n'
+        '*Total:* `(0.01465333 BTC)`'
+    )
 
 
-def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
+@pytest.mark.parametrize('direction,enter_signal,leverage', [
+    ('Long', 'long_signal_01', None),
+    ('Long', 'long_signal_01', 1.0),
+    ('Long', 'long_signal_01', 5.0),
+    ('Short', 'short_signal_01', 2.0),
+])
+def test_send_msg_sell_notification_no_fiat(
+        default_conf, mocker, direction, enter_signal, leverage) -> None:
     del default_conf['fiat_display_currency']
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 
@@ -2075,6 +2157,8 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
         'exchange': 'Binance',
         'pair': 'KEY/ETH',
         'gain': 'loss',
+        'leverage': leverage,
+        'direction': direction,
         'limit': 3.201e-05,
         'amount': 1333.3333333333335,
         'order_type': 'limit',
@@ -2084,21 +2168,26 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
         'profit_ratio': -0.57405275,
         'stake_currency': 'ETH',
         'fiat_currency': 'USD',
-        'buy_tag': 'buy_signal1',
-        'sell_reason': SellType.STOP_LOSS.value,
+        'enter_tag': enter_signal,
+        'sell_reason': ExitType.STOP_LOSS.value,
         'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3),
         'close_date': arrow.utcnow(),
     })
-    assert msg_mock.call_args[0][0] == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
-                                        '*Unrealized Profit:* `-57.41%`\n'
-                                        '*Buy Tag:* `buy_signal1`\n'
-                                        '*Sell Reason:* `stop_loss`\n'
-                                        '*Duration:* `2:35:03 (155.1 min)`\n'
-                                        '*Amount:* `1333.33333333`\n'
-                                        '*Open Rate:* `0.00007500`\n'
-                                        '*Current Rate:* `0.00003201`\n'
-                                        '*Close Rate:* `0.00003201`'
-                                        )
+
+    leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
+    assert msg_mock.call_args[0][0] == (
+        '\N{WARNING SIGN} *Binance:* Exiting KEY/ETH (#1)\n'
+        '*Unrealized Profit:* `-57.41%`\n'
+        f'*Enter Tag:* `{enter_signal}`\n'
+        '*Exit Reason:* `stop_loss`\n'
+        '*Duration:* `2:35:03 (155.1 min)`\n'
+        f'*Direction:* `{direction}`\n'
+        f'{leverage_text}'
+        '*Amount:* `1333.33333333`\n'
+        '*Open Rate:* `0.00007500`\n'
+        '*Current Rate:* `0.00003201`\n'
+        '*Close Rate:* `0.00003201`'
+    )
 
 
 @pytest.mark.parametrize('msg,expected', [
diff --git a/tests/rpc/test_rpc_webhook.py b/tests/rpc/test_rpc_webhook.py
index 17d1baca9..1d80b58e5 100644
--- a/tests/rpc/test_rpc_webhook.py
+++ b/tests/rpc/test_rpc_webhook.py
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock
 import pytest
 from requests import RequestException
 
-from freqtrade.enums import RPCMessageType, SellType
+from freqtrade.enums import ExitType, RPCMessageType
 from freqtrade.rpc import RPC
 from freqtrade.rpc.webhook import Webhook
 from tests.conftest import get_patched_freqtradebot, log_has
@@ -18,17 +18,23 @@ def get_webhook_dict() -> dict:
         "webhookbuy": {
             "value1": "Buying {pair}",
             "value2": "limit {limit:8f}",
-            "value3": "{stake_amount:8f} {stake_currency}"
+            "value3": "{stake_amount:8f} {stake_currency}",
+            "value4": "leverage {leverage:.1f}",
+            "value5": "direction {direction}"
         },
         "webhookbuycancel": {
             "value1": "Cancelling Open Buy Order for {pair}",
             "value2": "limit {limit:8f}",
-            "value3": "{stake_amount:8f} {stake_currency}"
+            "value3": "{stake_amount:8f} {stake_currency}",
+            "value4": "leverage {leverage:.1f}",
+            "value5": "direction {direction}"
         },
         "webhookbuyfill": {
             "value1": "Buy Order for {pair} filled",
             "value2": "at {open_rate:8f}",
-            "value3": "{stake_amount:8f} {stake_currency}"
+            "value3": "{stake_amount:8f} {stake_currency}",
+            "value4": "leverage {leverage:.1f}",
+            "value5": "direction {direction}"
         },
         "webhooksell": {
             "value1": "Selling {pair}",
@@ -71,6 +77,8 @@ def test_send_msg_webhook(default_conf, mocker):
         'type': RPCMessageType.BUY,
         'exchange': 'Binance',
         'pair': 'ETH/BTC',
+        'leverage': 1.0,
+        'direction': 'Long',
         'limit': 0.005,
         'stake_amount': 0.8,
         'stake_amount_fiat': 500,
@@ -85,6 +93,37 @@ def test_send_msg_webhook(default_conf, mocker):
             default_conf["webhook"]["webhookbuy"]["value2"].format(**msg))
     assert (msg_mock.call_args[0][0]["value3"] ==
             default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value4"] ==
+            default_conf["webhook"]["webhookbuy"]["value4"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value5"] ==
+            default_conf["webhook"]["webhookbuy"]["value5"].format(**msg))
+    # Test short
+    msg_mock.reset_mock()
+
+    msg = {
+        'type': RPCMessageType.SHORT,
+        'exchange': 'Binance',
+        'pair': 'ETH/BTC',
+        'leverage': 2.0,
+        'direction': 'Short',
+        'limit': 0.005,
+        'stake_amount': 0.8,
+        'stake_amount_fiat': 500,
+        'stake_currency': 'BTC',
+        'fiat_currency': 'EUR'
+    }
+    webhook.send_msg(msg=msg)
+    assert msg_mock.call_count == 1
+    assert (msg_mock.call_args[0][0]["value1"] ==
+            default_conf["webhook"]["webhookbuy"]["value1"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value2"] ==
+            default_conf["webhook"]["webhookbuy"]["value2"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value3"] ==
+            default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value4"] ==
+            default_conf["webhook"]["webhookbuy"]["value4"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value5"] ==
+            default_conf["webhook"]["webhookbuy"]["value5"].format(**msg))
     # Test buy cancel
     msg_mock.reset_mock()
 
@@ -92,6 +131,8 @@ def test_send_msg_webhook(default_conf, mocker):
         'type': RPCMessageType.BUY_CANCEL,
         'exchange': 'Binance',
         'pair': 'ETH/BTC',
+        'leverage': 1.0,
+        'direction': 'Long',
         'limit': 0.005,
         'stake_amount': 0.8,
         'stake_amount_fiat': 500,
@@ -106,6 +147,33 @@ def test_send_msg_webhook(default_conf, mocker):
             default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg))
     assert (msg_mock.call_args[0][0]["value3"] ==
             default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg))
+    # Test short cancel
+    msg_mock.reset_mock()
+
+    msg = {
+        'type': RPCMessageType.SHORT_CANCEL,
+        'exchange': 'Binance',
+        'pair': 'ETH/BTC',
+        'leverage': 2.0,
+        'direction': 'Short',
+        'limit': 0.005,
+        'stake_amount': 0.8,
+        'stake_amount_fiat': 500,
+        'stake_currency': 'BTC',
+        'fiat_currency': 'EUR'
+    }
+    webhook.send_msg(msg=msg)
+    assert msg_mock.call_count == 1
+    assert (msg_mock.call_args[0][0]["value1"] ==
+            default_conf["webhook"]["webhookbuycancel"]["value1"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value2"] ==
+            default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value3"] ==
+            default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value4"] ==
+            default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value5"] ==
+            default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
     # Test buy fill
     msg_mock.reset_mock()
 
@@ -113,6 +181,8 @@ def test_send_msg_webhook(default_conf, mocker):
         'type': RPCMessageType.BUY_FILL,
         'exchange': 'Binance',
         'pair': 'ETH/BTC',
+        'leverage': 1.0,
+        'direction': 'Long',
         'open_rate': 0.005,
         'stake_amount': 0.8,
         'stake_amount_fiat': 500,
@@ -127,8 +197,40 @@ def test_send_msg_webhook(default_conf, mocker):
             default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg))
     assert (msg_mock.call_args[0][0]["value3"] ==
             default_conf["webhook"]["webhookbuyfill"]["value3"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value4"] ==
+            default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value5"] ==
+            default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
+    # Test short fill
+    msg_mock.reset_mock()
+
+    msg = {
+        'type': RPCMessageType.SHORT_FILL,
+        'exchange': 'Binance',
+        'pair': 'ETH/BTC',
+        'leverage': 2.0,
+        'direction': 'Short',
+        'open_rate': 0.005,
+        'stake_amount': 0.8,
+        'stake_amount_fiat': 500,
+        'stake_currency': 'BTC',
+        'fiat_currency': 'EUR'
+    }
+    webhook.send_msg(msg=msg)
+    assert msg_mock.call_count == 1
+    assert (msg_mock.call_args[0][0]["value1"] ==
+            default_conf["webhook"]["webhookbuyfill"]["value1"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value2"] ==
+            default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value3"] ==
+            default_conf["webhook"]["webhookbuyfill"]["value3"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value4"] ==
+            default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
+    assert (msg_mock.call_args[0][0]["value5"] ==
+            default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
     # Test sell
     msg_mock.reset_mock()
+
     msg = {
         'type': RPCMessageType.SELL,
         'exchange': 'Binance',
@@ -142,7 +244,7 @@ def test_send_msg_webhook(default_conf, mocker):
         'profit_amount': 0.001,
         'profit_ratio': 0.20,
         'stake_currency': 'BTC',
-        'sell_reason': SellType.STOP_LOSS.value
+        'sell_reason': ExitType.STOP_LOSS.value
     }
     webhook.send_msg(msg=msg)
     assert msg_mock.call_count == 1
@@ -167,7 +269,7 @@ def test_send_msg_webhook(default_conf, mocker):
         'profit_amount': 0.001,
         'profit_ratio': 0.20,
         'stake_currency': 'BTC',
-        'sell_reason': SellType.STOP_LOSS.value
+        'sell_reason': ExitType.STOP_LOSS.value
     }
     webhook.send_msg(msg=msg)
     assert msg_mock.call_count == 1
@@ -192,7 +294,7 @@ def test_send_msg_webhook(default_conf, mocker):
         'profit_amount': 0.001,
         'profit_ratio': 0.20,
         'stake_currency': 'BTC',
-        'sell_reason': SellType.STOP_LOSS.value
+        'sell_reason': ExitType.STOP_LOSS.value
     }
     webhook.send_msg(msg=msg)
     assert msg_mock.call_count == 1
diff --git a/tests/strategy/strats/broken_strats/broken_futures_strategies.py b/tests/strategy/strats/broken_strats/broken_futures_strategies.py
new file mode 100644
index 000000000..7e6955d37
--- /dev/null
+++ b/tests/strategy/strats/broken_strats/broken_futures_strategies.py
@@ -0,0 +1,49 @@
+"""
+The strategies here are minimal strategies designed to fail loading in certain conditions.
+They are not operational, and don't aim to be.
+"""
+
+from datetime import datetime
+
+from pandas import DataFrame
+
+from freqtrade.strategy.interface import IStrategy
+
+
+class TestStrategyNoImplements(IStrategy):
+
+    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
+        return super().populate_indicators(dataframe, metadata)
+
+
+class TestStrategyNoImplementSell(TestStrategyNoImplements):
+    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
+        return super().populate_entry_trend(dataframe, metadata)
+
+
+class TestStrategyImplementCustomSell(TestStrategyNoImplementSell):
+    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
+        return super().populate_exit_trend(dataframe, metadata)
+
+    def custom_sell(self, pair: str, trade, current_time: datetime,
+                    current_rate: float, current_profit: float,
+                    **kwargs):
+        return False
+
+
+class TestStrategyImplementBuyTimeout(TestStrategyNoImplementSell):
+    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
+        return super().populate_exit_trend(dataframe, metadata)
+
+    def check_buy_timeout(self, pair: str, trade, order: dict,
+                          current_time: datetime, **kwargs) -> bool:
+        return False
+
+
+class TestStrategyImplementSellTimeout(TestStrategyNoImplementSell):
+    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
+        return super().populate_exit_trend(dataframe, metadata)
+
+    def check_sell_timeout(self, pair: str, trade, order: dict,
+                           current_time: datetime, **kwargs) -> bool:
+        return False
diff --git a/tests/strategy/strats/hyperoptable_strategy.py b/tests/strategy/strats/hyperoptable_strategy.py
index dc6b03a3e..f4dcf1a05 100644
--- a/tests/strategy/strats/hyperoptable_strategy.py
+++ b/tests/strategy/strats/hyperoptable_strategy.py
@@ -85,7 +85,7 @@ class HyperoptableStrategy(StrategyTestV2):
         Based on TA indicators, populates the sell signal for the given dataframe
         :param dataframe: DataFrame
         :param metadata: Additional information, like the currently traded pair
-        :return: DataFrame with buy column
+        :return: DataFrame with sell column
         """
         dataframe.loc[
             (
diff --git a/tests/strategy/strats/informative_decorator_strategy.py b/tests/strategy/strats/informative_decorator_strategy.py
index 17d4df018..8c1466de9 100644
--- a/tests/strategy/strats/informative_decorator_strategy.py
+++ b/tests/strategy/strats/informative_decorator_strategy.py
@@ -2,8 +2,7 @@
 
 from pandas import DataFrame
 
-from freqtrade.strategy import informative, merge_informative_pair
-from freqtrade.strategy.interface import IStrategy
+from freqtrade.strategy import IStrategy, informative, merge_informative_pair
 
 
 class InformativeDecoratorTest(IStrategy):
@@ -20,7 +19,12 @@ class InformativeDecoratorTest(IStrategy):
     startup_candle_count: int = 20
 
     def informative_pairs(self):
-        return [('NEO/USDT', '5m')]
+        # Intentionally return 2 tuples, must be converted to 3 in compatibility code
+        return [
+            ('NEO/USDT', '5m'),
+            ('NEO/USDT', '15m', ''),
+            ('NEO/USDT', '2h', 'futures'),
+            ]
 
     def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
         dataframe['buy'] = 0
@@ -44,7 +48,7 @@ class InformativeDecoratorTest(IStrategy):
         return dataframe
 
     # Quote currency different from stake currency test.
-    @informative('1h', 'ETH/BTC')
+    @informative('1h', 'ETH/BTC', candle_type='spot')
     def populate_indicators_eth_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
         dataframe['rsi'] = 14
         return dataframe
@@ -68,7 +72,7 @@ class InformativeDecoratorTest(IStrategy):
         dataframe['rsi_less'] = dataframe['rsi'] < dataframe['rsi_1h']
 
         # Mixing manual informative pairs with decorators.
-        informative = self.dp.get_pair_dataframe('NEO/USDT', '5m')
+        informative = self.dp.get_pair_dataframe('NEO/USDT', '5m', '')
         informative['rsi'] = 14
         dataframe = merge_informative_pair(dataframe, informative, self.timeframe, '5m', ffill=True)
 
diff --git a/tests/strategy/strats/legacy_strategy_v1.py b/tests/strategy/strats/legacy_strategy_v1.py
index d7ed2014b..bad2aa40d 100644
--- a/tests/strategy/strats/legacy_strategy_v1.py
+++ b/tests/strategy/strats/legacy_strategy_v1.py
@@ -4,7 +4,7 @@
 import talib.abstract as ta
 from pandas import DataFrame
 
-from freqtrade.strategy.interface import IStrategy
+from freqtrade.strategy import IStrategy
 
 
 # --------------------------------
diff --git a/tests/strategy/strats/strategy_test_v2.py b/tests/strategy/strats/strategy_test_v2.py
index 59f1f569e..a9ca7d9e2 100644
--- a/tests/strategy/strats/strategy_test_v2.py
+++ b/tests/strategy/strats/strategy_test_v2.py
@@ -36,8 +36,8 @@ class StrategyTestV2(IStrategy):
 
     # Optional order type mapping
     order_types = {
-        'buy': 'limit',
-        'sell': 'limit',
+        'entry': 'limit',
+        'exit': 'limit',
         'stoploss': 'limit',
         'stoploss_on_exchange': False
     }
@@ -47,8 +47,8 @@ class StrategyTestV2(IStrategy):
 
     # Optional time in force for orders
     order_time_in_force = {
-        'buy': 'gtc',
-        'sell': 'gtc',
+        'entry': 'gtc',
+        'exit': 'gtc',
     }
 
     # By default this strategy does not use Position Adjustments
diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py
new file mode 100644
index 000000000..168545bbb
--- /dev/null
+++ b/tests/strategy/strats/strategy_test_v3.py
@@ -0,0 +1,193 @@
+# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
+
+from datetime import datetime
+
+import talib.abstract as ta
+from pandas import DataFrame
+
+import freqtrade.vendor.qtpylib.indicators as qtpylib
+from freqtrade.persistence import Trade
+from freqtrade.strategy import (BooleanParameter, DecimalParameter, IntParameter, IStrategy,
+                                RealParameter)
+
+
+class StrategyTestV3(IStrategy):
+    """
+    Strategy used by tests freqtrade bot.
+    Please do not modify this strategy, it's  intended for internal use only.
+    Please look at the SampleStrategy in the user_data/strategy directory
+    or strategy repository https://github.com/freqtrade/freqtrade-strategies
+    for samples and inspiration.
+    """
+    INTERFACE_VERSION = 3
+
+    # Minimal ROI designed for the strategy
+    minimal_roi = {
+        "40": 0.0,
+        "30": 0.01,
+        "20": 0.02,
+        "0": 0.04
+    }
+
+    # Optimal stoploss designed for the strategy
+    stoploss = -0.10
+
+    # Optimal timeframe for the strategy
+    timeframe = '5m'
+
+    # Optional order type mapping
+    order_types = {
+        'entry': 'limit',
+        'exit': 'limit',
+        'stoploss': 'limit',
+        'stoploss_on_exchange': False
+    }
+
+    # Number of candles the strategy requires before producing valid signals
+    startup_candle_count: int = 20
+
+    # Optional time in force for orders
+    order_time_in_force = {
+        'entry': 'gtc',
+        'exit': 'gtc',
+    }
+
+    buy_params = {
+        'buy_rsi': 35,
+        # Intentionally not specified, so "default" is tested
+        # 'buy_plusdi': 0.4
+    }
+
+    sell_params = {
+        'sell_rsi': 74,
+        'sell_minusdi': 0.4
+    }
+
+    buy_rsi = IntParameter([0, 50], default=30, space='buy')
+    buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy')
+    sell_rsi = IntParameter(low=50, high=100, default=70, space='sell')
+    sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell',
+                                    load=False)
+    protection_enabled = BooleanParameter(default=True)
+    protection_cooldown_lookback = IntParameter([0, 50], default=30)
+
+    # TODO: Can this work with protection tests? (replace HyperoptableStrategy implicitly ... )
+    # @property
+    # def protections(self):
+    #     prot = []
+    #     if self.protection_enabled.value:
+    #         prot.append({
+    #             "method": "CooldownPeriod",
+    #             "stop_duration_candles": self.protection_cooldown_lookback.value
+    #         })
+    #     return prot
+
+    def informative_pairs(self):
+
+        return []
+
+    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
+
+        # Momentum Indicator
+        # ------------------------------------
+
+        # ADX
+        dataframe['adx'] = ta.ADX(dataframe)
+
+        # MACD
+        macd = ta.MACD(dataframe)
+        dataframe['macd'] = macd['macd']
+        dataframe['macdsignal'] = macd['macdsignal']
+        dataframe['macdhist'] = macd['macdhist']
+
+        # Minus Directional Indicator / Movement
+        dataframe['minus_di'] = ta.MINUS_DI(dataframe)
+
+        # Plus Directional Indicator / Movement
+        dataframe['plus_di'] = ta.PLUS_DI(dataframe)
+
+        # RSI
+        dataframe['rsi'] = ta.RSI(dataframe)
+
+        # Stoch fast
+        stoch_fast = ta.STOCHF(dataframe)
+        dataframe['fastd'] = stoch_fast['fastd']
+        dataframe['fastk'] = stoch_fast['fastk']
+
+        # Bollinger bands
+        bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
+        dataframe['bb_lowerband'] = bollinger['lower']
+        dataframe['bb_middleband'] = bollinger['mid']
+        dataframe['bb_upperband'] = bollinger['upper']
+
+        # EMA - Exponential Moving Average
+        dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
+
+        return dataframe
+
+    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
+
+        dataframe.loc[
+            (
+                (dataframe['rsi'] < self.buy_rsi.value) &
+                (dataframe['fastd'] < 35) &
+                (dataframe['adx'] > 30) &
+                (dataframe['plus_di'] > self.buy_plusdi.value)
+            ) |
+            (
+                (dataframe['adx'] > 65) &
+                (dataframe['plus_di'] > self.buy_plusdi.value)
+            ),
+            'enter_long'] = 1
+        dataframe.loc[
+            (
+                qtpylib.crossed_below(dataframe['rsi'], self.sell_rsi.value)
+            ),
+            'enter_short'] = 1
+
+        return dataframe
+
+    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
+        dataframe.loc[
+            (
+                (
+                    (qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) |
+                    (qtpylib.crossed_above(dataframe['fastd'], 70))
+                ) &
+                (dataframe['adx'] > 10) &
+                (dataframe['minus_di'] > 0)
+            ) |
+            (
+                (dataframe['adx'] > 70) &
+                (dataframe['minus_di'] > self.sell_minusdi.value)
+            ),
+            'exit_long'] = 1
+
+        dataframe.loc[
+            (
+                qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)
+            ),
+            'exit_short'] = 1
+
+        return dataframe
+
+    def leverage(self, pair: str, current_time: datetime, current_rate: float,
+                 proposed_leverage: float, max_leverage: float, side: str,
+                 **kwargs) -> float:
+        # Return 3.0 in all cases.
+        # Bot-logic must make sure it's an allowed leverage and eventually adjust accordingly.
+
+        return 3.0
+
+    def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float,
+                              current_profit: float, min_stake: float, max_stake: float, **kwargs):
+
+        if current_profit < -0.0075:
+            orders = trade.select_filled_orders(trade.enter_side)
+            return round(orders[0].cost, 0)
+
+        return None
+
+
+class StrategyTestV3Futures(StrategyTestV3):
+    can_short = True
diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py
index a2d7df720..5cb8fce16 100644
--- a/tests/strategy/test_default_strategy.py
+++ b/tests/strategy/test_default_strategy.py
@@ -1,23 +1,28 @@
 from datetime import datetime
 
+import pytest
 from pandas import DataFrame
 
 from freqtrade.persistence.models import Trade
 
-from .strats.strategy_test_v2 import StrategyTestV2
+from .strats.strategy_test_v3 import StrategyTestV3
 
 
-def test_strategy_test_v2_structure():
-    assert hasattr(StrategyTestV2, 'minimal_roi')
-    assert hasattr(StrategyTestV2, 'stoploss')
-    assert hasattr(StrategyTestV2, 'timeframe')
-    assert hasattr(StrategyTestV2, 'populate_indicators')
-    assert hasattr(StrategyTestV2, 'populate_buy_trend')
-    assert hasattr(StrategyTestV2, 'populate_sell_trend')
+def test_strategy_test_v3_structure():
+    assert hasattr(StrategyTestV3, 'minimal_roi')
+    assert hasattr(StrategyTestV3, 'stoploss')
+    assert hasattr(StrategyTestV3, 'timeframe')
+    assert hasattr(StrategyTestV3, 'populate_indicators')
+    assert hasattr(StrategyTestV3, 'populate_entry_trend')
+    assert hasattr(StrategyTestV3, 'populate_exit_trend')
 
 
-def test_strategy_test_v2(result, fee):
-    strategy = StrategyTestV2({})
+@pytest.mark.parametrize('is_short,side', [
+    (True, 'short'),
+    (False, 'long'),
+])
+def test_strategy_test_v3(result, fee, is_short, side):
+    strategy = StrategyTestV3({})
 
     metadata = {'pair': 'ETH/BTC'}
     assert type(strategy.minimal_roi) is dict
@@ -32,15 +37,19 @@ def test_strategy_test_v2(result, fee):
         open_rate=19_000,
         amount=0.1,
         pair='ETH/BTC',
-        fee_open=fee.return_value
+        fee_open=fee.return_value,
+        is_short=is_short
     )
 
     assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1,
                                         rate=20000, time_in_force='gtc',
-                                        current_time=datetime.utcnow(), entry_tag=None) is True
+                                        current_time=datetime.utcnow(),
+                                        side=side, entry_tag=None) is True
     assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1,
-                                       rate=20000, time_in_force='gtc', sell_reason='roi',
-                                       current_time=datetime.utcnow()) is True
+                                       rate=20000, time_in_force='gtc', exit_reason='roi',
+                                       sell_reason='roi',
+                                       current_time=datetime.utcnow(),
+                                       side=side) is True
 
     assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(),
                                     current_rate=20_000, current_profit=0.05) == strategy.stoploss
diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py
index 174ce95c6..a5325a680 100644
--- a/tests/strategy/test_interface.py
+++ b/tests/strategy/test_interface.py
@@ -11,22 +11,21 @@ from pandas import DataFrame
 from freqtrade.configuration import TimeRange
 from freqtrade.data.dataprovider import DataProvider
 from freqtrade.data.history import load_data
-from freqtrade.enums import SellType
+from freqtrade.enums import ExitCheckTuple, ExitType, SignalDirection
 from freqtrade.exceptions import OperationalException, StrategyError
 from freqtrade.optimize.space import SKDecimal
 from freqtrade.persistence import PairLocks, Trade
 from freqtrade.resolvers import StrategyResolver
 from freqtrade.strategy.hyper import (BaseParameter, BooleanParameter, CategoricalParameter,
                                       DecimalParameter, IntParameter, RealParameter)
-from freqtrade.strategy.interface import SellCheckTuple
 from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
-from tests.conftest import log_has, log_has_re
+from tests.conftest import CURRENT_TEST_STRATEGY, TRADE_SIDES, log_has, log_has_re
 
-from .strats.strategy_test_v2 import StrategyTestV2
+from .strats.strategy_test_v3 import StrategyTestV3
 
 
 # Avoid to reinit the same object again and again
-_STRATEGY = StrategyTestV2(config={})
+_STRATEGY = StrategyTestV3(config={})
 _STRATEGY.dp = DataProvider({}, None, None)
 
 
@@ -34,47 +33,72 @@ def test_returns_latest_signal(ohlcv_history):
     ohlcv_history.loc[1, 'date'] = arrow.utcnow()
     # Take a copy to correctly modify the call
     mocked_history = ohlcv_history.copy()
-    mocked_history['sell'] = 0
-    mocked_history['buy'] = 0
+    mocked_history['enter_long'] = 0
+    mocked_history['exit_long'] = 0
+    mocked_history['enter_short'] = 0
+    mocked_history['exit_short'] = 0
     # Set tags in lines that don't matter to test nan in the sell line
-    mocked_history.loc[0, 'buy_tag'] = 'wrong_line'
+    mocked_history.loc[0, 'enter_tag'] = 'wrong_line'
     mocked_history.loc[0, 'exit_tag'] = 'wrong_line'
+    mocked_history.loc[1, 'exit_long'] = 1
 
-    mocked_history.loc[1, 'sell'] = 1
+    assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history) == (None, None)
+    assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (False, True, None)
+    assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False, None)
+    mocked_history.loc[1, 'exit_long'] = 0
+    mocked_history.loc[1, 'enter_long'] = 1
 
-    assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, None, None)
-    mocked_history.loc[1, 'sell'] = 0
-    mocked_history.loc[1, 'buy'] = 1
+    assert _STRATEGY.get_entry_signal(
+        'ETH/BTC', '5m', mocked_history) == (SignalDirection.LONG, None)
+    assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (True, False, None)
+    assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False, None)
+    mocked_history.loc[1, 'exit_long'] = 0
+    mocked_history.loc[1, 'enter_long'] = 0
 
-    assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, None, None)
-    mocked_history.loc[1, 'sell'] = 0
-    mocked_history.loc[1, 'buy'] = 0
+    assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history) == (None, None)
+    assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (False, False, None)
+    assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False, None)
+    mocked_history.loc[1, 'exit_long'] = 0
+    mocked_history.loc[1, 'enter_long'] = 1
+    mocked_history.loc[1, 'enter_tag'] = 'buy_signal_01'
 
-    assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, None, None)
-    mocked_history.loc[1, 'sell'] = 0
-    mocked_history.loc[1, 'buy'] = 1
-    mocked_history.loc[1, 'buy_tag'] = 'buy_signal_01'
+    assert _STRATEGY.get_entry_signal(
+        'ETH/BTC', '5m', mocked_history) == (SignalDirection.LONG, 'buy_signal_01')
+    assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (True, False, None)
+    assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False, None)
 
-    assert _STRATEGY.get_signal(
-        'ETH/BTC',
-        '5m',
-        mocked_history) == (
-        True,
-        False,
-        'buy_signal_01',
-        None)
+    mocked_history.loc[1, 'exit_long'] = 0
+    mocked_history.loc[1, 'enter_long'] = 0
+    mocked_history.loc[1, 'enter_short'] = 1
+    mocked_history.loc[1, 'exit_short'] = 0
+    mocked_history.loc[1, 'enter_tag'] = 'sell_signal_01'
 
-    mocked_history.loc[1, 'buy_tag'] = None
-    mocked_history.loc[1, 'exit_tag'] = 'sell_signal_01'
+    # Don't provide short signal while in spot mode
+    assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history) == (None, None)
 
-    assert _STRATEGY.get_signal(
-        'ETH/BTC',
-        '5m',
-        mocked_history) == (
-        True,
-        False,
-        None,
-        'sell_signal_01')
+    _STRATEGY.config['trading_mode'] = 'futures'
+    # Short signal get's ignored as can_short is not set.
+    assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history) == (None, None)
+
+    _STRATEGY.can_short = True
+
+    assert _STRATEGY.get_entry_signal(
+        'ETH/BTC', '5m', mocked_history) == (SignalDirection.SHORT, 'sell_signal_01')
+    assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (False, False, None)
+    assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (True, False, None)
+
+    mocked_history.loc[1, 'enter_short'] = 0
+    mocked_history.loc[1, 'exit_short'] = 1
+    mocked_history.loc[1, 'exit_tag'] = 'sell_signal_02'
+    assert _STRATEGY.get_entry_signal(
+        'ETH/BTC', '5m', mocked_history) == (None, None)
+    assert _STRATEGY.get_exit_signal(
+        'ETH/BTC', '5m', mocked_history) == (False, False, 'sell_signal_02')
+    assert _STRATEGY.get_exit_signal(
+        'ETH/BTC', '5m', mocked_history, True) == (False, True, 'sell_signal_02')
+
+    _STRATEGY.can_short = False
+    _STRATEGY.config['trading_mode'] = 'spot'
 
 
 def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history):
@@ -90,25 +114,18 @@ def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history):
     assert log_has('Empty dataframe for pair ETH/BTC', caplog)
 
 
-def test_get_signal_empty(default_conf, mocker, caplog):
-    assert (False, False, None, None) == _STRATEGY.get_signal(
+def test_get_signal_empty(default_conf, caplog):
+    assert (None, None) == _STRATEGY.get_latest_candle(
         'foo', default_conf['timeframe'], DataFrame()
     )
     assert log_has('Empty candle (OHLCV) data for pair foo', caplog)
     caplog.clear()
 
-    assert (
-        False,
-        False,
-        None,
-        None) == _STRATEGY.get_signal(
-        'bar',
-        default_conf['timeframe'],
-        None)
+    assert (None, None) == _STRATEGY.get_latest_candle('bar', default_conf['timeframe'], None)
     assert log_has('Empty candle (OHLCV) data for pair bar', caplog)
     caplog.clear()
 
-    assert (False, False, None, None) == _STRATEGY.get_signal(
+    assert (None, None) == _STRATEGY.get_latest_candle(
         'baz',
         default_conf['timeframe'],
         DataFrame([])
@@ -116,7 +133,7 @@ def test_get_signal_empty(default_conf, mocker, caplog):
     assert log_has('Empty candle (OHLCV) data for pair baz', caplog)
 
 
-def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ohlcv_history):
+def test_get_signal_exception_valueerror(mocker, caplog, ohlcv_history):
     caplog.set_level(logging.INFO)
     mocker.patch.object(_STRATEGY.dp, 'ohlcv', return_value=ohlcv_history)
     mocker.patch.object(
@@ -141,14 +158,14 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history):
     ohlcv_history.loc[1, 'date'] = arrow.utcnow().shift(minutes=-16)
     # Take a copy to correctly modify the call
     mocked_history = ohlcv_history.copy()
-    mocked_history['sell'] = 0
-    mocked_history['buy'] = 0
-    mocked_history.loc[1, 'buy'] = 1
+    mocked_history['exit_long'] = 0
+    mocked_history['enter_long'] = 0
+    mocked_history.loc[1, 'enter_long'] = 1
 
     caplog.set_level(logging.INFO)
     mocker.patch.object(_STRATEGY, 'assert_df')
 
-    assert (False, False, None, None) == _STRATEGY.get_signal(
+    assert (None, None) == _STRATEGY.get_latest_candle(
         'xyz',
         default_conf['timeframe'],
         mocked_history
@@ -164,13 +181,13 @@ def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history):
     mocked_history = ohlcv_history.copy()
     # Intentionally don't set sell column
     # mocked_history['sell'] = 0
-    mocked_history['buy'] = 0
-    mocked_history.loc[1, 'buy'] = 1
+    mocked_history['enter_long'] = 0
+    mocked_history.loc[1, 'enter_long'] = 1
 
     caplog.set_level(logging.INFO)
     mocker.patch.object(_STRATEGY, 'assert_df')
 
-    assert (True, False, None, None) == _STRATEGY.get_signal(
+    assert (SignalDirection.LONG, None) == _STRATEGY.get_entry_signal(
         'xyz',
         default_conf['timeframe'],
         mocked_history
@@ -178,7 +195,6 @@ def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history):
 
 
 def test_ignore_expired_candle(default_conf):
-    default_conf.update({'strategy': 'StrategyTestV2'})
     strategy = StrategyResolver.load_strategy(default_conf)
     strategy.ignore_buying_expired_candle_after = 60
 
@@ -186,17 +202,21 @@ def test_ignore_expired_candle(default_conf):
     # Add 1 candle length as the "latest date" defines candle open.
     current_time = latest_date + timedelta(seconds=80 + 300)
 
-    assert strategy.ignore_expired_candle(latest_date=latest_date,
-                                          current_time=current_time,
-                                          timeframe_seconds=300,
-                                          buy=True) is True
+    assert strategy.ignore_expired_candle(
+        latest_date=latest_date,
+        current_time=current_time,
+        timeframe_seconds=300,
+        enter=True
+    ) is True
 
     current_time = latest_date + timedelta(seconds=30 + 300)
 
-    assert not strategy.ignore_expired_candle(latest_date=latest_date,
-                                              current_time=current_time,
-                                              timeframe_seconds=300,
-                                              buy=True) is True
+    assert not strategy.ignore_expired_candle(
+        latest_date=latest_date,
+        current_time=current_time,
+        timeframe_seconds=300,
+        enter=True
+    ) is True
 
 
 def test_assert_df_raise(mocker, caplog, ohlcv_history):
@@ -221,8 +241,8 @@ def test_assert_df_raise(mocker, caplog, ohlcv_history):
 
 def test_assert_df(ohlcv_history, caplog):
     df_len = len(ohlcv_history) - 1
-    ohlcv_history.loc[:, 'buy'] = 0
-    ohlcv_history.loc[:, 'sell'] = 0
+    ohlcv_history.loc[:, 'enter_long'] = 0
+    ohlcv_history.loc[:, 'exit_long'] = 0
     # Ensure it's running when passed correctly
     _STRATEGY.assert_df(ohlcv_history, len(ohlcv_history),
                         ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[df_len, 'date'])
@@ -245,8 +265,8 @@ def test_assert_df(ohlcv_history, caplog):
         _STRATEGY.assert_df(None, len(ohlcv_history),
                             ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date'])
     with pytest.raises(StrategyError,
-                       match="Buy column not set"):
-        _STRATEGY.assert_df(ohlcv_history.drop('buy', axis=1), len(ohlcv_history),
+                       match="enter_long/buy column not set."):
+        _STRATEGY.assert_df(ohlcv_history.drop('enter_long', axis=1), len(ohlcv_history),
                             ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date'])
 
     _STRATEGY.disable_dataframe_checks = True
@@ -259,7 +279,6 @@ def test_assert_df(ohlcv_history, caplog):
 
 
 def test_advise_all_indicators(default_conf, testdatadir) -> None:
-    default_conf.update({'strategy': 'StrategyTestV2'})
     strategy = StrategyResolver.load_strategy(default_conf)
 
     timerange = TimeRange.parse_timerange('1510694220-1510700340')
@@ -270,7 +289,6 @@ def test_advise_all_indicators(default_conf, testdatadir) -> None:
 
 
 def test_advise_all_indicators_copy(mocker, default_conf, testdatadir) -> None:
-    default_conf.update({'strategy': 'StrategyTestV2'})
     strategy = StrategyResolver.load_strategy(default_conf)
     aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators')
     timerange = TimeRange.parse_timerange('1510694220-1510700340')
@@ -288,7 +306,6 @@ def test_min_roi_reached(default_conf, fee) -> None:
     min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1},
                     {0: 0.1, 20: 0.05, 55: 0.01}]
     for roi in min_roi_list:
-        default_conf.update({'strategy': 'StrategyTestV2'})
         strategy = StrategyResolver.load_strategy(default_conf)
         strategy.minimal_roi = roi
         trade = Trade(
@@ -327,7 +344,6 @@ def test_min_roi_reached2(default_conf, fee) -> None:
                      },
                     ]
     for roi in min_roi_list:
-        default_conf.update({'strategy': 'StrategyTestV2'})
         strategy = StrategyResolver.load_strategy(default_conf)
         strategy.minimal_roi = roi
         trade = Trade(
@@ -362,7 +378,6 @@ def test_min_roi_reached3(default_conf, fee) -> None:
                30: 0.05,
                55: 0.30,
                }
-    default_conf.update({'strategy': 'StrategyTestV2'})
     strategy = StrategyResolver.load_strategy(default_conf)
     strategy.minimal_roi = min_roi
     trade = Trade(
@@ -394,29 +409,27 @@ def test_min_roi_reached3(default_conf, fee) -> None:
     'profit,adjusted,expected,trailing,custom,profit2,adjusted2,expected2,custom_stop', [
         # Profit, adjusted stoploss(absolute), profit for 2nd call, enable trailing,
         #   enable custom stoploss, expected after 1st call, expected after 2nd call
-        (0.2, 0.9, SellType.NONE, False, False, 0.3, 0.9, SellType.NONE, None),
-        (0.2, 0.9, SellType.NONE, False, False, -0.2, 0.9, SellType.STOP_LOSS, None),
-        (0.2, 1.14, SellType.NONE, True, False, 0.05, 1.14, SellType.TRAILING_STOP_LOSS, None),
-        (0.01, 0.96, SellType.NONE, True, False, 0.05, 1, SellType.NONE, None),
-        (0.05, 1, SellType.NONE, True, False, -0.01, 1, SellType.TRAILING_STOP_LOSS, None),
+        (0.2, 0.9, ExitType.NONE, False, False, 0.3, 0.9, ExitType.NONE, None),
+        (0.2, 0.9, ExitType.NONE, False, False, -0.2, 0.9, ExitType.STOP_LOSS, None),
+        (0.2, 1.14, ExitType.NONE, True, False, 0.05, 1.14, ExitType.TRAILING_STOP_LOSS, None),
+        (0.01, 0.96, ExitType.NONE, True, False, 0.05, 1, ExitType.NONE, None),
+        (0.05, 1, ExitType.NONE, True, False, -0.01, 1, ExitType.TRAILING_STOP_LOSS, None),
         # Default custom case - trails with 10%
-        (0.05, 0.95, SellType.NONE, False, True, -0.02, 0.95, SellType.NONE, None),
-        (0.05, 0.95, SellType.NONE, False, True, -0.06, 0.95, SellType.TRAILING_STOP_LOSS, None),
-        (0.05, 1, SellType.NONE, False, True, -0.06, 1, SellType.TRAILING_STOP_LOSS,
+        (0.05, 0.95, ExitType.NONE, False, True, -0.02, 0.95, ExitType.NONE, None),
+        (0.05, 0.95, ExitType.NONE, False, True, -0.06, 0.95, ExitType.TRAILING_STOP_LOSS, None),
+        (0.05, 1, ExitType.NONE, False, True, -0.06, 1, ExitType.TRAILING_STOP_LOSS,
          lambda **kwargs: -0.05),
-        (0.05, 1, SellType.NONE, False, True, 0.09, 1.04, SellType.NONE,
+        (0.05, 1, ExitType.NONE, False, True, 0.09, 1.04, ExitType.NONE,
          lambda **kwargs: -0.05),
-        (0.05, 0.95, SellType.NONE, False, True, 0.09, 0.98, SellType.NONE,
+        (0.05, 0.95, ExitType.NONE, False, True, 0.09, 0.98, ExitType.NONE,
          lambda current_profit, **kwargs: -0.1 if current_profit < 0.6 else -(current_profit * 2)),
         # Error case - static stoploss in place
-        (0.05, 0.9, SellType.NONE, False, True, 0.09, 0.9, SellType.NONE,
+        (0.05, 0.9, ExitType.NONE, False, True, 0.09, 0.9, ExitType.NONE,
          lambda **kwargs: None),
     ])
 def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, trailing, custom,
                            profit2, adjusted2, expected2, custom_stop) -> None:
 
-    default_conf.update({'strategy': 'StrategyTestV2'})
-
     strategy = StrategyResolver.load_strategy(default_conf)
     trade = Trade(
         pair='ETH/BTC',
@@ -441,31 +454,29 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
     sl_flag = strategy.stop_loss_reached(current_rate=current_rate, trade=trade,
                                          current_time=now, current_profit=profit,
                                          force_stoploss=0, high=None)
-    assert isinstance(sl_flag, SellCheckTuple)
-    assert sl_flag.sell_type == expected
-    if expected == SellType.NONE:
-        assert sl_flag.sell_flag is False
+    assert isinstance(sl_flag, ExitCheckTuple)
+    assert sl_flag.exit_type == expected
+    if expected == ExitType.NONE:
+        assert sl_flag.exit_flag is False
     else:
-        assert sl_flag.sell_flag is True
+        assert sl_flag.exit_flag is True
     assert round(trade.stop_loss, 2) == adjusted
     current_rate2 = trade.open_rate * (1 + profit2)
 
     sl_flag = strategy.stop_loss_reached(current_rate=current_rate2, trade=trade,
                                          current_time=now, current_profit=profit2,
                                          force_stoploss=0, high=None)
-    assert sl_flag.sell_type == expected2
-    if expected2 == SellType.NONE:
-        assert sl_flag.sell_flag is False
+    assert sl_flag.exit_type == expected2
+    if expected2 == ExitType.NONE:
+        assert sl_flag.exit_flag is False
     else:
-        assert sl_flag.sell_flag is True
+        assert sl_flag.exit_flag is True
     assert round(trade.stop_loss, 2) == adjusted2
 
     strategy.custom_stoploss = original_stopvalue
 
 
-def test_custom_sell(default_conf, fee, caplog) -> None:
-
-    default_conf.update({'strategy': 'StrategyTestV2'})
+def test_custom_exit(default_conf, fee, caplog) -> None:
 
     strategy = StrategyResolver.load_strategy(default_conf)
     trade = Trade(
@@ -480,50 +491,84 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
     )
 
     now = arrow.utcnow().datetime
-    res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
+    res = strategy.should_exit(trade, 1, now,
+                               enter=False, exit_=False,
+                               low=None, high=None)
 
-    assert res.sell_flag is False
-    assert res.sell_type == SellType.NONE
+    assert res.exit_flag is False
+    assert res.exit_type == ExitType.NONE
 
-    strategy.custom_sell = MagicMock(return_value=True)
-    res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
-    assert res.sell_flag is True
-    assert res.sell_type == SellType.CUSTOM_SELL
-    assert res.sell_reason == 'custom_sell'
+    strategy.custom_exit = MagicMock(return_value=True)
+    res = strategy.should_exit(trade, 1, now,
+                               enter=False, exit_=False,
+                               low=None, high=None)
+    assert res.exit_flag is True
+    assert res.exit_type == ExitType.CUSTOM_SELL
+    assert res.exit_reason == 'custom_sell'
 
-    strategy.custom_sell = MagicMock(return_value='hello world')
+    strategy.custom_exit = MagicMock(return_value='hello world')
 
-    res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
-    assert res.sell_type == SellType.CUSTOM_SELL
-    assert res.sell_flag is True
-    assert res.sell_reason == 'hello world'
+    res = strategy.should_exit(trade, 1, now,
+                               enter=False, exit_=False,
+                               low=None, high=None)
+    assert res.exit_type == ExitType.CUSTOM_SELL
+    assert res.exit_flag is True
+    assert res.exit_reason == 'hello world'
 
     caplog.clear()
-    strategy.custom_sell = MagicMock(return_value='h' * 100)
-    res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
-    assert res.sell_type == SellType.CUSTOM_SELL
-    assert res.sell_flag is True
-    assert res.sell_reason == 'h' * 64
-    assert log_has_re('Custom sell reason returned from custom_sell is too long.*', caplog)
+    strategy.custom_exit = MagicMock(return_value='h' * 100)
+    res = strategy.should_exit(trade, 1, now,
+                               enter=False, exit_=False,
+                               low=None, high=None)
+    assert res.exit_type == ExitType.CUSTOM_SELL
+    assert res.exit_flag is True
+    assert res.exit_reason == 'h' * 64
+    assert log_has_re('Custom sell reason returned from custom_exit is too long.*', caplog)
+
+
+@pytest.mark.parametrize('side', TRADE_SIDES)
+def test_leverage_callback(default_conf, side) -> None:
+    default_conf['strategy'] = 'StrategyTestV2'
+    strategy = StrategyResolver.load_strategy(default_conf)
+
+    assert strategy.leverage(
+        pair='XRP/USDT',
+        current_time=datetime.now(timezone.utc),
+        current_rate=2.2,
+        proposed_leverage=1.0,
+        max_leverage=5.0,
+        side=side,
+        ) == 1
+
+    default_conf['strategy'] = CURRENT_TEST_STRATEGY
+    strategy = StrategyResolver.load_strategy(default_conf)
+    assert strategy.leverage(
+        pair='XRP/USDT',
+        current_time=datetime.now(timezone.utc),
+        current_rate=2.2,
+        proposed_leverage=1.0,
+        max_leverage=5.0,
+        side=side,
+        ) == 3
 
 
 def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
     caplog.set_level(logging.DEBUG)
     ind_mock = MagicMock(side_effect=lambda x, meta: x)
-    buy_mock = MagicMock(side_effect=lambda x, meta: x)
-    sell_mock = MagicMock(side_effect=lambda x, meta: x)
+    entry_mock = MagicMock(side_effect=lambda x, meta: x)
+    exit_mock = MagicMock(side_effect=lambda x, meta: x)
     mocker.patch.multiple(
         'freqtrade.strategy.interface.IStrategy',
         advise_indicators=ind_mock,
-        advise_buy=buy_mock,
-        advise_sell=sell_mock,
+        advise_entry=entry_mock,
+        advise_exit=exit_mock,
 
     )
-    strategy = StrategyTestV2({})
+    strategy = StrategyTestV3({})
     strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'})
     assert ind_mock.call_count == 1
-    assert buy_mock.call_count == 1
-    assert buy_mock.call_count == 1
+    assert entry_mock.call_count == 1
+    assert entry_mock.call_count == 1
 
     assert log_has('TA Analysis Launched', caplog)
     assert not log_has('Skipping TA Analysis for already analyzed candle', caplog)
@@ -532,8 +577,8 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
     strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'})
     # No analysis happens as process_only_new_candles is true
     assert ind_mock.call_count == 2
-    assert buy_mock.call_count == 2
-    assert buy_mock.call_count == 2
+    assert entry_mock.call_count == 2
+    assert entry_mock.call_count == 2
     assert log_has('TA Analysis Launched', caplog)
     assert not log_has('Skipping TA Analysis for already analyzed candle', caplog)
 
@@ -541,16 +586,16 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
 def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> None:
     caplog.set_level(logging.DEBUG)
     ind_mock = MagicMock(side_effect=lambda x, meta: x)
-    buy_mock = MagicMock(side_effect=lambda x, meta: x)
-    sell_mock = MagicMock(side_effect=lambda x, meta: x)
+    entry_mock = MagicMock(side_effect=lambda x, meta: x)
+    exit_mock = MagicMock(side_effect=lambda x, meta: x)
     mocker.patch.multiple(
         'freqtrade.strategy.interface.IStrategy',
         advise_indicators=ind_mock,
-        advise_buy=buy_mock,
-        advise_sell=sell_mock,
+        advise_entry=entry_mock,
+        advise_exit=exit_mock,
 
     )
-    strategy = StrategyTestV2({})
+    strategy = StrategyTestV3({})
     strategy.dp = DataProvider({}, None, None)
     strategy.process_only_new_candles = True
 
@@ -560,8 +605,8 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) ->
     assert 'close' in ret.columns
     assert isinstance(ret, DataFrame)
     assert ind_mock.call_count == 1
-    assert buy_mock.call_count == 1
-    assert buy_mock.call_count == 1
+    assert entry_mock.call_count == 1
+    assert entry_mock.call_count == 1
     assert log_has('TA Analysis Launched', caplog)
     assert not log_has('Skipping TA Analysis for already analyzed candle', caplog)
     caplog.clear()
@@ -569,20 +614,19 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) ->
     ret = strategy._analyze_ticker_internal(ohlcv_history, {'pair': 'ETH/BTC'})
     # No analysis happens as process_only_new_candles is true
     assert ind_mock.call_count == 1
-    assert buy_mock.call_count == 1
-    assert buy_mock.call_count == 1
+    assert entry_mock.call_count == 1
+    assert entry_mock.call_count == 1
     # only skipped analyze adds buy and sell columns, otherwise it's all mocked
-    assert 'buy' in ret.columns
-    assert 'sell' in ret.columns
-    assert ret['buy'].sum() == 0
-    assert ret['sell'].sum() == 0
+    assert 'enter_long' in ret.columns
+    assert 'exit_long' in ret.columns
+    assert ret['enter_long'].sum() == 0
+    assert ret['exit_long'].sum() == 0
     assert not log_has('TA Analysis Launched', caplog)
     assert log_has('Skipping TA Analysis for already analyzed candle', caplog)
 
 
 @pytest.mark.usefixtures("init_persistence")
 def test_is_pair_locked(default_conf):
-    default_conf.update({'strategy': 'StrategyTestV2'})
     PairLocks.timeframe = default_conf['timeframe']
     PairLocks.use_db = True
     strategy = StrategyResolver.load_strategy(default_conf)
diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py
index 9e546869a..205fb4dac 100644
--- a/tests/strategy/test_strategy_helpers.py
+++ b/tests/strategy/test_strategy_helpers.py
@@ -5,6 +5,8 @@ import pandas as pd
 import pytest
 
 from freqtrade.data.dataprovider import DataProvider
+from freqtrade.enums import CandleType
+from freqtrade.resolvers.strategy_resolver import StrategyResolver
 from freqtrade.strategy import (merge_informative_pair, stoploss_from_absolute, stoploss_from_open,
                                 timeframe_to_minutes)
 from tests.conftest import get_patched_exchange
@@ -108,84 +110,123 @@ def test_stoploss_from_open():
         [1, 100, 30],
         [100, 10000, 30],
     ]
-    current_profit_range = [-0.99, 2, 30]
+    # profit range for long is [-1, inf] while for shorts is [-inf, 1]
+    current_profit_range_dict = {'long': [-0.99, 2, 30], 'short': [-2.0, 0.99, 30]}
     desired_stop_range = [-0.50, 0.50, 30]
 
-    for open_range in open_price_ranges:
-        for open_price in np.linspace(*open_range):
-            for desired_stop in np.linspace(*desired_stop_range):
+    for side, current_profit_range in current_profit_range_dict.items():
+        for open_range in open_price_ranges:
+            for open_price in np.linspace(*open_range):
+                for desired_stop in np.linspace(*desired_stop_range):
 
-                # -1 is not a valid current_profit, should return 1
-                assert stoploss_from_open(desired_stop, -1) == 1
-
-                for current_profit in np.linspace(*current_profit_range):
-                    current_price = open_price * (1 + current_profit)
-                    expected_stop_price = open_price * (1 + desired_stop)
-
-                    stoploss = stoploss_from_open(desired_stop, current_profit)
-
-                    assert stoploss >= 0
-                    assert stoploss <= 1
-
-                    stop_price = current_price * (1 - stoploss)
-
-                    # there is no correct answer if the expected stop price is above
-                    # the current price
-                    if expected_stop_price > current_price:
-                        assert stoploss == 0
+                    if side == 'long':
+                        # -1 is not a valid current_profit, should return 1
+                        assert stoploss_from_open(desired_stop, -1) == 1
                     else:
-                        assert isclose(stop_price, expected_stop_price, rel_tol=0.00001)
+                        # 1 is not a valid current_profit for shorts, should return 1
+                        assert stoploss_from_open(desired_stop, 1, True) == 1
+
+                    for current_profit in np.linspace(*current_profit_range):
+                        if side == 'long':
+                            current_price = open_price * (1 + current_profit)
+                            expected_stop_price = open_price * (1 + desired_stop)
+                            stoploss = stoploss_from_open(desired_stop, current_profit)
+                            stop_price = current_price * (1 - stoploss)
+                        else:
+                            current_price = open_price * (1 - current_profit)
+                            expected_stop_price = open_price * (1 - desired_stop)
+                            stoploss = stoploss_from_open(desired_stop, current_profit, True)
+                            stop_price = current_price * (1 + stoploss)
+
+                        assert stoploss >= 0
+                        # Technically the formula can yield values greater than 1 for shorts
+                        # eventhough it doesn't make sense because the position would be liquidated
+                        if side == 'long':
+                            assert stoploss <= 1
+
+                        # there is no correct answer if the expected stop price is above
+                        # the current price
+                        if ((side == 'long' and expected_stop_price > current_price)
+                                or (side == 'short' and expected_stop_price < current_price)):
+                            assert stoploss == 0
+                        else:
+                            assert isclose(stop_price, expected_stop_price, rel_tol=0.00001)
 
 
 def test_stoploss_from_absolute():
-    assert stoploss_from_absolute(90, 100) == 1 - (90 / 100)
-    assert stoploss_from_absolute(100, 100) == 0
-    assert stoploss_from_absolute(110, 100) == 0
-    assert stoploss_from_absolute(100, 0) == 1
-    assert stoploss_from_absolute(0, 100) == 1
+    assert pytest.approx(stoploss_from_absolute(90, 100)) == 1 - (90 / 100)
+    assert pytest.approx(stoploss_from_absolute(90, 100)) == 0.1
+    assert pytest.approx(stoploss_from_absolute(95, 100)) == 0.05
+    assert pytest.approx(stoploss_from_absolute(100, 100)) == 0
+    assert pytest.approx(stoploss_from_absolute(110, 100)) == 0
+    assert pytest.approx(stoploss_from_absolute(100, 0)) == 1
+    assert pytest.approx(stoploss_from_absolute(0, 100)) == 1
+
+    assert pytest.approx(stoploss_from_absolute(90, 100, True)) == 0
+    assert pytest.approx(stoploss_from_absolute(100, 100, True)) == 0
+    assert pytest.approx(stoploss_from_absolute(110, 100, True)) == -(1 - (110/100))
+    assert pytest.approx(stoploss_from_absolute(110, 100, True)) == 0.1
+    assert pytest.approx(stoploss_from_absolute(105, 100, True)) == 0.05
+    assert pytest.approx(stoploss_from_absolute(100, 0, True)) == 1
+    assert pytest.approx(stoploss_from_absolute(0, 100, True)) == 0
+    assert pytest.approx(stoploss_from_absolute(100, 1, True)) == 1
 
 
-def test_informative_decorator(mocker, default_conf):
+@pytest.mark.parametrize('trading_mode', ['futures', 'spot'])
+def test_informative_decorator(mocker, default_conf_usdt, trading_mode):
+    candle_def = CandleType.get_default(trading_mode)
+    default_conf_usdt['candle_type_def'] = candle_def
     test_data_5m = generate_test_data('5m', 40)
     test_data_30m = generate_test_data('30m', 40)
     test_data_1h = generate_test_data('1h', 40)
     data = {
-        ('XRP/USDT', '5m'): test_data_5m,
-        ('XRP/USDT', '30m'): test_data_30m,
-        ('XRP/USDT', '1h'): test_data_1h,
-        ('LTC/USDT', '5m'): test_data_5m,
-        ('LTC/USDT', '30m'): test_data_30m,
-        ('LTC/USDT', '1h'): test_data_1h,
-        ('NEO/USDT', '30m'): test_data_30m,
-        ('NEO/USDT', '5m'): test_data_5m,
-        ('NEO/USDT', '1h'): test_data_1h,
-        ('ETH/USDT', '1h'): test_data_1h,
-        ('ETH/USDT', '30m'): test_data_30m,
-        ('ETH/BTC', '1h'): test_data_1h,
+        ('XRP/USDT', '5m', candle_def): test_data_5m,
+        ('XRP/USDT', '30m', candle_def): test_data_30m,
+        ('XRP/USDT', '1h', candle_def): test_data_1h,
+        ('LTC/USDT', '5m', candle_def): test_data_5m,
+        ('LTC/USDT', '30m', candle_def): test_data_30m,
+        ('LTC/USDT', '1h', candle_def): test_data_1h,
+        ('NEO/USDT', '30m', candle_def): test_data_30m,
+        ('NEO/USDT', '5m', CandleType.SPOT): test_data_5m,  # Explicit request with '' as candletype
+        ('NEO/USDT', '15m', candle_def): test_data_5m,  # Explicit request with '' as candletype
+        ('NEO/USDT', '1h', candle_def): test_data_1h,
+        ('ETH/USDT', '1h', candle_def): test_data_1h,
+        ('ETH/USDT', '30m', candle_def): test_data_30m,
+        ('ETH/BTC', '1h', CandleType.SPOT): test_data_1h,  # Explicitly selected as spot
     }
-    from .strats.informative_decorator_strategy import InformativeDecoratorTest
-    default_conf['stake_currency'] = 'USDT'
-    strategy = InformativeDecoratorTest(config=default_conf)
-    exchange = get_patched_exchange(mocker, default_conf)
+    default_conf_usdt['strategy'] = 'InformativeDecoratorTest'
+    strategy = StrategyResolver.load_strategy(default_conf_usdt)
+    exchange = get_patched_exchange(mocker, default_conf_usdt)
     strategy.dp = DataProvider({}, exchange, None)
     mocker.patch.object(strategy.dp, 'current_whitelist', return_value=[
         'XRP/USDT', 'LTC/USDT', 'NEO/USDT'
     ])
 
     assert len(strategy._ft_informative) == 6   # Equal to number of decorators used
-    informative_pairs = [('XRP/USDT', '1h'), ('LTC/USDT', '1h'), ('XRP/USDT', '30m'),
-                         ('LTC/USDT', '30m'), ('NEO/USDT', '1h'), ('NEO/USDT', '30m'),
-                         ('NEO/USDT', '5m'), ('ETH/BTC', '1h'), ('ETH/USDT', '30m')]
+    informative_pairs = [
+        ('XRP/USDT', '1h', candle_def),
+        ('LTC/USDT', '1h', candle_def),
+        ('XRP/USDT', '30m', candle_def),
+        ('LTC/USDT', '30m', candle_def),
+        ('NEO/USDT', '1h', candle_def),
+        ('NEO/USDT', '30m', candle_def),
+        ('NEO/USDT', '5m', candle_def),
+        ('NEO/USDT', '15m', candle_def),
+        ('NEO/USDT', '2h', CandleType.FUTURES),
+        ('ETH/BTC', '1h', CandleType.SPOT),  # One candle remains as spot
+        ('ETH/USDT', '30m', candle_def)]
     for inf_pair in informative_pairs:
         assert inf_pair in strategy.gather_informative_pairs()
 
-    def test_historic_ohlcv(pair, timeframe):
-        return data[(pair, timeframe or strategy.timeframe)].copy()
+    def test_historic_ohlcv(pair, timeframe, candle_type):
+        return data[
+            (pair, timeframe or strategy.timeframe, CandleType.from_string(candle_type))].copy()
+
     mocker.patch('freqtrade.data.dataprovider.DataProvider.historic_ohlcv',
                  side_effect=test_historic_ohlcv)
 
     analyzed = strategy.advise_all_indicators(
-        {p: data[(p, strategy.timeframe)] for p in ('XRP/USDT', 'LTC/USDT')})
+        {p: data[(p, strategy.timeframe, candle_def)] for p in ('XRP/USDT', 'LTC/USDT')})
     expected_columns = [
         'rsi_1h', 'rsi_30m',                    # Stacked informative decorators
         'neo_usdt_rsi_1h',                      # NEO 1h informative
diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py
index cc924d1c2..b1b67dcf0 100644
--- a/tests/strategy/test_strategy_loading.py
+++ b/tests/strategy/test_strategy_loading.py
@@ -10,7 +10,7 @@ from pandas import DataFrame
 from freqtrade.exceptions import OperationalException
 from freqtrade.resolvers import StrategyResolver
 from freqtrade.strategy.interface import IStrategy
-from tests.conftest import log_has, log_has_re
+from tests.conftest import CURRENT_TEST_STRATEGY, log_has, log_has_re
 
 
 def test_search_strategy():
@@ -18,7 +18,7 @@ def test_search_strategy():
 
     s, _ = StrategyResolver._search_object(
         directory=default_location,
-        object_name='StrategyTestV2',
+        object_name=CURRENT_TEST_STRATEGY,
         add_source=True,
     )
     assert issubclass(s, IStrategy)
@@ -35,7 +35,7 @@ def test_search_all_strategies_no_failed():
     directory = Path(__file__).parent / "strats"
     strategies = StrategyResolver.search_all_objects(directory, enum_failed=False)
     assert isinstance(strategies, list)
-    assert len(strategies) == 4
+    assert len(strategies) == 6
     assert isinstance(strategies[0], dict)
 
 
@@ -43,10 +43,10 @@ def test_search_all_strategies_with_failed():
     directory = Path(__file__).parent / "strats"
     strategies = StrategyResolver.search_all_objects(directory, enum_failed=True)
     assert isinstance(strategies, list)
-    assert len(strategies) == 5
+    assert len(strategies) == 7
     # with enum_failed=True search_all_objects() shall find 2 good strategies
     # and 1 which fails to load
-    assert len([x for x in strategies if x['class'] is not None]) == 4
+    assert len([x for x in strategies if x['class'] is not None]) == 6
     assert len([x for x in strategies if x['class'] is None]) == 1
 
 
@@ -74,10 +74,10 @@ def test_load_strategy_base64(result, caplog, default_conf):
 
 
 def test_load_strategy_invalid_directory(result, caplog, default_conf):
-    default_conf['strategy'] = 'StrategyTestV2'
+    default_conf['strategy'] = 'StrategyTestV3'
     extra_dir = Path.cwd() / 'some/path'
     with pytest.raises(OperationalException):
-        StrategyResolver._load_strategy('StrategyTestV2', config=default_conf,
+        StrategyResolver._load_strategy(CURRENT_TEST_STRATEGY, config=default_conf,
                                         extra_dir=extra_dir)
 
     assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog)
@@ -99,8 +99,10 @@ def test_load_strategy_noname(default_conf):
         StrategyResolver.load_strategy(default_conf)
 
 
-def test_strategy(result, default_conf):
-    default_conf.update({'strategy': 'StrategyTestV2'})
+@pytest.mark.filterwarnings("ignore:deprecated")
+@pytest.mark.parametrize('strategy_name', ['StrategyTestV2', 'TestStrategyLegacyV1'])
+def test_strategy_pre_v3(result, default_conf, strategy_name):
+    default_conf.update({'strategy': strategy_name})
 
     strategy = StrategyResolver.load_strategy(default_conf)
     metadata = {'pair': 'ETH/BTC'}
@@ -116,17 +118,45 @@ def test_strategy(result, default_conf):
     df_indicators = strategy.advise_indicators(result, metadata=metadata)
     assert 'adx' in df_indicators
 
-    dataframe = strategy.advise_buy(df_indicators, metadata=metadata)
-    assert 'buy' in dataframe.columns
+    dataframe = strategy.advise_entry(df_indicators, metadata=metadata)
+    assert 'buy' not in dataframe.columns
+    assert 'enter_long' in dataframe.columns
 
-    dataframe = strategy.advise_sell(df_indicators, metadata=metadata)
-    assert 'sell' in dataframe.columns
+    dataframe = strategy.advise_exit(df_indicators, metadata=metadata)
+    assert 'sell' not in dataframe.columns
+    assert 'exit_long' in dataframe.columns
+
+
+def test_strategy_can_short(caplog, default_conf):
+    caplog.set_level(logging.INFO)
+    default_conf.update({
+        'strategy': CURRENT_TEST_STRATEGY,
+    })
+    strat = StrategyResolver.load_strategy(default_conf)
+    assert isinstance(strat, IStrategy)
+    default_conf['strategy'] = 'StrategyTestV3Futures'
+    with pytest.raises(ImportError, match=""):
+        StrategyResolver.load_strategy(default_conf)
+
+    default_conf['trading_mode'] = 'futures'
+    strat = StrategyResolver.load_strategy(default_conf)
+    assert isinstance(strat, IStrategy)
+
+
+def test_strategy_implements_populate_entry(caplog, default_conf):
+    caplog.set_level(logging.INFO)
+    default_conf.update({
+        'strategy': "StrategyTestV2",
+    })
+    default_conf['trading_mode'] = 'futures'
+    with pytest.raises(OperationalException, match="`populate_entry_trend` must be implemented."):
+        StrategyResolver.load_strategy(default_conf)
 
 
 def test_strategy_override_minimal_roi(caplog, default_conf):
     caplog.set_level(logging.INFO)
     default_conf.update({
-        'strategy': 'StrategyTestV2',
+        'strategy': CURRENT_TEST_STRATEGY,
         'minimal_roi': {
             "20": 0.1,
             "0": 0.5
@@ -143,7 +173,7 @@ def test_strategy_override_minimal_roi(caplog, default_conf):
 def test_strategy_override_stoploss(caplog, default_conf):
     caplog.set_level(logging.INFO)
     default_conf.update({
-        'strategy': 'StrategyTestV2',
+        'strategy': CURRENT_TEST_STRATEGY,
         'stoploss': -0.5
     })
     strategy = StrategyResolver.load_strategy(default_conf)
@@ -155,7 +185,7 @@ def test_strategy_override_stoploss(caplog, default_conf):
 def test_strategy_override_trailing_stop(caplog, default_conf):
     caplog.set_level(logging.INFO)
     default_conf.update({
-        'strategy': 'StrategyTestV2',
+        'strategy': CURRENT_TEST_STRATEGY,
         'trailing_stop': True
     })
     strategy = StrategyResolver.load_strategy(default_conf)
@@ -168,7 +198,7 @@ def test_strategy_override_trailing_stop(caplog, default_conf):
 def test_strategy_override_trailing_stop_positive(caplog, default_conf):
     caplog.set_level(logging.INFO)
     default_conf.update({
-        'strategy': 'StrategyTestV2',
+        'strategy': CURRENT_TEST_STRATEGY,
         'trailing_stop_positive': -0.1,
         'trailing_stop_positive_offset': -0.2
 
@@ -188,7 +218,7 @@ def test_strategy_override_timeframe(caplog, default_conf):
     caplog.set_level(logging.INFO)
 
     default_conf.update({
-        'strategy': 'StrategyTestV2',
+        'strategy': CURRENT_TEST_STRATEGY,
         'timeframe': 60,
         'stake_currency': 'ETH'
     })
@@ -204,7 +234,7 @@ def test_strategy_override_process_only_new_candles(caplog, default_conf):
     caplog.set_level(logging.INFO)
 
     default_conf.update({
-        'strategy': 'StrategyTestV2',
+        'strategy': CURRENT_TEST_STRATEGY,
         'process_only_new_candles': True
     })
     strategy = StrategyResolver.load_strategy(default_conf)
@@ -218,32 +248,32 @@ def test_strategy_override_order_types(caplog, default_conf):
     caplog.set_level(logging.INFO)
 
     order_types = {
-        'buy': 'market',
-        'sell': 'limit',
+        'entry': 'market',
+        'exit': 'limit',
         'stoploss': 'limit',
         'stoploss_on_exchange': True,
     }
     default_conf.update({
-        'strategy': 'StrategyTestV2',
+        'strategy': CURRENT_TEST_STRATEGY,
         'order_types': order_types
     })
     strategy = StrategyResolver.load_strategy(default_conf)
 
     assert strategy.order_types
-    for method in ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']:
+    for method in ['entry', 'exit', 'stoploss', 'stoploss_on_exchange']:
         assert strategy.order_types[method] == order_types[method]
 
     assert log_has("Override strategy 'order_types' with value in config file:"
-                   " {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit',"
+                   " {'entry': 'market', 'exit': 'limit', 'stoploss': 'limit',"
                    " 'stoploss_on_exchange': True}.", caplog)
 
     default_conf.update({
-        'strategy': 'StrategyTestV2',
-        'order_types': {'buy': 'market'}
+        'strategy': CURRENT_TEST_STRATEGY,
+        'order_types': {'exit': 'market'}
     })
     # Raise error for invalid configuration
     with pytest.raises(ImportError,
-                       match=r"Impossible to load Strategy 'StrategyTestV2'. "
+                       match=r"Impossible to load Strategy '" + CURRENT_TEST_STRATEGY + "'. "
                              r"Order-types mapping is incomplete."):
         StrategyResolver.load_strategy(default_conf)
 
@@ -252,38 +282,38 @@ def test_strategy_override_order_tif(caplog, default_conf):
     caplog.set_level(logging.INFO)
 
     order_time_in_force = {
-        'buy': 'fok',
-        'sell': 'gtc',
+        'entry': 'fok',
+        'exit': 'gtc',
     }
 
     default_conf.update({
-        'strategy': 'StrategyTestV2',
+        'strategy': CURRENT_TEST_STRATEGY,
         'order_time_in_force': order_time_in_force
     })
     strategy = StrategyResolver.load_strategy(default_conf)
 
     assert strategy.order_time_in_force
-    for method in ['buy', 'sell']:
+    for method in ['entry', 'exit']:
         assert strategy.order_time_in_force[method] == order_time_in_force[method]
 
     assert log_has("Override strategy 'order_time_in_force' with value in config file:"
-                   " {'buy': 'fok', 'sell': 'gtc'}.", caplog)
+                   " {'entry': 'fok', 'exit': 'gtc'}.", caplog)
 
     default_conf.update({
-        'strategy': 'StrategyTestV2',
-        'order_time_in_force': {'buy': 'fok'}
+        'strategy': CURRENT_TEST_STRATEGY,
+        'order_time_in_force': {'entry': 'fok'}
     })
     # Raise error for invalid configuration
     with pytest.raises(ImportError,
-                       match=r"Impossible to load Strategy 'StrategyTestV2'. "
-                             r"Order-time-in-force mapping is incomplete."):
+                       match=f"Impossible to load Strategy '{CURRENT_TEST_STRATEGY}'. "
+                             "Order-time-in-force mapping is incomplete."):
         StrategyResolver.load_strategy(default_conf)
 
 
 def test_strategy_override_use_sell_signal(caplog, default_conf):
     caplog.set_level(logging.INFO)
     default_conf.update({
-        'strategy': 'StrategyTestV2',
+        'strategy': CURRENT_TEST_STRATEGY,
     })
     strategy = StrategyResolver.load_strategy(default_conf)
     assert strategy.use_sell_signal
@@ -293,7 +323,7 @@ def test_strategy_override_use_sell_signal(caplog, default_conf):
     assert default_conf['use_sell_signal']
 
     default_conf.update({
-        'strategy': 'StrategyTestV2',
+        'strategy': CURRENT_TEST_STRATEGY,
         'use_sell_signal': False,
     })
     strategy = StrategyResolver.load_strategy(default_conf)
@@ -306,7 +336,7 @@ def test_strategy_override_use_sell_signal(caplog, default_conf):
 def test_strategy_override_use_sell_profit_only(caplog, default_conf):
     caplog.set_level(logging.INFO)
     default_conf.update({
-        'strategy': 'StrategyTestV2',
+        'strategy': CURRENT_TEST_STRATEGY,
     })
     strategy = StrategyResolver.load_strategy(default_conf)
     assert not strategy.sell_profit_only
@@ -316,7 +346,7 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf):
     assert not default_conf['sell_profit_only']
 
     default_conf.update({
-        'strategy': 'StrategyTestV2',
+        'strategy': CURRENT_TEST_STRATEGY,
         'sell_profit_only': True,
     })
     strategy = StrategyResolver.load_strategy(default_conf)
@@ -344,7 +374,7 @@ def test_deprecate_populate_indicators(result, default_conf):
     with warnings.catch_warnings(record=True) as w:
         # Cause all warnings to always be triggered.
         warnings.simplefilter("always")
-        strategy.advise_buy(indicators, {'pair': 'ETH/BTC'})
+        strategy.advise_entry(indicators, {'pair': 'ETH/BTC'})
         assert len(w) == 1
         assert issubclass(w[-1].category, DeprecationWarning)
         assert "deprecated - check out the Sample strategy to see the current function headers!" \
@@ -353,7 +383,7 @@ def test_deprecate_populate_indicators(result, default_conf):
     with warnings.catch_warnings(record=True) as w:
         # Cause all warnings to always be triggered.
         warnings.simplefilter("always")
-        strategy.advise_sell(indicators, {'pair': 'ETH_BTC'})
+        strategy.advise_exit(indicators, {'pair': 'ETH_BTC'})
         assert len(w) == 1
         assert issubclass(w[-1].category, DeprecationWarning)
         assert "deprecated - check out the Sample strategy to see the current function headers!" \
@@ -361,7 +391,50 @@ def test_deprecate_populate_indicators(result, default_conf):
 
 
 @pytest.mark.filterwarnings("ignore:deprecated")
-def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
+def test_missing_implements(default_conf):
+    default_location = Path(__file__).parent / "strats/broken_strats"
+    default_conf.update({'strategy': 'TestStrategyNoImplements',
+                         'strategy_path': default_location})
+    with pytest.raises(OperationalException,
+                       match=r"`populate_entry_trend` or `populate_buy_trend`.*"):
+        StrategyResolver.load_strategy(default_conf)
+
+    default_conf['strategy'] = 'TestStrategyNoImplementSell'
+
+    with pytest.raises(OperationalException,
+                       match=r"`populate_exit_trend` or `populate_sell_trend`.*"):
+        StrategyResolver.load_strategy(default_conf)
+
+    # Futures mode is more strict ...
+    default_conf['trading_mode'] = 'futures'
+
+    with pytest.raises(OperationalException,
+                       match=r"`populate_exit_trend` must be implemented.*"):
+        StrategyResolver.load_strategy(default_conf)
+
+    default_conf['strategy'] = 'TestStrategyNoImplements'
+    with pytest.raises(OperationalException,
+                       match=r"`populate_entry_trend` must be implemented.*"):
+        StrategyResolver.load_strategy(default_conf)
+
+    default_conf['strategy'] = 'TestStrategyImplementCustomSell'
+    with pytest.raises(OperationalException,
+                       match=r"Please migrate your implementation of `custom_sell`.*"):
+        StrategyResolver.load_strategy(default_conf)
+
+    default_conf['strategy'] = 'TestStrategyImplementBuyTimeout'
+    with pytest.raises(OperationalException,
+                       match=r"Please migrate your implementation of `check_buy_timeout`.*"):
+        StrategyResolver.load_strategy(default_conf)
+
+    default_conf['strategy'] = 'TestStrategyImplementSellTimeout'
+    with pytest.raises(OperationalException,
+                       match=r"Please migrate your implementation of `check_sell_timeout`.*"):
+        StrategyResolver.load_strategy(default_conf)
+
+
+@pytest.mark.filterwarnings("ignore:deprecated")
+def test_call_deprecated_function(result, default_conf, caplog):
     default_location = Path(__file__).parent / "strats"
     del default_conf['timeframe']
     default_conf.update({'strategy': 'TestStrategyLegacyV1',
@@ -380,16 +453,16 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
     assert isinstance(indicator_df, DataFrame)
     assert 'adx' in indicator_df.columns
 
-    buydf = strategy.advise_buy(result, metadata=metadata)
-    assert isinstance(buydf, DataFrame)
-    assert 'buy' in buydf.columns
+    enterdf = strategy.advise_entry(result, metadata=metadata)
+    assert isinstance(enterdf, DataFrame)
+    assert 'enter_long' in enterdf.columns
 
-    selldf = strategy.advise_sell(result, metadata=metadata)
-    assert isinstance(selldf, DataFrame)
-    assert 'sell' in selldf
+    exitdf = strategy.advise_exit(result, metadata=metadata)
+    assert isinstance(exitdf, DataFrame)
+    assert 'exit_long' in exitdf
 
 
-def test_strategy_interface_versioning(result, monkeypatch, default_conf):
+def test_strategy_interface_versioning(result, default_conf):
     default_conf.update({'strategy': 'StrategyTestV2'})
     strategy = StrategyResolver.load_strategy(default_conf)
     metadata = {'pair': 'ETH/BTC'}
@@ -404,10 +477,13 @@ def test_strategy_interface_versioning(result, monkeypatch, default_conf):
     assert isinstance(indicator_df, DataFrame)
     assert 'adx' in indicator_df.columns
 
-    buydf = strategy.advise_buy(result, metadata=metadata)
-    assert isinstance(buydf, DataFrame)
-    assert 'buy' in buydf.columns
+    enterdf = strategy.advise_entry(result, metadata=metadata)
+    assert isinstance(enterdf, DataFrame)
 
-    selldf = strategy.advise_sell(result, metadata=metadata)
-    assert isinstance(selldf, DataFrame)
-    assert 'sell' in selldf
+    assert 'buy' not in enterdf.columns
+    assert 'enter_long' in enterdf.columns
+
+    exitdf = strategy.advise_exit(result, metadata=metadata)
+    assert isinstance(exitdf, DataFrame)
+    assert 'sell' not in exitdf
+    assert 'exit_long' in exitdf
diff --git a/tests/test_arguments.py b/tests/test_arguments.py
index ba9d154e5..0c63f636d 100644
--- a/tests/test_arguments.py
+++ b/tests/test_arguments.py
@@ -7,6 +7,7 @@ import pytest
 
 from freqtrade.commands import Arguments
 from freqtrade.commands.cli_options import check_int_nonzero, check_int_positive
+from tests.conftest import CURRENT_TEST_STRATEGY
 
 
 # Parse common command-line-arguments. Used for all tools
@@ -123,7 +124,7 @@ def test_parse_args_backtesting_custom() -> None:
         '-c', 'test_conf.json',
         '--timeframe', '1m',
         '--strategy-list',
-        'StrategyTestV2',
+        CURRENT_TEST_STRATEGY,
         'SampleStrategy'
     ]
     call_args = Arguments(args).get_parsed_arg()
diff --git a/tests/test_configuration.py b/tests/test_configuration.py
index 1cd9b0ff7..3783e30a1 100644
--- a/tests/test_configuration.py
+++ b/tests/test_configuration.py
@@ -23,7 +23,8 @@ from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_
 from freqtrade.enums import RunMode
 from freqtrade.exceptions import OperationalException
 from freqtrade.loggers import FTBufferingHandler, _set_loggers, setup_logging, setup_logging_pre
-from tests.conftest import log_has, log_has_re, patched_configuration_load_config_file
+from tests.conftest import (CURRENT_TEST_STRATEGY, log_has, log_has_re,
+                            patched_configuration_load_config_file)
 
 
 @pytest.fixture(scope="function")
@@ -403,7 +404,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
     arglist = [
         'backtesting',
         '--config', 'config.json',
-        '--strategy', 'StrategyTestV2',
+        '--strategy', CURRENT_TEST_STRATEGY,
     ]
 
     args = Arguments(arglist).get_parsed_arg()
@@ -440,7 +441,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
     arglist = [
         'backtesting',
         '--config', 'config.json',
-        '--strategy', 'StrategyTestV2',
+        '--strategy', CURRENT_TEST_STRATEGY,
         '--datadir', '/foo/bar',
         '--userdir', "/tmp/freqtrade",
         '--timeframe', '1m',
@@ -497,7 +498,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non
         '--timeframe', '1m',
         '--export', 'trades',
         '--strategy-list',
-        'StrategyTestV2',
+        CURRENT_TEST_STRATEGY,
         'TestStrategy'
     ]
 
@@ -797,8 +798,8 @@ def test_validate_max_open_trades(default_conf):
 
 def test_validate_price_side(default_conf):
     default_conf['order_types'] = {
-        "buy": "limit",
-        "sell": "limit",
+        "entry": "limit",
+        "exit": "limit",
         "stoploss": "limit",
         "stoploss_on_exchange": False,
     }
@@ -806,23 +807,23 @@ def test_validate_price_side(default_conf):
     validate_config_consistency(default_conf)
 
     conf = deepcopy(default_conf)
-    conf['order_types']['buy'] = 'market'
+    conf['order_types']['entry'] = 'market'
     with pytest.raises(OperationalException,
-                       match='Market buy orders require bid_strategy.price_side = "ask".'):
+                       match='Market entry orders require entry_pricing.price_side = "other".'):
         validate_config_consistency(conf)
 
     conf = deepcopy(default_conf)
-    conf['order_types']['sell'] = 'market'
+    conf['order_types']['exit'] = 'market'
     with pytest.raises(OperationalException,
-                       match='Market sell orders require ask_strategy.price_side = "bid".'):
+                       match='Market exit orders require exit_pricing.price_side = "other".'):
         validate_config_consistency(conf)
 
     # Validate inversed case
     conf = deepcopy(default_conf)
-    conf['order_types']['sell'] = 'market'
-    conf['order_types']['buy'] = 'market'
-    conf['ask_strategy']['price_side'] = 'bid'
-    conf['bid_strategy']['price_side'] = 'ask'
+    conf['order_types']['exit'] = 'market'
+    conf['order_types']['entry'] = 'market'
+    conf['exit_pricing']['price_side'] = 'bid'
+    conf['entry_pricing']['price_side'] = 'ask'
 
     validate_config_consistency(conf)
 
@@ -925,18 +926,138 @@ def test_validate_protections(default_conf, protconf, expected):
 
 def test_validate_ask_orderbook(default_conf, caplog) -> None:
     conf = deepcopy(default_conf)
-    conf['ask_strategy']['use_order_book'] = True
-    conf['ask_strategy']['order_book_min'] = 2
-    conf['ask_strategy']['order_book_max'] = 2
+    conf['exit_pricing']['use_order_book'] = True
+    conf['exit_pricing']['order_book_min'] = 2
+    conf['exit_pricing']['order_book_max'] = 2
 
     validate_config_consistency(conf)
     assert log_has_re(r"DEPRECATED: Please use `order_book_top` instead of.*", caplog)
-    assert conf['ask_strategy']['order_book_top'] == 2
+    assert conf['exit_pricing']['order_book_top'] == 2
 
-    conf['ask_strategy']['order_book_max'] = 5
+    conf['exit_pricing']['order_book_max'] = 5
 
     with pytest.raises(OperationalException,
-                       match=r"Using order_book_max != order_book_min in ask_strategy.*"):
+                       match=r"Using order_book_max != order_book_min in exit_pricing.*"):
+        validate_config_consistency(conf)
+
+
+def test_validate_time_in_force(default_conf, caplog) -> None:
+    conf = deepcopy(default_conf)
+    conf['order_time_in_force'] = {
+        'buy': 'gtc',
+        'sell': 'gtc',
+    }
+    validate_config_consistency(conf)
+    assert log_has_re(r"DEPRECATED: Using 'buy' and 'sell' for time_in_force is.*", caplog)
+    assert conf['order_time_in_force']['entry'] == 'gtc'
+    assert conf['order_time_in_force']['exit'] == 'gtc'
+
+    conf = deepcopy(default_conf)
+    conf['order_time_in_force'] = {
+        'buy': 'gtc',
+        'sell': 'gtc',
+    }
+    conf['trading_mode'] = 'futures'
+    with pytest.raises(OperationalException,
+                       match=r"Please migrate your time_in_force settings .* 'entry' and 'exit'\."):
+        validate_config_consistency(conf)
+
+
+def test__validate_order_types(default_conf, caplog) -> None:
+    conf = deepcopy(default_conf)
+    conf['order_types'] = {
+        'buy': 'limit',
+        'sell': 'market',
+        'forcesell': 'market',
+        'forcebuy': 'limit',
+        'stoploss': 'market',
+        'stoploss_on_exchange': False,
+    }
+    validate_config_consistency(conf)
+    assert log_has_re(r"DEPRECATED: Using 'buy' and 'sell' for order_types is.*", caplog)
+    assert conf['order_types']['entry'] == 'limit'
+    assert conf['order_types']['exit'] == 'market'
+    assert conf['order_types']['forceentry'] == 'limit'
+    assert 'buy' not in conf['order_types']
+    assert 'sell' not in conf['order_types']
+    assert 'forcebuy' not in conf['order_types']
+    assert 'forcesell' not in conf['order_types']
+
+    conf = deepcopy(default_conf)
+    conf['order_types'] = {
+        'buy': 'limit',
+        'sell': 'market',
+        'forcesell': 'market',
+        'forcebuy': 'limit',
+        'stoploss': 'market',
+        'stoploss_on_exchange': False,
+    }
+    conf['trading_mode'] = 'futures'
+    with pytest.raises(OperationalException,
+                       match=r"Please migrate your order_types settings to use the new wording\."):
+        validate_config_consistency(conf)
+
+
+def test__validate_unfilledtimeout(default_conf, caplog) -> None:
+    conf = deepcopy(default_conf)
+    conf['unfilledtimeout'] = {
+        'buy': 30,
+        'sell': 35,
+    }
+    validate_config_consistency(conf)
+    assert log_has_re(r"DEPRECATED: Using 'buy' and 'sell' for unfilledtimeout is.*", caplog)
+    assert conf['unfilledtimeout']['entry'] == 30
+    assert conf['unfilledtimeout']['exit'] == 35
+    assert 'buy' not in conf['unfilledtimeout']
+    assert 'sell' not in conf['unfilledtimeout']
+
+    conf = deepcopy(default_conf)
+    conf['unfilledtimeout'] = {
+        'buy': 30,
+        'sell': 35,
+    }
+    conf['trading_mode'] = 'futures'
+    with pytest.raises(
+            OperationalException,
+            match=r"Please migrate your unfilledtimeout settings to use the new wording\."):
+        validate_config_consistency(conf)
+
+
+def test__validate_pricing_rules(default_conf, caplog) -> None:
+    def_conf = deepcopy(default_conf)
+    del def_conf['entry_pricing']
+    del def_conf['exit_pricing']
+
+    def_conf['ask_strategy'] = {
+        'price_side': 'ask',
+        'use_order_book': True,
+        'bid_last_balance': 0.5
+    }
+    def_conf['bid_strategy'] = {
+        'price_side': 'bid',
+        'use_order_book': False,
+        'ask_last_balance': 0.7
+    }
+    conf = deepcopy(def_conf)
+
+    validate_config_consistency(conf)
+    assert log_has_re(
+        r"DEPRECATED: Using 'ask_strategy' and 'bid_strategy' is.*", caplog)
+    assert conf['exit_pricing']['price_side'] == 'ask'
+    assert conf['exit_pricing']['use_order_book'] is True
+    assert conf['exit_pricing']['price_last_balance'] == 0.5
+    assert conf['entry_pricing']['price_side'] == 'bid'
+    assert conf['entry_pricing']['use_order_book'] is False
+    assert conf['entry_pricing']['price_last_balance'] == 0.7
+    assert 'ask_strategy' not in conf
+    assert 'bid_strategy' not in conf
+
+    conf = deepcopy(def_conf)
+
+    conf['trading_mode'] = 'futures'
+    with pytest.raises(
+            OperationalException,
+            match=r"Please migrate your pricing settings to use the new wording\."):
         validate_config_consistency(conf)
 
 
@@ -1257,11 +1378,14 @@ def test_process_deprecated_setting(mocker, default_conf, caplog):
     # The value of the new setting shall have been set to the
     # value of the deprecated one
     assert default_conf['sectionA']['new_setting'] == 'valB'
+    # Old setting is removed
+    assert 'deprecated_setting' not in default_conf['sectionB']
 
     caplog.clear()
 
     # Delete new setting (deprecated exists)
     del default_conf['sectionA']['new_setting']
+    default_conf['sectionB']['deprecated_setting'] = 'valB'
     process_deprecated_setting(default_conf,
                                'sectionB', 'deprecated_setting',
                                'sectionA', 'new_setting')
@@ -1275,7 +1399,7 @@ def test_process_deprecated_setting(mocker, default_conf, caplog):
     # Assign new setting
     default_conf['sectionA']['new_setting'] = 'valA'
     # Delete deprecated setting
-    del default_conf['sectionB']['deprecated_setting']
+    default_conf['sectionB'].pop('deprecated_setting', None)
     process_deprecated_setting(default_conf,
                                'sectionB', 'deprecated_setting',
                                'sectionA', 'new_setting')
@@ -1348,15 +1472,15 @@ def test_flat_vars_to_nested_dict(caplog):
         'FREQTRADE__EXCHANGE__SOME_SETTING': 'true',
         'FREQTRADE__EXCHANGE__SOME_FALSE_SETTING': 'false',
         'FREQTRADE__EXCHANGE__CONFIG__whatever': 'sometime',
-        'FREQTRADE__ASK_STRATEGY__PRICE_SIDE': 'bid',
-        'FREQTRADE__ASK_STRATEGY__cccc': '500',
+        'FREQTRADE__EXIT_PRICING__PRICE_SIDE': 'bid',
+        'FREQTRADE__EXIT_PRICING__cccc': '500',
         'FREQTRADE__STAKE_AMOUNT': '200.05',
         'FREQTRADE__TELEGRAM__CHAT_ID': '2151',
         'NOT_RELEVANT': '200.0',  # Will be ignored
     }
     expected = {
         'stake_amount': 200.05,
-        'ask_strategy': {
+        'exit_pricing': {
             'price_side': 'bid',
             'cccc': 500,
         },
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index bafed8488..8de94d249 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -10,23 +10,24 @@ from unittest.mock import ANY, MagicMock, PropertyMock, patch
 
 import arrow
 import pytest
+from pandas import DataFrame
 
 from freqtrade.constants import CANCEL_REASON, MATH_CLOSE_PREC, UNLIMITED_STAKE_AMOUNT
-from freqtrade.enums import RPCMessageType, RunMode, SellType, State
+from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RPCMessageType, RunMode,
+                             SignalDirection, State)
 from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
                                   InvalidOrderException, OperationalException, PricingError,
                                   TemporaryError)
 from freqtrade.freqtradebot import FreqtradeBot
 from freqtrade.persistence import Order, PairLocks, Trade
 from freqtrade.persistence.models import PairLock
-from freqtrade.strategy.interface import SellCheckTuple
 from freqtrade.worker import Worker
 from tests.conftest import (create_mock_trades, get_patched_freqtradebot, get_patched_worker,
                             log_has, log_has_re, patch_edge, patch_exchange, patch_get_signal,
                             patch_wallet, patch_whitelist)
-from tests.conftest_trades import (MOCK_TRADE_COUNT, mock_order_1, mock_order_2, mock_order_2_sell,
-                                   mock_order_3, mock_order_3_sell, mock_order_4,
-                                   mock_order_5_stoploss, mock_order_6_sell)
+from tests.conftest_trades import (MOCK_TRADE_COUNT, enter_side, exit_side, mock_order_1,
+                                   mock_order_2, mock_order_2_sell, mock_order_3, mock_order_3_sell,
+                                   mock_order_4, mock_order_5_stoploss, mock_order_6_sell)
 
 
 def patch_RPCManager(mocker) -> MagicMock:
@@ -42,6 +43,7 @@ def patch_RPCManager(mocker) -> MagicMock:
 
 # Unit tests
 
+
 def test_freqtradebot_state(mocker, default_conf_usdt, markets) -> None:
     mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
@@ -89,12 +91,12 @@ def test_order_dict(default_conf_usdt, mocker, runmode, caplog) -> None:
     conf = default_conf_usdt.copy()
     conf['runmode'] = runmode
     conf['order_types'] = {
-        'buy': 'market',
-        'sell': 'limit',
+        'entry': 'market',
+        'exit': 'limit',
         'stoploss': 'limit',
         'stoploss_on_exchange': True,
     }
-    conf['bid_strategy']['price_side'] = 'ask'
+    conf['entry_pricing']['price_side'] = 'ask'
 
     freqtrade = FreqtradeBot(conf)
     if runmode == RunMode.LIVE:
@@ -106,8 +108,8 @@ def test_order_dict(default_conf_usdt, mocker, runmode, caplog) -> None:
     conf = default_conf_usdt.copy()
     conf['runmode'] = runmode
     conf['order_types'] = {
-        'buy': 'market',
-        'sell': 'limit',
+        'entry': 'market',
+        'exit': 'limit',
         'stoploss': 'limit',
         'stoploss_on_exchange': False,
     }
@@ -192,12 +194,10 @@ def test_edge_overrides_stake_amount(mocker, edge_conf) -> None:
 
 
 @pytest.mark.parametrize('buy_price_mult,ignore_strat_sl', [
-    # Override stoploss
-    (0.79, False),
-    # Override strategy stoploss
-    (0.85, True)
+    (0.79, False),   # Override stoploss
+    (0.85, True),    # Override strategy stoploss
 ])
-def test_edge_overrides_stoploss(limit_buy_order_usdt, fee, caplog, mocker,
+def test_edge_overrides_stoploss(limit_order, fee, caplog, mocker,
                                  buy_price_mult, ignore_strat_sl, edge_conf) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
@@ -207,14 +207,14 @@ def test_edge_overrides_stoploss(limit_buy_order_usdt, fee, caplog, mocker,
     # Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2
     # Thus, if price falls 21%, stoploss should be triggered
     #
-    # mocking the ticker_usdt: price is falling ...
-    buy_price = limit_buy_order_usdt['price']
+    # mocking the ticker: price is falling ...
+    enter_price = limit_order['buy']['price']
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=MagicMock(return_value={
-            'bid': buy_price * buy_price_mult,
-            'ask': buy_price * buy_price_mult,
-            'last': buy_price * buy_price_mult,
+            'bid': enter_price * buy_price_mult,
+            'ask': enter_price * buy_price_mult,
+            'last': enter_price * buy_price_mult,
         }),
         get_fee=fee,
     )
@@ -227,15 +227,16 @@ def test_edge_overrides_stoploss(limit_buy_order_usdt, fee, caplog, mocker,
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
     freqtrade.enter_positions()
     trade = Trade.query.first()
-    oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy')
+    caplog.clear()
+    oobj = Order.parse_from_ccxt_object(limit_order['buy'], 'ADA/USDT', 'buy')
     trade.update_trade(oobj)
     #############################################
 
     # stoploss shoud be hit
     assert freqtrade.handle_trade(trade) is not ignore_strat_sl
     if not ignore_strat_sl:
-        assert log_has_re(r'Executing Sell for NEO/BTC. Reason: stop_loss.*', caplog)
-        assert trade.sell_reason == SellType.STOP_LOSS.value
+        assert log_has_re('Exit for NEO/BTC detected. Reason: stop_loss.*', caplog)
+        assert trade.sell_reason == ExitType.STOP_LOSS.value
 
 
 def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) -> None:
@@ -269,7 +270,12 @@ def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) -
     assert Trade.total_open_trades_stakes() == 120.0
 
 
-def test_create_trade(default_conf_usdt, ticker_usdt, limit_buy_order_usdt, fee, mocker) -> None:
+@pytest.mark.parametrize("is_short,open_rate", [
+    (False, 2.0),
+    (True, 2.2)
+])
+def test_create_trade(default_conf_usdt, ticker_usdt, limit_order,
+                      fee, mocker, is_short, open_rate) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -282,21 +288,23 @@ def test_create_trade(default_conf_usdt, ticker_usdt, limit_buy_order_usdt, fee,
     # Save state of current whitelist
     whitelist = deepcopy(default_conf_usdt['exchange']['pair_whitelist'])
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.create_trade('ETH/USDT')
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade is not None
-    assert trade.stake_amount == 60.0
+    assert pytest.approx(trade.stake_amount) == 60.0
     assert trade.is_open
     assert trade.open_date is not None
     assert trade.exchange == 'binance'
 
     # Simulate fulfilled LIMIT_BUY order for trade
-    oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy')
+    oobj = Order.parse_from_ccxt_object(
+        limit_order[enter_side(is_short)], 'ADA/USDT', enter_side(is_short))
     trade.update_trade(oobj)
 
-    assert trade.open_rate == 2.0
+    assert trade.open_rate == open_rate
     assert trade.amount == 30.0
 
     assert whitelist == default_conf_usdt['exchange']['pair_whitelist']
@@ -318,34 +326,35 @@ def test_create_trade_no_stake_amount(default_conf_usdt, ticker_usdt, fee, mocke
         freqtrade.create_trade('ETH/USDT')
 
 
+@pytest.mark.parametrize("is_short", [False, True])
 @pytest.mark.parametrize('stake_amount,create,amount_enough,max_open_trades', [
     (5.0, True, True, 99),
-    (0.04, True, False, 99),  # Amount will be adjusted to min - which is 0.051
+    (0.049, True, False, 99),  # Amount will be adjusted to min - which is 0.051
     (0, False, True, 99),
     (UNLIMITED_STAKE_AMOUNT, False, True, 0),
 ])
 def test_create_trade_minimal_amount(
-    default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open, fee, mocker,
-    stake_amount, create, amount_enough, max_open_trades, caplog
+    default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker,
+    stake_amount, create, amount_enough, max_open_trades, caplog, is_short
 ) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
-    buy_mock = MagicMock(return_value=limit_buy_order_usdt_open)
+    enter_mock = MagicMock(return_value=limit_order_open[enter_side(is_short)])
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
-        create_order=buy_mock,
+        create_order=enter_mock,
         get_fee=fee,
     )
     default_conf_usdt['max_open_trades'] = max_open_trades
     freqtrade = FreqtradeBot(default_conf_usdt)
     freqtrade.config['stake_amount'] = stake_amount
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     if create:
         assert freqtrade.create_trade('ETH/USDT')
         if amount_enough:
-            rate, amount = buy_mock.call_args[1]['rate'], buy_mock.call_args[1]['amount']
+            rate, amount = enter_mock.call_args[1]['rate'], enter_mock.call_args[1]['amount']
             assert rate * amount <= default_conf_usdt['stake_amount']
         else:
             assert log_has_re(
@@ -415,7 +424,8 @@ def test_enter_positions_global_pairlock(default_conf_usdt, ticker_usdt, limit_b
     assert log_has_re(message, caplog)
 
 
-def test_handle_protections(mocker, default_conf_usdt, fee):
+@pytest.mark.parametrize('is_short', [False, True])
+def test_handle_protections(mocker, default_conf_usdt, fee, is_short):
     default_conf_usdt['protections'] = [
         {"method": "CooldownPeriod", "stop_duration": 60},
         {
@@ -430,7 +440,7 @@ def test_handle_protections(mocker, default_conf_usdt, fee):
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
     freqtrade.protections._protection_handlers[1].global_stop = MagicMock(
         return_value=(True, arrow.utcnow().shift(hours=1).datetime, "asdf"))
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short)
     freqtrade.handle_protections('ETC/BTC')
     send_msg_mock = freqtrade.rpc.send_msg
     assert send_msg_mock.call_count == 2
@@ -449,7 +459,7 @@ def test_create_trade_no_signal(default_conf_usdt, fee, mocker) -> None:
     )
     default_conf_usdt['stake_amount'] = 10
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade, value=(False, False, None, None))
+    patch_get_signal(freqtrade, enter_long=False, exit_long=False)
 
     Trade.query = MagicMock()
     Trade.query.filter = MagicMock()
@@ -515,19 +525,22 @@ def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker,
     assert len(trades) == 4
 
 
-def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_buy_order_usdt,
-                                limit_buy_order_usdt_open, fee, mocker, caplog) -> None:
+@pytest.mark.parametrize('is_short', [False, True])
+def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_order, limit_order_open,
+                                is_short, fee, mocker, caplog
+                                ) -> None:
+    ticker_side = 'ask' if is_short else 'bid'
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
-        create_order=MagicMock(return_value=limit_buy_order_usdt_open),
-        fetch_order=MagicMock(return_value=limit_buy_order_usdt),
+        create_order=MagicMock(return_value=limit_order_open[enter_side(is_short)]),
+        fetch_order=MagicMock(return_value=limit_order[enter_side(is_short)]),
         get_fee=fee,
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     trades = Trade.query.filter(Trade.is_open.is_(True)).all()
     assert not trades
@@ -538,15 +551,16 @@ def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_buy_order_
     assert len(trades) == 1
     trade = trades[0]
     assert trade is not None
-    assert trade.stake_amount == default_conf_usdt['stake_amount']
+    assert pytest.approx(trade.stake_amount) == default_conf_usdt['stake_amount']
     assert trade.is_open
     assert trade.open_date is not None
     assert trade.exchange == 'binance'
-    assert trade.open_rate == 2.0
-    assert trade.amount == 30.0
+    assert trade.open_rate == ticker_usdt.return_value[ticker_side]
+    assert isclose(trade.amount, 60 / ticker_usdt.return_value[ticker_side])
 
     assert log_has(
-        'Buy signal found: about create a new trade for ETH/USDT with stake_amount: 60.0 ...',
+        f'{"Short" if is_short else "Long"} signal found: about create a new trade for ETH/USDT '
+        'with stake_amount: 60.0 ...',
         caplog
     )
 
@@ -670,10 +684,14 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker)
         create_order=MagicMock(side_effect=TemporaryError),
         refresh_latest_ohlcv=refresh_mock,
     )
-    inf_pairs = MagicMock(return_value=[("BTC/ETH", '1m'), ("ETH/USDT", "1h")])
-    mocker.patch(
-        'freqtrade.strategy.interface.IStrategy.get_signal',
-        return_value=(False, False, '', '')
+    inf_pairs = MagicMock(return_value=[
+        ("BTC/ETH", '1m', CandleType.SPOT),
+        ("ETH/USDT", "1h", CandleType.SPOT)
+    ])
+    mocker.patch.multiple(
+        'freqtrade.strategy.interface.IStrategy',
+        get_exit_signal=MagicMock(return_value=(False, False)),
+        get_entry_signal=MagicMock(return_value=(None, None))
     )
     mocker.patch('time.sleep', return_value=None)
 
@@ -684,153 +702,220 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker)
     freqtrade.process()
     assert inf_pairs.call_count == 1
     assert refresh_mock.call_count == 1
-    assert ("BTC/ETH", "1m") in refresh_mock.call_args[0][0]
-    assert ("ETH/USDT", "1h") in refresh_mock.call_args[0][0]
-    assert ("ETH/USDT", default_conf_usdt["timeframe"]) in refresh_mock.call_args[0][0]
+    assert ("BTC/ETH", "1m", CandleType.SPOT) in refresh_mock.call_args[0][0]
+    assert ("ETH/USDT", "1h", CandleType.SPOT) in refresh_mock.call_args[0][0]
+    assert ("ETH/USDT", default_conf_usdt["timeframe"],
+            CandleType.SPOT) in refresh_mock.call_args[0][0]
 
 
-def test_execute_entry(mocker, default_conf_usdt, fee, limit_buy_order_usdt,
-                       limit_buy_order_usdt_open) -> None:
+@pytest.mark.parametrize("is_short,trading_mode,exchange_name,margin_mode,liq_buffer,liq_price", [
+    (False, 'spot', 'binance', None, 0.0, None),
+    (True, 'spot', 'binance', None, 0.0, None),
+    (False, 'spot', 'gateio', None, 0.0, None),
+    (True, 'spot', 'gateio', None, 0.0, None),
+    (False, 'spot', 'okx', None, 0.0, None),
+    (True, 'spot', 'okx', None, 0.0, None),
+    (True, 'futures', 'binance', 'isolated', 0.0, 11.89108910891089),
+    (False, 'futures', 'binance', 'isolated', 0.0, 8.070707070707071),
+    (True, 'futures', 'gateio', 'isolated', 0.0, 11.87413417771621),
+    (False, 'futures', 'gateio', 'isolated', 0.0, 8.085708510208207),
+    (True, 'futures', 'binance', 'isolated', 0.05, 11.796534653465345),
+    (False, 'futures', 'binance', 'isolated', 0.05, 8.167171717171717),
+    (True, 'futures', 'gateio', 'isolated', 0.05, 11.7804274688304),
+    (False, 'futures', 'gateio', 'isolated', 0.05, 8.181423084697796),
+    (True, 'futures', 'okx', 'isolated', 0.0, 11.87413417771621),
+    (False, 'futures', 'okx', 'isolated', 0.0, 8.085708510208207),
+])
+def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
+                       limit_order_open, is_short, trading_mode,
+                       exchange_name, margin_mode, liq_buffer, liq_price) -> None:
+    """
+    exchange_name = binance, is_short = true
+        leverage = 5
+        position = 0.2 * 5
+        ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
+        ((2 + 0.01) - ((-1) * 1 * 10)) / ((1 * 0.01) - ((-1) * 1)) = 11.89108910891089
+
+    exchange_name = binance, is_short = false
+        ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
+        ((2 + 0.01) - (1 * 1 * 10)) / ((1 * 0.01) - (1 * 1)) = 8.070707070707071
+
+    exchange_name = gateio/okx, is_short = true
+        (open_rate + (wallet_balance / position)) / (1 + (mm_ratio + taker_fee_rate))
+        (10 + (2 / 1)) / (1 + (0.01 + 0.0006)) = 11.87413417771621
+
+    exchange_name = gateio/okx, is_short = false
+        (open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate))
+        (10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207
+    """
+    # TODO: Split this test into multiple tests to improve readability
+    open_order = limit_order_open[enter_side(is_short)]
+    order = limit_order[enter_side(is_short)]
+    default_conf_usdt['trading_mode'] = trading_mode
+    default_conf_usdt['liquidation_buffer'] = liq_buffer
+    leverage = 1.0 if trading_mode == 'spot' else 5.0
+    default_conf_usdt['exchange']['name'] = exchange_name
+    if margin_mode:
+        default_conf_usdt['margin_mode'] = margin_mode
+    mocker.patch('freqtrade.exchange.Gateio.validate_ordertypes')
     patch_RPCManager(mocker)
-    patch_exchange(mocker)
+    patch_exchange(mocker, id=exchange_name)
     freqtrade = FreqtradeBot(default_conf_usdt)
     freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False)
+    freqtrade.strategy.leverage = MagicMock(return_value=leverage)
     stake_amount = 2
     bid = 0.11
-    buy_rate_mock = MagicMock(return_value=bid)
-    buy_mm = MagicMock(return_value=limit_buy_order_usdt_open)
+    enter_rate_mock = MagicMock(return_value=bid)
+    enter_mm = MagicMock(return_value=open_order)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
-        get_rate=buy_rate_mock,
+        get_rate=enter_rate_mock,
         fetch_ticker=MagicMock(return_value={
             'bid': 1.9,
             'ask': 2.2,
             'last': 1.9
         }),
-        create_order=buy_mm,
+        create_order=enter_mm,
         get_min_pair_stake_amount=MagicMock(return_value=1),
+        get_max_pair_stake_amount=MagicMock(return_value=500000),
         get_fee=fee,
+        get_funding_fees=MagicMock(return_value=0),
+        name=exchange_name,
+        get_maintenance_ratio_and_amt=MagicMock(return_value=(0.01, 0.01)),
+        get_max_leverage=MagicMock(return_value=10),
+    )
+    mocker.patch.multiple(
+        'freqtrade.exchange.Okx',
+        get_max_pair_stake_amount=MagicMock(return_value=500000),
     )
     pair = 'ETH/USDT'
 
-    assert not freqtrade.execute_entry(pair, stake_amount)
-    assert buy_rate_mock.call_count == 1
-    assert buy_mm.call_count == 0
+    assert not freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
+    assert enter_rate_mock.call_count == 1
+    assert enter_mm.call_count == 0
     assert freqtrade.strategy.confirm_trade_entry.call_count == 1
-    buy_rate_mock.reset_mock()
+    enter_rate_mock.reset_mock()
 
-    limit_buy_order_usdt_open['id'] = '22'
+    open_order['id'] = '22'
     freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
     assert freqtrade.execute_entry(pair, stake_amount)
-    assert buy_rate_mock.call_count == 1
-    assert buy_mm.call_count == 1
-    call_args = buy_mm.call_args_list[0][1]
+    assert enter_rate_mock.call_count == 1
+    assert enter_mm.call_count == 1
+    call_args = enter_mm.call_args_list[0][1]
     assert call_args['pair'] == pair
     assert call_args['rate'] == bid
-    assert call_args['amount'] == stake_amount / bid
-    buy_rate_mock.reset_mock()
+    assert pytest.approx(call_args['amount']) == round(stake_amount / bid * leverage, 8)
+    enter_rate_mock.reset_mock()
 
     # Should create an open trade with an open order id
     # As the order is not fulfilled yet
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
     assert trade.is_open is True
     assert trade.open_order_id == '22'
 
     # Test calling with price
-    limit_buy_order_usdt_open['id'] = '33'
+    open_order['id'] = '33'
     fix_price = 0.06
-    assert freqtrade.execute_entry(pair, stake_amount, fix_price)
+    assert freqtrade.execute_entry(pair, stake_amount, fix_price, is_short=is_short)
     # Make sure get_rate wasn't called again
-    assert buy_rate_mock.call_count == 0
+    assert enter_rate_mock.call_count == 0
 
-    assert buy_mm.call_count == 2
-    call_args = buy_mm.call_args_list[1][1]
+    assert enter_mm.call_count == 2
+    call_args = enter_mm.call_args_list[1][1]
     assert call_args['pair'] == pair
     assert call_args['rate'] == fix_price
-    assert call_args['amount'] == stake_amount / fix_price
+    assert pytest.approx(call_args['amount']) == round(stake_amount / fix_price * leverage, 8)
 
     # In case of closed order
-    limit_buy_order_usdt['status'] = 'closed'
-    limit_buy_order_usdt['price'] = 10
-    limit_buy_order_usdt['cost'] = 100
-    limit_buy_order_usdt['id'] = '444'
+    order['status'] = 'closed'
+    order['price'] = 10
+    order['cost'] = 100
+    order['id'] = '444'
 
     mocker.patch('freqtrade.exchange.Exchange.create_order',
-                 MagicMock(return_value=limit_buy_order_usdt))
-    assert freqtrade.execute_entry(pair, stake_amount)
+                 MagicMock(return_value=order))
+    assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
     trade = Trade.query.all()[2]
+    trade.is_short = is_short
     assert trade
     assert trade.open_order_id is None
     assert trade.open_rate == 10
-    assert trade.stake_amount == 100
+    assert trade.stake_amount == round(order['price'] * order['filled'] / leverage, 8)
 
     # In case of rejected or expired order and partially filled
-    limit_buy_order_usdt['status'] = 'expired'
-    limit_buy_order_usdt['amount'] = 30.0
-    limit_buy_order_usdt['filled'] = 20.0
-    limit_buy_order_usdt['remaining'] = 10.00
-    limit_buy_order_usdt['price'] = 0.5
-    limit_buy_order_usdt['cost'] = 15.0
-    limit_buy_order_usdt['id'] = '555'
+    order['status'] = 'expired'
+    order['amount'] = 30.0
+    order['filled'] = 20.0
+    order['remaining'] = 10.00
+    order['price'] = 0.5
+    order['cost'] = 15.0
+    order['id'] = '555'
     mocker.patch('freqtrade.exchange.Exchange.create_order',
-                 MagicMock(return_value=limit_buy_order_usdt))
+                 MagicMock(return_value=order))
     assert freqtrade.execute_entry(pair, stake_amount)
     trade = Trade.query.all()[3]
+    trade.is_short = is_short
     assert trade
     assert trade.open_order_id == '555'
     assert trade.open_rate == 0.5
-    assert trade.stake_amount == 15.0
+    assert trade.stake_amount == round(order['price'] * order['filled'] / leverage, 8)
 
     # Test with custom stake
-    limit_buy_order_usdt['status'] = 'open'
-    limit_buy_order_usdt['id'] = '556'
+    order['status'] = 'open'
+    order['id'] = '556'
 
     freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0
-    assert freqtrade.execute_entry(pair, stake_amount)
+    assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
     trade = Trade.query.all()[4]
+    trade.is_short = is_short
     assert trade
-    assert trade.stake_amount == 150
+    assert pytest.approx(trade.stake_amount) == 150
 
     # Exception case
-    limit_buy_order_usdt['id'] = '557'
+    order['id'] = '557'
     freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
-    assert freqtrade.execute_entry(pair, stake_amount)
+    assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
     trade = Trade.query.all()[5]
+    trade.is_short = is_short
     assert trade
-    assert trade.stake_amount == 2.0
+    assert pytest.approx(trade.stake_amount) == 2.0
 
     # In case of the order is rejected and not filled at all
-    limit_buy_order_usdt['status'] = 'rejected'
-    limit_buy_order_usdt['amount'] = 30.0
-    limit_buy_order_usdt['filled'] = 0.0
-    limit_buy_order_usdt['remaining'] = 30.0
-    limit_buy_order_usdt['price'] = 0.5
-    limit_buy_order_usdt['cost'] = 0.0
-    limit_buy_order_usdt['id'] = '66'
+    order['status'] = 'rejected'
+    order['amount'] = 30.0 * leverage
+    order['filled'] = 0.0
+    order['remaining'] = 30.0
+    order['price'] = 0.5
+    order['cost'] = 0.0
+    order['id'] = '66'
     mocker.patch('freqtrade.exchange.Exchange.create_order',
-                 MagicMock(return_value=limit_buy_order_usdt))
+                 MagicMock(return_value=order))
     assert not freqtrade.execute_entry(pair, stake_amount)
+    assert freqtrade.strategy.leverage.call_count == 0 if trading_mode == 'spot' else 2
 
     # Fail to get price...
     mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0))
 
-    with pytest.raises(PricingError, match="Could not determine buy price."):
-        freqtrade.execute_entry(pair, stake_amount)
+    with pytest.raises(PricingError, match="Could not determine entry price."):
+        freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
 
     # In case of custom entry price
     mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50)
-    limit_buy_order_usdt['status'] = 'open'
-    limit_buy_order_usdt['id'] = '5566'
+    order['status'] = 'open'
+    order['id'] = '5566'
     freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508
-    assert freqtrade.execute_entry(pair, stake_amount)
+    assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
     trade = Trade.query.all()[6]
+    trade.is_short = is_short
     assert trade
     assert trade.open_rate_requested == 0.508
 
     # In case of custom entry price set to None
-    limit_buy_order_usdt['status'] = 'open'
-    limit_buy_order_usdt['id'] = '5567'
+
+    order['status'] = 'open'
+    order['id'] = '5567'
     freqtrade.strategy.custom_entry_price = lambda **kwargs: None
 
     mocker.patch.multiple(
@@ -838,22 +923,48 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_buy_order_usdt,
         get_rate=MagicMock(return_value=10),
     )
 
-    assert freqtrade.execute_entry(pair, stake_amount)
+    assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
     trade = Trade.query.all()[7]
+    trade.is_short = is_short
     assert trade
     assert trade.open_rate_requested == 10
 
     # In case of custom entry price not float type
-    limit_buy_order_usdt['status'] = 'open'
-    limit_buy_order_usdt['id'] = '5568'
+    freqtrade.exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01))
+    freqtrade.exchange.name = exchange_name
+    order['status'] = 'open'
+    order['id'] = '5568'
     freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price"
-    assert freqtrade.execute_entry(pair, stake_amount)
+    assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
     trade = Trade.query.all()[8]
+    # Trade(id=9, pair=ETH/USDT, amount=0.20000000, is_short=False,
+    #   leverage=1.0, open_rate=10.00000000, open_since=...)
+    # Trade(id=9, pair=ETH/USDT, amount=0.60000000, is_short=True,
+    #   leverage=3.0, open_rate=10.00000000, open_since=...)
+    trade.is_short = is_short
     assert trade
     assert trade.open_rate_requested == 10
+    assert trade.liquidation_price == liq_price
+
+    # In case of too high stake amount
+
+    order['status'] = 'open'
+    order['id'] = '55672'
+
+    mocker.patch.multiple(
+        'freqtrade.exchange.Exchange',
+        get_max_pair_stake_amount=MagicMock(return_value=500),
+    )
+    freqtrade.exchange.get_max_pair_stake_amount = MagicMock(return_value=500)
+
+    assert freqtrade.execute_entry(pair, 2000, is_short=is_short)
+    trade = Trade.query.all()[9]
+    trade.is_short = is_short
+    assert pytest.approx(trade.stake_amount) == 500
 
 
-def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_buy_order_usdt) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order, is_short) -> None:
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -862,7 +973,7 @@ def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_buy_o
             'ask': 2.2,
             'last': 1.9
         }),
-        create_order=MagicMock(return_value=limit_buy_order_usdt),
+        create_order=MagicMock(return_value=limit_order[enter_side(is_short)]),
         get_rate=MagicMock(return_value=0.11),
         get_min_pair_stake_amount=MagicMock(return_value=1),
         get_fee=fee,
@@ -873,11 +984,11 @@ def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_buy_o
     freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=ValueError)
     assert freqtrade.execute_entry(pair, stake_amount)
 
-    limit_buy_order_usdt['id'] = '222'
+    limit_order[enter_side(is_short)]['id'] = '222'
     freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=Exception)
     assert freqtrade.execute_entry(pair, stake_amount)
 
-    limit_buy_order_usdt['id'] = '2223'
+    limit_order[enter_side(is_short)]['id'] = '2223'
     freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
     assert freqtrade.execute_entry(pair, stake_amount)
 
@@ -885,14 +996,46 @@ def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_buy_o
     assert not freqtrade.execute_entry(pair, stake_amount)
 
 
-def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_buy_order_usdt) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_execute_entry_min_leverage(mocker, default_conf_usdt, fee, limit_order, is_short) -> None:
+    default_conf_usdt['trading_mode'] = 'futures'
+    default_conf_usdt['margin_mode'] = 'isolated'
+    freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
+    mocker.patch.multiple(
+        'freqtrade.exchange.Exchange',
+        fetch_ticker=MagicMock(return_value={
+            'bid': 1.9,
+            'ask': 2.2,
+            'last': 1.9
+        }),
+        create_order=MagicMock(return_value=limit_order[enter_side(is_short)]),
+        get_rate=MagicMock(return_value=0.11),
+        # Minimum stake-amount is ~5$
+        get_maintenance_ratio_and_amt=MagicMock(return_value=(0.0, 0.0)),
+        _fetch_and_calculate_funding_fees=MagicMock(return_value=0),
+        get_fee=fee,
+        get_max_leverage=MagicMock(return_value=5.0),
+    )
+    stake_amount = 2
+    pair = 'SOL/BUSD:BUSD'
+    freqtrade.strategy.leverage = MagicMock(return_value=5.0)
+
+    assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
+    trade = Trade.query.first()
+    assert trade.leverage == 5.0
+    # assert trade.stake_amount == 2
+
+
+@pytest.mark.parametrize("is_short", [False, True])
+def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_short) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
+    order = limit_order[enter_side(is_short)]
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
-    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt)
+    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
-                 return_value=limit_buy_order_usdt['amount'])
+                 return_value=order['amount'])
 
     stoploss = MagicMock(return_value={'id': 13434334})
     mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
@@ -901,6 +1044,7 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_buy_order_usd
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
 
     trade = MagicMock()
+    trade.is_short = is_short
     trade.open_order_id = None
     trade.stoploss_order_id = None
     trade.is_open = True
@@ -912,9 +1056,12 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_buy_order_usd
     assert trade.is_open is True
 
 
-def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
-                                     limit_buy_order_usdt, limit_sell_order_usdt) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_short,
+                                     limit_order) -> None:
     stoploss = MagicMock(return_value={'id': 13434334})
+    enter_order = limit_order[enter_side(is_short)]
+    exit_order = limit_order[exit_side(is_short)]
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -925,20 +1072,20 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
             'last': 1.9
         }),
         create_order=MagicMock(side_effect=[
-            {'id': limit_buy_order_usdt['id']},
-            limit_sell_order_usdt,
-            # {'id': limit_sell_order_usdt['id']},
+            {'id': enter_order['id']},
+            exit_order,
         ]),
         get_fee=fee,
         stoploss=stoploss
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # First case: when stoploss is not yet set but the order is open
     # should get the stoploss order id immediately
     # and should return false as no trade actually happened
     trade = MagicMock()
+    trade.is_short = is_short
     trade.is_open = True
     trade.open_order_id = None
     trade.stoploss_order_id = None
@@ -980,6 +1127,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
     caplog.clear()
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
     trade.open_order_id = None
     trade.stoploss_order_id = "100"
@@ -997,7 +1145,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
         'type': 'stop_loss_limit',
         'price': 3,
         'average': 2,
-        'amount': limit_buy_order_usdt['amount'],
+        'amount': enter_order['amount'],
     })
     mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hit)
     assert freqtrade.handle_stoploss_on_exchange(trade) is True
@@ -1043,7 +1191,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
         'type': 'stop_loss_limit',
         'price': 3,
         'average': 2,
-        'amount': limit_buy_order_usdt['amount'],
+        'amount': enter_order['amount'],
         'info': {'stopPrice': 22},
     }])
     trade.stoploss_order_id = 100
@@ -1060,12 +1208,15 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog,
     assert freqtrade.handle_stoploss_on_exchange(trade) is False
     assert trade.stoploss_order_id is None
     assert trade.is_open is False
-    assert trade.sell_reason == str(SellType.EMERGENCY_SELL)
+    assert trade.sell_reason == str(ExitType.EMERGENCY_SELL)
 
 
-def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
-                                         limit_buy_order_usdt, limit_sell_order_usdt) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, is_short,
+                                         limit_order) -> None:
     # Sixth case: stoploss order was cancelled but couldn't create new one
+    enter_order = limit_order[enter_side(is_short)]
+    exit_order = limit_order[exit_side(is_short)]
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -1076,8 +1227,8 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
             'last': 1.9
         }),
         create_order=MagicMock(side_effect=[
-            {'id': limit_buy_order_usdt['id']},
-            {'id': limit_sell_order_usdt['id']},
+            {'id': enter_order['id']},
+            {'id': exit_order['id']},
         ]),
         get_fee=fee,
     )
@@ -1087,10 +1238,11 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
         stoploss=MagicMock(side_effect=ExchangeError()),
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
     trade.open_order_id = None
     trade.stoploss_order_id = 100
@@ -1102,13 +1254,17 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
     assert trade.is_open is True
 
 
-def test_create_stoploss_order_invalid_order(mocker, default_conf_usdt, caplog, fee,
-                                             limit_buy_order_usdt_open, limit_sell_order_usdt):
+@pytest.mark.parametrize("is_short", [False, True])
+def test_create_stoploss_order_invalid_order(
+    mocker, default_conf_usdt, caplog, fee, is_short, limit_order, limit_order_open
+):
+    open_order = limit_order_open[enter_side(is_short)]
+    order = limit_order[exit_side(is_short)]
     rpc_mock = patch_RPCManager(mocker)
     patch_exchange(mocker)
     create_order_mock = MagicMock(side_effect=[
-        limit_buy_order_usdt_open,
-        {'id': limit_sell_order_usdt['id']}
+        open_order,
+        {'id': order['id']}
     ])
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -1126,15 +1282,16 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf_usdt, caplog,
         stoploss=MagicMock(side_effect=InvalidOrderException()),
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
 
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    trade.is_short = is_short
     caplog.clear()
     freqtrade.create_stoploss_order(trade, 200)
     assert trade.stoploss_order_id is None
-    assert trade.sell_reason == SellType.EMERGENCY_SELL.value
+    assert trade.sell_reason == ExitType.EMERGENCY_SELL.value
     assert log_has("Unable to place a stoploss order on exchange. ", caplog)
     assert log_has("Exiting the trade forcefully", caplog)
 
@@ -1146,13 +1303,15 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf_usdt, caplog,
 
     # Rpc is sending first buy, then sell
     assert rpc_mock.call_count == 2
-    assert rpc_mock.call_args_list[1][0][0]['sell_reason'] == SellType.EMERGENCY_SELL.value
+    assert rpc_mock.call_args_list[1][0][0]['sell_reason'] == ExitType.EMERGENCY_SELL.value
     assert rpc_mock.call_args_list[1][0][0]['order_type'] == 'market'
 
 
-def test_create_stoploss_order_insufficient_funds(mocker, default_conf_usdt, caplog, fee,
-                                                  limit_buy_order_usdt_open, limit_sell_order_usdt):
-    sell_mock = MagicMock(return_value={'id': limit_sell_order_usdt['id']})
+@pytest.mark.parametrize("is_short", [False, True])
+def test_create_stoploss_order_insufficient_funds(
+    mocker, default_conf_usdt, caplog, fee, limit_order, is_short
+):
+    exit_order = limit_order[exit_side(is_short)]['id']
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
 
     mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
@@ -1164,8 +1323,8 @@ def test_create_stoploss_order_insufficient_funds(mocker, default_conf_usdt, cap
             'last': 1.9
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
-            sell_mock,
+            limit_order[enter_side(is_short)],
+            exit_order,
         ]),
         get_fee=fee,
         fetch_order=MagicMock(return_value={'status': 'canceled'}),
@@ -1174,11 +1333,12 @@ def test_create_stoploss_order_insufficient_funds(mocker, default_conf_usdt, cap
         'freqtrade.exchange.Binance',
         stoploss=MagicMock(side_effect=InsufficientFundsError()),
     )
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
 
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    trade.is_short = is_short
     caplog.clear()
     freqtrade.create_stoploss_order(trade, 200)
     # stoploss_orderid was empty before
@@ -1193,10 +1353,17 @@ def test_create_stoploss_order_insufficient_funds(mocker, default_conf_usdt, cap
     assert mock_insuf.call_count == 1
 
 
+@pytest.mark.parametrize("is_short,bid,ask,stop_price,amt,hang_price", [
+    (False, [4.38, 4.16], [4.4, 4.17], ['2.0805', 4.4 * 0.95], 27.39726027, 3),
+    (True, [1.09, 1.21], [1.1, 1.22], ['2.321', 1.09 * 1.05], 27.27272727, 1.5),
+])
 @pytest.mark.usefixtures("init_persistence")
-def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
-                                              limit_buy_order_usdt, limit_sell_order_usdt) -> None:
+def test_handle_stoploss_on_exchange_trailing(
+    mocker, default_conf_usdt, fee, is_short, bid, ask, limit_order, stop_price, amt, hang_price
+) -> None:
     # When trailing stoploss is set
+    enter_order = limit_order[enter_side(is_short)]
+    exit_order = limit_order[exit_side(is_short)]
     stoploss = MagicMock(return_value={'id': 13434334})
     patch_RPCManager(mocker)
     mocker.patch.multiple(
@@ -1204,11 +1371,11 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
         fetch_ticker=MagicMock(return_value={
             'bid': 2.19,
             'ask': 2.2,
-            'last': 2.19
+            'last': 2.19,
         }),
         create_order=MagicMock(side_effect=[
-            {'id': limit_buy_order_usdt['id']},
-            {'id': limit_sell_order_usdt['id']},
+            {'id': enter_order['id']},
+            {'id': exit_order['id']},
         ]),
         get_fee=fee,
     )
@@ -1230,15 +1397,16 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
 
     # setting stoploss
-    freqtrade.strategy.stoploss = -0.05
+    freqtrade.strategy.stoploss = 0.05 if is_short else -0.05
 
     # setting stoploss_on_exchange_interval to 60 seconds
     freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
 
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
     trade.open_order_id = None
     trade.stoploss_order_id = 100
@@ -1247,10 +1415,10 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
         'id': 100,
         'status': 'open',
         'type': 'stop_loss_limit',
-        'price': 3,
+        'price': hang_price,
         'average': 2,
         'info': {
-            'stopPrice': '2.0805'
+            'stopPrice': stop_price[0]
         }
     })
 
@@ -1264,9 +1432,9 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
     mocker.patch(
         'freqtrade.exchange.Exchange.fetch_ticker',
         MagicMock(return_value={
-            'bid': 4.38,
-            'ask': 4.4,
-            'last': 4.38
+            'bid': bid[0],
+            'ask': ask[0],
+            'last': bid[0],
         })
     )
 
@@ -1282,7 +1450,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
     stoploss_order_mock.assert_not_called()
 
     assert freqtrade.handle_trade(trade) is False
-    assert trade.stop_loss == 4.4 * 0.95
+    assert trade.stop_loss == stop_price[1]
 
     # setting stoploss_on_exchange_interval to 0 seconds
     freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
@@ -1291,27 +1459,32 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee,
 
     cancel_order_mock.assert_called_once_with(100, 'ETH/USDT')
     stoploss_order_mock.assert_called_once_with(
-        amount=pytest.approx(27.39726027),
+        amount=pytest.approx(amt),
         pair='ETH/USDT',
         order_types=freqtrade.strategy.order_types,
-        stop_price=4.4 * 0.95
+        stop_price=stop_price[1],
+        side=exit_side(is_short),
+        leverage=1.0
     )
 
     # price fell below stoploss, so dry-run sells trade.
     mocker.patch(
         'freqtrade.exchange.Exchange.fetch_ticker',
         MagicMock(return_value={
-            'bid': 4.16,
-            'ask': 4.17,
-            'last': 4.16
+            'bid': bid[1],
+            'ask': ask[1],
+            'last': bid[1],
         })
     )
     assert freqtrade.handle_trade(trade) is True
 
 
+@pytest.mark.parametrize("is_short", [False, True])
 def test_handle_stoploss_on_exchange_trailing_error(
-        mocker, default_conf_usdt, fee, caplog, limit_buy_order_usdt, limit_sell_order_usdt
+    mocker, default_conf_usdt, fee, caplog, limit_order, is_short
 ) -> None:
+    enter_order = limit_order[enter_side(is_short)]
+    exit_order = limit_order[exit_side(is_short)]
     # When trailing stoploss is set
     stoploss = MagicMock(return_value={'id': 13434334})
     patch_exchange(mocker)
@@ -1324,8 +1497,8 @@ def test_handle_stoploss_on_exchange_trailing_error(
             'last': 1.9
         }),
         create_order=MagicMock(side_effect=[
-            {'id': limit_buy_order_usdt['id']},
-            {'id': limit_sell_order_usdt['id']},
+            {'id': enter_order['id']},
+            {'id': exit_order['id']},
         ]),
         get_fee=fee,
     )
@@ -1343,18 +1516,20 @@ def test_handle_stoploss_on_exchange_trailing_error(
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
 
     # setting stoploss
-    freqtrade.strategy.stoploss = -0.05
+    freqtrade.strategy.stoploss = 0.05 if is_short else -0.05
 
     # setting stoploss_on_exchange_interval to 60 seconds
     freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
     trade.open_order_id = None
     trade.stoploss_order_id = "abcd"
     trade.stop_loss = 0.2
     trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime.replace(tzinfo=None)
+    trade.is_short = is_short
 
     stoploss_order_hanging = {
         'id': "abcd",
@@ -1411,9 +1586,13 @@ def test_stoploss_on_exchange_price_rounding(
     assert adjust_mock.call_args_list[0][0][0] == 222
 
 
+@pytest.mark.parametrize("is_short", [False, True])
 @pytest.mark.usefixtures("init_persistence")
 def test_handle_stoploss_on_exchange_custom_stop(
-        mocker, default_conf_usdt, fee, limit_buy_order_usdt, limit_sell_order_usdt) -> None:
+    mocker, default_conf_usdt, fee, is_short, limit_order
+) -> None:
+    enter_order = limit_order[enter_side(is_short)]
+    exit_order = limit_order[exit_side(is_short)]
     # When trailing stoploss is set
     stoploss = MagicMock(return_value={'id': 13434334})
     patch_RPCManager(mocker)
@@ -1425,8 +1604,8 @@ def test_handle_stoploss_on_exchange_custom_stop(
             'last': 1.9
         }),
         create_order=MagicMock(side_effect=[
-            {'id': limit_buy_order_usdt['id']},
-            {'id': limit_sell_order_usdt['id']},
+            {'id': enter_order['id']},
+            {'id': exit_order['id']},
         ]),
         get_fee=fee,
     )
@@ -1453,10 +1632,11 @@ def test_handle_stoploss_on_exchange_custom_stop(
     # setting stoploss_on_exchange_interval to 60 seconds
     freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
 
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
     trade.open_order_id = None
     trade.stoploss_order_id = 100
@@ -1481,9 +1661,9 @@ def test_handle_stoploss_on_exchange_custom_stop(
     mocker.patch(
         'freqtrade.exchange.Exchange.fetch_ticker',
         MagicMock(return_value={
-            'bid': 4.38,
-            'ask': 4.4,
-            'last': 4.38
+            'bid': 4.38 if not is_short else 1.9 / 2,
+            'ask': 4.4 if not is_short else 2.2 / 2,
+            'last': 4.38 if not is_short else 1.9 / 2,
         })
     )
 
@@ -1499,8 +1679,8 @@ def test_handle_stoploss_on_exchange_custom_stop(
     stoploss_order_mock.assert_not_called()
 
     assert freqtrade.handle_trade(trade) is False
-    assert trade.stop_loss == 4.4 * 0.96
-    assert trade.stop_loss_pct == -0.04
+    assert trade.stop_loss == 4.4 * 0.96 if not is_short else 1.1
+    assert trade.stop_loss_pct == -0.04 if not is_short else 0.04
 
     # setting stoploss_on_exchange_interval to 0 seconds
     freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
@@ -1508,11 +1688,14 @@ def test_handle_stoploss_on_exchange_custom_stop(
     assert freqtrade.handle_stoploss_on_exchange(trade) is False
 
     cancel_order_mock.assert_called_once_with(100, 'ETH/USDT')
+    # Long uses modified ask - offset, short modified bid + offset
     stoploss_order_mock.assert_called_once_with(
-        amount=pytest.approx(31.57894736),
+        amount=pytest.approx(trade.amount),
         pair='ETH/USDT',
         order_types=freqtrade.strategy.order_types,
-        stop_price=4.4 * 0.96
+        stop_price=4.4 * 0.96 if not is_short else 0.95 * 1.04,
+        side=exit_side(is_short),
+        leverage=1.0
     )
 
     # price fell below stoploss, so dry-run sells trade.
@@ -1527,8 +1710,11 @@ def test_handle_stoploss_on_exchange_custom_stop(
     assert freqtrade.handle_trade(trade) is True
 
 
-def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee,
-                                              limit_buy_order_usdt, limit_sell_order_usdt) -> None:
+def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
+                                              limit_order) -> None:
+
+    enter_order = limit_order['buy']
+    exit_order = limit_order['sell']
 
     # When trailing stoploss is set
     stoploss = MagicMock(return_value={'id': 13434334})
@@ -1546,8 +1732,8 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee,
             'last': 2.19
         }),
         create_order=MagicMock(side_effect=[
-            {'id': limit_buy_order_usdt['id']},
-            {'id': limit_sell_order_usdt['id']},
+            {'id': enter_order['id']},
+            {'id': exit_order['id']},
         ]),
         get_fee=fee,
         stoploss=stoploss,
@@ -1637,12 +1823,14 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee,
         amount=pytest.approx(11.41438356),
         pair='NEO/BTC',
         order_types=freqtrade.strategy.order_types,
-        stop_price=4.4 * 0.99
+        stop_price=4.4 * 0.99,
+        side='sell',
+        leverage=1.0
     )
 
 
 @pytest.mark.parametrize('return_value,side_effect,log_message', [
-    (False, None, 'Found no buy signals for whitelisted currencies. Trying again...'),
+    (False, None, 'Found no enter signals for whitelisted currencies. Trying again...'),
     (None, DependencyException, 'Unable to create trade for ETH/USDT: ')
 ])
 def test_enter_positions(mocker, default_conf_usdt, return_value, side_effect,
@@ -1664,11 +1852,43 @@ def test_enter_positions(mocker, default_conf_usdt, return_value, side_effect,
     assert mock_ct.call_count == len(default_conf_usdt['exchange']['pair_whitelist'])
 
 
-def test_exit_positions_exception(mocker, default_conf_usdt, limit_buy_order_usdt, caplog) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog) -> None:
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
-    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt)
+
+    mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
+    mocker.patch('freqtrade.exchange.Exchange.fetch_order',
+                 return_value=limit_order[enter_side(is_short)])
+    mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
+    mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
+                 return_value=limit_order[enter_side(is_short)]['amount'])
 
     trade = MagicMock()
+    trade.is_short = is_short
+    trade.open_order_id = '123'
+    trade.open_fee = 0.001
+    trades = [trade]
+    n = freqtrade.exit_positions(trades)
+    assert n == 0
+    # Test amount not modified by fee-logic
+    assert not log_has(
+        'Applying fee to amount for Trade {} from 30.0 to 90.81'.format(trade), caplog
+    )
+
+    mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
+    # test amount modified by fee-logic
+    n = freqtrade.exit_positions(trades)
+    assert n == 0
+
+
+@pytest.mark.parametrize("is_short", [False, True])
+def test_exit_positions_exception(mocker, default_conf_usdt, limit_order, caplog, is_short) -> None:
+    freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
+    order = limit_order[enter_side(is_short)]
+    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
+
+    trade = MagicMock()
+    trade.is_short = is_short
     trade.open_order_id = None
     trade.pair = 'ETH/USDT'
     trades = [trade]
@@ -1678,20 +1898,24 @@ def test_exit_positions_exception(mocker, default_conf_usdt, limit_buy_order_usd
         'freqtrade.freqtradebot.FreqtradeBot.handle_trade',
         side_effect=DependencyException()
     )
+    caplog.clear()
     n = freqtrade.exit_positions(trades)
     assert n == 0
-    assert log_has('Unable to sell trade ETH/USDT: ', caplog)
+    assert log_has('Unable to exit trade ETH/USDT: ', caplog)
 
 
-def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, caplog) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, caplog) -> None:
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
+    order = limit_order[enter_side(is_short)]
 
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
-    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt)
+    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
-                 return_value=limit_buy_order_usdt['amount'])
-    order_id = limit_buy_order_usdt['id']
+                 return_value=order['amount'])
+    order_id = order['id']
+
     trade = Trade(
         open_order_id=order_id,
         fee_open=0.001,
@@ -1700,9 +1924,11 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, cap
         open_date=arrow.utcnow().datetime,
         amount=11,
         exchange="binance",
+        is_short=is_short,
+        leverage=1,
     )
     trade.orders.append(Order(
-        ft_order_side='buy',
+        ft_order_side=enter_side(is_short),
         price=0.01,
         order_id=order_id,
 
@@ -1716,7 +1942,7 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, cap
     assert not log_has_re(r'Applying fee to .*', caplog)
     caplog.clear()
     assert trade.open_order_id is None
-    assert trade.amount == limit_buy_order_usdt['amount']
+    assert trade.amount == order['amount']
 
     trade.open_order_id = order_id
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
@@ -1732,9 +1958,9 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, cap
     freqtrade.update_trade_state(trade, order_id)
 
     assert log_has_re('Found open order for.*', caplog)
-    limit_buy_order_usdt_new = deepcopy(limit_buy_order_usdt)
+    limit_buy_order_usdt_new = deepcopy(limit_order)
     limit_buy_order_usdt_new['filled'] = 0.0
-    limit_buy_order_usdt_new['statuss'] = 'canceled'
+    limit_buy_order_usdt_new['status'] = 'canceled'
 
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', side_effect=ValueError)
     mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt_new)
@@ -1743,15 +1969,19 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, cap
     assert res is True
 
 
+@pytest.mark.parametrize("is_short", [False, True])
 @pytest.mark.parametrize('initial_amount,has_rounding_fee', [
     (30.0 + 1e-14, True),
     (8.0, False)
 ])
-def test_update_trade_state_withorderdict(default_conf_usdt, trades_for_order, limit_buy_order_usdt,
-                                          fee, mocker, initial_amount, has_rounding_fee, caplog):
+def test_update_trade_state_withorderdict(
+    default_conf_usdt, trades_for_order, limit_order, fee, mocker, initial_amount,
+    has_rounding_fee, is_short, caplog
+):
+    order = limit_order[enter_side(is_short)]
     trades_for_order[0]['amount'] = initial_amount
     order_id = "oid_123456"
-    limit_buy_order_usdt['id'] = order_id
+    order['id'] = order_id
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
     # fetch_order should not be called!!
     mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError))
@@ -1769,30 +1999,34 @@ def test_update_trade_state_withorderdict(default_conf_usdt, trades_for_order, l
         fee_close=fee.return_value,
         open_order_id=order_id,
         is_open=True,
+        leverage=1,
+        is_short=is_short,
     )
     trade.orders.append(
         Order(
-            ft_order_side='buy',
+            ft_order_side=enter_side(is_short),
             ft_pair=trade.pair,
             ft_is_open=True,
             order_id=order_id,
         )
     )
-    freqtrade.update_trade_state(trade, order_id, limit_buy_order_usdt)
-    assert trade.amount != amount
     log_text = r'Applying fee on amount for .*'
+    freqtrade.update_trade_state(trade, order_id, order)
+    assert trade.amount != amount
     if has_rounding_fee:
         assert pytest.approx(trade.amount) == 29.992
         assert log_has_re(log_text, caplog)
     else:
-        assert pytest.approx(trade.amount) == limit_buy_order_usdt['amount']
+        assert pytest.approx(trade.amount) == order['amount']
         assert not log_has_re(log_text, caplog)
 
 
-def test_update_trade_state_exception(mocker, default_conf_usdt,
-                                      limit_buy_order_usdt, caplog) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_update_trade_state_exception(mocker, default_conf_usdt, is_short, limit_order,
+                                      caplog) -> None:
+    order = limit_order[enter_side(is_short)]
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
-    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt)
+    mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
 
     trade = MagicMock()
     trade.open_order_id = '123'
@@ -1821,8 +2055,12 @@ def test_update_trade_state_orderexception(mocker, default_conf_usdt, caplog) ->
     assert log_has(f'Unable to fetch order {trade.open_order_id}: ', caplog)
 
 
-def test_update_trade_state_sell(default_conf_usdt, trades_for_order, limit_sell_order_usdt_open,
-                                 limit_sell_order_usdt, mocker):
+@pytest.mark.parametrize("is_short", [False, True])
+def test_update_trade_state_sell(
+    default_conf_usdt, trades_for_order, limit_order_open, limit_order, is_short, mocker,
+):
+    open_order = limit_order_open[exit_side(is_short)]
+    l_order = limit_order[exit_side(is_short)]
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
     # fetch_order should not be called!!
     mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError))
@@ -1830,8 +2068,8 @@ def test_update_trade_state_sell(default_conf_usdt, trades_for_order, limit_sell
     mocker.patch('freqtrade.wallets.Wallets.update', wallet_mock)
 
     patch_exchange(mocker)
-    amount = limit_sell_order_usdt["amount"]
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
+    amount = l_order["amount"]
     wallet_mock.reset_mock()
     trade = Trade(
         pair='LTC/ETH',
@@ -1841,14 +2079,17 @@ def test_update_trade_state_sell(default_conf_usdt, trades_for_order, limit_sell
         fee_open=0.0025,
         fee_close=0.0025,
         open_date=arrow.utcnow().datetime,
-        open_order_id=limit_sell_order_usdt_open['id'],
+        open_order_id=open_order['id'],
         is_open=True,
+        interest_rate=0.0005,
+        leverage=1,
+        is_short=is_short,
     )
-    order = Order.parse_from_ccxt_object(limit_sell_order_usdt_open, 'LTC/ETH', 'sell')
+    order = Order.parse_from_ccxt_object(open_order, 'LTC/ETH', exit_side(is_short))
     trade.orders.append(order)
     assert order.status == 'open'
-    freqtrade.update_trade_state(trade, trade.open_order_id, limit_sell_order_usdt)
-    assert trade.amount == limit_sell_order_usdt['amount']
+    freqtrade.update_trade_state(trade, trade.open_order_id, l_order)
+    assert trade.amount == l_order['amount']
     # Wallet needs to be updated after closing a limit-sell order to reenable buying
     assert wallet_mock.call_count == 1
     assert not trade.is_open
@@ -1856,110 +2097,144 @@ def test_update_trade_state_sell(default_conf_usdt, trades_for_order, limit_sell
     assert order.status == 'closed'
 
 
-def test_handle_trade(default_conf_usdt, limit_buy_order_usdt, limit_sell_order_usdt_open,
-                      limit_sell_order_usdt, fee, mocker) -> None:
+@pytest.mark.parametrize('is_short,close_profit', [
+    (False, 0.09451372),
+    (True, 0.08635224),
+])
+def test_handle_trade(
+    default_conf_usdt, limit_order_open, limit_order, fee, mocker, is_short, close_profit
+) -> None:
+    open_order = limit_order_open[exit_side(is_short)]
+    enter_order = limit_order[enter_side(is_short)]
+    exit_order = limit_order[exit_side(is_short)]
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=MagicMock(return_value={
-            'bid': 1.9,
+            'bid': 2.19,
             'ask': 2.2,
-            'last': 1.9
+            'last': 2.19
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt,
-            limit_sell_order_usdt_open,
+            enter_order,
+            open_order,
         ]),
         get_fee=fee,
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
 
     time.sleep(0.01)  # Race condition fix
-    oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy')
+    oobj = Order.parse_from_ccxt_object(enter_order, enter_order['symbol'], enter_side(is_short))
     trade.update_trade(oobj)
     assert trade.is_open is True
     freqtrade.wallets.update()
 
-    patch_get_signal(freqtrade, value=(False, True, None, 'sell_signal1'))
+    patch_get_signal(freqtrade, enter_long=False, exit_short=is_short,
+                     exit_long=not is_short, exit_tag='sell_signal1')
     assert freqtrade.handle_trade(trade) is True
-    assert trade.open_order_id == limit_sell_order_usdt['id']
+    assert trade.open_order_id == exit_order['id']
 
     # Simulate fulfilled LIMIT_SELL order for trade
-    oobj = Order.parse_from_ccxt_object(
-        limit_sell_order_usdt, limit_sell_order_usdt['symbol'], 'sell')
+    oobj = Order.parse_from_ccxt_object(exit_order, exit_order['symbol'], exit_side(is_short))
     trade.update_trade(oobj)
 
-    assert trade.close_rate == 2.2
-    assert trade.close_profit == 0.09451372
+    assert trade.close_rate == 2.0 if is_short else 2.2
+    assert trade.close_profit == close_profit
     assert trade.calc_profit() == 5.685
     assert trade.close_date is not None
     assert trade.sell_reason == 'sell_signal1'
 
 
-def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open,
-                                    fee, mocker) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_overlapping_signals(
+    default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, is_short
+) -> None:
+    open_order = limit_order_open[exit_side(is_short)]
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            open_order,
             {'id': 1234553382},
         ]),
         get_fee=fee,
     )
 
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade, value=(True, True, None, None))
+    if is_short:
+        patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
+    else:
+        patch_get_signal(freqtrade, enter_long=True, exit_long=True)
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
 
     freqtrade.enter_positions()
 
     # Buy and Sell triggering, so doing nothing ...
     trades = Trade.query.all()
+
     nb_trades = len(trades)
     assert nb_trades == 0
 
     # Buy is triggering, so buying ...
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.enter_positions()
     trades = Trade.query.all()
+    for trade in trades:
+        trade.is_short = is_short
     nb_trades = len(trades)
     assert nb_trades == 1
     assert trades[0].is_open is True
 
     # Buy and Sell are not triggering, so doing nothing ...
-    patch_get_signal(freqtrade, value=(False, False, None, None))
+    patch_get_signal(freqtrade, enter_long=False)
     assert freqtrade.handle_trade(trades[0]) is False
     trades = Trade.query.all()
+    for trade in trades:
+        trade.is_short = is_short
     nb_trades = len(trades)
     assert nb_trades == 1
     assert trades[0].is_open is True
 
     # Buy and Sell are triggering, so doing nothing ...
-    patch_get_signal(freqtrade, value=(True, True, None, None))
+    if is_short:
+        patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
+    else:
+        patch_get_signal(freqtrade, enter_long=True, exit_long=True)
     assert freqtrade.handle_trade(trades[0]) is False
     trades = Trade.query.all()
+    for trade in trades:
+        trade.is_short = is_short
     nb_trades = len(trades)
     assert nb_trades == 1
     assert trades[0].is_open is True
 
     # Sell is triggering, guess what : we are Selling!
-    patch_get_signal(freqtrade, value=(False, True, None, None))
+    if is_short:
+        patch_get_signal(freqtrade, enter_long=False, exit_short=True)
+    else:
+        patch_get_signal(freqtrade, enter_long=False, exit_long=True)
     trades = Trade.query.all()
+    for trade in trades:
+        trade.is_short = is_short
     assert freqtrade.handle_trade(trades[0]) is True
 
 
-def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open,
-                          fee, mocker, caplog) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog,
+                          is_short) -> None:
+
+    open_order = limit_order_open[enter_side(is_short)]
+
     caplog.set_level(logging.DEBUG)
 
     patch_RPCManager(mocker)
@@ -1967,19 +2242,20 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_o
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            open_order,
             {'id': 1234553382},
         ]),
         get_fee=fee,
     )
 
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
 
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
 
     # FIX: sniffing logs, suggest handle_trade should not execute_trade_exit
@@ -1987,14 +2263,21 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_o
     #      we might just want to check if we are in a sell condition without
     #      executing
     # if ROI is reached we must sell
-    patch_get_signal(freqtrade, value=(False, False, None, None))
+    caplog.clear()
+    patch_get_signal(freqtrade)
     assert freqtrade.handle_trade(trade)
-    assert log_has("ETH/USDT - Required profit reached. sell_type=SellType.ROI",
+    assert log_has("ETH/USDT - Required profit reached. sell_type=ExitType.ROI",
                    caplog)
 
 
-def test_handle_trade_use_sell_signal(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open,
-                                      limit_sell_order_usdt_open, fee, mocker, caplog) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_trade_use_sell_signal(
+    default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog, is_short
+) -> None:
+
+    enter_open_order = limit_order_open[exit_side(is_short)]
+    exit_open_order = limit_order_open[enter_side(is_short)]
+
     # use_sell_signal is True buy default
     caplog.set_level(logging.DEBUG)
     patch_RPCManager(mocker)
@@ -2002,52 +2285,61 @@ def test_handle_trade_use_sell_signal(default_conf_usdt, ticker_usdt, limit_buy_
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
-            limit_sell_order_usdt_open,
+            enter_open_order,
+            exit_open_order,
         ]),
         get_fee=fee,
     )
 
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
 
-    patch_get_signal(freqtrade, value=(False, False, None, None))
+    patch_get_signal(freqtrade, enter_long=False, exit_long=False)
     assert not freqtrade.handle_trade(trade)
 
-    patch_get_signal(freqtrade, value=(False, True, None, None))
+    if is_short:
+        patch_get_signal(freqtrade, enter_long=False, exit_short=True)
+    else:
+        patch_get_signal(freqtrade, enter_long=False, exit_long=True)
     assert freqtrade.handle_trade(trade)
-    assert log_has("ETH/USDT - Sell signal received. sell_type=SellType.SELL_SIGNAL",
+    assert log_has("ETH/USDT - Sell signal received. sell_type=ExitType.SELL_SIGNAL",
                    caplog)
 
 
-def test_close_trade(default_conf_usdt, ticker_usdt, limit_buy_order_usdt,
-                     limit_buy_order_usdt_open, limit_sell_order_usdt, fee, mocker) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_close_trade(
+    default_conf_usdt, ticker_usdt, limit_order_open, limit_order, fee, mocker, is_short
+) -> None:
+    open_order = limit_order_open[exit_side(is_short)]
+    enter_order = limit_order[exit_side(is_short)]
+    exit_order = limit_order[enter_side(is_short)]
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
-        create_order=MagicMock(return_value=limit_buy_order_usdt_open),
+        create_order=MagicMock(return_value=open_order),
         get_fee=fee,
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # Create trade and sell it
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
 
-    oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy')
+    oobj = Order.parse_from_ccxt_object(enter_order, enter_order['symbol'], 'buy')
     trade.update_trade(oobj)
-    oobj = Order.parse_from_ccxt_object(
-        limit_sell_order_usdt, limit_sell_order_usdt['symbol'], 'sell')
+    oobj = Order.parse_from_ccxt_object(exit_order, exit_order['symbol'], 'sell')
     trade.update_trade(oobj)
     assert trade.is_open is False
 
@@ -2068,27 +2360,36 @@ def test_bot_loop_start_called_once(mocker, default_conf_usdt, caplog):
     assert ftbot.strategy.analyze.call_count == 1
 
 
-def test_check_handle_timedout_buy_usercustom(default_conf_usdt, ticker_usdt, limit_buy_order_old,
-                                              open_trade, fee, mocker) -> None:
-    default_conf_usdt["unfilledtimeout"] = {"buy": 1400, "sell": 30}
-    limit_buy_order_old['id'] = open_trade.open_order_id
+@pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_entry_usercustom(
+    default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
+    limit_sell_order_old, fee, mocker, is_short
+) -> None:
+
+    old_order = limit_sell_order_old if is_short else limit_buy_order_old
+    old_order['id'] = open_trade.open_order_id
+
+    default_conf_usdt["unfilledtimeout"] = {"entry": 1400, "exit": 30}
+
     rpc_mock = patch_RPCManager(mocker)
-    cancel_order_mock = MagicMock(return_value=limit_buy_order_old)
-    cancel_buy_order = deepcopy(limit_buy_order_old)
-    cancel_buy_order['status'] = 'canceled'
-    cancel_order_wr_mock = MagicMock(return_value=cancel_buy_order)
+    cancel_order_mock = MagicMock(return_value=old_order)
+    cancel_enter_order = deepcopy(old_order)
+    cancel_enter_order['status'] = 'canceled'
+    cancel_order_wr_mock = MagicMock(return_value=cancel_enter_order)
 
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
-        fetch_order=MagicMock(return_value=limit_buy_order_old),
+        fetch_order=MagicMock(return_value=old_order),
         cancel_order_with_result=cancel_order_wr_mock,
         cancel_order=cancel_order_mock,
         get_fee=fee
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-
+    open_trade.is_short = is_short
+    open_trade.orders[0].side = 'sell' if is_short else 'buy'
+    open_trade.orders[0].ft_order_side = 'sell' if is_short else 'buy'
     Trade.query.session.add(open_trade)
 
     # Ensure default is to return empty (so not mocked yet)
@@ -2096,24 +2397,23 @@ def test_check_handle_timedout_buy_usercustom(default_conf_usdt, ticker_usdt, li
     assert cancel_order_mock.call_count == 0
 
     # Return false - trade remains open
-    freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
+    freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
     trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
     nb_trades = len(trades)
     assert nb_trades == 1
-    assert freqtrade.strategy.check_buy_timeout.call_count == 1
+    assert freqtrade.strategy.check_entry_timeout.call_count == 1
+    freqtrade.strategy.check_entry_timeout = MagicMock(side_effect=KeyError)
 
-    # Raise Keyerror ... (no impact on trade)
-    freqtrade.strategy.check_buy_timeout = MagicMock(side_effect=KeyError)
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
     trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
     nb_trades = len(trades)
     assert nb_trades == 1
-    assert freqtrade.strategy.check_buy_timeout.call_count == 1
+    assert freqtrade.strategy.check_entry_timeout.call_count == 1
+    freqtrade.strategy.check_entry_timeout = MagicMock(return_value=True)
 
-    freqtrade.strategy.check_buy_timeout = MagicMock(return_value=True)
     # Trade should be closed since the function returns true
     freqtrade.check_handle_timedout()
     assert cancel_order_wr_mock.call_count == 1
@@ -2121,29 +2421,34 @@ def test_check_handle_timedout_buy_usercustom(default_conf_usdt, ticker_usdt, li
     trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
     nb_trades = len(trades)
     assert nb_trades == 0
-    assert freqtrade.strategy.check_buy_timeout.call_count == 1
+    assert freqtrade.strategy.check_entry_timeout.call_count == 1
 
 
-def test_check_handle_timedout_buy(default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
-                                   fee, mocker) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_entry(
+    default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
+    limit_sell_order_old, fee, mocker, is_short
+) -> None:
+    old_order = limit_sell_order_old if is_short else limit_buy_order_old
     rpc_mock = patch_RPCManager(mocker)
-    limit_buy_order_old['id'] = open_trade.open_order_id
-    limit_buy_cancel = deepcopy(limit_buy_order_old)
+    old_order['id'] = open_trade.open_order_id
+    limit_buy_cancel = deepcopy(old_order)
     limit_buy_cancel['status'] = 'canceled'
     cancel_order_mock = MagicMock(return_value=limit_buy_cancel)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
-        fetch_order=MagicMock(return_value=limit_buy_order_old),
+        fetch_order=MagicMock(return_value=old_order),
         cancel_order_with_result=cancel_order_mock,
         get_fee=fee
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
 
+    open_trade.is_short = is_short
     Trade.query.session.add(open_trade)
 
-    freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
+    freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
     # check it does cancel buy orders over the time limit
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 1
@@ -2152,25 +2457,29 @@ def test_check_handle_timedout_buy(default_conf_usdt, ticker_usdt, limit_buy_ord
     nb_trades = len(trades)
     assert nb_trades == 0
     # Custom user buy-timeout is never called
-    assert freqtrade.strategy.check_buy_timeout.call_count == 0
+    assert freqtrade.strategy.check_entry_timeout.call_count == 0
 
 
-def test_check_handle_cancelled_buy(default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
-                                    fee, mocker, caplog) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_cancelled_buy(
+    default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
+    limit_sell_order_old, fee, mocker, caplog, is_short
+) -> None:
     """ Handle Buy order cancelled on exchange"""
+    old_order = limit_sell_order_old if is_short else limit_buy_order_old
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
     patch_exchange(mocker)
-    limit_buy_order_old.update({"status": "canceled", 'filled': 0.0})
+    old_order.update({"status": "canceled", 'filled': 0.0})
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
-        fetch_order=MagicMock(return_value=limit_buy_order_old),
+        fetch_order=MagicMock(return_value=old_order),
         cancel_order=cancel_order_mock,
         get_fee=fee
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-
+    open_trade.is_short = is_short
     Trade.query.session.add(open_trade)
 
     # check it does cancel buy orders over the time limit
@@ -2180,11 +2489,14 @@ def test_check_handle_cancelled_buy(default_conf_usdt, ticker_usdt, limit_buy_or
     trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
     nb_trades = len(trades)
     assert nb_trades == 0
-    assert log_has_re("Buy order cancelled on exchange for Trade.*", caplog)
+    assert log_has_re(
+        f"{'Sell' if is_short else 'Buy'} order cancelled on exchange for Trade.*", caplog)
 
 
-def test_check_handle_timedout_buy_exception(default_conf_usdt, ticker_usdt,
-                                             open_trade, fee, mocker) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_buy_exception(
+    default_conf_usdt, ticker_usdt, open_trade, is_short, fee, mocker
+) -> None:
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
     patch_exchange(mocker)
@@ -2198,6 +2510,7 @@ def test_check_handle_timedout_buy_exception(default_conf_usdt, ticker_usdt,
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
 
+    open_trade.is_short = is_short
     Trade.query.session.add(open_trade)
 
     # check it does cancel buy orders over the time limit
@@ -2209,10 +2522,16 @@ def test_check_handle_timedout_buy_exception(default_conf_usdt, ticker_usdt,
     assert nb_trades == 1
 
 
-def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, limit_sell_order_old,
-                                               mocker, open_trade, caplog) -> None:
-    default_conf_usdt["unfilledtimeout"] = {"buy": 1440, "sell": 1440, "exit_timeout_count": 1}
-    limit_sell_order_old['id'] = open_trade.open_order_id
+@pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_exit_usercustom(
+    default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker,
+    is_short, open_trade_usdt, caplog
+) -> None:
+    default_conf_usdt["unfilledtimeout"] = {"entry": 1440, "exit": 1440, "exit_timeout_count": 1}
+    limit_sell_order_old['id'] = open_trade_usdt.open_order_id
+    if is_short:
+        limit_sell_order_old['side'] = 'buy'
+        open_trade_usdt.is_short = is_short
 
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
@@ -2226,43 +2545,49 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
 
-    open_trade.open_date = arrow.utcnow().shift(hours=-5).datetime
-    open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime
-    open_trade.close_profit_abs = 0.001
-    open_trade.is_open = False
+    open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime
+    open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime
+    open_trade_usdt.close_profit_abs = 0.001
+    open_trade_usdt.is_open = False
 
-    Trade.query.session.add(open_trade)
+    Trade.query.session.add(open_trade_usdt)
     # Ensure default is false
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
 
-    freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
+    freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False)
+    freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
     # Return false - No impact
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
     assert rpc_mock.call_count == 0
-    assert open_trade.is_open is False
-    assert freqtrade.strategy.check_sell_timeout.call_count == 1
+    assert open_trade_usdt.is_open is False
+    assert freqtrade.strategy.check_exit_timeout.call_count == 1
+    assert freqtrade.strategy.check_entry_timeout.call_count == 0
 
-    freqtrade.strategy.check_sell_timeout = MagicMock(side_effect=KeyError)
+    freqtrade.strategy.check_exit_timeout = MagicMock(side_effect=KeyError)
+    freqtrade.strategy.check_entry_timeout = MagicMock(side_effect=KeyError)
     # Return Error - No impact
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
     assert rpc_mock.call_count == 0
-    assert open_trade.is_open is False
-    assert freqtrade.strategy.check_sell_timeout.call_count == 1
+    assert open_trade_usdt.is_open is False
+    assert freqtrade.strategy.check_exit_timeout.call_count == 1
+    assert freqtrade.strategy.check_entry_timeout.call_count == 0
 
     # Return True - sells!
-    freqtrade.strategy.check_sell_timeout = MagicMock(return_value=True)
+    freqtrade.strategy.check_exit_timeout = MagicMock(return_value=True)
+    freqtrade.strategy.check_entry_timeout = MagicMock(return_value=True)
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 1
     assert rpc_mock.call_count == 1
-    assert open_trade.is_open is True
-    assert freqtrade.strategy.check_sell_timeout.call_count == 1
+    assert open_trade_usdt.is_open is True
+    assert freqtrade.strategy.check_exit_timeout.call_count == 1
+    assert freqtrade.strategy.check_entry_timeout.call_count == 0
 
     # 2nd canceled trade - Fail execute sell
     caplog.clear()
-    open_trade.open_order_id = limit_sell_order_old['id']
+    open_trade_usdt.open_order_id = limit_sell_order_old['id']
     mocker.patch('freqtrade.persistence.Trade.get_exit_order_count', return_value=1)
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit',
                  side_effect=DependencyException)
@@ -2272,7 +2597,7 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l
     et_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit')
     caplog.clear()
     # 2nd canceled trade ...
-    open_trade.open_order_id = limit_sell_order_old['id']
+    open_trade_usdt.open_order_id = limit_sell_order_old['id']
 
     # If cancelling fails - no emergency sell!
     with patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit', return_value=False):
@@ -2280,15 +2605,18 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l
         assert et_mock.call_count == 0
 
     freqtrade.check_handle_timedout()
-    assert log_has_re('Emergencyselling trade.*', caplog)
+    assert log_has_re('Emergency exiting trade.*', caplog)
     assert et_mock.call_count == 1
 
 
-def test_check_handle_timedout_sell(default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker,
-                                    open_trade) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_exit(
+    default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker, is_short, open_trade_usdt
+) -> None:
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
-    limit_sell_order_old['id'] = open_trade.open_order_id
+    limit_sell_order_old['id'] = open_trade_usdt.open_order_id
+    limit_sell_order_old['side'] = 'buy' if is_short else 'sell'
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -2298,29 +2626,36 @@ def test_check_handle_timedout_sell(default_conf_usdt, ticker_usdt, limit_sell_o
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
 
-    open_trade.open_date = arrow.utcnow().shift(hours=-5).datetime
-    open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime
-    open_trade.close_profit_abs = 0.001
-    open_trade.is_open = False
+    open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime
+    open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime
+    open_trade_usdt.close_profit_abs = 0.001
+    open_trade_usdt.is_open = False
+    open_trade_usdt.is_short = is_short
 
-    Trade.query.session.add(open_trade)
+    Trade.query.session.add(open_trade_usdt)
 
-    freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
+    freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False)
+    freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
     # check it does cancel sell orders over the time limit
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 1
     assert rpc_mock.call_count == 1
-    assert open_trade.is_open is True
+    assert open_trade_usdt.is_open is True
     # Custom user sell-timeout is never called
-    assert freqtrade.strategy.check_sell_timeout.call_count == 0
+    assert freqtrade.strategy.check_exit_timeout.call_count == 0
+    assert freqtrade.strategy.check_entry_timeout.call_count == 0
 
 
-def test_check_handle_cancelled_sell(default_conf_usdt, ticker_usdt, limit_sell_order_old,
-                                     open_trade, mocker, caplog) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_cancelled_exit(
+    default_conf_usdt, ticker_usdt, limit_sell_order_old, open_trade_usdt,
+    is_short, mocker, caplog
+) -> None:
     """ Handle sell order cancelled on exchange"""
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
     limit_sell_order_old.update({"status": "canceled", 'filled': 0.0})
+    limit_sell_order_old['side'] = 'buy' if is_short else 'sell'
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -2330,24 +2665,33 @@ def test_check_handle_cancelled_sell(default_conf_usdt, ticker_usdt, limit_sell_
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
 
-    open_trade.open_date = arrow.utcnow().shift(hours=-5).datetime
-    open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime
-    open_trade.is_open = False
+    open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime
+    open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime
+    open_trade_usdt.is_open = False
+    open_trade_usdt.is_short = is_short
 
-    Trade.query.session.add(open_trade)
+    Trade.query.session.add(open_trade_usdt)
 
     # check it does cancel sell orders over the time limit
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
     assert rpc_mock.call_count == 1
-    assert open_trade.is_open is True
-    assert log_has_re("Sell order cancelled on exchange for Trade.*", caplog)
+    assert open_trade_usdt.is_open is True
+    exit_name = 'Buy' if is_short else 'Sell'
+    assert log_has_re(f"{exit_name} order cancelled on exchange for Trade.*", caplog)
 
 
-def test_check_handle_timedout_partial(default_conf_usdt, ticker_usdt, limit_buy_order_old_partial,
-                                       open_trade, mocker) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+@pytest.mark.parametrize("leverage", [1, 3, 5, 10])
+def test_check_handle_timedout_partial(
+    default_conf_usdt, ticker_usdt, limit_buy_order_old_partial, is_short, leverage,
+    open_trade, mocker
+) -> None:
     rpc_mock = patch_RPCManager(mocker)
+    open_trade.is_short = is_short
+    open_trade.leverage = leverage
     limit_buy_order_old_partial['id'] = open_trade.open_order_id
+    limit_buy_order_old_partial['side'] = 'sell' if is_short else 'buy'
     limit_buy_canceled = deepcopy(limit_buy_order_old_partial)
     limit_buy_canceled['status'] = 'canceled'
 
@@ -2360,7 +2704,7 @@ def test_check_handle_timedout_partial(default_conf_usdt, ticker_usdt, limit_buy
         cancel_order_with_result=cancel_order_mock
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-
+    prior_stake = open_trade.stake_amount
     Trade.query.session.add(open_trade)
 
     # check it does cancel buy orders over the time limit
@@ -2371,15 +2715,24 @@ def test_check_handle_timedout_partial(default_conf_usdt, ticker_usdt, limit_buy
     trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
     assert len(trades) == 1
     assert trades[0].amount == 23.0
-    assert trades[0].stake_amount == open_trade.open_rate * trades[0].amount
+    assert trades[0].stake_amount == open_trade.open_rate * trades[0].amount / leverage
+    assert trades[0].stake_amount != prior_stake
 
 
-def test_check_handle_timedout_partial_fee(default_conf_usdt, ticker_usdt, open_trade, caplog, fee,
-                                           limit_buy_order_old_partial, trades_for_order,
-                                           limit_buy_order_old_partial_canceled, mocker) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_partial_fee(
+    default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short,
+    limit_buy_order_old_partial, trades_for_order,
+    limit_buy_order_old_partial_canceled, mocker
+) -> None:
+    open_trade.is_short = is_short
+    open_trade.orders[0].ft_order_side = 'sell' if is_short else 'buy'
     rpc_mock = patch_RPCManager(mocker)
     limit_buy_order_old_partial['id'] = open_trade.open_order_id
     limit_buy_order_old_partial_canceled['id'] = open_trade.open_order_id
+    limit_buy_order_old_partial['side'] = 'sell' if is_short else 'buy'
+    limit_buy_order_old_partial_canceled['side'] = 'sell' if is_short else 'buy'
+
     cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
     mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=0))
     patch_exchange(mocker)
@@ -2411,16 +2764,22 @@ def test_check_handle_timedout_partial_fee(default_conf_usdt, ticker_usdt, open_
     assert trades[0].amount == (limit_buy_order_old_partial['amount'] -
                                 limit_buy_order_old_partial['remaining']) - 0.023
     assert trades[0].open_order_id is None
-    assert trades[0].fee_updated('buy')
+    assert trades[0].fee_updated(open_trade.enter_side)
     assert pytest.approx(trades[0].fee_open) == 0.001
 
 
-def test_check_handle_timedout_partial_except(default_conf_usdt, ticker_usdt, open_trade, caplog,
-                                              fee, limit_buy_order_old_partial, trades_for_order,
-                                              limit_buy_order_old_partial_canceled, mocker) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_check_handle_timedout_partial_except(
+    default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short,
+    limit_buy_order_old_partial, trades_for_order,
+    limit_buy_order_old_partial_canceled, mocker
+) -> None:
+    open_trade.is_short = is_short
     rpc_mock = patch_RPCManager(mocker)
     limit_buy_order_old_partial_canceled['id'] = open_trade.open_order_id
     limit_buy_order_old_partial['id'] = open_trade.open_order_id
+    if is_short:
+        limit_buy_order_old_partial['side'] = 'sell'
     cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -2478,18 +2837,22 @@ def test_check_handle_timedout_exception(default_conf_usdt, ticker_usdt, open_tr
 
     Trade.query.session.add(open_trade_usdt)
 
+    caplog.clear()
     freqtrade.check_handle_timedout()
     assert log_has_re(r"Cannot query order for Trade\(id=1, pair=ADA/USDT, amount=30.00000000, "
+                      r"is_short=False, leverage=1.0, "
                       r"open_rate=2.00000000, open_since="
                       f"{open_trade_usdt.open_date.strftime('%Y-%m-%d %H:%M:%S')}"
                       r"\) due to Traceback \(most recent call last\):\n*",
                       caplog)
 
 
-def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_buy_order_usdt) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_short) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
-    cancel_buy_order = deepcopy(limit_buy_order_usdt)
+    l_order = limit_order[enter_side(is_short)]
+    cancel_buy_order = deepcopy(limit_order[enter_side(is_short)])
     cancel_buy_order['status'] = 'canceled'
     del cancel_buy_order['filled']
 
@@ -2502,39 +2865,42 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_buy_order_
     trade = MagicMock()
     trade.pair = 'LTC/USDT'
     trade.open_rate = 200
-    limit_buy_order_usdt['filled'] = 0.0
-    limit_buy_order_usdt['status'] = 'open'
+    trade.is_short = False
+    trade.enter_side = "buy"
+    l_order['filled'] = 0.0
+    l_order['status'] = 'open'
     reason = CANCEL_REASON['TIMEOUT']
-    assert freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+    assert freqtrade.handle_cancel_enter(trade, l_order, reason)
     assert cancel_order_mock.call_count == 1
 
     cancel_order_mock.reset_mock()
     caplog.clear()
-    limit_buy_order_usdt['filled'] = 0.01
-    assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+    l_order['filled'] = 0.01
+    assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
     assert cancel_order_mock.call_count == 0
-    assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unsellable.*", caplog)
+    assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unexitable.*", caplog)
 
     caplog.clear()
     cancel_order_mock.reset_mock()
-    limit_buy_order_usdt['filled'] = 2
-    assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+    l_order['filled'] = 2
+    assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
     assert cancel_order_mock.call_count == 1
 
     # Order remained open for some reason (cancel failed)
     cancel_buy_order['status'] = 'open'
     cancel_order_mock = MagicMock(return_value=cancel_buy_order)
     mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
-    assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+    assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
     assert log_has_re(r"Order .* for .* not cancelled.", caplog)
     # min_pair_stake empty should not crash
     mocker.patch('freqtrade.exchange.Exchange.get_min_pair_stake_amount', return_value=None)
-    assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+    assert not freqtrade.handle_cancel_enter(trade, limit_order[enter_side(is_short)], reason)
 
 
+@pytest.mark.parametrize("is_short", [False, True])
 @pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],
                          indirect=['limit_buy_order_canceled_empty'])
-def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt,
+def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_short,
                                        limit_buy_order_canceled_empty) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
@@ -2547,22 +2913,29 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt,
     reason = CANCEL_REASON['TIMEOUT']
     trade = MagicMock()
     trade.pair = 'LTC/ETH'
+    trade.enter_side = "sell" if is_short else "buy"
     assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason)
     assert cancel_order_mock.call_count == 0
-    assert log_has_re(r'Buy order fully cancelled. Removing .* from database\.', caplog)
+    assert log_has_re(
+        f'{trade.enter_side.capitalize()} order fully cancelled. '
+        r'Removing .* from database\.',
+        caplog
+    )
     assert nofiy_mock.call_count == 1
 
 
+@pytest.mark.parametrize("is_short", [False, True])
 @pytest.mark.parametrize('cancelorder', [
     {},
     {'remaining': None},
     'String Return value',
     123
 ])
-def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_buy_order_usdt,
+def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order, is_short,
                                           cancelorder) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
+    l_order = limit_order[enter_side(is_short)]
     cancel_order_mock = MagicMock(return_value=cancelorder)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -2574,16 +2947,18 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_buy_o
 
     trade = MagicMock()
     trade.pair = 'LTC/USDT'
+    trade.enter_side = "buy"
     trade.open_rate = 200
-    limit_buy_order_usdt['filled'] = 0.0
-    limit_buy_order_usdt['status'] = 'open'
+    trade.enter_side = "buy"
+    l_order['filled'] = 0.0
+    l_order['status'] = 'open'
     reason = CANCEL_REASON['TIMEOUT']
-    assert freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+    assert freqtrade.handle_cancel_enter(trade, l_order, reason)
     assert cancel_order_mock.call_count == 1
 
     cancel_order_mock.reset_mock()
-    limit_buy_order_usdt['filled'] = 1.0
-    assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
+    l_order['filled'] = 1.0
+    assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
     assert cancel_order_mock.call_count == 1
 
 
@@ -2657,8 +3032,12 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
     assert not freqtrade.handle_cancel_exit(trade, order, reason)
 
 
-def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, mocker
-                               ) -> None:
+@pytest.mark.parametrize("is_short, open_rate, amt", [
+    (False, 2.0, 30.0),
+    (True, 2.02, 29.70297029),
+])
+def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, mocker,
+                               ticker_usdt_sell_down, is_short, open_rate, amt) -> None:
     rpc_mock = patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -2669,7 +3048,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
     )
     patch_whitelist(mocker, default_conf_usdt)
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=False)
 
     # Create some test data
@@ -2677,17 +3056,21 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
     rpc_mock.reset_mock()
 
     trade = Trade.query.first()
+    assert trade.is_short == is_short
     assert trade
     assert freqtrade.strategy.confirm_trade_exit.call_count == 0
 
     # Increase the price and sell it
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
-        fetch_ticker=ticker_usdt_sell_up
+        fetch_ticker=ticker_usdt_sell_down if is_short else ticker_usdt_sell_up
     )
     # Prevented sell ...
-    freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
-                                 sell_reason=SellCheckTuple(sell_type=SellType.ROI))
+    freqtrade.execute_trade_exit(
+        trade=trade,
+        limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
+        exit_check=ExitCheckTuple(exit_type=ExitType.ROI)
+    )
     assert rpc_mock.call_count == 0
     assert freqtrade.strategy.confirm_trade_exit.call_count == 1
     assert id(freqtrade.strategy.confirm_trade_exit.call_args_list[0][1]['trade']) != id(trade)
@@ -2695,9 +3078,11 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
 
     # Repatch with true
     freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
-
-    freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
-                                 sell_reason=SellCheckTuple(sell_type=SellType.ROI))
+    freqtrade.execute_trade_exit(
+        trade=trade,
+        limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
+        exit_check=ExitCheckTuple(exit_type=ExitType.ROI)
+    )
     assert freqtrade.strategy.confirm_trade_exit.call_count == 1
 
     assert rpc_mock.call_count == 1
@@ -2708,25 +3093,29 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
         'exchange': 'Binance',
         'pair': 'ETH/USDT',
         'gain': 'profit',
-        'limit': 2.2,
-        'amount': 30.0,
+        'limit': 2.0 if is_short else 2.2,
+        'amount': pytest.approx(amt),
         'order_type': 'limit',
         'buy_tag': None,
-        'open_rate': 2.0,
-        'current_rate': 2.3,
-        'profit_amount': 5.685,
-        'profit_ratio': 0.09451372,
+        'direction': 'Short' if trade.is_short else 'Long',
+        'leverage': 1.0,
+        'enter_tag': None,
+        'open_rate': open_rate,
+        'current_rate': 2.01 if is_short else 2.3,
+        'profit_amount': 0.29554455 if is_short else 5.685,
+        'profit_ratio': 0.00493809 if is_short else 0.09451372,
         'stake_currency': 'USDT',
         'fiat_currency': 'USD',
-        'sell_reason': SellType.ROI.value,
+        'sell_reason': ExitType.ROI.value,
         'open_date': ANY,
         'close_date': ANY,
         'close_rate': ANY,
     } == last_msg
 
 
+@pytest.mark.parametrize("is_short", [False, True])
 def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down,
-                                 mocker) -> None:
+                                 ticker_usdt_sell_up, mocker, is_short) -> None:
     rpc_mock = patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -2737,22 +3126,23 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
     )
     patch_whitelist(mocker, default_conf_usdt)
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # Create some test data
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
 
     # Decrease the price and sell it
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
-        fetch_ticker=ticker_usdt_sell_down
+        fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down
     )
-
-    freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_down()['bid'],
-                                 sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
+    freqtrade.execute_trade_exit(
+        trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down)()['bid'],
+        exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS))
 
     assert rpc_mock.call_count == 2
     last_msg = rpc_mock.call_args_list[-1][0][0]
@@ -2761,26 +3151,35 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
         'trade_id': 1,
         'exchange': 'Binance',
         'pair': 'ETH/USDT',
+        'direction': 'Short' if trade.is_short else 'Long',
+        'leverage': 1.0,
         'gain': 'loss',
-        'limit': 2.01,
-        'amount': 30.0,
+        'limit': 2.2 if is_short else 2.01,
+        'amount': pytest.approx(29.70297029) if is_short else 30.0,
         'order_type': 'limit',
         'buy_tag': None,
-        'open_rate': 2.0,
-        'current_rate': 2.0,
-        'profit_amount': -0.00075,
-        'profit_ratio': -1.247e-05,
+        'enter_tag': None,
+        'open_rate': 2.02 if is_short else 2.0,
+        'current_rate': 2.2 if is_short else 2.0,
+        'profit_amount': -5.65990099 if is_short else -0.00075,
+        'profit_ratio': -0.0945681 if is_short else -1.247e-05,
         'stake_currency': 'USDT',
         'fiat_currency': 'USD',
-        'sell_reason': SellType.STOP_LOSS.value,
+        'sell_reason': ExitType.STOP_LOSS.value,
         'open_date': ANY,
         'close_date': ANY,
         'close_rate': ANY,
     } == last_msg
 
 
-def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fee,
-                                              ticker_usdt_sell_up, mocker) -> None:
+@pytest.mark.parametrize(
+    "is_short,amount,open_rate,current_rate,limit,profit_amount,profit_ratio,profit_or_loss", [
+        (False, 30, 2.0, 2.3, 2.25, 7.18125, 0.11938903, 'profit'),
+        (True, 29.70297029, 2.02, 2.2, 2.25, -7.14876237, -0.11944465, 'loss'),
+    ])
+def test_execute_trade_exit_custom_exit_price(
+        default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, is_short, amount, open_rate,
+        current_rate, limit, profit_amount, profit_ratio, profit_or_loss, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -2793,7 +3192,7 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
     config['custom_price_max_distance_ratio'] = 0.1
     patch_whitelist(mocker, config)
     freqtrade = FreqtradeBot(config)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=False)
 
     # Create some test data
@@ -2801,6 +3200,7 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
     rpc_mock.reset_mock()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
     assert freqtrade.strategy.confirm_trade_exit.call_count == 0
 
@@ -2814,9 +3214,11 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
 
     # Set a custom exit price
     freqtrade.strategy.custom_exit_price = lambda **kwargs: 2.25
-
-    freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
-                                 sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL))
+    freqtrade.execute_trade_exit(
+        trade=trade,
+        limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
+        exit_check=ExitCheckTuple(exit_type=ExitType.SELL_SIGNAL)
+    )
 
     # Sell price must be different to default bid price
 
@@ -2829,26 +3231,31 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
         'type': RPCMessageType.SELL,
         'exchange': 'Binance',
         'pair': 'ETH/USDT',
-        'gain': 'profit',
-        'limit': 2.25,
-        'amount': 30.0,
+        'direction': 'Short' if trade.is_short else 'Long',
+        'leverage': 1.0,
+        'gain': profit_or_loss,
+        'limit': limit,
+        'amount': pytest.approx(amount),
         'order_type': 'limit',
         'buy_tag': None,
-        'open_rate': 2.0,
-        'current_rate': 2.3,
-        'profit_amount': 7.18125,
-        'profit_ratio': 0.11938903,
+        'enter_tag': None,
+        'open_rate': open_rate,
+        'current_rate': current_rate,
+        'profit_amount': pytest.approx(profit_amount),
+        'profit_ratio': profit_ratio,
         'stake_currency': 'USDT',
         'fiat_currency': 'USD',
-        'sell_reason': SellType.SELL_SIGNAL.value,
+        'sell_reason': ExitType.SELL_SIGNAL.value,
         'open_date': ANY,
         'close_date': ANY,
         'close_rate': ANY,
     } == last_msg
 
 
+@pytest.mark.parametrize("is_short", [False, True])
 def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
-        default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down, mocker) -> None:
+        default_conf_usdt, ticker_usdt, fee, is_short, ticker_usdt_sell_down,
+        ticker_usdt_sell_up, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -2859,27 +3266,29 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
     )
     patch_whitelist(mocker, default_conf_usdt)
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # Create some test data
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    assert trade.is_short == is_short
     assert trade
 
     # Decrease the price and sell it
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
-        fetch_ticker=ticker_usdt_sell_down
+        fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down
     )
 
     default_conf_usdt['dry_run'] = True
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
     # Setting trade stoploss to 0.01
 
-    trade.stop_loss = 2.0 * 0.99
-    freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_down()['bid'],
-                                 sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
+    trade.stop_loss = 2.0 * 1.01 if is_short else 2.0 * 0.99
+    freqtrade.execute_trade_exit(
+        trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down())['bid'],
+        exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS))
 
     assert rpc_mock.call_count == 2
     last_msg = rpc_mock.call_args_list[-1][0][0]
@@ -2889,18 +3298,21 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
         'trade_id': 1,
         'exchange': 'Binance',
         'pair': 'ETH/USDT',
+        'direction': 'Short' if trade.is_short else 'Long',
+        'leverage': 1.0,
         'gain': 'loss',
-        'limit': 1.98,
-        'amount': 30.0,
+        'limit': 2.02 if is_short else 1.98,
+        'amount': pytest.approx(29.70297029 if is_short else 30.0),
         'order_type': 'limit',
         'buy_tag': None,
-        'open_rate': 2.0,
-        'current_rate': 2.0,
-        'profit_amount': -0.8985,
-        'profit_ratio': -0.01493766,
+        'enter_tag': None,
+        'open_rate': 2.02 if is_short else 2.0,
+        'current_rate': 2.2 if is_short else 2.0,
+        'profit_amount': -0.3 if is_short else -0.8985,
+        'profit_ratio': -0.00501253 if is_short else -0.01493766,
         'stake_currency': 'USDT',
         'fiat_currency': 'USD',
-        'sell_reason': SellType.STOP_LOSS.value,
+        'sell_reason': ExitType.STOP_LOSS.value,
         'open_date': ANY,
         'close_date': ANY,
         'close_rate': ANY,
@@ -2936,13 +3348,14 @@ def test_execute_trade_exit_sloe_cancel_exception(
     trade.stoploss_order_id = "abcd"
 
     freqtrade.execute_trade_exit(trade=trade, limit=1234,
-                                 sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
+                                 exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS))
     assert create_order_mock.call_count == 2
     assert log_has('Could not cancel stoploss order abcd', caplog)
 
 
-def test_execute_trade_exit_with_stoploss_on_exchange(default_conf_usdt, ticker_usdt, fee,
-                                                      ticker_usdt_sell_up, mocker) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_execute_trade_exit_with_stoploss_on_exchange(
+        default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, is_short, mocker) -> None:
 
     default_conf_usdt['exchange']['name'] = 'binance'
     rpc_mock = patch_RPCManager(mocker)
@@ -2968,12 +3381,13 @@ def test_execute_trade_exit_with_stoploss_on_exchange(default_conf_usdt, ticker_
 
     freqtrade = FreqtradeBot(default_conf_usdt)
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # Create some test data
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
     trades = [trade]
 
@@ -2986,17 +3400,22 @@ def test_execute_trade_exit_with_stoploss_on_exchange(default_conf_usdt, ticker_
         fetch_ticker=ticker_usdt_sell_up
     )
 
-    freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
-                                 sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
+    freqtrade.execute_trade_exit(
+        trade=trade,
+        limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
+        exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS)
+    )
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
     assert cancel_order.call_count == 1
     assert rpc_mock.call_count == 3
 
 
-def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt, ticker_usdt, fee,
-                                                               mocker) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
+        default_conf_usdt, ticker_usdt, fee, mocker, is_short) -> None:
     default_conf_usdt['exchange']['name'] = 'binance'
     rpc_mock = patch_RPCManager(mocker)
     patch_exchange(mocker)
@@ -3020,7 +3439,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt
 
     freqtrade = FreqtradeBot(default_conf_usdt)
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_long=not is_short, enter_short=is_short)
 
     # Create some test data
     freqtrade.enter_positions()
@@ -3034,7 +3453,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt
     assert trade.stoploss_order_id == '123'
     assert trade.open_order_id is None
 
-    # Assuming stoploss on exchnage is hit
+    # Assuming stoploss on exchange is hit
     # stoploss_order_id should become None
     # and trade should be sold at the price of stoploss
     stoploss_executed = MagicMock(return_value={
@@ -3060,15 +3479,46 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt
     freqtrade.exit_positions(trades)
     assert trade.stoploss_order_id is None
     assert trade.is_open is False
-    assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value
+    assert trade.sell_reason == ExitType.STOPLOSS_ON_EXCHANGE.value
     assert rpc_mock.call_count == 3
-    assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.BUY
-    assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.BUY_FILL
-    assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
+    if is_short:
+        assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.SHORT
+        assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.SHORT_FILL
+        assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
+
+    else:
+        assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.BUY
+        assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.BUY_FILL
+        assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
 
 
-def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
-                                         ticker_usdt_sell_up, mocker) -> None:
+@pytest.mark.parametrize(
+    "is_short,amount,current_rate,limit,profit_amount,profit_ratio,profit_or_loss", [
+        (False, 30, 2.3, 2.2, 5.685, 0.09451372, 'profit'),
+        (True, 29.70297029, 2.2, 2.3, -8.63762376, -0.1443212, 'loss'),
+    ])
+def test_execute_trade_exit_market_order(
+    default_conf_usdt, ticker_usdt, fee, is_short, current_rate, amount,
+    limit, profit_amount, profit_ratio, profit_or_loss, ticker_usdt_sell_up, mocker
+) -> None:
+    """
+    amount
+        long: 60 / 2.0 = 30
+        short: 60 / 2.02 = 29.70297029
+    open_value
+        long: (30 * 2.0) + (30 * 2.0 * 0.0025) = 60.15
+        short: (29.702970297029704 * 2.02) - (29.702970297029704 * 2.02 * 0.0025) = 59.85
+    close_value
+        long: (30 * 2.2) - (30 * 2.2 * 0.0025) = 65.835
+        short: (29.702970297029704 * 2.3) + (29.702970297029704 * 2.3 * 0.0025) = 68.48762376237624
+    profit
+        long: 65.835 - 60.15 = 5.684999999999995
+        short: 59.85 - 68.48762376237624 = -8.637623762376244
+    profit_ratio
+        long: (65.835/60.15) - 1 = 0.0945137157107232
+        short: 1 - (68.48762376237624/59.85) = -0.1443211990371971
+    """
+    open_rate = ticker_usdt.return_value['ask' if is_short else 'bid']
     rpc_mock = patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -3079,12 +3529,13 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
     )
     patch_whitelist(mocker, default_conf_usdt)
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # Create some test data
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
 
     # Increase the price and sell it
@@ -3092,13 +3543,16 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt_sell_up
     )
-    freqtrade.config['order_types']['sell'] = 'market'
+    freqtrade.config['order_types']['exit'] = 'market'
 
-    freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['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'],
+        exit_check=ExitCheckTuple(exit_type=ExitType.ROI)
+    )
 
     assert not trade.is_open
-    assert trade.close_profit == 0.09451372
+    assert trade.close_profit == profit_ratio
 
     assert rpc_mock.call_count == 3
     last_msg = rpc_mock.call_args_list[-2][0][0]
@@ -3107,18 +3561,21 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
         'trade_id': 1,
         'exchange': 'Binance',
         'pair': 'ETH/USDT',
-        'gain': 'profit',
-        'limit': 2.2,
-        'amount': 30.0,
+        'direction': 'Short' if trade.is_short else 'Long',
+        'leverage': 1.0,
+        'gain': profit_or_loss,
+        'limit': limit,
+        'amount': pytest.approx(amount),
         'order_type': 'market',
         'buy_tag': None,
-        'open_rate': 2.0,
-        'current_rate': 2.3,
-        'profit_amount': 5.685,
-        'profit_ratio': 0.09451372,
+        'enter_tag': None,
+        'open_rate': open_rate,
+        'current_rate': current_rate,
+        'profit_amount': pytest.approx(profit_amount),
+        'profit_ratio': profit_ratio,
         'stake_currency': 'USDT',
         'fiat_currency': 'USD',
-        'sell_reason': SellType.ROI.value,
+        'sell_reason': ExitType.ROI.value,
         'open_date': ANY,
         'close_date': ANY,
         'close_rate': ANY,
@@ -3126,7 +3583,8 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
     } == last_msg
 
 
-def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_usdt, fee,
+@pytest.mark.parametrize("is_short", [False, True])
+def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_usdt, fee, is_short,
                                                      ticker_usdt_sell_up, mocker) -> None:
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
     mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
@@ -3139,12 +3597,13 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u
             InsufficientFundsError(),
         ]),
     )
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # Create some test data
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
 
     # Increase the price and sell it
@@ -3153,28 +3612,36 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u
         fetch_ticker=ticker_usdt_sell_up
     )
 
-    sell_reason = SellCheckTuple(sell_type=SellType.ROI)
-    assert not freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
-                                            sell_reason=sell_reason)
+    sell_reason = ExitCheckTuple(exit_type=ExitType.ROI)
+    assert not freqtrade.execute_trade_exit(
+        trade=trade,
+        limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
+        exit_check=sell_reason
+    )
     assert mock_insuf.call_count == 1
 
 
-@pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,sell_type', [
+@pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,sell_type,is_short', [
     # Enable profit
-    (True, 1.9, 2.2, False, True, SellType.SELL_SIGNAL.value),
-    # Disable profit
-    (False, 2.9, 3.2, True,  False, SellType.SELL_SIGNAL.value),
-    # Enable loss
-    # * Shouldn't this be SellType.STOP_LOSS.value
-    (True, 0.19, 0.22, False, False, None),
+    (True, 2.18, 2.2, False, True, ExitType.SELL_SIGNAL.value, False),
+    (True, 2.18, 2.2, False, True, ExitType.SELL_SIGNAL.value, True),
+    # # Disable profit
+    (False, 3.19, 3.2, True,  False, ExitType.SELL_SIGNAL.value, False),
+    (False, 3.19, 3.2, True,  False, ExitType.SELL_SIGNAL.value, True),
+    # # Enable loss
+    # # * Shouldn't this be ExitType.STOP_LOSS.value
+    (True, 0.21, 0.22, False, False, None, False),
+    (True, 2.41, 2.42, False, False, None, True),
     # Disable loss
-    (False, 0.10, 0.22, True, False, SellType.SELL_SIGNAL.value),
+    (False, 0.10, 0.22, True, False, ExitType.SELL_SIGNAL.value, False),
+    (False, 0.10, 0.22, True, False, ExitType.SELL_SIGNAL.value, True),
 ])
 def test_sell_profit_only(
-        default_conf_usdt, limit_buy_order_usdt, limit_buy_order_usdt_open,
+        default_conf_usdt, limit_order, limit_order_open, is_short,
         fee, mocker, profit_only, bid, ask, handle_first, handle_second, sell_type) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
+    eside = enter_side(is_short)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=MagicMock(return_value={
@@ -3183,7 +3650,7 @@ def test_sell_profit_only(
             'last': bid
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            limit_order_open[eside],
             {'id': 1234553382},
         ]),
         get_fee=fee,
@@ -3194,19 +3661,20 @@ def test_sell_profit_only(
         'sell_profit_offset': 0.1,
     })
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
-    if sell_type == SellType.SELL_SIGNAL.value:
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
+    if sell_type == ExitType.SELL_SIGNAL.value:
         freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
     else:
-        freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple(
-            sell_type=SellType.NONE))
+        freqtrade.strategy.stop_loss_reached = MagicMock(return_value=ExitCheckTuple(
+            exit_type=ExitType.NONE))
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
-    oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy')
+    trade.is_short = is_short
+    oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]['symbol'], eside)
     trade.update_trade(oobj)
     freqtrade.wallets.update()
-    patch_get_signal(freqtrade, value=(False, True, None, None))
+    patch_get_signal(freqtrade, enter_long=False, exit_short=is_short, exit_long=not is_short)
     assert freqtrade.handle_trade(trade) is handle_first
 
     if handle_second:
@@ -3214,7 +3682,7 @@ def test_sell_profit_only(
         assert freqtrade.handle_trade(trade) is True
 
 
-def test_sell_not_enough_balance(default_conf_usdt, limit_buy_order_usdt, limit_buy_order_usdt_open,
+def test_sell_not_enough_balance(default_conf_usdt, limit_order, limit_order_open,
                                  fee, mocker, caplog) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
@@ -3226,7 +3694,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_buy_order_usdt, limit_
             'last': 0.00002172
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            limit_order_open['buy'],
             {'id': 1234553382},
         ]),
         get_fee=fee,
@@ -3241,9 +3709,9 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_buy_order_usdt, limit_
     trade = Trade.query.first()
     amnt = trade.amount
 
-    oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy')
+    oobj = Order.parse_from_ccxt_object(limit_order['buy'], limit_order['buy']['symbol'], 'buy')
     trade.update_trade(oobj)
-    patch_get_signal(freqtrade, value=(False, True, None, None))
+    patch_get_signal(freqtrade, enter_long=False, exit_long=True)
     mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=trade.amount * 0.985))
 
     assert freqtrade.handle_trade(trade) is True
@@ -3274,7 +3742,7 @@ def test__safe_exit_amount(default_conf_usdt, fee, caplog, mocker, amount_wallet
     freqtrade = FreqtradeBot(default_conf_usdt)
     patch_get_signal(freqtrade)
     if has_err:
-        with pytest.raises(DependencyException, match=r"Not enough amount to sell."):
+        with pytest.raises(DependencyException, match=r"Not enough amount to exit trade."):
             assert freqtrade._safe_exit_amount(trade.pair, trade.amount)
     else:
         wallet_update.reset_mock()
@@ -3288,8 +3756,9 @@ def test__safe_exit_amount(default_conf_usdt, fee, caplog, mocker, amount_wallet
         assert wallet_update.call_count == 1
 
 
-def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down, mocker,
-                      caplog) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_locked_pairs(default_conf_usdt, ticker_usdt, fee,
+                      ticker_usdt_sell_down, mocker, caplog, is_short) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -3298,12 +3767,13 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down
         get_fee=fee,
     )
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
 
     # Create some test data
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
+    trade.is_short = is_short
     assert trade
 
     # Decrease the price and sell it
@@ -3312,8 +3782,11 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down
         fetch_ticker=ticker_usdt_sell_down
     )
 
-    freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_down()['bid'],
-                                 sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
+    freqtrade.execute_trade_exit(
+        trade=trade,
+        limit=ticker_usdt_sell_down()['ask' if is_short else 'bid'],
+        exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS)
+    )
     trade.close(ticker_usdt_sell_down()['bid'])
     assert freqtrade.strategy.is_pair_locked(trade.pair)
 
@@ -3324,10 +3797,12 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down
     assert log_has_re(f"Pair {trade.pair} is still locked.*", caplog)
 
 
-def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt,
-                                  limit_buy_order_usdt_open, fee, mocker) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_open, is_short,
+                                  fee, mocker) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
+    eside = enter_side(is_short)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=MagicMock(return_value={
@@ -3336,7 +3811,7 @@ def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt,
             'last': 2.19
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            limit_order_open[eside],
             {'id': 1234553382},
         ]),
         get_fee=fee,
@@ -3344,26 +3819,39 @@ def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt,
     default_conf_usdt['ignore_roi_if_buy_signal'] = True
 
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
 
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
-    oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy')
+    trade.is_short = is_short
+    oobj = Order.parse_from_ccxt_object(
+        limit_order[eside], limit_order[eside]['symbol'], eside)
     trade.update_trade(oobj)
     freqtrade.wallets.update()
-    patch_get_signal(freqtrade, value=(True, True, None, None))
+    if is_short:
+        patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
+    else:
+        patch_get_signal(freqtrade, enter_long=True, exit_long=True)
+
     assert freqtrade.handle_trade(trade) is False
 
-    # Test if buy-signal is absent (should sell due to roi = true)
-    patch_get_signal(freqtrade, value=(False, False, None, None))
+    # Test if entry-signal is absent (should sell due to roi = true)
+    if is_short:
+        patch_get_signal(freqtrade, enter_long=False, exit_short=False)
+    else:
+        patch_get_signal(freqtrade, enter_long=False, exit_long=False)
     assert freqtrade.handle_trade(trade) is True
-    assert trade.sell_reason == SellType.ROI.value
+    assert trade.sell_reason == ExitType.ROI.value
 
 
-def test_trailing_stop_loss(default_conf_usdt, limit_buy_order_usdt_open,
-                            fee, caplog, mocker) -> None:
+@pytest.mark.parametrize("is_short,val1,val2", [
+    (False, 1.5, 1.1),
+    (True, 0.5, 0.9)
+])
+def test_trailing_stop_loss(default_conf_usdt, limit_order_open,
+                            is_short, val1, val2, fee, caplog, mocker) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
@@ -3374,7 +3862,7 @@ def test_trailing_stop_loss(default_conf_usdt, limit_buy_order_usdt_open,
             'last': 2.0
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            limit_order_open[enter_side(is_short)],
             {'id': 1234553382},
         ]),
         get_fee=fee,
@@ -3382,19 +3870,20 @@ def test_trailing_stop_loss(default_conf_usdt, limit_buy_order_usdt_open,
     default_conf_usdt['trailing_stop'] = True
     patch_whitelist(mocker, default_conf_usdt)
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
 
     freqtrade.enter_positions()
     trade = Trade.query.first()
+    assert trade.is_short == is_short
     assert freqtrade.handle_trade(trade) is False
 
-    # Raise ticker_usdt above buy price
+    # Raise praise into profits
     mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
                  MagicMock(return_value={
-                     'bid': 2.0 * 1.5,
-                     'ask': 2.0 * 1.5,
-                     'last': 2.0 * 1.5
+                     'bid': 2.0 * val1,
+                     'ask': 2.0 * val1,
+                     'last': 2.0 * val1
                  }))
 
     # Stoploss should be adjusted
@@ -3403,40 +3892,47 @@ def test_trailing_stop_loss(default_conf_usdt, limit_buy_order_usdt_open,
     # Price fell
     mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
                  MagicMock(return_value={
-                     'bid': 2.0 * 1.1,
-                     'ask': 2.0 * 1.1,
-                     'last': 2.0 * 1.1
+                     'bid': 2.0 * val2,
+                     'ask': 2.0 * val2,
+                     'last': 2.0 * val2
                  }))
 
     caplog.set_level(logging.DEBUG)
     # Sell as trailing-stop is reached
     assert freqtrade.handle_trade(trade) is True
-    assert log_has("ETH/USDT - HIT STOP: current price at 2.200000, stoploss is 2.700000, "
-                   "initial stoploss was at 1.800000, trade opened at 2.000000", caplog)
-    assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
+    stop_multi = 1.1 if is_short else 0.9
+    assert log_has(f"ETH/USDT - HIT STOP: current price at {(2.0 * val2):6f}, "
+                   f"stoploss is {(2.0 * val1 * stop_multi):6f}, "
+                   f"initial stoploss was at {(2.0 * stop_multi):6f}, trade opened at 2.000000",
+                   caplog)
+    assert trade.sell_reason == ExitType.TRAILING_STOP_LOSS.value
 
 
-@pytest.mark.parametrize('offset,trail_if_reached,second_sl', [
-    (0, False, 2.0394),
-    (0.011, False, 2.0394),
-    (0.055, True, 1.8),
+@pytest.mark.parametrize('offset,trail_if_reached,second_sl,is_short', [
+    (0, False, 2.0394, False),
+    (0.011, False, 2.0394, False),
+    (0.055, True, 1.8, False),
+    (0, False, 2.1614, True),
+    (0.011, False, 2.1614, True),
+    (0.055, True, 2.42, True),
 ])
 def test_trailing_stop_loss_positive(
-    default_conf_usdt, limit_buy_order_usdt, limit_buy_order_usdt_open,
-    offset, fee, caplog, mocker, trail_if_reached, second_sl
+    default_conf_usdt, limit_order, limit_order_open,
+    offset, fee, caplog, mocker, trail_if_reached, second_sl, is_short
 ) -> None:
-    buy_price = limit_buy_order_usdt['price']
+    enter_price = limit_order[enter_side(is_short)]['price']
     patch_RPCManager(mocker)
     patch_exchange(mocker)
+    eside = enter_side(is_short)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=MagicMock(return_value={
-            'bid': buy_price - 0.01,
-            'ask': buy_price - 0.01,
-            'last': buy_price - 0.01
+            'bid': enter_price - (-0.01 if is_short else 0.01),
+            'ask': enter_price - (-0.01 if is_short else 0.01),
+            'last': enter_price - (-0.01 if is_short else 0.01),
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            limit_order_open[eside],
             {'id': 1234553382},
         ]),
         get_fee=fee,
@@ -3449,12 +3945,13 @@ def test_trailing_stop_loss_positive(
     patch_whitelist(mocker, default_conf_usdt)
 
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
-    oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy')
+    assert trade.is_short == is_short
+    oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]['symbol'], eside)
     trade.update_trade(oobj)
     caplog.set_level(logging.DEBUG)
     # stop-loss not reached
@@ -3464,34 +3961,36 @@ def test_trailing_stop_loss_positive(
     mocker.patch(
         'freqtrade.exchange.Exchange.fetch_ticker',
         MagicMock(return_value={
-            'bid': buy_price + 0.06,
-            'ask': buy_price + 0.06,
-            'last': buy_price + 0.06
+            'bid': enter_price + (-0.06 if is_short else 0.06),
+            'ask': enter_price + (-0.06 if is_short else 0.06),
+            'last': enter_price + (-0.06 if is_short else 0.06),
         })
     )
     # stop-loss not reached, adjusted stoploss
     assert freqtrade.handle_trade(trade) is False
-    caplog_text = f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: 2.49%"
+    caplog_text = (f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "
+                   f"{'2.49' if not is_short else '2.24'}%")
     if trail_if_reached:
         assert not log_has(caplog_text, caplog)
         assert not log_has("ETH/USDT - Adjusting stoploss...", caplog)
     else:
         assert log_has(caplog_text, caplog)
         assert log_has("ETH/USDT - Adjusting stoploss...", caplog)
-    assert trade.stop_loss == second_sl
+    assert pytest.approx(trade.stop_loss) == second_sl
     caplog.clear()
 
     mocker.patch(
         'freqtrade.exchange.Exchange.fetch_ticker',
         MagicMock(return_value={
-            'bid': buy_price + 0.125,
-            'ask': buy_price + 0.125,
-            'last': buy_price + 0.125,
+            'bid': enter_price + (-0.135 if is_short else 0.125),
+            'ask': enter_price + (-0.135 if is_short else 0.125),
+            'last': enter_price + (-0.135 if is_short else 0.125),
         })
     )
     assert freqtrade.handle_trade(trade) is False
     assert log_has(
-        f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: 5.72%",
+        f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "
+        f"{'5.72' if not is_short else '5.67'}%",
         caplog
     )
     assert log_has("ETH/USDT - Adjusting stoploss...", caplog)
@@ -3499,24 +3998,28 @@ def test_trailing_stop_loss_positive(
     mocker.patch(
         'freqtrade.exchange.Exchange.fetch_ticker',
         MagicMock(return_value={
-            'bid': buy_price + 0.02,
-            'ask': buy_price + 0.02,
-            'last': buy_price + 0.02
+            'bid': enter_price + (-0.02 if is_short else 0.02),
+            'ask': enter_price + (-0.02 if is_short else 0.02),
+            'last': enter_price + (-0.02 if is_short else 0.02),
         })
     )
     # Lower price again (but still positive)
     assert freqtrade.handle_trade(trade) is True
     assert log_has(
-        f"ETH/USDT - HIT STOP: current price at {buy_price + 0.02:.6f}, "
+        f"ETH/USDT - HIT STOP: current price at {enter_price + (-0.02 if is_short else 0.02):.6f}, "
         f"stoploss is {trade.stop_loss:.6f}, "
-        f"initial stoploss was at 1.800000, trade opened at 2.000000", caplog)
-    assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
+        f"initial stoploss was at {'2.42' if is_short else '1.80'}0000, "
+        f"trade opened at {2.2 if is_short else 2.0}00000",
+        caplog)
+    assert trade.sell_reason == ExitType.TRAILING_STOP_LOSS.value
 
 
-def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt,
-                                          limit_buy_order_usdt_open, fee, mocker) -> None:
+@pytest.mark.parametrize("is_short", [False, True])
+def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_open,
+                                          is_short, fee, mocker) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
+    eside = enter_side(is_short)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=MagicMock(return_value={
@@ -3525,33 +4028,36 @@ def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usd
             'last': 2.0
         }),
         create_order=MagicMock(side_effect=[
-            limit_buy_order_usdt_open,
+            limit_order_open[eside],
             {'id': 1234553382},
             {'id': 1234553383}
         ]),
         get_fee=fee,
         _is_dry_limit_order_filled=MagicMock(return_value=False),
     )
-    default_conf_usdt['ask_strategy'] = {
+    default_conf_usdt['exit_pricing'] = {
         'ignore_roi_if_buy_signal': False
     }
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
 
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
-    oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy')
+    trade.is_short = is_short
+
+    oobj = Order.parse_from_ccxt_object(
+        limit_order[eside], limit_order[eside]['symbol'], eside)
     trade.update_trade(oobj)
     # Sell due to min_roi_reached
-    patch_get_signal(freqtrade, value=(True, False, None, None))
+    patch_get_signal(freqtrade, enter_long=not is_short, enter_short=is_short, exit_short=is_short)
     assert freqtrade.handle_trade(trade) is True
 
-    # Test if buy-signal is absent
-    patch_get_signal(freqtrade, value=(False, False, None, None))
+    # Test if entry-signal is absent
+    patch_get_signal(freqtrade)
     assert freqtrade.handle_trade(trade) is True
-    assert trade.sell_reason == SellType.ROI.value
+    assert trade.sell_reason == ExitType.ROI.value
 
 
 def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fee, fee, caplog,
@@ -3568,12 +4074,16 @@ def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fe
         open_order_id="123456"
     )
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
+
+    caplog.clear()
     order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
     # Amount is reduced by "fee"
     assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount - (amount * 0.001)
-    assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
-                   'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992).',
-                   caplog)
+    assert log_has(
+        'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, is_short=False,'
+        ' leverage=1.0, open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992).',
+        caplog
+    )
 
 
 def test_get_real_amount_quote_dust(default_conf_usdt, trades_for_order, buy_order_fee, fee,
@@ -3620,9 +4130,12 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock
     order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
     # Amount is reduced by "fee"
     assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount
-    assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
-                   'open_rate=0.24544100, open_since=closed) failed: myTrade-Dict empty found',
-                   caplog)
+    assert log_has(
+        'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
+        'is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed) failed: '
+        'myTrade-Dict empty found',
+        caplog
+    )
 
 
 @pytest.mark.parametrize(
@@ -3633,13 +4146,15 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock
         ({'cost': 0.004, 'currency': None}, 0, True, None),
         # BNB no rate
         ({'cost': 0.00094518, 'currency': 'BNB'}, 0, True, (
-            'Fee for Trade Trade(id=None, pair=LTC/ETH, amount=8.00000000, open_rate=0.24544100,'
-            ' open_since=closed) [buy]: 0.00094518 BNB - rate: None'
+            'Fee for Trade Trade(id=None, pair=LTC/ETH, amount=8.00000000, is_short=False, '
+            'leverage=1.0, open_rate=0.24544100, open_since=closed) [buy]: 0.00094518 BNB -'
+            ' rate: None'
         )),
         # from order
         ({'cost': 0.004, 'currency': 'LTC'}, 0.004, False, (
             'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
-            'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.996).'
+            'is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed) (from'
+            ' 8.0 to 7.996).'
         )),
         # invalid, no currency in from fee dict
         ({'cost': 0.008, 'currency': None}, 0, True, None),
@@ -3723,7 +4238,8 @@ def test_get_real_amount_multi(
     assert log_has(
         (
             'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
-            f'open_rate=0.24544100, open_since=closed) (from 8.0 to {expected_log_amount}).'
+            'is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed) '
+            f'(from 8.0 to {expected_log_amount}).'
         ),
         caplog
     )
@@ -3837,7 +4353,7 @@ def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_ord
     )
 
 
-def test_get_real_amount_open_trade(default_conf_usdt, fee, mocker):
+def test_get_real_amount_open_trade_usdt(default_conf_usdt, fee, mocker):
     amount = 12345
     trade = Trade(
         pair='LTC/ETH',
@@ -3892,34 +4408,38 @@ def test_apply_fee_conditional(default_conf_usdt, fee, mocker,
     (0.1, False),
     (100, True),
 ])
+@pytest.mark.parametrize('is_short', [False, True])
 def test_order_book_depth_of_market(
-    default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open, limit_buy_order_usdt,
-    fee, mocker, order_book_l2, delta, is_high_delta
+    default_conf_usdt, ticker_usdt, limit_order_open,
+    fee, mocker, order_book_l2, delta, is_high_delta, is_short
 ):
-    default_conf_usdt['bid_strategy']['check_depth_of_market']['enabled'] = True
-    default_conf_usdt['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = delta
+    ticker_side = 'ask' if is_short else 'bid'
+
+    default_conf_usdt['entry_pricing']['check_depth_of_market']['enabled'] = True
+    default_conf_usdt['entry_pricing']['check_depth_of_market']['bids_to_ask_delta'] = delta
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         fetch_ticker=ticker_usdt,
-        create_order=MagicMock(return_value=limit_buy_order_usdt_open),
+        create_order=MagicMock(return_value=limit_order_open[enter_side(is_short)]),
         get_fee=fee,
     )
 
     # Save state of current whitelist
     whitelist = deepcopy(default_conf_usdt['exchange']['pair_whitelist'])
     freqtrade = FreqtradeBot(default_conf_usdt)
-    patch_get_signal(freqtrade)
+    patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
     freqtrade.enter_positions()
 
     trade = Trade.query.first()
     if is_high_delta:
         assert trade is None
     else:
+        trade.is_short = is_short
         assert trade is not None
-        assert trade.stake_amount == 60.0
+        assert pytest.approx(trade.stake_amount) == 60.0
         assert trade.is_open
         assert trade.open_date is not None
         assert trade.exchange == 'binance'
@@ -3927,10 +4447,11 @@ def test_order_book_depth_of_market(
         assert len(Trade.query.all()) == 1
 
         # Simulate fulfilled LIMIT_BUY order for trade
-        oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy')
+        oobj = Order.parse_from_ccxt_object(
+            limit_order_open[enter_side(is_short)], 'ADA/USDT', enter_side(is_short))
         trade.update_trade(oobj)
 
-        assert trade.open_rate == 2.0
+        assert trade.open_rate == ticker_usdt.return_value[ticker_side]
         assert whitelist == default_conf_usdt['exchange']['pair_whitelist']
 
 
@@ -3938,8 +4459,8 @@ def test_order_book_depth_of_market(
     (False, 0.045, 0.046, 2, None),
     (True,  0.042, 0.046, 1, {'bids': [[]], 'asks': [[]]})
 ])
-def test_order_book_bid_strategy1(mocker, default_conf_usdt, order_book_l2, exception_thrown,
-                                  ask, last, order_book_top, order_book, caplog) -> None:
+def test_order_book_entry_pricing1(mocker, default_conf_usdt, order_book_l2, exception_thrown,
+                                   ask, last, order_book_top, order_book, caplog) -> None:
     """
     test if function get_rate will return the order book price instead of the ask rate
     """
@@ -3951,23 +4472,24 @@ def test_order_book_bid_strategy1(mocker, default_conf_usdt, order_book_l2, exce
         fetch_ticker=ticker_usdt_mock,
     )
     default_conf_usdt['exchange']['name'] = 'binance'
-    default_conf_usdt['bid_strategy']['use_order_book'] = True
-    default_conf_usdt['bid_strategy']['order_book_top'] = order_book_top
-    default_conf_usdt['bid_strategy']['ask_last_balance'] = 0
+    default_conf_usdt['entry_pricing']['use_order_book'] = True
+    default_conf_usdt['entry_pricing']['order_book_top'] = order_book_top
+    default_conf_usdt['entry_pricing']['price_last_balance'] = 0
     default_conf_usdt['telegram']['enabled'] = False
 
     freqtrade = FreqtradeBot(default_conf_usdt)
     if exception_thrown:
         with pytest.raises(PricingError):
-            freqtrade.exchange.get_rate('ETH/USDT', refresh=True, side="buy")
+            freqtrade.exchange.get_rate('ETH/USDT', side="entry", is_short=False, refresh=True)
         assert log_has_re(
-            r'Buy Price at location 1 from orderbook could not be determined.', caplog)
+            r'Entry Price at location 1 from orderbook could not be determined.', caplog)
     else:
-        assert freqtrade.exchange.get_rate('ETH/USDT', refresh=True, side="buy") == 0.043935
+        assert freqtrade.exchange.get_rate(
+            'ETH/USDT', side="entry", is_short=False, refresh=True) == 0.043935
         assert ticker_usdt_mock.call_count == 0
 
 
-def test_check_depth_of_market_buy(default_conf_usdt, mocker, order_book_l2) -> None:
+def test_check_depth_of_market(default_conf_usdt, mocker, order_book_l2) -> None:
     """
     test check depth of market
     """
@@ -3978,25 +4500,26 @@ def test_check_depth_of_market_buy(default_conf_usdt, mocker, order_book_l2) ->
     )
     default_conf_usdt['telegram']['enabled'] = False
     default_conf_usdt['exchange']['name'] = 'binance'
-    default_conf_usdt['bid_strategy']['check_depth_of_market']['enabled'] = True
+    default_conf_usdt['entry_pricing']['check_depth_of_market']['enabled'] = True
     # delta is 100 which is impossible to reach. hence function will return false
-    default_conf_usdt['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 100
+    default_conf_usdt['entry_pricing']['check_depth_of_market']['bids_to_ask_delta'] = 100
     freqtrade = FreqtradeBot(default_conf_usdt)
 
-    conf = default_conf_usdt['bid_strategy']['check_depth_of_market']
-    assert freqtrade._check_depth_of_market_buy('ETH/USDT', conf) is False
+    conf = default_conf_usdt['entry_pricing']['check_depth_of_market']
+    assert freqtrade._check_depth_of_market('ETH/BTC', conf, side=SignalDirection.LONG) is False
 
 
-def test_order_book_ask_strategy(
-        default_conf_usdt, limit_buy_order_usdt_open, limit_buy_order_usdt, fee,
+@pytest.mark.parametrize('is_short', [False, True])
+def test_order_book_exit_pricing(
+        default_conf_usdt, limit_buy_order_usdt_open, limit_buy_order_usdt, fee, is_short,
         limit_sell_order_usdt_open, mocker, order_book_l2, caplog) -> None:
     """
     test order book ask strategy
     """
     mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
     default_conf_usdt['exchange']['name'] = 'binance'
-    default_conf_usdt['ask_strategy']['use_order_book'] = True
-    default_conf_usdt['ask_strategy']['order_book_top'] = 1
+    default_conf_usdt['exit_pricing']['use_order_book'] = True
+    default_conf_usdt['exit_pricing']['order_book_top'] = 1
     default_conf_usdt['telegram']['enabled'] = False
     patch_RPCManager(mocker)
     patch_exchange(mocker)
@@ -4027,7 +4550,10 @@ def test_order_book_ask_strategy(
     freqtrade.wallets.update()
     assert trade.is_open is True
 
-    patch_get_signal(freqtrade, value=(False, True, None, None))
+    if is_short:
+        patch_get_signal(freqtrade, enter_long=False,  exit_short=True)
+    else:
+        patch_get_signal(freqtrade, enter_long=False, exit_long=True)
     assert freqtrade.handle_trade(trade) is True
     assert trade.close_rate_requested == order_book_l2.return_value['asks'][0][0]
 
@@ -4035,7 +4561,7 @@ def test_order_book_ask_strategy(
                  return_value={'bids': [[]], 'asks': [[]]})
     with pytest.raises(PricingError):
         freqtrade.handle_trade(trade)
-    assert log_has_re(r'Sell Price at location 1 from orderbook could not be determined\..*',
+    assert log_has_re(r'Exit Price at location 1 from orderbook could not be determined\..*',
                       caplog)
 
 
@@ -4099,37 +4625,45 @@ def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_
 
 
 @pytest.mark.usefixtures("init_persistence")
-def test_cancel_all_open_orders(mocker, default_conf_usdt, fee, limit_buy_order_usdt,
-                                limit_sell_order_usdt):
+@pytest.mark.parametrize("is_short,buy_calls,sell_calls", [
+    (False, 1, 2),
+    (True, 1, 2),
+])
+def test_cancel_all_open_orders(mocker, default_conf_usdt, fee, limit_order, limit_order_open,
+                                is_short, buy_calls, sell_calls):
     default_conf_usdt['cancel_open_orders_on_exit'] = True
-    mocker.patch('freqtrade.exchange.Exchange.fetch_order',
-                 side_effect=[
-                     ExchangeError(),
-                     limit_sell_order_usdt,
-                     limit_buy_order_usdt,
-                     limit_sell_order_usdt
-                 ])
+    mocker.patch(
+        'freqtrade.exchange.Exchange.fetch_order',
+        side_effect=[
+            ExchangeError(),
+            limit_order[exit_side(is_short)],
+            limit_order_open[enter_side(is_short)],
+            limit_order_open[exit_side(is_short)],
+        ]
+    )
     buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_enter')
     sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit')
 
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short=is_short)
     trades = Trade.query.all()
     assert len(trades) == MOCK_TRADE_COUNT
     freqtrade.cancel_all_open_orders()
-    assert buy_mock.call_count == 1
-    assert sell_mock.call_count == 2
+    assert buy_mock.call_count == buy_calls
+    assert sell_mock.call_count == sell_calls
 
 
 @pytest.mark.usefixtures("init_persistence")
-def test_check_for_open_trades(mocker, default_conf_usdt, fee):
+@pytest.mark.parametrize("is_short", [False, True])
+def test_check_for_open_trades(mocker, default_conf_usdt, fee, is_short):
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
 
     freqtrade.check_for_open_trades()
     assert freqtrade.rpc.send_msg.call_count == 0
 
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short)
     trade = Trade.query.first()
+    trade.is_short = is_short
     trade.is_open = True
 
     freqtrade.check_for_open_trades()
@@ -4137,10 +4671,11 @@ def test_check_for_open_trades(mocker, default_conf_usdt, fee):
     assert 'Handle these trades manually' in freqtrade.rpc.send_msg.call_args[0][0]['status']
 
 
+@pytest.mark.parametrize("is_short", [False, True])
 @pytest.mark.usefixtures("init_persistence")
-def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog):
+def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_short):
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short=is_short)
 
     freqtrade.startup_update_open_orders()
     assert not log_has_re(r"Error updating Order .*", caplog)
@@ -4153,7 +4688,7 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog):
     caplog.clear()
 
     assert len(Order.get_open_orders()) == 3
-    matching_buy_order = mock_order_4()
+    matching_buy_order = mock_order_4(is_short=is_short)
     matching_buy_order.update({
         'status': 'closed',
     })
@@ -4164,7 +4699,8 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog):
 
 
 @pytest.mark.usefixtures("init_persistence")
-def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, fee):
+@pytest.mark.parametrize("is_short", [False, True])
+def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, fee, is_short):
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
 
     def patch_with_fee(order):
@@ -4174,19 +4710,20 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, f
 
     mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
                  side_effect=[
-                     patch_with_fee(mock_order_2_sell()),
-                     patch_with_fee(mock_order_3_sell()),
-                     patch_with_fee(mock_order_1()),
-                     patch_with_fee(mock_order_2()),
-                     patch_with_fee(mock_order_3()),
-                     patch_with_fee(mock_order_4()),
+                     patch_with_fee(mock_order_2_sell(is_short=is_short)),
+                     patch_with_fee(mock_order_3_sell(is_short=is_short)),
+                     patch_with_fee(mock_order_1(is_short=is_short)),
+                     patch_with_fee(mock_order_2(is_short=is_short)),
+                     patch_with_fee(mock_order_3(is_short=is_short)),
+                     patch_with_fee(mock_order_4(is_short=is_short)),
                  ]
                  )
 
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short=is_short)
     trades = Trade.get_trades().all()
     assert len(trades) == MOCK_TRADE_COUNT
     for trade in trades:
+        trade.is_short = is_short
         assert trade.fee_open_cost is None
         assert trade.fee_open_currency is None
         assert trade.fee_close_cost is None
@@ -4213,7 +4750,7 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, f
     for trade in trades:
         if trade.is_open:
             # Exclude Trade 4 - as the order is still open.
-            if trade.select_order('buy', False):
+            if trade.select_order(enter_side(is_short), False):
                 assert trade.fee_open_cost is not None
                 assert trade.fee_open_currency is not None
             else:
@@ -4226,20 +4763,21 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, f
 
 
 @pytest.mark.usefixtures("init_persistence")
-def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog):
+@pytest.mark.parametrize("is_short", [False, True])
+def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_short):
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
     mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
 
     mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
                  return_value={'status': 'open'})
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short)
     trades = Trade.get_trades().all()
 
     freqtrade.handle_insufficient_funds(trades[3])
     # assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
     assert mock_uts.call_count == 1
     assert mock_uts.call_args_list[0][0][0] == trades[3]
-    assert mock_uts.call_args_list[0][0][1] == mock_order_4()['id']
+    assert mock_uts.call_args_list[0][0][1] == mock_order_4(is_short)['id']
     assert log_has_re(r"Trying to refind lost order for .*", caplog)
     mock_uts.reset_mock()
     caplog.clear()
@@ -4255,6 +4793,7 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog):
         amount=30,
         open_rate=2.0,
         exchange='binance',
+        is_short=is_short
     )
     Trade.query.session.add(trade)
 
@@ -4264,7 +4803,8 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog):
 
 
 @pytest.mark.usefixtures("init_persistence")
-def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, caplog):
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, caplog):
     caplog.set_level(logging.DEBUG)
     freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
     mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
@@ -4275,8 +4815,9 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, caplog):
     def reset_open_orders(trade):
         trade.open_order_id = None
         trade.stoploss_order_id = None
+        trade.is_short = is_short
 
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short=is_short)
     trades = Trade.get_trades().all()
 
     caplog.clear()
@@ -4288,7 +4829,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, caplog):
     assert trade.stoploss_order_id is None
 
     freqtrade.handle_insufficient_funds(trade)
-    order = mock_order_1()
+    order = mock_order_1(is_short=is_short)
     assert log_has_re(r"Order Order(.*order_id=" + order['id'] + ".*) is no longer open.", caplog)
     assert mock_fo.call_count == 0
     assert mock_uts.call_count == 0
@@ -4306,7 +4847,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, caplog):
     assert trade.stoploss_order_id is None
 
     freqtrade.handle_insufficient_funds(trade)
-    order = mock_order_4()
+    order = mock_order_4(is_short=is_short)
     assert log_has_re(r"Trying to refind Order\(.*", caplog)
     assert mock_fo.call_count == 1
     assert mock_uts.call_count == 1
@@ -4324,7 +4865,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, caplog):
     assert trade.stoploss_order_id is None
 
     freqtrade.handle_insufficient_funds(trade)
-    order = mock_order_5_stoploss()
+    order = mock_order_5_stoploss(is_short=is_short)
     assert log_has_re(r"Trying to refind Order\(.*", caplog)
     assert mock_fo.call_count == 1
     assert mock_uts.call_count == 2
@@ -4343,7 +4884,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, caplog):
     assert trade.stoploss_order_id is None
 
     freqtrade.handle_insufficient_funds(trade)
-    order = mock_order_6_sell()
+    order = mock_order_6_sell(is_short=is_short)
     assert log_has_re(r"Trying to refind Order\(.*", caplog)
     assert mock_fo.call_count == 1
     assert mock_uts.call_count == 1
@@ -4356,7 +4897,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, caplog):
     # Test error case
     mock_fo = mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
                            side_effect=ExchangeError())
-    order = mock_order_5_stoploss()
+    order = mock_order_5_stoploss(is_short=is_short)
 
     freqtrade.handle_insufficient_funds(trades[4])
     assert log_has(f"Error updating {order['id']}.", caplog)
@@ -4402,6 +4943,195 @@ def test_get_valid_price(mocker, default_conf_usdt) -> None:
     assert valid_price_at_min_alwd < proposed_price
 
 
+@pytest.mark.parametrize('trading_mode,calls,t1,t2', [
+    ('spot', 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
+    ('margin', 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
+    ('futures', 31, "2021-09-01 00:00:02", "2021-09-01 08:00:01"),
+    ('futures', 32, "2021-08-31 23:59:59", "2021-09-01 08:00:01"),
+    ('futures', 32, "2021-09-01 00:00:02", "2021-09-01 08:00:02"),
+    ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:02"),
+    ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:03"),
+    ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:04"),
+    ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:05"),
+    ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:06"),
+    ('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:07"),
+    ('futures', 33, "2021-08-31 23:59:58", "2021-09-01 08:00:07"),
+])
+def test_update_funding_fees_schedule(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['margin_mode'] = '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
+
+
+@pytest.mark.parametrize('schedule_off', [False, True])
+@pytest.mark.parametrize('is_short', [True, False])
+def test_update_funding_fees(
+    mocker,
+    default_conf,
+    time_machine,
+    fee,
+    ticker_usdt_sell_up,
+    is_short,
+    limit_order_open,
+    schedule_off
+):
+    """
+    nominal_value = mark_price * size
+    funding_fee = nominal_value * funding_rate
+    size = 123
+    "LTC/USDT"
+        time: 0, mark: 3.3, fundRate: 0.00032583, nominal_value: 405.9, fundFee: 0.132254397
+        time: 8, mark: 3.2, fundRate: 0.00024472, nominal_value: 393.6, fundFee: 0.096321792
+    "ETH/USDT"
+        time: 0, mark: 2.4, fundRate: 0.0001, nominal_value: 295.2, fundFee: 0.02952
+        time: 8, mark: 2.5, fundRate: 0.0001, nominal_value: 307.5, fundFee: 0.03075
+    "ETC/USDT"
+        time: 0, mark: 4.3, fundRate: 0.00031077, nominal_value: 528.9, fundFee: 0.164366253
+        time: 8, mark: 4.1, fundRate: 0.00022655, nominal_value: 504.3, fundFee: 0.114249165
+    "XRP/USDT"
+        time: 0, mark: 1.2, fundRate: 0.00049426, nominal_value: 147.6, fundFee: 0.072952776
+        time: 8, mark: 1.2, fundRate: 0.00032715, nominal_value: 147.6, fundFee: 0.04828734
+    """
+    # SETUP
+    time_machine.move_to("2021-09-01 00:00:00 +00:00")
+
+    open_order = limit_order_open[enter_side(is_short)]
+    open_exit_order = limit_order_open[exit_side(is_short)]
+    bid = 0.11
+    enter_rate_mock = MagicMock(return_value=bid)
+    enter_mm = MagicMock(return_value=open_order)
+    patch_RPCManager(mocker)
+    patch_exchange(mocker)
+    default_conf['trading_mode'] = 'futures'
+    default_conf['margin_mode'] = 'isolated'
+
+    date_midnight = arrow.get('2021-09-01 00:00:00').datetime
+    date_eight = arrow.get('2021-09-01 08:00:00').datetime
+    date_sixteen = arrow.get('2021-09-01 16:00:00').datetime
+    columns = ['date', 'open', 'high', 'low', 'close', 'volume']
+    # 16:00 entry is actually never used
+    # But should be kept in the test to ensure we're filtering correctly.
+    funding_rates = {
+        "LTC/USDT":
+            DataFrame([
+                [date_midnight, 0.00032583, 0, 0, 0, 0],
+                [date_eight, 0.00024472, 0, 0, 0, 0],
+                [date_sixteen, 0.00024472, 0, 0, 0, 0],
+            ], columns=columns),
+        "ETH/USDT":
+            DataFrame([
+                [date_midnight, 0.0001, 0, 0, 0, 0],
+                [date_eight, 0.0001, 0, 0, 0, 0],
+                [date_sixteen, 0.0001, 0, 0, 0, 0],
+            ], columns=columns),
+        "XRP/USDT":
+            DataFrame([
+                [date_midnight, 0.00049426, 0, 0, 0, 0],
+                [date_eight, 0.00032715, 0, 0, 0, 0],
+                [date_sixteen, 0.00032715, 0, 0, 0, 0],
+            ], columns=columns)
+    }
+
+    mark_prices = {
+        "LTC/USDT":
+            DataFrame([
+                [date_midnight, 3.3, 0, 0, 0, 0],
+                [date_eight, 3.2, 0, 0, 0, 0],
+                [date_sixteen, 3.2, 0, 0, 0, 0],
+            ], columns=columns),
+        "ETH/USDT":
+            DataFrame([
+                [date_midnight, 2.4, 0, 0, 0, 0],
+                [date_eight, 2.5, 0, 0, 0, 0],
+                [date_sixteen, 2.5, 0, 0, 0, 0],
+            ], columns=columns),
+        "XRP/USDT":
+            DataFrame([
+                [date_midnight, 1.2, 0, 0, 0, 0],
+                [date_eight, 1.2, 0, 0, 0, 0],
+                [date_sixteen, 1.2, 0, 0, 0, 0],
+            ], columns=columns)
+    }
+
+    def refresh_latest_ohlcv_mock(pairlist, **kwargs):
+        ret = {}
+        for p, tf, ct in pairlist:
+            if ct == CandleType.MARK:
+                ret[(p, tf, ct)] = mark_prices[p]
+            else:
+                ret[(p, tf, ct)] = funding_rates[p]
+
+        return ret
+
+    mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv',
+                 side_effect=refresh_latest_ohlcv_mock)
+
+    mocker.patch.multiple(
+        'freqtrade.exchange.Exchange',
+        get_rate=enter_rate_mock,
+        fetch_ticker=MagicMock(return_value={
+            'bid': 1.9,
+            'ask': 2.2,
+            'last': 1.9
+        }),
+        create_order=enter_mm,
+        get_min_pair_stake_amount=MagicMock(return_value=1),
+        get_fee=fee,
+        get_maintenance_ratio_and_amt=MagicMock(return_value=(0.01, 0.01)),
+    )
+
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
+
+    # initial funding fees,
+    freqtrade.execute_entry('ETH/USDT', 123, is_short=is_short)
+    freqtrade.execute_entry('LTC/USDT', 2.0, is_short=is_short)
+    freqtrade.execute_entry('XRP/USDT', 123, is_short=is_short)
+    multipl = 1 if is_short else -1
+    trades = Trade.get_open_trades()
+    assert len(trades) == 3
+    for trade in trades:
+        assert pytest.approx(trade.funding_fees) == 0
+    mocker.patch('freqtrade.exchange.Exchange.create_order', return_value=open_exit_order)
+    time_machine.move_to("2021-09-01 08:00:00 +00:00")
+    if schedule_off:
+        for trade in trades:
+            freqtrade.execute_trade_exit(
+                trade=trade,
+                # The values of the next 2 params are irrelevant for this test
+                limit=ticker_usdt_sell_up()['bid'],
+                exit_check=ExitCheckTuple(exit_type=ExitType.ROI)
+            )
+            assert trade.funding_fees == pytest.approx(sum(
+                trade.amount *
+                mark_prices[trade.pair].iloc[1:2]['open'] *
+                funding_rates[trade.pair].iloc[1:2]['open'] * multipl
+            ))
+
+    else:
+        freqtrade._schedule.run_pending()
+
+    # Funding fees for 00:00 and 08:00
+    for trade in trades:
+        assert trade.funding_fees == pytest.approx(sum(
+            trade.amount *
+            mark_prices[trade.pair].iloc[1:2]['open'] *
+            funding_rates[trade.pair].iloc[1:2]['open'] *
+            multipl
+        ))
+
+
 def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
@@ -4571,7 +5301,8 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
         'cost': 108,
         'ft_is_open': False,
         'id': '651',
-        'order_id': '651'
+        'order_id': '651',
+        'datetime': arrow.utcnow().isoformat(),
     }
 
     mocker.patch('freqtrade.exchange.Exchange.create_order',
diff --git a/tests/test_integration.py b/tests/test_integration.py
index db3b1b5fc..d1fac3d71 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -2,11 +2,10 @@ from unittest.mock import MagicMock
 
 import pytest
 
-from freqtrade.enums import SellType
+from freqtrade.enums import ExitCheckTuple, ExitType
 from freqtrade.persistence import Trade
 from freqtrade.persistence.models import Order
 from freqtrade.rpc.rpc import RPC
-from freqtrade.strategy.interface import SellCheckTuple
 from tests.conftest import get_patched_freqtradebot, patch_get_signal
 
 
@@ -53,8 +52,8 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
         side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open])
     # Sell 3rd trade (not called for the first trade)
     should_sell_mock = MagicMock(side_effect=[
-        SellCheckTuple(sell_type=SellType.NONE),
-        SellCheckTuple(sell_type=SellType.SELL_SIGNAL)]
+        ExitCheckTuple(exit_type=ExitType.NONE),
+        ExitCheckTuple(exit_type=ExitType.SELL_SIGNAL)]
     )
     cancel_order_mock = MagicMock()
     mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
@@ -73,14 +72,14 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
         create_stoploss_order=MagicMock(return_value=True),
         _notify_exit=MagicMock(),
     )
-    mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
+    mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
     wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock())
     mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=1000))
 
     freqtrade = get_patched_freqtradebot(mocker, default_conf)
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
     # Switch ordertype to market to close trade immediately
-    freqtrade.strategy.order_types['sell'] = 'market'
+    freqtrade.strategy.order_types['exit'] = 'market'
     freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
     freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
     patch_get_signal(freqtrade)
@@ -116,7 +115,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
     assert wallets_mock.call_count == 4
 
     trade = trades[0]
-    assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value
+    assert trade.sell_reason == ExitType.STOPLOSS_ON_EXCHANGE.value
     assert not trade.is_open
 
     trade = trades[1]
@@ -124,7 +123,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
     assert trade.is_open
 
     trade = trades[2]
-    assert trade.sell_reason == SellType.SELL_SIGNAL.value
+    assert trade.sell_reason == ExitType.SELL_SIGNAL.value
     assert not trade.is_open
 
 
@@ -161,19 +160,19 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
         _notify_exit=MagicMock(),
     )
     should_sell_mock = MagicMock(side_effect=[
-        SellCheckTuple(sell_type=SellType.NONE),
-        SellCheckTuple(sell_type=SellType.SELL_SIGNAL),
-        SellCheckTuple(sell_type=SellType.NONE),
-        SellCheckTuple(sell_type=SellType.NONE),
-        SellCheckTuple(sell_type=SellType.NONE)]
+        ExitCheckTuple(exit_type=ExitType.NONE),
+        ExitCheckTuple(exit_type=ExitType.SELL_SIGNAL),
+        ExitCheckTuple(exit_type=ExitType.NONE),
+        ExitCheckTuple(exit_type=ExitType.NONE),
+        ExitCheckTuple(exit_type=ExitType.NONE)]
     )
-    mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
+    mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
 
     freqtrade = get_patched_freqtradebot(mocker, default_conf)
     rpc = RPC(freqtrade)
     freqtrade.strategy.order_types['stoploss_on_exchange'] = True
     # Switch ordertype to market to close trade immediately
-    freqtrade.strategy.order_types['sell'] = 'market'
+    freqtrade.strategy.order_types['exit'] = 'market'
     patch_get_signal(freqtrade)
 
     # Create 4 trades
@@ -184,7 +183,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
     assert len(trades) == 4
     assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC') == result1
 
-    rpc._rpc_forcebuy('TKN/BTC', None)
+    rpc._rpc_force_entry('TKN/BTC', None)
 
     trades = Trade.query.all()
     assert len(trades) == 5
@@ -231,13 +230,13 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
     assert len(Trade.get_trades().all()) == 1
     trade = Trade.get_trades().first()
     assert len(trade.orders) == 1
-    assert trade.stake_amount == 60
+    assert pytest.approx(trade.stake_amount) == 60
     assert trade.open_rate == 2.0
     # No adjustment
     freqtrade.process()
     trade = Trade.get_trades().first()
     assert len(trade.orders) == 1
-    assert trade.stake_amount == 60
+    assert pytest.approx(trade.stake_amount) == 60
 
     # Reduce bid amount
     ticker_usdt_modif = ticker_usdt.return_value
@@ -266,9 +265,10 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
 
     assert trade.amount == trade.orders[0].amount + trade.orders[1].amount
     assert trade.nr_of_successful_buys == 2
+    assert trade.nr_of_successful_entries == 2
 
     # Sell
-    patch_get_signal(freqtrade, value=(False, True, None, None))
+    patch_get_signal(freqtrade, enter_long=False, exit_long=True)
     freqtrade.process()
     trade = Trade.get_trades().first()
     assert trade.is_open is False
@@ -280,3 +280,74 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
     assert trade.orders[2].amount == trade.amount
 
     assert trade.nr_of_successful_buys == 2
+    assert trade.nr_of_successful_entries == 2
+
+
+def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
+    default_conf_usdt['position_adjustment_enable'] = True
+
+    freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
+    mocker.patch.multiple(
+        'freqtrade.exchange.Exchange',
+        fetch_ticker=ticker_usdt,
+        get_fee=fee,
+        amount_to_precision=lambda s, x, y: y,
+        price_to_precision=lambda s, x, y: y,
+    )
+
+    patch_get_signal(freqtrade, enter_long=False, enter_short=True)
+    freqtrade.enter_positions()
+
+    assert len(Trade.get_trades().all()) == 1
+    trade = Trade.get_trades().first()
+    assert len(trade.orders) == 1
+    assert pytest.approx(trade.stake_amount) == 60
+    assert trade.open_rate == 2.02
+    # No adjustment
+    freqtrade.process()
+    trade = Trade.get_trades().first()
+    assert len(trade.orders) == 1
+    assert pytest.approx(trade.stake_amount) == 60
+
+    # Reduce bid amount
+    ticker_usdt_modif = ticker_usdt.return_value
+    ticker_usdt_modif['ask'] = ticker_usdt_modif['ask'] * 1.004
+    mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value=ticker_usdt_modif)
+
+    # additional buy order
+    freqtrade.process()
+    trade = Trade.get_trades().first()
+    assert len(trade.orders) == 2
+    for o in trade.orders:
+        assert o.status == "closed"
+    assert pytest.approx(trade.stake_amount) == 120
+
+    # Open-rate averaged between 2.0 and 2.0 * 1.015
+    assert trade.open_rate >= 2.02
+    assert trade.open_rate < 2.02 * 1.015
+
+    # No action - profit raised above 1% (the bar set in the strategy).
+    freqtrade.process()
+    trade = Trade.get_trades().first()
+    assert len(trade.orders) == 2
+    assert pytest.approx(trade.stake_amount) == 120
+    # assert trade.orders[0].amount == 30
+    assert trade.orders[1].amount == 60 / ticker_usdt_modif['ask']
+
+    assert trade.amount == trade.orders[0].amount + trade.orders[1].amount
+    assert trade.nr_of_successful_entries == 2
+
+    # Buy
+    patch_get_signal(freqtrade, enter_long=False, exit_short=True)
+    freqtrade.process()
+    trade = Trade.get_trades().first()
+    assert trade.is_open is False
+    # assert trade.orders[0].amount == 30
+    assert trade.orders[0].side == 'sell'
+    assert trade.orders[1].amount == 60 / ticker_usdt_modif['ask']
+    # Sold everything
+    assert trade.orders[-1].side == 'buy'
+    assert trade.orders[2].amount == trade.amount
+
+    assert trade.nr_of_successful_entries == 2
+    assert trade.nr_of_successful_exits == 1
diff --git a/tests/test_misc.py b/tests/test_misc.py
index 4fd5338ad..107932be4 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -1,15 +1,16 @@
 # pragma pylint: disable=missing-docstring,C0103
 
 import datetime
+from copy import deepcopy
 from pathlib import Path
 from unittest.mock import MagicMock
 
 import pytest
 
-from freqtrade.misc import (decimals_per_coin, file_dump_json, file_load_json, format_ms_time,
-                            pair_to_filename, parse_db_uri_for_logging, plural, render_template,
-                            render_template_with_fallback, round_coin_value, safe_value_fallback,
-                            safe_value_fallback2, shorten_date)
+from freqtrade.misc import (decimals_per_coin, deep_merge_dicts, file_dump_json, file_load_json,
+                            format_ms_time, pair_to_filename, parse_db_uri_for_logging, plural,
+                            render_template, render_template_with_fallback, round_coin_value,
+                            safe_value_fallback, safe_value_fallback2, shorten_date)
 
 
 def test_decimals_per_coin():
@@ -72,14 +73,17 @@ def test_file_load_json(mocker, testdatadir) -> None:
     ("ETH/BTC", 'ETH_BTC'),
     ("ETH/USDT", 'ETH_USDT'),
     ("ETH/USDT:USDT", 'ETH_USDT_USDT'),  # swap with USDT as settlement currency
-    ("ETH/USDT:USDT-210625", 'ETH_USDT_USDT_210625'),  # expiring futures
+    ("ETH/USD:USD", 'ETH_USD_USD'),  # swap with USD as settlement currency
+    ("AAVE/USD:USD", 'AAVE_USD_USD'),  # swap with USDT as settlement currency
+    ("ETH/USDT:USDT-210625", 'ETH_USDT_USDT-210625'),  # expiring futures
     ("Fabric Token/ETH", 'Fabric_Token_ETH'),
     ("ETHH20", 'ETHH20'),
     (".XBTBON2H", '_XBTBON2H'),
     ("ETHUSD.d", 'ETHUSD_d'),
-    ("ADA-0327", 'ADA_0327'),
-    ("BTC-USD-200110", 'BTC_USD_200110'),
-    ("F-AKRO/USDT", 'F_AKRO_USDT'),
+    ("ADA-0327", 'ADA-0327'),
+    ("BTC-USD-200110", 'BTC-USD-200110'),
+    ("BTC-PERP:USDT", 'BTC-PERP_USDT'),
+    ("F-AKRO/USDT", 'F-AKRO_USDT'),
     ("LC+/ETH", 'LC__ETH'),
     ("CMT@18/ETH", 'CMT_18_ETH'),
     ("LBTC:1022/SAI", 'LBTC_1022_SAI'),
@@ -202,3 +206,16 @@ def test_render_template_fallback(mocker):
 def test_parse_db_uri_for_logging(conn_url, expected) -> None:
 
     assert parse_db_uri_for_logging(conn_url) == expected
+
+
+def test_deep_merge_dicts():
+    a = {'first': {'rows': {'pass': 'dog', 'number': '1', 'test': None}}}
+    b = {'first': {'rows': {'fail': 'cat', 'number': '5', 'test': 'asdf'}}}
+    res = {'first': {'rows': {'pass': 'dog', 'fail': 'cat', 'number': '5', 'test': 'asdf'}}}
+    res2 = {'first': {'rows': {'pass': 'dog', 'fail': 'cat', 'number': '1', 'test': None}}}
+    assert deep_merge_dicts(b, deepcopy(a)) == res
+
+    assert deep_merge_dicts(a, deepcopy(b)) == res2
+
+    res2['first']['rows']['test'] = 'asdf'
+    assert deep_merge_dicts(a, deepcopy(b), allow_null_overrides=False) == res2
diff --git a/tests/test_persistence.py b/tests/test_persistence.py
index 32253d1cb..881d2e6c3 100644
--- a/tests/test_persistence.py
+++ b/tests/test_persistence.py
@@ -11,10 +11,14 @@ import pytest
 from sqlalchemy import create_engine, 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 freqtrade.persistence.migrations import get_last_sequence_ids, set_sequence_ids
-from tests.conftest import create_mock_trades, create_mock_trades_usdt, log_has, log_has_re
+from tests.conftest import create_mock_trades, create_mock_trades_with_leverage, log_has, log_has_re
+
+
+spot, margin, futures = TradingMode.SPOT, TradingMode.MARGIN, TradingMode.FUTURES
 
 
 def test_init_create_session(default_conf):
@@ -69,30 +73,290 @@ def test_init_dryrun_db(default_conf, tmpdir):
     assert Path(filename).is_file()
 
 
+@pytest.mark.parametrize('is_short', [False, True])
 @pytest.mark.usefixtures("init_persistence")
-def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, caplog):
+def test_enter_exit_side(fee, is_short):
+    enter_side, exit_side = ("sell", "buy") if is_short else ("buy", "sell")
+    trade = Trade(
+        id=2,
+        pair='ADA/USDT',
+        stake_amount=0.001,
+        open_rate=0.01,
+        amount=5,
+        is_open=True,
+        open_date=arrow.utcnow().datetime,
+        fee_open=fee.return_value,
+        fee_close=fee.return_value,
+        exchange='binance',
+        is_short=is_short,
+        leverage=2.0,
+        trading_mode=margin
+    )
+    assert trade.enter_side == enter_side
+    assert trade.exit_side == exit_side
+    assert trade.trade_direction == 'short' if is_short else 'long'
+
+
+@pytest.mark.usefixtures("init_persistence")
+def test_set_stop_loss_isolated_liq(fee):
+    trade = Trade(
+        id=2,
+        pair='ADA/USDT',
+        stake_amount=60.0,
+        open_rate=2.0,
+        amount=30.0,
+        is_open=True,
+        open_date=arrow.utcnow().datetime,
+        fee_open=fee.return_value,
+        fee_close=fee.return_value,
+        exchange='binance',
+        is_short=False,
+        leverage=2.0,
+        trading_mode=margin
+    )
+    trade.set_isolated_liq(0.09)
+    assert trade.liquidation_price == 0.09
+    assert trade.stop_loss is None
+    assert trade.initial_stop_loss is None
+
+    trade._set_stop_loss(0.1, (1.0/9.0))
+    assert trade.liquidation_price == 0.09
+    assert trade.stop_loss == 0.1
+    assert trade.initial_stop_loss == 0.1
+
+    trade.set_isolated_liq(0.08)
+    assert trade.liquidation_price == 0.08
+    assert trade.stop_loss == 0.1
+    assert trade.initial_stop_loss == 0.1
+
+    trade.set_isolated_liq(0.11)
+    trade._set_stop_loss(0.1, 0)
+    assert trade.liquidation_price == 0.11
+    assert trade.stop_loss == 0.11
+    assert trade.initial_stop_loss == 0.1
+
+    # lower stop doesn't move stoploss
+    trade._set_stop_loss(0.1, 0)
+    assert trade.liquidation_price == 0.11
+    assert trade.stop_loss == 0.11
+    assert trade.initial_stop_loss == 0.1
+
+    trade.stop_loss = None
+    trade.liquidation_price = None
+    trade.initial_stop_loss = None
+
+    trade._set_stop_loss(0.07, 0)
+    assert trade.liquidation_price is None
+    assert trade.stop_loss == 0.07
+    assert trade.initial_stop_loss == 0.07
+
+    trade.is_short = True
+    trade.recalc_open_trade_value()
+    trade.stop_loss = None
+    trade.initial_stop_loss = None
+
+    trade.set_isolated_liq(0.09)
+    assert trade.liquidation_price == 0.09
+    assert trade.stop_loss is None
+    assert trade.initial_stop_loss is None
+
+    trade._set_stop_loss(0.08, (1.0/9.0))
+    assert trade.liquidation_price == 0.09
+    assert trade.stop_loss == 0.08
+    assert trade.initial_stop_loss == 0.08
+
+    trade.set_isolated_liq(0.1)
+    assert trade.liquidation_price == 0.1
+    assert trade.stop_loss == 0.08
+    assert trade.initial_stop_loss == 0.08
+
+    trade.set_isolated_liq(0.07)
+    trade._set_stop_loss(0.1, (1.0/8.0))
+    assert trade.liquidation_price == 0.07
+    assert trade.stop_loss == 0.07
+    assert trade.initial_stop_loss == 0.08
+
+    # Stop doesn't move stop higher
+    trade._set_stop_loss(0.1, (1.0/9.0))
+    assert trade.liquidation_price == 0.07
+    assert trade.stop_loss == 0.07
+    assert trade.initial_stop_loss == 0.08
+
+
+@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),
+
+    ("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(fee, exchange, is_short, lev, minutes, rate, interest,
+                  trading_mode):
     """
-        On this test we will buy and sell a crypto currency.
-        fee: 0.25% quote
+        10min, 5hr limit trade on Binance/Kraken at 3x,5x leverage
+        fee: 0.25 % quote
+        interest_rate: 0.05 % per 4 hrs
         open_rate: 2.00 quote
         close_rate: 2.20 quote
         amount: = 30.0 crypto
         stake_amount
-            60.0  quote
+            3x, -3x: 20.0  quote
+            5x, -5x: 12.0  quote
         borrowed
-             0 quote
-        open_value: (amount * open_rate) + (amount * open_rate * fee)
-             30 * 2 + 30 * 2 * 0.0025 = 60.15 quote
-        close_value:
-            (amount * close_rate) - (amount * close_rate * fee) - interest
-            (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835
-        total_profit:
-            close_value - open_value
-            65.835 - 60.15             = 5.685
-        total_profit_ratio:
-            ((close_value/open_value) - 1) * leverage
-            ((65.835 / 60.15) - 1)  * 1 = 0.0945137157107232
+          10min
+             3x: 40 quote
+            -3x: 30 crypto
+             5x: 48 quote
+            -5x: 30 crypto
+             1x: 0
+            -1x: 30 crypto
+        hours: 1/6 (10 minutes)
+        time-periods:
+            10min
+                kraken: (1 + 1) 4hr_periods = 2 4hr_periods
+                binance: 1/24 24hr_periods
+            4.95hr
+                kraken: ceil(1 + 4.95/4) 4hr_periods = 3 4hr_periods
+                binance: ceil(4.95)/24 24hr_periods = 5/24 24hr_periods
+        interest: borrowed * interest_rate * time-periods
+          10min
+            binance     3x: 40 * 0.0005 * 1/24 = 0.0008333333333333334 quote
+            kraken      3x: 40 * 0.0005 * 2    = 0.040 quote
+            binace     -3x: 30 * 0.0005 * 1/24 = 0.000625 crypto
+            kraken     -3x: 30 * 0.0005 * 2    = 0.030 crypto
+          5hr
+            binance     3x: 40 * 0.0005 * 5/24 = 0.004166666666666667 quote
+            kraken      3x: 40 * 0.0005 * 3    = 0.06 quote
+            binace     -3x: 30 * 0.0005 * 5/24 = 0.0031249999999999997 crypto
+            kraken     -3x: 30 * 0.0005 * 3    = 0.045 crypto
+          0.00025 interest
+            binance     3x: 40 * 0.00025 * 5/24 = 0.0020833333333333333 quote
+            kraken      3x: 40 * 0.00025 * 3    = 0.03 quote
+            binace     -3x: 30 * 0.00025 * 5/24 = 0.0015624999999999999 crypto
+            kraken     -3x: 30 * 0.00025 * 3    = 0.0225 crypto
+          5x leverage, 0.0005 interest, 5hr
+            binance     5x: 48 * 0.0005 * 5/24 = 0.005 quote
+            kraken      5x: 48 * 0.0005 * 3    = 0.07200000000000001 quote
+            binace     -5x: 30 * 0.0005 * 5/24 = 0.0031249999999999997 crypto
+            kraken     -5x: 30 * 0.0005 * 3    = 0.045 crypto
+          1x leverage, 0.0005 interest, 5hr
+            binance,kraken 1x: 0.0 quote
+            binace        -1x: 30 * 0.0005 * 5/24 = 0.003125 crypto
+            kraken        -1x: 30 * 0.0005 * 3    = 0.045 crypto
+    """
 
+    trade = Trade(
+        pair='ADA/USDT',
+        stake_amount=20.0,
+        amount=30.0,
+        open_rate=2.0,
+        open_date=datetime.utcnow() - timedelta(minutes=minutes),
+        fee_open=fee.return_value,
+        fee_close=fee.return_value,
+        exchange=exchange,
+        leverage=lev,
+        interest_rate=rate,
+        is_short=is_short,
+        trading_mode=trading_mode
+    )
+
+    assert round(float(trade.calculate_interest()), 8) == interest
+
+
+@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, trading_mode):
+    """
+        10 minute limit trade on Binance/Kraken at 1x, 3x leverage
+        fee: 0.25% quote
+        interest_rate: 0.05% per 4 hrs
+        open_rate: 2.00 quote
+        close_rate: 2.20 quote
+        amount: = 30.0 crypto
+        stake_amount
+            1x,-1x: 60.0  quote
+            3x,-3x: 20.0  quote
+        borrowed
+             1x:  0 quote
+             3x: 40 quote
+            -1x: 30 crypto
+            -3x: 30 crypto
+        hours: 1/6 (10 minutes)
+        time-periods:
+            kraken: (1 + 1) 4hr_periods = 2 4hr_periods
+            binance: 1/24 24hr_periods
+        interest: borrowed * interest_rate * time-periods
+            1x            :  /
+            binance     3x: 40 * 0.0005 * 1/24 = 0.0008333333333333334 quote
+            kraken      3x: 40 * 0.0005 * 2 = 0.040 quote
+            binace -1x,-3x: 30 * 0.0005 * 1/24 = 0.000625 crypto
+            kraken -1x,-3x: 30 * 0.0005 * 2 = 0.030 crypto
+        open_value: (amount * open_rate) ± (amount * open_rate * fee)
+             1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote
+            -1x,-3x: 30 * 2 - 30 * 2 * 0.0025 = 59.850 quote
+        amount_closed:
+            1x, 3x         : amount
+            -1x, -3x       : amount + interest
+            binance -1x,-3x: 30 + 0.000625 = 30.000625 crypto
+            kraken  -1x,-3x: 30 + 0.03 = 30.03 crypto
+        close_value:
+             1x, 3x: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest
+            -1x,-3x: (amount_closed * close_rate) + (amount_closed * close_rate * fee)
+            binance,kraken 1x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025)         = 65.835
+            binance        3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.00083333 = 65.83416667
+            kraken         3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.040 = 65.795
+            binance   -1x,-3x: (30.000625 * 2.20) + (30.000625 * 2.20 * 0.0025) = 66.16637843750001
+            kraken    -1x,-3x: (30.03 * 2.20) + (30.03 * 2.20 * 0.0025)         = 66.231165
+        total_profit:
+            1x, 3x : close_value - open_value
+            -1x,-3x: open_value  - close_value
+            binance,kraken 1x: 65.835 - 60.15             = 5.685
+            binance        3x: 65.83416667 - 60.15        = 5.684166670000003
+            kraken         3x: 65.795 - 60.15             = 5.645
+            binance   -1x,-3x: 59.850 - 66.16637843750001 = -6.316378437500013
+            kraken    -1x,-3x: 59.850 - 66.231165          = -6.381165
+        total_profit_ratio:
+            1x, 3x : ((close_value/open_value) - 1) * leverage
+            -1x,-3x: (1 - (close_value/open_value)) * leverage
+            binance  1x: ((65.835 / 60.15) - 1)  * 1 = 0.0945137157107232
+            binance  3x: ((65.83416667 / 60.15) - 1)  * 3 = 0.2834995845386534
+            kraken   1x: ((65.835 / 60.15) - 1)  * 1 = 0.0945137157107232
+            kraken   3x: ((65.795 / 60.15) - 1)  * 3 = 0.2815461346633419
+            binance -1x: (1-(66.1663784375 / 59.85)) * 1 = -0.1055368159983292
+            binance -3x: (1-(66.1663784375 / 59.85)) * 3 = -0.3166104479949876
+            kraken  -1x: (1-(66.2311650 / 59.85)) * 1    = -0.106619298245614
+            kraken  -3x: (1-(66.2311650 / 59.85)) * 3    = -0.319857894736842
     """
 
     trade = Trade(
@@ -106,32 +370,141 @@ def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, ca
         fee_open=fee.return_value,
         fee_close=fee.return_value,
         exchange='binance',
+        is_short=is_short,
+        leverage=lev,
+        trading_mode=trading_mode
+    )
+    assert trade.borrowed == borrowed
+
+
+@pytest.mark.parametrize('is_short,open_rate,close_rate,lev,profit,trading_mode', [
+    (False, 2.0, 2.2, 1.0, 0.09451372, spot),
+    (True, 2.2, 2.0, 3.0, 0.25894253, margin),
+])
+@pytest.mark.usefixtures("init_persistence")
+def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_usdt, time_machine,
+                            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
+        interest_rate: 0.05% per 4 hrs
+        open_rate: 2.00 quote
+        close_rate: 2.20 quote
+        amount: = 30.0 crypto
+        stake_amount
+            1x,-1x: 60.0  quote
+            3x,-3x: 20.0  quote
+        borrowed
+             1x:  0 quote
+             3x: 40 quote
+            -1x: 30 crypto
+            -3x: 30 crypto
+        hours: 1/6 (10 minutes)
+        time-periods:
+            kraken: (1 + 1) 4hr_periods = 2 4hr_periods
+            binance: 1/24 24hr_periods
+        interest: borrowed * interest_rate * time-periods
+            1x            :  /
+            binance     3x: 40 * 0.0005 * 1/24 = 0.0008333333333333334 quote
+            kraken      3x: 40 * 0.0005 * 2 = 0.040 quote
+            binace -1x,-3x: 30 * 0.0005 * 1/24 = 0.000625 crypto
+            kraken -1x,-3x: 30 * 0.0005 * 2 = 0.030 crypto
+        open_value: (amount * open_rate) ± (amount * open_rate * fee)
+             1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote
+            -1x,-3x: 30 * 2 - 30 * 2 * 0.0025 = 59.850 quote
+        amount_closed:
+            1x, 3x         : amount
+            -1x, -3x       : amount + interest
+            binance -1x,-3x: 30 + 0.000625 = 30.000625 crypto
+            kraken  -1x,-3x: 30 + 0.03 = 30.03 crypto
+        close_value:
+             1x, 3x: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest
+            -1x,-3x: (amount_closed * close_rate) + (amount_closed * close_rate * fee)
+            binance,kraken 1x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025)         = 65.835
+            binance        3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.00083333 = 65.83416667
+            kraken         3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.040 = 65.795
+            binance   -1x,-3x: (30.000625 * 2.20) + (30.000625 * 2.20 * 0.0025) = 66.16637843750001
+            kraken    -1x,-3x: (30.03 * 2.20) + (30.03 * 2.20 * 0.0025)         = 66.231165
+        total_profit:
+            1x, 3x : close_value - open_value
+            -1x,-3x: open_value  - close_value
+            binance,kraken 1x: 65.835 - 60.15             = 5.685
+            binance        3x: 65.83416667 - 60.15        = 5.684166670000003
+            kraken         3x: 65.795 - 60.15             = 5.645
+            binance   -1x,-3x: 59.850 - 66.16637843750001 = -6.316378437500013
+            kraken    -1x,-3x: 59.850 - 66.231165          = -6.381165
+        total_profit_ratio:
+            1x, 3x : ((close_value/open_value) - 1) * leverage
+            -1x,-3x: (1 - (close_value/open_value)) * leverage
+            binance  1x: ((65.835 / 60.15) - 1)  * 1 = 0.0945137157107232
+            binance  3x: ((65.83416667 / 60.15) - 1)  * 3 = 0.2834995845386534
+            kraken   1x: ((65.835 / 60.15) - 1)  * 1 = 0.0945137157107232
+            kraken   3x: ((65.795 / 60.15) - 1)  * 3 = 0.2815461346633419
+            binance -1x: (1-(66.1663784375 / 59.85)) * 1 = -0.1055368159983292
+            binance -3x: (1-(66.1663784375 / 59.85)) * 3 = -0.3166104479949876
+            kraken  -1x: (1-(66.2311650 / 59.85)) * 1    = -0.106619298245614
+            kraken  -3x: (1-(66.2311650 / 59.85)) * 3    = -0.319857894736842
+        open_rate: 2.2, close_rate: 2.0, -3x, binance, short
+            open_value: 30 * 2.2 - 30 * 2.2 * 0.0025 = 65.835 quote
+            amount_closed: 30 + 0.000625 = 30.000625 crypto
+            close_value: (30.000625 * 2.0) + (30.000625 * 2.0 * 0.0025) = 60.151253125
+            total_profit: 65.835 - 60.151253125 = 5.683746874999997
+            total_profit_ratio: (1-(60.151253125/65.835)) * 3 = 0.2589996297562085
+
+    """
+    time_machine.move_to("2022-03-31 20:45:00 +00:00")
+
+    enter_order = limit_sell_order_usdt if is_short else limit_buy_order_usdt
+    exit_order = limit_buy_order_usdt if is_short else limit_sell_order_usdt
+    enter_side, exit_side = ("sell", "buy") if is_short else ("buy", "sell")
+
+    trade = Trade(
+        id=2,
+        pair='ADA/USDT',
+        stake_amount=60.0,
+        open_rate=open_rate,
+        amount=30.0,
+        is_open=True,
+        open_date=arrow.utcnow().datetime,
+        fee_open=fee.return_value,
+        fee_close=fee.return_value,
+        exchange='binance',
+        is_short=is_short,
+        interest_rate=0.0005,
+        leverage=lev,
+        trading_mode=trading_mode
     )
     assert trade.open_order_id is None
     assert trade.close_profit is None
     assert trade.close_date is None
 
     trade.open_order_id = 'something'
-    oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy')
+    oobj = Order.parse_from_ccxt_object(enter_order, 'ADA/USDT', enter_side)
     trade.update_trade(oobj)
     assert trade.open_order_id is None
-    assert trade.open_rate == 2.00
+    assert trade.open_rate == open_rate
     assert trade.close_profit is None
     assert trade.close_date is None
-    assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=2, "
-                      r"pair=ADA/USDT, amount=30.00000000, open_rate=2.00000000, open_since=.*\).",
+    assert log_has_re(f"LIMIT_{enter_side.upper()} has been fulfilled for "
+                      r"Trade\(id=2, pair=ADA/USDT, amount=30.00000000, "
+                      f"is_short={is_short}, leverage={lev}, open_rate={open_rate}0000000, "
+                      r"open_since=.*\).",
                       caplog)
 
     caplog.clear()
     trade.open_order_id = 'something'
-    oobj = Order.parse_from_ccxt_object(limit_sell_order_usdt, 'ADA/USDT', 'sell')
+    time_machine.move_to("2022-03-31 21:45:00 +00:00")
+    oobj = Order.parse_from_ccxt_object(exit_order, 'ADA/USDT', exit_side)
     trade.update_trade(oobj)
+
     assert trade.open_order_id is None
-    assert trade.close_rate == 2.20
-    assert trade.close_profit == round(0.0945137157107232, 8)
+    assert trade.close_rate == close_rate
+    assert trade.close_profit == profit
     assert trade.close_date is not None
-    assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=2, "
-                      r"pair=ADA/USDT, amount=30.00000000, open_rate=2.00000000, open_since=.*\).",
+    assert log_has_re(f"LIMIT_{exit_side.upper()} has been fulfilled for "
+                      r"Trade\(id=2, pair=ADA/USDT, amount=30.00000000, "
+                      f"is_short={is_short}, leverage={lev}, open_rate={open_rate}0000000, "
+                      r"open_since=.*\).",
                       caplog)
     caplog.clear()
 
@@ -149,6 +522,8 @@ 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,
+        leverage=1.0,
     )
 
     trade.open_order_id = 'something'
@@ -159,7 +534,8 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
     assert trade.close_profit is None
     assert trade.close_date is None
     assert log_has_re(r"MARKET_BUY has been fulfilled for Trade\(id=1, "
-                      r"pair=ADA/USDT, amount=30.00000000, open_rate=2.00000000, open_since=.*\).",
+                      r"pair=ADA/USDT, amount=30.00000000, is_short=False, leverage=1.0, "
+                      r"open_rate=2.00000000, open_since=.*\).",
                       caplog)
 
     caplog.clear()
@@ -172,35 +548,66 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
     assert trade.close_profit == round(0.0945137157107232, 8)
     assert trade.close_date is not None
     assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, "
-                      r"pair=ADA/USDT, amount=30.00000000, open_rate=2.00000000, open_since=.*\).",
+                      r"pair=ADA/USDT, amount=30.00000000, is_short=False, leverage=1.0, "
+                      r"open_rate=2.00000000, open_since=.*\).",
                       caplog)
 
 
+@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.09451371, spot, 0.0),
+        ("binance", True, 1, 59.850, 66.1663784375, -6.3163784375, -0.1055368, margin, 0.0),
+        ("binance", False, 3, 60.15, 65.83416667, 5.68416667, 0.28349958, margin, 0.0),
+        ("binance", True, 3, 59.85, 66.1663784375, -6.3163784375, -0.31661044, margin, 0.0),
+
+        ("kraken", False, 1, 60.15, 65.835, 5.685, 0.09451371, spot, 0.0),
+        ("kraken", True, 1, 59.850, 66.231165, -6.381165, -0.1066192, margin, 0.0),
+        ("kraken", False, 3, 60.15, 65.795, 5.645, 0.28154613, margin, 0.0),
+        ("kraken", True, 3, 59.850, 66.231165, -6.381165, -0.3198578, margin, 0.0),
+
+        ("binance", False, 1, 60.15, 65.835,  5.685, 0.09451371, futures, 0.0),
+        ("binance", False, 1, 60.15, 66.835,  6.685, 0.11113881, futures, 1.0),
+        ("binance", True, 1, 59.85,  66.165, -6.315, -0.10551378, futures, 0.0),
+        ("binance", True, 1, 59.85,  67.165, -7.315, -0.12222222, futures, -1.0),
+        ("binance", False, 3, 60.15, 64.835,  4.685, 0.23366583, futures, -1.0),
+        ("binance", True, 3, 59.85,  65.165, -5.315, -0.26641604, futures, 1.0),
+    ])
 @pytest.mark.usefixtures("init_persistence")
-def test_calc_open_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, fee):
-    trade = Trade(
+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,
         open_rate=2.0,
         amount=30.0,
+        open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
+        interest_rate=0.0005,
         fee_open=fee.return_value,
         fee_close=fee.return_value,
-        exchange='binance',
+        exchange=exchange,
+        is_short=is_short,
+        leverage=lev,
+        trading_mode=trading_mode,
+        funding_fees=funding_fees
     )
 
-    trade.open_order_id = 'something'
+    trade.open_order_id = f'something-{is_short}-{lev}-{exchange}'
+
     oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy')
     trade.update_trade(oobj)
-    assert trade._calc_open_trade_value() == 60.15
+
     oobj = Order.parse_from_ccxt_object(limit_sell_order_usdt, 'ADA/USDT', 'sell')
     trade.update_trade(oobj)
-    assert isclose(trade.calc_close_trade_value(), 65.835)
 
-    # Profit in USDT
-    assert trade.calc_profit() == 5.685
-
-    # Profit in percent
-    assert trade.calc_profit_ratio() == round(0.0945137157107232, 8)
+    trade.open_rate = 2.0
+    trade.close_rate = 2.2
+    trade.recalc_open_trade_value()
+    assert isclose(trade._calc_open_trade_value(), open_value)
+    assert isclose(trade.calc_close_trade_value(), close_value)
+    assert isclose(trade.calc_profit(), round(profit, 8))
+    assert pytest.approx(trade.calc_profit_ratio()) == profit_ratio
 
 
 @pytest.mark.usefixtures("init_persistence")
@@ -213,8 +620,10 @@ def test_trade_close(limit_buy_order_usdt, limit_sell_order_usdt, fee):
         is_open=True,
         fee_open=fee.return_value,
         fee_close=fee.return_value,
-        open_date=arrow.Arrow(2020, 2, 1, 15, 5, 1).datetime,
+        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
@@ -243,6 +652,8 @@ 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,
+        leverage=1.0,
     )
 
     trade.open_order_id = 'something'
@@ -261,6 +672,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
@@ -286,6 +698,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'
     oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'meep')
@@ -293,72 +706,203 @@ def test_update_invalid_order(limit_buy_order_usdt):
         trade.update_trade(oobj)
 
 
+@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),
+    (False, 0.0025, 60.15),
+    (False, 0.003, 60.18),
+    (False, 0.0025, 60.15),
+    (True, 0.003, 59.82),
+    (True, 0.0025, 59.85),
+    (True, 0.003, 59.82),
+    (True, 0.0025, 59.85)
+])
 @pytest.mark.usefixtures("init_persistence")
-def test_calc_open_trade_value(limit_buy_order_usdt, fee):
-    """
-        fee: 0.25 %, 0.3% quote
-        open_rate: 2.00 quote
-        amount: = 30.0 crypto
-        stake_amount
-            60.0  quote
-        open_value: (amount * open_rate) + (amount * open_rate * fee)
-        0.25% fee
-            30 * 2 + 30 * 2 * 0.0025 = 60.15 quote
-        0.3% fee
-            30 * 2 + 30 * 2 * 0.003  = 60.18 quote
-    """
+def test_calc_open_trade_value(
+    limit_buy_order_usdt,
+    exchange,
+    lev,
+    is_short,
+    fee_rate,
+    result,
+    trading_mode
+):
+    # 10 minute limit trade on Binance/Kraken at 1x, 3x leverage
+    # fee: 0.25 %, 0.3% quote
+    # open_rate: 2.00 quote
+    # amount: = 30.0 crypto
+    # stake_amount
+    #     1x, -1x: 60.0  quote
+    #     3x, -3x: 20.0  quote
+    # open_value: (amount * open_rate) ± (amount * open_rate * fee)
+    # 0.25% fee
+    #      1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote
+    #     -1x,-3x: 30 * 2 - 30 * 2 * 0.0025 = 59.85 quote
+    # 0.3% fee
+    #      1x, 3x: 30 * 2 + 30 * 2 * 0.003  = 60.18 quote
+    #     -1x,-3x: 30 * 2 - 30 * 2 * 0.003  = 59.82 quote
     trade = Trade(
         pair='ADA/USDT',
         stake_amount=60.0,
         amount=30.0,
         open_rate=2.0,
-        fee_open=fee.return_value,
-        fee_close=fee.return_value,
-        exchange='binance',
+        open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
+        fee_open=fee_rate,
+        fee_close=fee_rate,
+        exchange=exchange,
+        leverage=lev,
+        is_short=is_short,
+        trading_mode=trading_mode
     )
     trade.open_order_id = 'open_trade'
-    oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy')
+    oobj = Order.parse_from_ccxt_object(
+        limit_buy_order_usdt, 'ADA/USDT', 'sell' if is_short else 'buy')
     trade.update_trade(oobj)  # Buy @ 2.0
 
     # Get the open rate price with the standard fee rate
-    assert trade._calc_open_trade_value() == 60.15
-    trade.fee_open = 0.003
-    # Get the open rate price with a custom fee rate
-    assert trade._calc_open_trade_value() == 60.18
+    assert trade._calc_open_trade_value() == result
 
 
+@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, fee):
+def test_calc_close_trade_price(
+    open_rate, exchange, is_short,
+    lev, close_rate, fee_rate, result, trading_mode, funding_fees
+):
     trade = Trade(
         pair='ADA/USDT',
         stake_amount=60.0,
         amount=30.0,
-        open_rate=2.0,
-        fee_open=fee.return_value,
-        fee_close=fee.return_value,
-        exchange='binance',
+        open_rate=open_rate,
+        open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
+        fee_open=fee_rate,
+        fee_close=fee_rate,
+        exchange=exchange,
+        interest_rate=0.0005,
+        is_short=is_short,
+        leverage=lev,
+        trading_mode=trading_mode,
+        funding_fees=funding_fees
     )
     trade.open_order_id = 'close_trade'
-    oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy')
-    trade.update_trade(oobj)  # Buy @ 2.0
-
-    # Get the close rate price with a custom close rate and a regular fee rate
-    assert trade.calc_close_trade_value(rate=2.5) == 74.8125
-    # Get the close rate price with a custom close rate and a custom fee rate
-    assert trade.calc_close_trade_value(rate=2.5, fee=0.003) == 74.775
-    # Test when we apply a Sell order, and ask price with a custom fee rate
-    oobj = Order.parse_from_ccxt_object(limit_sell_order_usdt, 'ADA/USDT', 'sell')
-    trade.update_trade(oobj)
-    assert trade.calc_close_trade_value(fee=0.005) == 65.67
+    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,trading_mode,funding_fees', [
+        ('binance', False, 1, 2.1, 0.0025, 2.6925, 0.044763092, spot, 0),
+        ('binance', False, 3, 2.1, 0.0025, 2.69166667, 0.134247714, margin, 0),
+        ('binance', True, 1, 2.1, 0.0025, -3.3088157, -0.055285142, margin, 0),
+        ('binance', True, 3, 2.1, 0.0025, -3.3088157, -0.16585542, margin, 0),
+
+        ('binance', False, 1, 1.9, 0.0025, -3.2925, -0.054738154, margin, 0),
+        ('binance', False, 3, 1.9, 0.0025, -3.29333333, -0.164256026, margin, 0),
+        ('binance', True, 1, 1.9, 0.0025, 2.70630953, 0.0452182043, margin, 0),
+        ('binance', True, 3, 1.9, 0.0025, 2.70630953, 0.135654613, margin, 0),
+
+        ('binance', False, 1, 2.2, 0.0025, 5.685, 0.09451371, margin, 0),
+        ('binance', False, 3, 2.2, 0.0025, 5.68416667, 0.28349958, margin, 0),
+        ('binance', True, 1, 2.2, 0.0025, -6.3163784, -0.10553681, margin, 0),
+        ('binance', True, 3, 2.2, 0.0025, -6.3163784, -0.31661044, margin, 0),
+
+        # Kraken
+        ('kraken', False, 1, 2.1, 0.0025, 2.6925, 0.044763092, spot, 0),
+        ('kraken', False, 3, 2.1, 0.0025, 2.6525, 0.132294264, margin, 0),
+        ('kraken', True, 1, 2.1, 0.0025, -3.3706575, -0.056318421, margin, 0),
+        ('kraken', True, 3, 2.1, 0.0025, -3.3706575, -0.168955263, margin, 0),
+
+        ('kraken', False, 1, 1.9, 0.0025, -3.2925, -0.054738154, margin, 0),
+        ('kraken', False, 3, 1.9, 0.0025, -3.3325, -0.166209476, margin, 0),
+        ('kraken', True, 1, 1.9, 0.0025, 2.6503575, 0.044283333, margin, 0),
+        ('kraken', True, 3, 1.9, 0.0025, 2.6503575, 0.132850000, margin, 0),
+
+        ('kraken', False, 1, 2.2, 0.0025, 5.685, 0.09451371, margin, 0),
+        ('kraken', False, 3, 2.2, 0.0025, 5.645, 0.28154613, margin, 0),
+        ('kraken', True, 1, 2.2, 0.0025, -6.381165, -0.1066192, margin, 0),
+        ('kraken', True, 3, 2.2, 0.0025, -6.381165, -0.3198578, margin, 0),
+
+        ('binance', False, 1, 2.1, 0.003, 2.66100000, 0.044239401, spot, 0),
+        ('binance', False, 1, 1.9, 0.003, -3.3209999, -0.055211970, spot, 0),
+        ('binance', False, 1, 2.2, 0.003, 5.6520000, 0.093965087, spot, 0),
+
+        # # FUTURES, funding_fee=1
+        ('binance', False, 1, 2.1, 0.0025, 3.6925, 0.06138819, futures, 1),
+        ('binance', False, 3, 2.1, 0.0025, 3.6925, 0.18416458, futures, 1),
+        ('binance', True, 1, 2.1, 0.0025, -2.3074999, -0.03855472, futures, 1),
+        ('binance', True, 3, 2.1, 0.0025, -2.3074999, -0.11566416, futures, 1),
+
+        ('binance', False, 1, 1.9, 0.0025, -2.2925, -0.03811305, futures, 1),
+        ('binance', False, 3, 1.9, 0.0025, -2.2925, -0.11433915, futures, 1),
+        ('binance', True, 1, 1.9, 0.0025, 3.7075, 0.06194653, futures, 1),
+        ('binance', True, 3, 1.9, 0.0025, 3.7075, 0.18583959, futures, 1),
+
+        ('binance', False, 1, 2.2, 0.0025, 6.685, 0.11113881, futures, 1),
+        ('binance', False, 3, 2.2, 0.0025, 6.685, 0.33341645, futures, 1),
+        ('binance', True, 1, 2.2, 0.0025, -5.315, -0.08880534, futures, 1),
+        ('binance', True, 3, 2.2, 0.0025, -5.315, -0.26641604, futures, 1),
+
+        # FUTURES, funding_fee=-1
+        ('binance', False, 1, 2.1, 0.0025, 1.6925, 0.02813798, futures, -1),
+        ('binance', False, 3, 2.1, 0.0025, 1.6925, 0.08441396, futures, -1),
+        ('binance', True, 1, 2.1, 0.0025, -4.307499, -0.07197159, futures, -1),
+        ('binance', True, 3, 2.1, 0.0025, -4.307499, -0.21591478, futures, -1),
+
+        ('binance', False, 1, 1.9, 0.0025, -4.292499, -0.07136325, futures, -1),
+        ('binance', False, 3, 1.9, 0.0025, -4.292499, -0.21408977, futures, -1),
+        ('binance', True, 1, 1.9, 0.0025, 1.7075, 0.02852965, futures, -1),
+        ('binance', True, 3, 1.9, 0.0025, 1.7075, 0.08558897, futures, -1),
+
+        ('binance', False, 1, 2.2, 0.0025, 4.684999, 0.07788861, futures, -1),
+        ('binance', False, 3, 2.2, 0.0025, 4.684999, 0.23366583, futures, -1),
+        ('binance', True, 1, 2.2, 0.0025, -7.315, -0.12222222, futures, -1),
+        ('binance', True, 3, 2.2, 0.0025, -7.315, -0.36666666, futures, -1),
+    ])
 @pytest.mark.usefixtures("init_persistence")
-def test_calc_profit(limit_buy_order_usdt, limit_sell_order_usdt, fee):
+def test_calc_profit(
+    exchange,
+    is_short,
+    lev,
+    close_rate,
+    fee_close,
+    profit,
+    profit_ratio,
+    trading_mode,
+    funding_fees
+):
     """
+        10 minute limit trade on Binance/Kraken at 1x, 3x leverage
         arguments:
             fee:
                 0.25% quote
                 0.30% quote
+            interest_rate: 0.05% per 4 hrs
             open_rate: 2.0 quote
             close_rate:
                 1.9 quote
@@ -366,127 +910,222 @@ def test_calc_profit(limit_buy_order_usdt, limit_sell_order_usdt, fee):
                 2.2 quote
             amount: = 30.0 crypto
             stake_amount
-                60.0  quote
-        open_value: (amount * open_rate) + (amount * open_rate * fee)
+                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
+            -1x: 30 crypto
+            -3x: 30 crypto
+        time-periods:
+            kraken: (1 + 1) 4hr_periods = 2 4hr_periods
+            binance: 1/24 24hr_periods
+        interest: borrowed * interest_rate * time-periods
+            1x            :  /
+            binance     3x: 40 * 0.0005 * 1/24 = 0.0008333333333333334 quote
+            kraken      3x: 40 * 0.0005 * 2    = 0.040 quote
+            binace -1x,-3x: 30 * 0.0005 * 1/24 = 0.000625 crypto
+            kraken -1x,-3x: 30 * 0.0005 * 2    = 0.030 crypto
+        open_value: (amount * open_rate) ± (amount * open_rate * fee)
           0.0025 fee
-            30 * 2 + 30 * 2 * 0.0025 = 60.15 quote
-            30 * 2 - 30 * 2 * 0.0025 = 59.85 quote
+             1x, 3x: 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote
+            -1x,-3x: 30 * 2 - 30 * 2 * 0.0025 = 59.85 quote
           0.003 fee: Is only applied to close rate in this test
+        amount_closed:
+            1x, 3x                         = amount
+            -1x, -3x                       = amount + interest
+            binance -1x,-3x: 30 + 0.000625 = 30.000625 crypto
+            kraken  -1x,-3x: 30 + 0.03     = 30.03 crypto
         close_value:
             equations:
-                (amount_closed * close_rate) - (amount_closed * close_rate * fee)
+                1x, 3x: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest
+                -1x,-3x: (amount_closed * close_rate) + (amount_closed * close_rate * fee)
             2.1 quote
-                (30.00 * 2.1) - (30.00 * 2.1 * 0.0025)   = 62.8425
+                bin,krak  1x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025)                = 62.8425
+                bin       3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) - 0.0008333333 = 62.8416666667
+                krak      3x: (30.00 * 2.1) - (30.00 * 2.1 * 0.0025) - 0.040        = 62.8025
+                bin  -1x,-3x: (30.000625 * 2.1) + (30.000625 * 2.1 * 0.0025)        = 63.15881578125
+                krak -1x,-3x: (30.03 * 2.1) + (30.03 * 2.1 * 0.0025)                = 63.2206575
             1.9 quote
-                (30.00 * 1.9) - (30.00 * 1.9 * 0.0025)   = 56.8575
+                bin,krak  1x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025)                = 56.8575
+                bin       3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) - 0.0008333333 = 56.85666667
+                krak      3x: (30.00 * 1.9) - (30.00 * 1.9 * 0.0025) - 0.040        = 56.8175
+                bin  -1x,-3x: (30.000625 * 1.9) + (30.000625 * 1.9 * 0.0025)        = 57.14369046875
+                krak -1x,-3x: (30.03 * 1.9) + (30.03 * 1.9 * 0.0025)                = 57.1996425
             2.2 quote
-                (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835
+                bin,krak  1x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025)              = 65.835
+                bin       3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.00083333 = 65.83416667
+                krak      3x: (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) - 0.040      = 65.795
+                bin  -1x,-3x: (30.000625 * 2.20) + (30.000625 * 2.20 * 0.0025)      = 66.1663784375
+                krak -1x,-3x: (30.03 * 2.20) + (30.03 * 2.20 * 0.0025)              = 66.231165
         total_profit:
             equations:
-                close_value - open_value
+                1x, 3x : close_value - open_value
+                -1x,-3x: open_value - close_value
             2.1 quote
-                62.8425 - 60.15 = 2.6925
+                binance,kraken 1x: 62.8425     - 60.15          = 2.6925
+                binance        3x: 62.84166667 - 60.15          = 2.69166667
+                kraken         3x: 62.8025     - 60.15          = 2.6525
+                binance   -1x,-3x: 59.850      - 63.15881578125 = -3.308815781249997
+                kraken    -1x,-3x: 59.850      - 63.2206575     = -3.3706575
             1.9 quote
-                56.8575 - 60.15 = -3.2925
+                binance,kraken 1x: 56.8575     - 60.15          = -3.2925
+                binance        3x: 56.85666667 - 60.15          = -3.29333333
+                kraken         3x: 56.8175     - 60.15          = -3.3325
+                binance   -1x,-3x: 59.850      - 57.14369046875 = 2.7063095312499996
+                kraken    -1x,-3x: 59.850      - 57.1996425     = 2.6503575
             2.2 quote
-                65.835  - 60.15 = 5.685
+                binance,kraken 1x: 65.835      - 60.15          = 5.685
+                binance        3x: 65.83416667 - 60.15          = 5.68416667
+                kraken         3x: 65.795      - 60.15          = 5.645
+                binance   -1x,-3x: 59.850      - 66.1663784375  = -6.316378437499999
+                kraken    -1x,-3x: 59.850      - 66.231165      = -6.381165
         total_profit_ratio:
             equations:
-                ((close_value/open_value) - 1) * leverage
+                1x, 3x : ((close_value/open_value) - 1) * leverage
+                -1x,-3x: (1 - (close_value/open_value)) * leverage
             2.1 quote
-                (62.8425 / 60.15) - 1 = 0.04476309226932673
+                binance,kraken 1x: (62.8425 / 60.15) - 1             = 0.04476309226932673
+                binance        3x: ((62.84166667 / 60.15) - 1)*3     = 0.13424771421446402
+                kraken         3x: ((62.8025 / 60.15) - 1)*3         = 0.13229426433915248
+                binance       -1x: 1 - (63.15881578125 / 59.850)     = -0.05528514254385963
+                binance       -3x: (1 - (63.15881578125 / 59.850))*3 = -0.1658554276315789
+                kraken        -1x: 1 - (63.2206575 / 59.850)         = -0.05631842105263152
+                kraken        -3x: (1 - (63.2206575 / 59.850))*3     = -0.16895526315789455
             1.9 quote
-                (56.8575 / 60.15) - 1 = -0.05473815461346632
+                binance,kraken 1x: (56.8575 / 60.15) - 1             = -0.05473815461346632
+                binance        3x: ((56.85666667 / 60.15) - 1)*3     = -0.16425602643391513
+                kraken         3x: ((56.8175 / 60.15) - 1)*3         = -0.16620947630922667
+                binance       -1x: 1 - (57.14369046875 / 59.850)     = 0.045218204365079395
+                binance       -3x: (1 - (57.14369046875 / 59.850))*3 = 0.13565461309523819
+                kraken        -1x: 1 - (57.1996425 / 59.850)         = 0.04428333333333334
+                kraken        -3x: (1 - (57.1996425 / 59.850))*3     = 0.13285000000000002
             2.2 quote
-                (65.835 / 60.15) - 1  = 0.0945137157107232
-        fee: 0.003
+                binance,kraken 1x: (65.835 / 60.15) - 1             = 0.0945137157107232
+                binance        3x: ((65.83416667 / 60.15) - 1)*3     = 0.2834995845386534
+                kraken         3x: ((65.795 / 60.15) - 1)*3         = 0.2815461346633419
+                binance       -1x: 1 - (66.1663784375 / 59.850)     = -0.1055368159983292
+                binance       -3x: (1 - (66.1663784375 / 59.850))*3 = -0.3166104479949876
+                kraken        -1x: 1 - (66.231165 / 59.850)         = -0.106619298245614
+                kraken        -3x: (1 - (66.231165 / 59.850))*3     = -0.319857894736842
+        fee: 0.003, 1x
             close_value:
                 2.1 quote: (30.00 * 2.1) - (30.00 * 2.1 * 0.003) = 62.811
                 1.9 quote: (30.00 * 1.9) - (30.00 * 1.9 * 0.003) = 56.829
                 2.2 quote: (30.00 * 2.2) - (30.00 * 2.2 * 0.003) = 65.802
             total_profit
-                fee: 0.003
+                fee: 0.003, 1x
                     2.1 quote: 62.811 - 60.15 = 2.6610000000000014
                     1.9 quote: 56.829 - 60.15 = -3.320999999999998
                     2.2 quote: 65.802 - 60.15 = 5.652000000000008
             total_profit_ratio
-                fee: 0.003
+                fee: 0.003, 1x
                     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',
         stake_amount=60.0,
         amount=30.0,
         open_rate=2.0,
-        fee_open=fee.return_value,
-        fee_close=fee.return_value,
-        exchange='binance',
-    )
-    trade.open_order_id = 'something'
-    oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy')
-
-    trade.update_trade(oobj)  # Buy @ 2.0
-
-    # Custom closing rate and regular fee rate
-    # Higher than open rate - 2.1 quote
-    assert trade.calc_profit(rate=2.1) == 2.6925
-    # Lower than open rate - 1.9 quote
-    assert trade.calc_profit(rate=1.9) == round(-3.292499999999997, 8)
-
-    # fee 0.003
-    # Higher than open rate - 2.1 quote
-    assert trade.calc_profit(rate=2.1, fee=0.003) == 2.661
-    # Lower than open rate - 1.9 quote
-    assert trade.calc_profit(rate=1.9, fee=0.003) == round(-3.320999999999998, 8)
-
-    # Test when we apply a Sell order. Sell higher than open rate @ 2.2
-    oobj = Order.parse_from_ccxt_object(limit_sell_order_usdt, 'ADA/USDT', 'sell')
-    trade.update_trade(oobj)
-    assert trade.calc_profit() == round(5.684999999999995, 8)
-
-    # Test with a custom fee rate on the close trade
-    assert trade.calc_profit(fee=0.003) == round(5.652000000000008, 8)
-
-
-@pytest.mark.usefixtures("init_persistence")
-def test_calc_profit_ratio(limit_buy_order_usdt, limit_sell_order_usdt, fee):
-    trade = Trade(
-        pair='ADA/USDT',
-        stake_amount=60.0,
-        amount=30.0,
-        open_rate=2.0,
-        fee_open=fee.return_value,
-        fee_close=fee.return_value,
-        exchange='binance'
+        open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
+        interest_rate=0.0005,
+        exchange=exchange,
+        is_short=is_short,
+        leverage=lev,
+        fee_open=0.0025,
+        fee_close=fee_close,
+        trading_mode=trading_mode,
+        funding_fees=funding_fees
     )
     trade.open_order_id = 'something'
 
-    oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy')
-    trade.update_trade(oobj)  # Buy @ 2.0
-
-    # Higher than open rate - 2.1 quote
-    assert trade.calc_profit_ratio(rate=2.1) == round(0.04476309226932673, 8)
-    # Lower than open rate - 1.9 quote
-    assert trade.calc_profit_ratio(rate=1.9) == round(-0.05473815461346632, 8)
-
-    # fee 0.003
-    # Higher than open rate - 2.1 quote
-    assert trade.calc_profit_ratio(rate=2.1, fee=0.003) == round(0.04423940149625927, 8)
-    # Lower than open rate - 1.9 quote
-    assert trade.calc_profit_ratio(rate=1.9, fee=0.003) == round(-0.05521197007481293, 8)
-
-    # Test when we apply a Sell order. Sell higher than open rate @ 2.2
-    oobj = Order.parse_from_ccxt_object(limit_sell_order_usdt, 'ADA/USDT', 'sell')
-    trade.update_trade(oobj)
-    assert trade.calc_profit_ratio() == round(0.0945137157107232, 8)
-
-    # Test with a custom fee rate on the close trade
-    assert trade.calc_profit_ratio(fee=0.003) == round(0.09396508728179565, 8)
-
-    trade.open_trade_value = 0.0
-    assert trade.calc_profit_ratio(fee=0.003) == 0.0
+    assert pytest.approx(trade.calc_profit(rate=close_rate)) == round(profit, 8)
+    assert pytest.approx(trade.calc_profit_ratio(rate=close_rate)) == round(profit_ratio, 8)
 
 
 @pytest.mark.usefixtures("init_persistence")
@@ -786,6 +1425,59 @@ def test_adjust_stop_loss(fee):
     assert trade.stop_loss_pct == -0.1
 
 
+def test_adjust_stop_loss_short(fee):
+    trade = Trade(
+        pair='ADA/USDT',
+        stake_amount=0.001,
+        amount=5,
+        fee_open=fee.return_value,
+        fee_close=fee.return_value,
+        exchange='binance',
+        open_rate=1,
+        max_rate=1,
+        is_short=True,
+    )
+    trade.adjust_stop_loss(trade.open_rate, 0.05, True)
+    assert trade.stop_loss == 1.05
+    assert trade.stop_loss_pct == -0.05
+    assert trade.initial_stop_loss == 1.05
+    assert trade.initial_stop_loss_pct == -0.05
+    # Get percent of profit with a lower rate
+    trade.adjust_stop_loss(1.04, 0.05)
+    assert trade.stop_loss == 1.05
+    assert trade.stop_loss_pct == -0.05
+    assert trade.initial_stop_loss == 1.05
+    assert trade.initial_stop_loss_pct == -0.05
+    # Get percent of profit with a custom rate (Higher than open rate)
+    trade.adjust_stop_loss(0.7, 0.1)
+    # If the price goes down to 0.7, with a trailing stop of 0.1,
+    # the new stoploss at 0.1 above 0.7 would be 0.7*0.1 higher
+    assert round(trade.stop_loss, 8) == 0.77
+    assert trade.stop_loss_pct == -0.1
+    assert trade.initial_stop_loss == 1.05
+    assert trade.initial_stop_loss_pct == -0.05
+    # current rate lower again ... should not change
+    trade.adjust_stop_loss(0.8, -0.1)
+    assert round(trade.stop_loss, 8) == 0.77
+    assert trade.initial_stop_loss == 1.05
+    assert trade.initial_stop_loss_pct == -0.05
+    # current rate higher... should raise stoploss
+    trade.adjust_stop_loss(0.6, -0.1)
+    assert round(trade.stop_loss, 8) == 0.66
+    assert trade.initial_stop_loss == 1.05
+    assert trade.initial_stop_loss_pct == -0.05
+    #  Initial is true but stop_loss set - so doesn't do anything
+    trade.adjust_stop_loss(0.3, -0.1, True)
+    assert round(trade.stop_loss, 8) == 0.66
+    assert trade.initial_stop_loss == 1.05
+    assert trade.initial_stop_loss_pct == -0.05
+    assert trade.stop_loss_pct == -0.1
+    trade.set_isolated_liq(0.63)
+    trade.adjust_stop_loss(0.59, -0.1)
+    assert trade.stop_loss == 0.63
+    assert trade.liquidation_price == 0.63
+
+
 def test_adjust_min_max_rates(fee):
     trade = Trade(
         pair='ADA/USDT',
@@ -824,22 +1516,35 @@ def test_adjust_min_max_rates(fee):
 
 @pytest.mark.usefixtures("init_persistence")
 @pytest.mark.parametrize('use_db', [True, False])
-def test_get_open(fee, use_db):
+@pytest.mark.parametrize('is_short', [True, False])
+def test_get_open(fee, is_short, use_db):
     Trade.use_db = use_db
     Trade.reset_trades()
 
-    create_mock_trades(fee, use_db)
+    create_mock_trades(fee, is_short, use_db)
     assert len(Trade.get_open_trades()) == 4
 
     Trade.use_db = True
 
 
 @pytest.mark.usefixtures("init_persistence")
-def test_to_json(default_conf, fee):
+@pytest.mark.parametrize('use_db', [True, False])
+def test_get_open_lev(fee, use_db):
+    Trade.use_db = use_db
+    Trade.reset_trades()
+
+    create_mock_trades_with_leverage(fee, use_db)
+    assert len(Trade.get_open_trades()) == 5
+
+    Trade.use_db = True
+
+
+@pytest.mark.usefixtures("init_persistence")
+def test_to_json(fee):
 
     # Simulate dry_run entries
     trade = Trade(
-        pair='ETH/BTC',
+        pair='ADA/USDT',
         stake_amount=0.001,
         amount=123.0,
         amount_requested=123.0,
@@ -848,14 +1553,14 @@ def test_to_json(default_conf, fee):
         open_date=arrow.utcnow().shift(hours=-2).datetime,
         open_rate=0.123,
         exchange='binance',
-        buy_tag=None,
+        enter_tag=None,
         open_order_id='dry_run_buy_12345'
     )
     result = trade.to_json()
     assert isinstance(result, dict)
 
     assert result == {'trade_id': None,
-                      'pair': 'ETH/BTC',
+                      'pair': 'ADA/USDT',
                       'is_open': None,
                       'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"),
                       'open_timestamp': int(trade.open_date.timestamp() * 1000),
@@ -899,8 +1604,15 @@ def test_to_json(default_conf, fee):
                       'max_rate': None,
                       'strategy': None,
                       'buy_tag': None,
+                      'enter_tag': None,
                       'timeframe': None,
                       'exchange': 'binance',
+                      'leverage': None,
+                      'interest_rate': None,
+                      'liquidation_price': None,
+                      'is_short': None,
+                      'trading_mode': None,
+                      'funding_fees': None,
                       'orders': [],
                       }
 
@@ -916,7 +1628,7 @@ def test_to_json(default_conf, fee):
         close_date=arrow.utcnow().shift(hours=-1).datetime,
         open_rate=0.123,
         close_rate=0.125,
-        buy_tag='buys_signal_001',
+        enter_tag='buys_signal_001',
         exchange='binance',
     )
     result = trade.to_json()
@@ -967,8 +1679,15 @@ def test_to_json(default_conf, fee):
                       'sell_order_status': None,
                       'strategy': None,
                       'buy_tag': 'buys_signal_001',
+                      'enter_tag': 'buys_signal_001',
                       'timeframe': None,
                       'exchange': 'binance',
+                      'leverage': None,
+                      'interest_rate': None,
+                      'liquidation_price': None,
+                      'is_short': None,
+                      'trading_mode': None,
+                      'funding_fees': None,
                       'orders': [],
                       }
 
@@ -1033,6 +1752,126 @@ def test_stoploss_reinitialization(default_conf, fee):
     assert trade_adj.initial_stop_loss_pct == -0.04
 
 
+def test_stoploss_reinitialization_leverage(default_conf, fee):
+    init_db(default_conf['db_url'])
+    trade = Trade(
+        pair='ADA/USDT',
+        stake_amount=30.0,
+        fee_open=fee.return_value,
+        open_date=arrow.utcnow().shift(hours=-2).datetime,
+        amount=30.0,
+        fee_close=fee.return_value,
+        exchange='binance',
+        open_rate=1,
+        max_rate=1,
+        leverage=5.0,
+    )
+
+    trade.adjust_stop_loss(trade.open_rate, 0.1, True)
+    assert trade.stop_loss == 0.98
+    assert trade.stop_loss_pct == -0.1
+    assert trade.initial_stop_loss == 0.98
+    assert trade.initial_stop_loss_pct == -0.1
+    Trade.query.session.add(trade)
+
+    # Lower stoploss
+    Trade.stoploss_reinitialization(0.15)
+
+    trades = Trade.get_open_trades()
+    assert len(trades) == 1
+    trade_adj = trades[0]
+    assert trade_adj.stop_loss == 0.97
+    assert trade_adj.stop_loss_pct == -0.15
+    assert trade_adj.initial_stop_loss == 0.97
+    assert trade_adj.initial_stop_loss_pct == -0.15
+
+    # Raise stoploss
+    Trade.stoploss_reinitialization(0.05)
+
+    trades = Trade.get_open_trades()
+    assert len(trades) == 1
+    trade_adj = trades[0]
+    assert trade_adj.stop_loss == 0.99
+    assert trade_adj.stop_loss_pct == -0.05
+    assert trade_adj.initial_stop_loss == 0.99
+    assert trade_adj.initial_stop_loss_pct == -0.05
+
+    # Trailing stoploss (move stoplos up a bit)
+    trade.adjust_stop_loss(1.02, 0.05)
+    assert trade_adj.stop_loss == 1.0098
+    assert trade_adj.initial_stop_loss == 0.99
+
+    Trade.stoploss_reinitialization(0.05)
+
+    trades = Trade.get_open_trades()
+    assert len(trades) == 1
+    trade_adj = trades[0]
+    # Stoploss should not change in this case.
+    assert trade_adj.stop_loss == 1.0098
+    assert trade_adj.stop_loss_pct == -0.05
+    assert trade_adj.initial_stop_loss == 0.99
+    assert trade_adj.initial_stop_loss_pct == -0.05
+
+
+def test_stoploss_reinitialization_short(default_conf, fee):
+    init_db(default_conf['db_url'])
+    trade = Trade(
+        pair='ADA/USDT',
+        stake_amount=0.001,
+        fee_open=fee.return_value,
+        open_date=arrow.utcnow().shift(hours=-2).datetime,
+        amount=10,
+        fee_close=fee.return_value,
+        exchange='binance',
+        open_rate=1,
+        max_rate=1,
+        is_short=True,
+        leverage=5.0,
+    )
+    trade.adjust_stop_loss(trade.open_rate, -0.1, True)
+    assert trade.stop_loss == 1.02
+    assert trade.stop_loss_pct == -0.1
+    assert trade.initial_stop_loss == 1.02
+    assert trade.initial_stop_loss_pct == -0.1
+    Trade.query.session.add(trade)
+    # Lower stoploss
+    Trade.stoploss_reinitialization(-0.15)
+    trades = Trade.get_open_trades()
+    assert len(trades) == 1
+    trade_adj = trades[0]
+    assert trade_adj.stop_loss == 1.03
+    assert trade_adj.stop_loss_pct == -0.15
+    assert trade_adj.initial_stop_loss == 1.03
+    assert trade_adj.initial_stop_loss_pct == -0.15
+    # Raise stoploss
+    Trade.stoploss_reinitialization(-0.05)
+    trades = Trade.get_open_trades()
+    assert len(trades) == 1
+    trade_adj = trades[0]
+    assert trade_adj.stop_loss == 1.01
+    assert trade_adj.stop_loss_pct == -0.05
+    assert trade_adj.initial_stop_loss == 1.01
+    assert trade_adj.initial_stop_loss_pct == -0.05
+    # Trailing stoploss
+    trade.adjust_stop_loss(0.98, -0.05)
+    assert trade_adj.stop_loss == 0.9898
+    assert trade_adj.initial_stop_loss == 1.01
+    Trade.stoploss_reinitialization(-0.05)
+    trades = Trade.get_open_trades()
+    assert len(trades) == 1
+    trade_adj = trades[0]
+    # Stoploss should not change in this case.
+    assert trade_adj.stop_loss == 0.9898
+    assert trade_adj.stop_loss_pct == -0.05
+    assert trade_adj.initial_stop_loss == 1.01
+    assert trade_adj.initial_stop_loss_pct == -0.05
+    # Stoploss can't go above liquidation price
+    trade_adj.set_isolated_liq(0.985)
+    trade.adjust_stop_loss(0.9799, -0.05)
+    assert trade_adj.stop_loss == 0.985
+    assert trade_adj.stop_loss == 0.985
+
+
 def test_update_fee(fee):
     trade = Trade(
         pair='ADA/USDT',
@@ -1103,14 +1942,15 @@ def test_fee_updated(fee):
 
 
 @pytest.mark.usefixtures("init_persistence")
+@pytest.mark.parametrize('is_short', [True, False])
 @pytest.mark.parametrize('use_db', [True, False])
-def test_total_open_trades_stakes(fee, use_db):
+def test_total_open_trades_stakes(fee, is_short, use_db):
 
     Trade.use_db = use_db
     Trade.reset_trades()
     res = Trade.total_open_trades_stakes()
     assert res == 0
-    create_mock_trades(fee, use_db)
+    create_mock_trades(fee, is_short, use_db)
     res = Trade.total_open_trades_stakes()
     assert res == 0.004
 
@@ -1118,26 +1958,32 @@ def test_total_open_trades_stakes(fee, use_db):
 
 
 @pytest.mark.usefixtures("init_persistence")
+@pytest.mark.parametrize('is_short,result', [
+    (True, -0.006739127),
+    (False, 0.000739127),
+    (None, -0.005429127),
+])
 @pytest.mark.parametrize('use_db', [True, False])
-def test_get_total_closed_profit(fee, use_db):
+def test_get_total_closed_profit(fee, use_db, is_short, result):
 
     Trade.use_db = use_db
     Trade.reset_trades()
     res = Trade.get_total_closed_profit()
     assert res == 0
-    create_mock_trades(fee, use_db)
+    create_mock_trades(fee, is_short, use_db)
     res = Trade.get_total_closed_profit()
-    assert res == 0.000739127
+    assert pytest.approx(res) == result
 
     Trade.use_db = True
 
 
 @pytest.mark.usefixtures("init_persistence")
+@pytest.mark.parametrize('is_short', [True, False])
 @pytest.mark.parametrize('use_db', [True, False])
-def test_get_trades_proxy(fee, use_db):
+def test_get_trades_proxy(fee, use_db, is_short):
     Trade.use_db = use_db
     Trade.reset_trades()
-    create_mock_trades(fee, use_db)
+    create_mock_trades(fee, is_short, use_db)
     trades = Trade.get_trades_proxy()
     assert len(trades) == 6
 
@@ -1166,9 +2012,10 @@ def test_get_trades_backtest():
 
 
 @pytest.mark.usefixtures("init_persistence")
+# @pytest.mark.parametrize('is_short', [True, False])
 def test_get_overall_performance(fee):
 
-    create_mock_trades(fee)
+    create_mock_trades(fee, False)
     res = Trade.get_overall_performance()
 
     assert len(res) == 2
@@ -1178,39 +2025,58 @@ def test_get_overall_performance(fee):
 
 
 @pytest.mark.usefixtures("init_persistence")
-def test_get_best_pair(fee):
+@pytest.mark.parametrize('is_short,pair,profit', [
+    (True, 'ETC/BTC', -0.005),
+    (False, 'XRP/BTC', 0.01),
+    (None, 'XRP/BTC', 0.01),
+])
+def test_get_best_pair(fee, is_short, pair, profit):
 
     res = Trade.get_best_pair()
     assert res is None
 
-    create_mock_trades(fee)
+    create_mock_trades(fee, is_short)
     res = Trade.get_best_pair()
     assert len(res) == 2
-    assert res[0] == 'XRP/BTC'
-    assert res[1] == 0.01
+    assert res[0] == pair
+    assert res[1] == profit
 
 
 @pytest.mark.usefixtures("init_persistence")
-def test_get_exit_order_count(fee):
+def test_get_best_pair_lev(fee):
 
-    create_mock_trades_usdt(fee)
-    trade = Trade.get_trades([Trade.pair == 'ETC/USDT']).first()
+    res = Trade.get_best_pair()
+    assert res is None
+
+    create_mock_trades_with_leverage(fee)
+    res = Trade.get_best_pair()
+    assert len(res) == 2
+    assert res[0] == 'DOGE/BTC'
+    assert res[1] == 0.1713156134055116
+
+
+@pytest.mark.usefixtures("init_persistence")
+@pytest.mark.parametrize('is_short', [True, False])
+def test_get_exit_order_count(fee, is_short):
+
+    create_mock_trades(fee, is_short=is_short)
+    trade = Trade.get_trades([Trade.pair == 'ETC/BTC']).first()
     assert trade.get_exit_order_count() == 1
 
 
 @pytest.mark.usefixtures("init_persistence")
 def test_update_order_from_ccxt(caplog):
     # Most basic order return (only has orderid)
-    o = Order.parse_from_ccxt_object({'id': '1234'}, 'ETH/BTC', 'buy')
+    o = Order.parse_from_ccxt_object({'id': '1234'}, 'ADA/USDT', 'buy')
     assert isinstance(o, Order)
-    assert o.ft_pair == 'ETH/BTC'
+    assert o.ft_pair == 'ADA/USDT'
     assert o.ft_order_side == 'buy'
     assert o.order_id == '1234'
     assert o.ft_is_open
     ccxt_order = {
         'id': '1234',
         'side': 'buy',
-        'symbol': 'ETH/BTC',
+        'symbol': 'ADA/USDT',
         'type': 'limit',
         'price': 1234.5,
         'amount':  20.0,
@@ -1219,9 +2085,9 @@ def test_update_order_from_ccxt(caplog):
         'status': 'open',
         'timestamp': 1599394315123
     }
-    o = Order.parse_from_ccxt_object(ccxt_order, 'ETH/BTC', 'buy')
+    o = Order.parse_from_ccxt_object(ccxt_order, 'ADA/USDT', 'buy')
     assert isinstance(o, Order)
-    assert o.ft_pair == 'ETH/BTC'
+    assert o.ft_pair == 'ADA/USDT'
     assert o.ft_order_side == 'buy'
     assert o.order_id == '1234'
     assert o.order_type == 'limit'
@@ -1260,45 +2126,46 @@ def test_update_order_from_ccxt(caplog):
 
 
 @pytest.mark.usefixtures("init_persistence")
-def test_select_order(fee):
-    create_mock_trades(fee)
+@pytest.mark.parametrize('is_short', [True, False])
+def test_select_order(fee, is_short):
+    create_mock_trades(fee, is_short)
 
     trades = Trade.get_trades().all()
 
     # Open buy order, no sell order
-    order = trades[0].select_order('buy', True)
+    order = trades[0].select_order(trades[0].enter_side, True)
     assert order is None
-    order = trades[0].select_order('buy', False)
+    order = trades[0].select_order(trades[0].enter_side, False)
     assert order is not None
-    order = trades[0].select_order('sell', None)
+    order = trades[0].select_order(trades[0].exit_side, None)
     assert order is None
 
     # closed buy order, and open sell order
-    order = trades[1].select_order('buy', True)
+    order = trades[1].select_order(trades[1].enter_side, True)
     assert order is None
-    order = trades[1].select_order('buy', False)
+    order = trades[1].select_order(trades[1].enter_side, False)
     assert order is not None
-    order = trades[1].select_order('buy', None)
+    order = trades[1].select_order(trades[1].enter_side, None)
     assert order is not None
-    order = trades[1].select_order('sell', True)
+    order = trades[1].select_order(trades[1].exit_side, True)
     assert order is None
-    order = trades[1].select_order('sell', False)
+    order = trades[1].select_order(trades[1].exit_side, False)
     assert order is not None
 
     # Has open buy order
-    order = trades[3].select_order('buy', True)
+    order = trades[3].select_order(trades[3].enter_side, True)
     assert order is not None
-    order = trades[3].select_order('buy', False)
+    order = trades[3].select_order(trades[3].enter_side, False)
     assert order is None
 
     # Open sell order
-    order = trades[4].select_order('buy', True)
+    order = trades[4].select_order(trades[4].enter_side, True)
     assert order is None
-    order = trades[4].select_order('buy', False)
+    order = trades[4].select_order(trades[4].enter_side, False)
     assert order is not None
 
-    trades[4].orders[1].ft_order_side = 'sell'
-    order = trades[4].select_order('sell', True)
+    trades[4].orders[1].ft_order_side = trades[4].exit_side
+    order = trades[4].select_order(trades[4].exit_side, True)
     assert order is not None
 
     trades[4].orders[1].ft_order_side = 'stoploss'
@@ -1324,12 +2191,12 @@ def test_Trade_object_idem():
         'get_overall_performance',
         'get_total_closed_profit',
         'total_open_trades_stakes',
-        'get_sold_trades_without_assigned_fees',
+        'get_closed_trades_without_assigned_fees',
         'get_open_trades_without_assigned_fees',
         'get_open_order_trades',
         'get_trades',
         'get_sell_reason_performance',
-        'get_buy_tag_performance',
+        'get_enter_tag_performance',
         'get_mix_tag_performance',
 
     )
@@ -1366,6 +2233,7 @@ def test_recalc_trade_from_orders(fee):
         exchange='binance',
         open_rate=o1_rate,
         max_rate=o1_rate,
+        leverage=1,
     )
 
     assert fee.return_value == 0.0025
@@ -1508,13 +2376,16 @@ def test_recalc_trade_from_orders(fee):
     assert pytest.approx(trade.open_trade_value) == o1_trade_val + o2_trade_val + o3_trade_val
 
 
-def test_recalc_trade_from_orders_ignores_bad_orders(fee):
+@pytest.mark.parametrize('is_short', [True, False])
+def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short):
 
     o1_amount = 100
     o1_rate = 1
     o1_cost = o1_amount * o1_rate
     o1_fee_cost = o1_cost * fee.return_value
-    o1_trade_val = o1_cost + o1_fee_cost
+    o1_trade_val = o1_cost - o1_fee_cost if is_short else o1_cost + o1_fee_cost
+    enter_side = "sell" if is_short else "buy"
+    exit_side = "buy" if is_short else "sell"
 
     trade = Trade(
         pair='ADA/USDT',
@@ -1526,17 +2397,19 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
         exchange='binance',
         open_rate=o1_rate,
         max_rate=o1_rate,
+        is_short=is_short,
+        leverage=1.0,
     )
-    trade.update_fee(o1_fee_cost, 'BNB', fee.return_value, 'buy')
+    trade.update_fee(o1_fee_cost, 'BNB', fee.return_value, enter_side)
     # Check with 1 order
     order1 = Order(
-        ft_order_side='buy',
+        ft_order_side=enter_side,
         ft_pair=trade.pair,
         ft_is_open=False,
         status="closed",
         symbol=trade.pair,
         order_type="market",
-        side="buy",
+        side=enter_side,
         price=o1_rate,
         average=o1_rate,
         filled=o1_amount,
@@ -1554,16 +2427,16 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
     assert trade.open_rate == o1_rate
     assert trade.fee_open_cost == o1_fee_cost
     assert trade.open_trade_value == o1_trade_val
-    assert trade.nr_of_successful_buys == 1
+    assert trade.nr_of_successful_entries == 1
 
     order2 = Order(
-        ft_order_side='buy',
+        ft_order_side=enter_side,
         ft_pair=trade.pair,
         ft_is_open=True,
         status="open",
         symbol=trade.pair,
         order_type="market",
-        side="buy",
+        side=enter_side,
         price=o1_rate,
         average=o1_rate,
         filled=o1_amount,
@@ -1581,17 +2454,17 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
     assert trade.open_rate == o1_rate
     assert trade.fee_open_cost == o1_fee_cost
     assert trade.open_trade_value == o1_trade_val
-    assert trade.nr_of_successful_buys == 1
+    assert trade.nr_of_successful_entries == 1
 
     # Let's try with some other orders
     order3 = Order(
-        ft_order_side='buy',
+        ft_order_side=enter_side,
         ft_pair=trade.pair,
         ft_is_open=False,
         status="cancelled",
         symbol=trade.pair,
         order_type="market",
-        side="buy",
+        side=enter_side,
         price=1,
         average=2,
         filled=0,
@@ -1609,16 +2482,16 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
     assert trade.open_rate == o1_rate
     assert trade.fee_open_cost == o1_fee_cost
     assert trade.open_trade_value == o1_trade_val
-    assert trade.nr_of_successful_buys == 1
+    assert trade.nr_of_successful_entries == 1
 
     order4 = Order(
-        ft_order_side='buy',
+        ft_order_side=enter_side,
         ft_pair=trade.pair,
         ft_is_open=False,
         status="closed",
         symbol=trade.pair,
         order_type="market",
-        side="buy",
+        side=enter_side,
         price=o1_rate,
         average=o1_rate,
         filled=o1_amount,
@@ -1636,17 +2509,17 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
     assert trade.open_rate == o1_rate
     assert trade.fee_open_cost == 2 * o1_fee_cost
     assert trade.open_trade_value == 2 * o1_trade_val
-    assert trade.nr_of_successful_buys == 2
+    assert trade.nr_of_successful_entries == 2
 
-    # Just to make sure sell orders are ignored, let's calculate one more time.
+    # Just to make sure exit orders are ignored, let's calculate one more time.
     sell1 = Order(
-        ft_order_side='sell',
+        ft_order_side=exit_side,
         ft_pair=trade.pair,
         ft_is_open=False,
         status="closed",
         symbol=trade.pair,
         order_type="market",
-        side="sell",
+        side=exit_side,
         price=4,
         average=3,
         filled=2,
@@ -1663,16 +2536,17 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
     assert trade.open_rate == o1_rate
     assert trade.fee_open_cost == 2 * o1_fee_cost
     assert trade.open_trade_value == 2 * o1_trade_val
-    assert trade.nr_of_successful_buys == 2
+    assert trade.nr_of_successful_entries == 2
+
     # Check with 1 order
     order_noavg = Order(
-        ft_order_side='buy',
+        ft_order_side=enter_side,
         ft_pair=trade.pair,
         ft_is_open=False,
         status="closed",
         symbol=trade.pair,
         order_type="market",
-        side="buy",
+        side=enter_side,
         price=o1_rate,
         average=None,
         filled=o1_amount,
@@ -1690,7 +2564,7 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
     assert trade.open_rate == o1_rate
     assert trade.fee_open_cost == 3 * o1_fee_cost
     assert trade.open_trade_value == 3 * o1_trade_val
-    assert trade.nr_of_successful_buys == 3
+    assert trade.nr_of_successful_entries == 3
 
 
 @pytest.mark.usefixtures("init_persistence")
diff --git a/tests/test_plotting.py b/tests/test_plotting.py
index 6846bb5f2..5f8b20251 100644
--- a/tests/test_plotting.py
+++ b/tests/test_plotting.py
@@ -200,8 +200,8 @@ def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, t
     timerange = TimeRange(None, 'line', 0, -1000)
     data = history.load_pair_history(pair=pair, timeframe='1m',
                                      datadir=testdatadir, timerange=timerange)
-    data['buy'] = 0
-    data['sell'] = 0
+    data['enter_long'] = 0
+    data['exit_long'] = 0
 
     indicators1 = []
     indicators2 = []
@@ -259,13 +259,13 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir)
 
     buy = find_trace_in_fig_data(figure.data, "buy")
     assert isinstance(buy, go.Scatter)
-    # All buy-signals should be plotted
-    assert int(data.buy.sum()) == len(buy.x)
+    # All entry-signals should be plotted
+    assert int(data['enter_long'].sum()) == len(buy.x)
 
     sell = find_trace_in_fig_data(figure.data, "sell")
     assert isinstance(sell, go.Scatter)
-    # All buy-signals should be plotted
-    assert int(data.sell.sum()) == len(sell.x)
+    # All entry-signals should be plotted
+    assert int(data['exit_long'].sum()) == len(sell.x)
 
     assert find_trace_in_fig_data(figure.data, "Bollinger Band")
 
diff --git a/tests/test_wallets.py b/tests/test_wallets.py
index 12297e5ed..73a34bbae 100644
--- a/tests/test_wallets.py
+++ b/tests/test_wallets.py
@@ -6,7 +6,7 @@ import pytest
 
 from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
 from freqtrade.exceptions import DependencyException
-from tests.conftest import get_patched_freqtradebot, patch_wallet
+from tests.conftest import create_mock_trades, get_patched_freqtradebot, patch_wallet
 
 
 def test_sync_wallet_at_boot(mocker, default_conf):
@@ -180,24 +180,32 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r
     assert result == 0
 
 
-@pytest.mark.parametrize('stake_amount,min_stake_amount,max_stake_amount,expected', [
-    (22, 11, 50, 22),
-    (100, 11, 500, 100),
-    (1000, 11, 500, 500),  # Above max-stake
-    (20, 15, 10, 0),  # Minimum stake > max-stake
-    (9, 11, 100, 11),  # Below min stake
-    (1, 15, 10, 0),  # Below min stake and min_stake > max_stake
-    (20, 50, 100, 0),  # Below min stake and stake * 1.3 > min_stake
-    (1000, None, 1000, 1000),  # No min-stake-amount could be determined
+@pytest.mark.parametrize('stake_amount,min_stake,stake_available,max_stake,expected', [
+    (22, 11, 50, 10000, 22),
+    (100, 11, 500, 10000, 100),
+    (1000, 11, 500, 10000, 500),  # Above stake_available
+    (700, 11, 1000, 400, 400),  # Above max_stake, below stake available
+    (20, 15, 10, 10000, 0),  # Minimum stake > stake_available
+    (9, 11, 100, 10000, 11),  # Below min stake
+    (1, 15, 10, 10000, 0),  # Below min stake and min_stake > stake_available
+    (20, 50, 100, 10000, 0),  # Below min stake and stake * 1.3 > min_stake
+    (1000, None, 1000, 10000, 1000),  # No min-stake-amount could be determined
 
 ])
-def test_validate_stake_amount(mocker, default_conf,
-                               stake_amount, min_stake_amount, max_stake_amount, expected):
+def test_validate_stake_amount(
+    mocker,
+    default_conf,
+    stake_amount,
+    min_stake,
+    stake_available,
+    max_stake,
+    expected,
+):
     freqtrade = get_patched_freqtradebot(mocker, default_conf)
 
     mocker.patch("freqtrade.wallets.Wallets.get_available_stake_amount",
-                 return_value=max_stake_amount)
-    res = freqtrade.wallets.validate_stake_amount('XRP/USDT', stake_amount, min_stake_amount)
+                 return_value=stake_available)
+    res = freqtrade.wallets.validate_stake_amount('XRP/USDT', stake_amount, min_stake, max_stake)
     assert res == expected
 
 
@@ -226,3 +234,131 @@ def test_get_starting_balance(mocker, default_conf, available_capital, closed_pr
     freqtrade = get_patched_freqtradebot(mocker, default_conf)
 
     assert freqtrade.wallets.get_starting_balance() == expected
+
+
+def test_sync_wallet_futures_live(mocker, default_conf):
+    default_conf['dry_run'] = False
+    default_conf['trading_mode'] = 'futures'
+    default_conf['margin_mode'] = 'isolated'
+    mock_result = [
+        {
+            "symbol": "ETH/USDT:USDT",
+            "timestamp": None,
+            "datetime": None,
+            "initialMargin": 0.0,
+            "initialMarginPercentage": None,
+            "maintenanceMargin": 0.0,
+            "maintenanceMarginPercentage": 0.005,
+            "entryPrice": 0.0,
+            "notional": 100.0,
+            "leverage": 5.0,
+            "unrealizedPnl": 0.0,
+            "contracts": 100.0,
+            "contractSize": 1,
+            "marginRatio": None,
+            "liquidationPrice": 0.0,
+            "markPrice": 2896.41,
+            "collateral": 20,
+            "marginType": "isolated",
+            "side": 'short',
+            "percentage": None
+        },
+        {
+            "symbol": "ADA/USDT:USDT",
+            "timestamp": None,
+            "datetime": None,
+            "initialMargin": 0.0,
+            "initialMarginPercentage": None,
+            "maintenanceMargin": 0.0,
+            "maintenanceMarginPercentage": 0.005,
+            "entryPrice": 0.0,
+            "notional": 100.0,
+            "leverage": 5.0,
+            "unrealizedPnl": 0.0,
+            "contracts": 100.0,
+            "contractSize": 1,
+            "marginRatio": None,
+            "liquidationPrice": 0.0,
+            "markPrice": 0.91,
+            "collateral": 20,
+            "marginType": "isolated",
+            "side": 'short',
+            "percentage": None
+        },
+        {
+            # Closed position
+            "symbol": "SOL/BUSD:BUSD",
+            "timestamp": None,
+            "datetime": None,
+            "initialMargin": 0.0,
+            "initialMarginPercentage": None,
+            "maintenanceMargin": 0.0,
+            "maintenanceMarginPercentage": 0.005,
+            "entryPrice": 0.0,
+            "notional": 0.0,
+            "leverage": 5.0,
+            "unrealizedPnl": 0.0,
+            "contracts": 0.0,
+            "contractSize": 1,
+            "marginRatio": None,
+            "liquidationPrice": 0.0,
+            "markPrice": 15.41,
+            "collateral": 0.0,
+            "marginType": "isolated",
+            "side": 'short',
+            "percentage": None
+        }
+    ]
+    mocker.patch.multiple(
+        'freqtrade.exchange.Exchange',
+        get_balances=MagicMock(return_value={
+            "USDT": {
+                "free": 900,
+                "used": 100,
+                "total": 1000
+            },
+        }),
+        fetch_positions=MagicMock(return_value=mock_result)
+    )
+
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
+
+    assert len(freqtrade.wallets._wallets) == 1
+    assert len(freqtrade.wallets._positions) == 2
+
+    assert 'USDT' in freqtrade.wallets._wallets
+    assert 'ETH/USDT:USDT' in freqtrade.wallets._positions
+    assert freqtrade.wallets._last_wallet_refresh > 0
+
+    # Remove ETH/USDT:USDT position
+    del mock_result[0]
+    freqtrade.wallets.update()
+    assert len(freqtrade.wallets._positions) == 1
+    assert 'ETH/USDT:USDT' not in freqtrade.wallets._positions
+
+
+def test_sync_wallet_futures_dry(mocker, default_conf, fee):
+    default_conf['dry_run'] = True
+    default_conf['trading_mode'] = 'futures'
+    default_conf['margin_mode'] = 'isolated'
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
+    assert len(freqtrade.wallets._wallets) == 1
+    assert len(freqtrade.wallets._positions) == 0
+
+    create_mock_trades(fee, is_short=None)
+
+    freqtrade.wallets.update()
+
+    assert len(freqtrade.wallets._wallets) == 1
+    assert len(freqtrade.wallets._positions) == 4
+    positions = freqtrade.wallets.get_all_positions()
+    positions['ETH/BTC'].side == 'short'
+    positions['ETC/BTC'].side == 'long'
+    positions['XRP/BTC'].side == 'long'
+    positions['LTC/BTC'].side == 'short'
+
+    assert freqtrade.wallets.get_starting_balance() == default_conf['dry_run_wallet']
+    total = freqtrade.wallets.get_total('BTC')
+    free = freqtrade.wallets.get_free('BTC')
+    used = freqtrade.wallets.get_used('BTC')
+    assert free + used == total
diff --git a/tests/testdata/backtest-result_multistrat.json b/tests/testdata/backtest-result_multistrat.json
index 80827fa79..6bb60e398 100644
--- a/tests/testdata/backtest-result_multistrat.json
+++ b/tests/testdata/backtest-result_multistrat.json
@@ -1 +1 @@
-{"strategy":{"StrategyTestV2":{"trades":[{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.37344398340249,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:20:00+00:00","open_rate":9.64e-05,"close_rate":0.00010074887218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":4.348872180451118e-06,"sell_reason":"roi","initial_stop_loss_abs":8.676e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.676e-05,"stop_loss_ratio":0.1,"min_rate":9.64e-05,"max_rate":0.00010074887218045112,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515568800000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":21.026072329688816,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:30:00+00:00","open_rate":4.756e-05,"close_rate":4.9705563909774425e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":2.1455639097744267e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2804e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2804e-05,"stop_loss_ratio":0.1,"min_rate":4.756e-05,"max_rate":4.9705563909774425e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515569400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.94908655286014,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:35:00+00:00","open_rate":3.339e-05,"close_rate":3.489631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":1.506315789473681e-06,"sell_reason":"roi","initial_stop_loss_abs":3.0050999999999997e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0050999999999997e-05,"stop_loss_ratio":0.1,"min_rate":3.339e-05,"max_rate":3.489631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515569700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.313531353135314,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:40:00+00:00","open_rate":9.696e-05,"close_rate":0.00010133413533834584,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":4.3741353383458455e-06,"sell_reason":"roi","initial_stop_loss_abs":8.7264e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.7264e-05,"stop_loss_ratio":0.1,"min_rate":9.696e-05,"max_rate":0.00010133413533834584,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515570000000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010604453870625663,"open_date":"2018-01-10 07:35:00+00:00","close_date":"2018-01-10 08:35:00+00:00","open_rate":0.0943,"close_rate":0.09477268170426063,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.0004726817042606385,"sell_reason":"roi","initial_stop_loss_abs":0.08487,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08487,"stop_loss_ratio":0.1,"min_rate":0.0943,"max_rate":0.09477268170426063,"is_open":false,"buy_tag":null,"open_timestamp":1515569700000.0,"close_timestamp":1515573300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03677001860930642,"open_date":"2018-01-10 07:40:00+00:00","close_date":"2018-01-10 08:10:00+00:00","open_rate":0.02719607,"close_rate":0.02760503345864661,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00040896345864661204,"sell_reason":"roi","initial_stop_loss_abs":0.024476463,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024476463,"stop_loss_ratio":0.1,"min_rate":0.02719607,"max_rate":0.02760503345864661,"is_open":false,"buy_tag":null,"open_timestamp":1515570000000.0,"close_timestamp":1515571800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021575196463739,"open_date":"2018-01-10 08:15:00+00:00","close_date":"2018-01-10 09:55:00+00:00","open_rate":0.04634952,"close_rate":0.046581848421052625,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":100,"profit_ratio":0.0,"profit_abs":0.0002323284210526272,"sell_reason":"roi","initial_stop_loss_abs":0.041714568,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041714568,"stop_loss_ratio":0.1,"min_rate":0.04634952,"max_rate":0.046581848421052625,"is_open":false,"buy_tag":null,"open_timestamp":1515572100000.0,"close_timestamp":1515578100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.615786040443574,"open_date":"2018-01-10 14:45:00+00:00","close_date":"2018-01-10 15:50:00+00:00","open_rate":3.066e-05,"close_rate":3.081368421052631e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":1.5368421052630647e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7594e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7594e-05,"stop_loss_ratio":0.1,"min_rate":3.066e-05,"max_rate":3.081368421052631e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515595500000.0,"close_timestamp":1515599400000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.05917194776300452,"open_date":"2018-01-10 16:35:00+00:00","close_date":"2018-01-10 17:15:00+00:00","open_rate":0.0168999,"close_rate":0.016984611278195488,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.471127819548868e-05,"sell_reason":"roi","initial_stop_loss_abs":0.01520991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01520991,"stop_loss_ratio":0.1,"min_rate":0.0168999,"max_rate":0.016984611278195488,"is_open":false,"buy_tag":null,"open_timestamp":1515602100000.0,"close_timestamp":1515604500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010949822656672253,"open_date":"2018-01-10 16:40:00+00:00","close_date":"2018-01-10 17:20:00+00:00","open_rate":0.09132568,"close_rate":0.0917834528320802,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004577728320801916,"sell_reason":"roi","initial_stop_loss_abs":0.08219311200000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08219311200000001,"stop_loss_ratio":0.1,"min_rate":0.09132568,"max_rate":0.0917834528320802,"is_open":false,"buy_tag":null,"open_timestamp":1515602400000.0,"close_timestamp":1515604800000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011238476768326556,"open_date":"2018-01-10 18:50:00+00:00","close_date":"2018-01-10 19:45:00+00:00","open_rate":0.08898003,"close_rate":0.08942604518796991,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00044601518796991146,"sell_reason":"roi","initial_stop_loss_abs":0.080082027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080082027,"stop_loss_ratio":0.1,"min_rate":0.08898003,"max_rate":0.08942604518796991,"is_open":false,"buy_tag":null,"open_timestamp":1515610200000.0,"close_timestamp":1515613500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011682232072680307,"open_date":"2018-01-10 22:15:00+00:00","close_date":"2018-01-10 23:00:00+00:00","open_rate":0.08560008,"close_rate":0.08602915308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":0.00042907308270676014,"sell_reason":"roi","initial_stop_loss_abs":0.077040072,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077040072,"stop_loss_ratio":0.1,"min_rate":0.08560008,"max_rate":0.08602915308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515622500000.0,"close_timestamp":1515625200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4014726015023105,"open_date":"2018-01-10 22:50:00+00:00","close_date":"2018-01-10 23:20:00+00:00","open_rate":0.00249083,"close_rate":0.0025282860902255634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.745609022556351e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002241747,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002241747,"stop_loss_ratio":0.1,"min_rate":0.00249083,"max_rate":0.0025282860902255634,"is_open":false,"buy_tag":null,"open_timestamp":1515624600000.0,"close_timestamp":1515626400000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.090668431502316,"open_date":"2018-01-10 23:15:00+00:00","close_date":"2018-01-11 00:15:00+00:00","open_rate":3.022e-05,"close_rate":3.037147869674185e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":1.5147869674185174e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7198e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7198e-05,"stop_loss_ratio":0.1,"min_rate":3.022e-05,"max_rate":3.037147869674185e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515626100000.0,"close_timestamp":1515629700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.41034058268362744,"open_date":"2018-01-10 23:40:00+00:00","close_date":"2018-01-11 00:05:00+00:00","open_rate":0.002437,"close_rate":0.0024980776942355883,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":6.107769423558838e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0021933,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0021933,"stop_loss_ratio":0.1,"min_rate":0.002437,"max_rate":0.0024980776942355883,"is_open":false,"buy_tag":null,"open_timestamp":1515627600000.0,"close_timestamp":1515629100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02095643931654345,"open_date":"2018-01-11 00:00:00+00:00","close_date":"2018-01-11 00:35:00+00:00","open_rate":0.04771803,"close_rate":0.04843559436090225,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0007175643609022495,"sell_reason":"roi","initial_stop_loss_abs":0.042946227000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.042946227000000003,"stop_loss_ratio":0.1,"min_rate":0.04771803,"max_rate":0.04843559436090225,"is_open":false,"buy_tag":null,"open_timestamp":1515628800000.0,"close_timestamp":1515630900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.389756231169542,"open_date":"2018-01-11 03:40:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":3.651e-05,"close_rate":3.2859000000000005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.10448878,"profit_abs":-3.650999999999996e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":3.2859000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2859000000000005e-05,"stop_loss_ratio":0.1,"min_rate":3.2859000000000005e-05,"max_rate":3.651e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515642000000.0,"close_timestamp":1515644700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011332594070446804,"open_date":"2018-01-11 03:55:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":0.08824105,"close_rate":0.08956798308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013269330827067605,"sell_reason":"roi","initial_stop_loss_abs":0.079416945,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079416945,"stop_loss_ratio":0.1,"min_rate":0.08824105,"max_rate":0.08956798308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515642900000.0,"close_timestamp":1515644700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.411522633744856,"open_date":"2018-01-11 04:00:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":0.00243,"close_rate":0.002442180451127819,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":1.2180451127819219e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002187,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002187,"stop_loss_ratio":0.1,"min_rate":0.00243,"max_rate":0.002442180451127819,"is_open":false,"buy_tag":null,"open_timestamp":1515643200000.0,"close_timestamp":1515646200000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022001890402423376,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:55:00+00:00","open_rate":0.04545064,"close_rate":0.046589753784461146,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":0.001139113784461146,"sell_reason":"roi","initial_stop_loss_abs":0.040905576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040905576,"stop_loss_ratio":0.1,"min_rate":0.04545064,"max_rate":0.046589753784461146,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.655990510083036,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":3.372e-05,"close_rate":3.456511278195488e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":8.4511278195488e-07,"sell_reason":"roi","initial_stop_loss_abs":3.0348e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0348e-05,"stop_loss_ratio":0.1,"min_rate":3.372e-05,"max_rate":3.456511278195488e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037821482602118005,"open_date":"2018-01-11 04:55:00+00:00","close_date":"2018-01-11 05:15:00+00:00","open_rate":0.02644,"close_rate":0.02710265664160401,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0006626566416040071,"sell_reason":"roi","initial_stop_loss_abs":0.023796,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023796,"stop_loss_ratio":0.1,"min_rate":0.02644,"max_rate":0.02710265664160401,"is_open":false,"buy_tag":null,"open_timestamp":1515646500000.0,"close_timestamp":1515647700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011348161597821153,"open_date":"2018-01-11 11:20:00+00:00","close_date":"2018-01-11 12:00:00+00:00","open_rate":0.08812,"close_rate":0.08856170426065162,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004417042606516125,"sell_reason":"roi","initial_stop_loss_abs":0.079308,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079308,"stop_loss_ratio":0.1,"min_rate":0.08812,"max_rate":0.08856170426065162,"is_open":false,"buy_tag":null,"open_timestamp":1515669600000.0,"close_timestamp":1515672000000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037263696923919086,"open_date":"2018-01-11 11:35:00+00:00","close_date":"2018-01-11 12:15:00+00:00","open_rate":0.02683577,"close_rate":0.026970285137844607,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013451513784460897,"sell_reason":"roi","initial_stop_loss_abs":0.024152193,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024152193,"stop_loss_ratio":0.1,"min_rate":0.02683577,"max_rate":0.026970285137844607,"is_open":false,"buy_tag":null,"open_timestamp":1515670500000.0,"close_timestamp":1515672900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.329335230737954,"open_date":"2018-01-11 14:00:00+00:00","close_date":"2018-01-11 14:25:00+00:00","open_rate":4.919e-05,"close_rate":5.04228320802005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.232832080200495e-06,"sell_reason":"roi","initial_stop_loss_abs":4.4271000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4271000000000004e-05,"stop_loss_ratio":0.1,"min_rate":4.919e-05,"max_rate":5.04228320802005e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515679200000.0,"close_timestamp":1515680700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.01138317402960718,"open_date":"2018-01-11 19:25:00+00:00","close_date":"2018-01-11 20:35:00+00:00","open_rate":0.08784896,"close_rate":0.08828930566416039,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":70,"profit_ratio":-0.0,"profit_abs":0.0004403456641603881,"sell_reason":"roi","initial_stop_loss_abs":0.079064064,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079064064,"stop_loss_ratio":0.1,"min_rate":0.08784896,"max_rate":0.08828930566416039,"is_open":false,"buy_tag":null,"open_timestamp":1515698700000.0,"close_timestamp":1515702900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.58863858961802,"open_date":"2018-01-11 22:35:00+00:00","close_date":"2018-01-11 23:30:00+00:00","open_rate":5.105e-05,"close_rate":5.130588972431077e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.558897243107704e-07,"sell_reason":"roi","initial_stop_loss_abs":4.5945e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.5945e-05,"stop_loss_ratio":0.1,"min_rate":5.105e-05,"max_rate":5.130588972431077e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515710100000.0,"close_timestamp":1515713400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.252525252525253,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:25:00+00:00","open_rate":3.96e-05,"close_rate":4.019548872180451e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":5.954887218045116e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5640000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5640000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.96e-05,"max_rate":4.019548872180451e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.66204506065858,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:35:00+00:00","open_rate":2.885e-05,"close_rate":2.899461152882205e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4461152882205115e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5965e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5965e-05,"stop_loss_ratio":0.1,"min_rate":2.885e-05,"max_rate":2.899461152882205e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03780718336483932,"open_date":"2018-01-11 23:30:00+00:00","close_date":"2018-01-12 00:05:00+00:00","open_rate":0.02645,"close_rate":0.026847744360902256,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0003977443609022545,"sell_reason":"roi","initial_stop_loss_abs":0.023805000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023805000000000003,"stop_loss_ratio":0.1,"min_rate":0.02645,"max_rate":0.026847744360902256,"is_open":false,"buy_tag":null,"open_timestamp":1515713400000.0,"close_timestamp":1515715500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020833333333333332,"open_date":"2018-01-11 23:55:00+00:00","close_date":"2018-01-12 01:15:00+00:00","open_rate":0.048,"close_rate":0.04824060150375939,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00024060150375938838,"sell_reason":"roi","initial_stop_loss_abs":0.0432,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0432,"stop_loss_ratio":0.1,"min_rate":0.048,"max_rate":0.04824060150375939,"is_open":false,"buy_tag":null,"open_timestamp":1515714900000.0,"close_timestamp":1515719700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.31287297527707,"open_date":"2018-01-12 21:15:00+00:00","close_date":"2018-01-12 21:40:00+00:00","open_rate":4.692e-05,"close_rate":4.809593984962405e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1759398496240516e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2227999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2227999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.692e-05,"max_rate":4.809593984962405e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515791700000.0,"close_timestamp":1515793200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38915654211062944,"open_date":"2018-01-13 00:55:00+00:00","close_date":"2018-01-13 06:20:00+00:00","open_rate":0.00256966,"close_rate":0.0025825405012531327,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":325,"profit_ratio":-0.0,"profit_abs":1.2880501253132587e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002312694,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002312694,"stop_loss_ratio":0.1,"min_rate":0.00256966,"max_rate":0.0025825405012531327,"is_open":false,"buy_tag":null,"open_timestamp":1515804900000.0,"close_timestamp":1515824400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":15.96933886937081,"open_date":"2018-01-13 10:55:00+00:00","close_date":"2018-01-13 11:35:00+00:00","open_rate":6.262e-05,"close_rate":6.293388471177944e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":3.138847117794446e-07,"sell_reason":"roi","initial_stop_loss_abs":5.6358e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.6358e-05,"stop_loss_ratio":0.1,"min_rate":6.262e-05,"max_rate":6.293388471177944e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515840900000.0,"close_timestamp":1515843300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.141649048625794,"open_date":"2018-01-13 13:05:00+00:00","close_date":"2018-01-15 14:10:00+00:00","open_rate":4.73e-05,"close_rate":4.753709273182957e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":2945,"profit_ratio":0.0,"profit_abs":2.3709273182957117e-07,"sell_reason":"roi","initial_stop_loss_abs":4.257e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.257e-05,"stop_loss_ratio":0.1,"min_rate":4.73e-05,"max_rate":4.753709273182957e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515848700000.0,"close_timestamp":1516025400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.49348507339601,"open_date":"2018-01-13 13:30:00+00:00","close_date":"2018-01-13 14:45:00+00:00","open_rate":6.063e-05,"close_rate":6.0933909774436085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":3.039097744360846e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4567e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4567e-05,"stop_loss_ratio":0.1,"min_rate":6.063e-05,"max_rate":6.0933909774436085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515850200000.0,"close_timestamp":1515854700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.023641941887746,"open_date":"2018-01-13 13:40:00+00:00","close_date":"2018-01-13 23:30:00+00:00","open_rate":0.00011082,"close_rate":0.00011137548872180448,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":590,"profit_ratio":-0.0,"profit_abs":5.554887218044781e-07,"sell_reason":"roi","initial_stop_loss_abs":9.9738e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.9738e-05,"stop_loss_ratio":0.1,"min_rate":0.00011082,"max_rate":0.00011137548872180448,"is_open":false,"buy_tag":null,"open_timestamp":1515850800000.0,"close_timestamp":1515886200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.863406408094438,"open_date":"2018-01-13 15:15:00+00:00","close_date":"2018-01-13 15:55:00+00:00","open_rate":5.93e-05,"close_rate":5.9597243107769415e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.9724310776941686e-07,"sell_reason":"roi","initial_stop_loss_abs":5.337e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.337e-05,"stop_loss_ratio":0.1,"min_rate":5.93e-05,"max_rate":5.9597243107769415e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515856500000.0,"close_timestamp":1515858900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020618543947292404,"open_date":"2018-01-13 16:30:00+00:00","close_date":"2018-01-13 17:10:00+00:00","open_rate":0.04850003,"close_rate":0.04874313791979949,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00024310791979949287,"sell_reason":"roi","initial_stop_loss_abs":0.043650027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.043650027,"stop_loss_ratio":0.1,"min_rate":0.04850003,"max_rate":0.04874313791979949,"is_open":false,"buy_tag":null,"open_timestamp":1515861000000.0,"close_timestamp":1515863400000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010178097365511457,"open_date":"2018-01-13 22:05:00+00:00","close_date":"2018-01-14 06:25:00+00:00","open_rate":0.09825019,"close_rate":0.09874267215538848,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":500,"profit_ratio":-0.0,"profit_abs":0.0004924821553884823,"sell_reason":"roi","initial_stop_loss_abs":0.088425171,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.088425171,"stop_loss_ratio":0.1,"min_rate":0.09825019,"max_rate":0.09874267215538848,"is_open":false,"buy_tag":null,"open_timestamp":1515881100000.0,"close_timestamp":1515911100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.616816218012627,"open_date":"2018-01-14 00:20:00+00:00","close_date":"2018-01-14 22:55:00+00:00","open_rate":6.018e-05,"close_rate":6.048165413533834e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":1355,"profit_ratio":0.0,"profit_abs":3.0165413533833987e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4162e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4162e-05,"stop_loss_ratio":0.1,"min_rate":6.018e-05,"max_rate":6.048165413533834e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515889200000.0,"close_timestamp":1515970500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010246952581919518,"open_date":"2018-01-14 12:45:00+00:00","close_date":"2018-01-14 13:25:00+00:00","open_rate":0.09758999,"close_rate":0.0980791628822055,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004891728822054991,"sell_reason":"roi","initial_stop_loss_abs":0.087830991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.087830991,"stop_loss_ratio":0.1,"min_rate":0.09758999,"max_rate":0.0980791628822055,"is_open":false,"buy_tag":null,"open_timestamp":1515933900000.0,"close_timestamp":1515936300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3215434083601286,"open_date":"2018-01-14 15:30:00+00:00","close_date":"2018-01-14 16:00:00+00:00","open_rate":0.00311,"close_rate":0.0031567669172932328,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.676691729323286e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002799,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002799,"stop_loss_ratio":0.1,"min_rate":0.00311,"max_rate":0.0031567669172932328,"is_open":false,"buy_tag":null,"open_timestamp":1515943800000.0,"close_timestamp":1515945600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.32010140812609433,"open_date":"2018-01-14 20:45:00+00:00","close_date":"2018-01-14 22:15:00+00:00","open_rate":0.00312401,"close_rate":0.003139669197994987,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":-0.0,"profit_abs":1.5659197994987058e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002811609,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002811609,"stop_loss_ratio":0.1,"min_rate":0.00312401,"max_rate":0.003139669197994987,"is_open":false,"buy_tag":null,"open_timestamp":1515962700000.0,"close_timestamp":1515968100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.057247866085791646,"open_date":"2018-01-14 23:35:00+00:00","close_date":"2018-01-15 00:30:00+00:00","open_rate":0.0174679,"close_rate":0.017555458395989976,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":8.755839598997492e-05,"sell_reason":"roi","initial_stop_loss_abs":0.015721110000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.015721110000000003,"stop_loss_ratio":0.1,"min_rate":0.0174679,"max_rate":0.017555458395989976,"is_open":false,"buy_tag":null,"open_timestamp":1515972900000.0,"close_timestamp":1515976200000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.013611282991367997,"open_date":"2018-01-14 23:45:00+00:00","close_date":"2018-01-15 00:25:00+00:00","open_rate":0.07346846,"close_rate":0.07383672295739348,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00036826295739347814,"sell_reason":"roi","initial_stop_loss_abs":0.066121614,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.066121614,"stop_loss_ratio":0.1,"min_rate":0.07346846,"max_rate":0.07383672295739348,"is_open":false,"buy_tag":null,"open_timestamp":1515973500000.0,"close_timestamp":1515975900000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010204706410596568,"open_date":"2018-01-15 02:25:00+00:00","close_date":"2018-01-15 03:05:00+00:00","open_rate":0.097994,"close_rate":0.09848519799498744,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004911979949874384,"sell_reason":"roi","initial_stop_loss_abs":0.0881946,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0881946,"stop_loss_ratio":0.1,"min_rate":0.097994,"max_rate":0.09848519799498744,"is_open":false,"buy_tag":null,"open_timestamp":1515983100000.0,"close_timestamp":1515985500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010353038616834042,"open_date":"2018-01-15 07:20:00+00:00","close_date":"2018-01-15 08:00:00+00:00","open_rate":0.09659,"close_rate":0.09707416040100247,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004841604010024786,"sell_reason":"roi","initial_stop_loss_abs":0.086931,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.086931,"stop_loss_ratio":0.1,"min_rate":0.09659,"max_rate":0.09707416040100247,"is_open":false,"buy_tag":null,"open_timestamp":1516000800000.0,"close_timestamp":1516003200000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.013016921998599,"open_date":"2018-01-15 08:20:00+00:00","close_date":"2018-01-15 08:55:00+00:00","open_rate":9.987e-05,"close_rate":0.00010137180451127818,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":1.501804511278178e-06,"sell_reason":"roi","initial_stop_loss_abs":8.9883e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.9883e-05,"stop_loss_ratio":0.1,"min_rate":9.987e-05,"max_rate":0.00010137180451127818,"is_open":false,"buy_tag":null,"open_timestamp":1516004400000.0,"close_timestamp":1516006500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010537752023511832,"open_date":"2018-01-15 12:10:00+00:00","close_date":"2018-01-16 02:50:00+00:00","open_rate":0.0948969,"close_rate":0.09537257368421052,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":880,"profit_ratio":0.0,"profit_abs":0.0004756736842105175,"sell_reason":"roi","initial_stop_loss_abs":0.08540721000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08540721000000001,"stop_loss_ratio":0.1,"min_rate":0.0948969,"max_rate":0.09537257368421052,"is_open":false,"buy_tag":null,"open_timestamp":1516018200000.0,"close_timestamp":1516071000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014084507042253523,"open_date":"2018-01-15 14:10:00+00:00","close_date":"2018-01-15 17:40:00+00:00","open_rate":0.071,"close_rate":0.07135588972431077,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":210,"profit_ratio":-0.0,"profit_abs":0.00035588972431077615,"sell_reason":"roi","initial_stop_loss_abs":0.0639,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0639,"stop_loss_ratio":0.1,"min_rate":0.071,"max_rate":0.07135588972431077,"is_open":false,"buy_tag":null,"open_timestamp":1516025400000.0,"close_timestamp":1516038000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021736763017766978,"open_date":"2018-01-15 14:30:00+00:00","close_date":"2018-01-15 15:10:00+00:00","open_rate":0.04600501,"close_rate":0.046235611553884705,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00023060155388470588,"sell_reason":"roi","initial_stop_loss_abs":0.041404509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041404509,"stop_loss_ratio":0.1,"min_rate":0.04600501,"max_rate":0.046235611553884705,"is_open":false,"buy_tag":null,"open_timestamp":1516026600000.0,"close_timestamp":1516029000000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.595465140919686,"open_date":"2018-01-15 18:10:00+00:00","close_date":"2018-01-15 19:25:00+00:00","open_rate":9.438e-05,"close_rate":9.485308270676693e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":4.7308270676692514e-07,"sell_reason":"roi","initial_stop_loss_abs":8.4942e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.4942e-05,"stop_loss_ratio":0.1,"min_rate":9.438e-05,"max_rate":9.485308270676693e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516039800000.0,"close_timestamp":1516044300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.032894726021471705,"open_date":"2018-01-15 18:35:00+00:00","close_date":"2018-01-15 19:15:00+00:00","open_rate":0.03040001,"close_rate":0.030552391002506264,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0001523810025062626,"sell_reason":"roi","initial_stop_loss_abs":0.027360009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027360009,"stop_loss_ratio":0.1,"min_rate":0.03040001,"max_rate":0.030552391002506264,"is_open":false,"buy_tag":null,"open_timestamp":1516041300000.0,"close_timestamp":1516043700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.13208840157615,"open_date":"2018-01-15 20:25:00+00:00","close_date":"2018-01-16 08:25:00+00:00","open_rate":5.837e-05,"close_rate":5.2533e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":720,"profit_ratio":-0.10448878,"profit_abs":-5.8369999999999985e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":5.2533e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2533e-05,"stop_loss_ratio":0.1,"min_rate":5.2533e-05,"max_rate":5.837e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516047900000.0,"close_timestamp":1516091100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021722130506560085,"open_date":"2018-01-15 20:40:00+00:00","close_date":"2018-01-15 22:00:00+00:00","open_rate":0.046036,"close_rate":0.04626675689223057,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00023075689223057277,"sell_reason":"roi","initial_stop_loss_abs":0.0414324,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0414324,"stop_loss_ratio":0.1,"min_rate":0.046036,"max_rate":0.04626675689223057,"is_open":false,"buy_tag":null,"open_timestamp":1516048800000.0,"close_timestamp":1516053600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.34861425832316545,"open_date":"2018-01-16 00:30:00+00:00","close_date":"2018-01-16 01:10:00+00:00","open_rate":0.0028685,"close_rate":0.0028828784461152877,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4378446115287727e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00258165,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00258165,"stop_loss_ratio":0.1,"min_rate":0.0028685,"max_rate":0.0028828784461152877,"is_open":false,"buy_tag":null,"open_timestamp":1516062600000.0,"close_timestamp":1516065000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014854967241083492,"open_date":"2018-01-16 01:15:00+00:00","close_date":"2018-01-16 02:35:00+00:00","open_rate":0.06731755,"close_rate":0.0676549813283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":0.0,"profit_abs":0.00033743132832080025,"sell_reason":"roi","initial_stop_loss_abs":0.060585795000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060585795000000005,"stop_loss_ratio":0.1,"min_rate":0.06731755,"max_rate":0.0676549813283208,"is_open":false,"buy_tag":null,"open_timestamp":1516065300000.0,"close_timestamp":1516070100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010848794492804754,"open_date":"2018-01-16 07:45:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":0.09217614,"close_rate":0.09263817578947368,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":0.0004620357894736804,"sell_reason":"roi","initial_stop_loss_abs":0.082958526,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082958526,"stop_loss_ratio":0.1,"min_rate":0.09217614,"max_rate":0.09263817578947368,"is_open":false,"buy_tag":null,"open_timestamp":1516088700000.0,"close_timestamp":1516092000000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06060606060606061,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:55:00+00:00","open_rate":0.0165,"close_rate":0.016913533834586467,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.00041353383458646656,"sell_reason":"roi","initial_stop_loss_abs":0.01485,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01485,"stop_loss_ratio":0.1,"min_rate":0.0165,"max_rate":0.016913533834586467,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":12.57387149503332,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":7.953e-05,"close_rate":8.311781954887218e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":3.587819548872171e-06,"sell_reason":"roi","initial_stop_loss_abs":7.157700000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.157700000000001e-05,"stop_loss_ratio":0.1,"min_rate":7.953e-05,"max_rate":8.311781954887218e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022122914915269236,"open_date":"2018-01-16 08:45:00+00:00","close_date":"2018-01-16 09:50:00+00:00","open_rate":0.045202,"close_rate":0.04542857644110275,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":0.00022657644110275071,"sell_reason":"roi","initial_stop_loss_abs":0.0406818,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0406818,"stop_loss_ratio":0.1,"min_rate":0.045202,"max_rate":0.04542857644110275,"is_open":false,"buy_tag":null,"open_timestamp":1516092300000.0,"close_timestamp":1516096200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.054878048780488,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:45:00+00:00","open_rate":5.248e-05,"close_rate":5.326917293233082e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.891729323308177e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7232e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7232e-05,"stop_loss_ratio":0.1,"min_rate":5.248e-05,"max_rate":5.326917293233082e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516095900000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03457434486802627,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:55:00+00:00","open_rate":0.02892318,"close_rate":0.02906815834586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0001449783458646603,"sell_reason":"roi","initial_stop_loss_abs":0.026030862000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.026030862000000002,"stop_loss_ratio":0.1,"min_rate":0.02892318,"max_rate":0.02906815834586466,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516096500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.38735944164405,"open_date":"2018-01-16 09:50:00+00:00","close_date":"2018-01-16 10:10:00+00:00","open_rate":5.158e-05,"close_rate":5.287273182957392e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.2927318295739246e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6422e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6422e-05,"stop_loss_ratio":0.1,"min_rate":5.158e-05,"max_rate":5.287273182957392e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516096200000.0,"close_timestamp":1516097400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035357778286929785,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:35:00+00:00","open_rate":0.02828232,"close_rate":0.02870761804511278,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042529804511277913,"sell_reason":"roi","initial_stop_loss_abs":0.025454088,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025454088,"stop_loss_ratio":0.1,"min_rate":0.02828232,"max_rate":0.02870761804511278,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516098900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022948496230938982,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:40:00+00:00","open_rate":0.04357584,"close_rate":0.044231115789473675,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0006552757894736777,"sell_reason":"roi","initial_stop_loss_abs":0.039218256,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039218256,"stop_loss_ratio":0.1,"min_rate":0.04357584,"max_rate":0.044231115789473675,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516099200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.64975755315181,"open_date":"2018-01-16 13:45:00+00:00","close_date":"2018-01-16 14:20:00+00:00","open_rate":5.362e-05,"close_rate":5.442631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":8.063157894736843e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8258e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8258e-05,"stop_loss_ratio":0.1,"min_rate":5.362e-05,"max_rate":5.442631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516110300000.0,"close_timestamp":1516112400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.86080724254998,"open_date":"2018-01-16 17:30:00+00:00","close_date":"2018-01-16 18:25:00+00:00","open_rate":5.302e-05,"close_rate":5.328576441102756e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.6576441102756397e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7718e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7718e-05,"stop_loss_ratio":0.1,"min_rate":5.302e-05,"max_rate":5.328576441102756e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516123800000.0,"close_timestamp":1516127100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010952903718828448,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:45:00+00:00","open_rate":0.09129999,"close_rate":0.09267292218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013729321804511196,"sell_reason":"roi","initial_stop_loss_abs":0.082169991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082169991,"stop_loss_ratio":0.1,"min_rate":0.09129999,"max_rate":0.09267292218045112,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516128300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":26.26050420168067,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:35:00+00:00","open_rate":3.808e-05,"close_rate":3.903438596491228e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":9.543859649122774e-07,"sell_reason":"roi","initial_stop_loss_abs":3.4272e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.4272e-05,"stop_loss_ratio":0.1,"min_rate":3.808e-05,"max_rate":3.903438596491228e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516127700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035574376772493324,"open_date":"2018-01-16 19:00:00+00:00","close_date":"2018-01-16 19:30:00+00:00","open_rate":0.02811012,"close_rate":0.028532828571428567,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042270857142856846,"sell_reason":"roi","initial_stop_loss_abs":0.025299108,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025299108,"stop_loss_ratio":0.1,"min_rate":0.02811012,"max_rate":0.028532828571428567,"is_open":false,"buy_tag":null,"open_timestamp":1516129200000.0,"close_timestamp":1516131000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.387028357567759,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":0.00258379,"close_rate":0.002325411,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.10448878,"profit_abs":-0.000258379,"sell_reason":"stop_loss","initial_stop_loss_abs":0.002325411,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002325411,"stop_loss_ratio":0.1,"min_rate":0.002325411,"max_rate":0.00258379,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516141500000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":39.07776475185619,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":2.559e-05,"close_rate":2.3031e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.10448878,"profit_abs":-2.5590000000000004e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":2.3031e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.3031e-05,"stop_loss_ratio":0.1,"min_rate":2.3031e-05,"max_rate":2.559e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516142700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":13.123359580052494,"open_date":"2018-01-16 21:35:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":7.62e-05,"close_rate":6.858e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.10448878,"profit_abs":-7.619999999999998e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":6.858e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.858e-05,"stop_loss_ratio":0.1,"min_rate":6.858e-05,"max_rate":7.62e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516138500000.0,"close_timestamp":1516141500000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4350777048780912,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:35:00+00:00","open_rate":0.00229844,"close_rate":0.002402129022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010368902255639091,"sell_reason":"roi","initial_stop_loss_abs":0.0020685960000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0020685960000000002,"stop_loss_ratio":0.1,"min_rate":0.00229844,"max_rate":0.002402129022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06622516556291391,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:40:00+00:00","open_rate":0.0151,"close_rate":0.015781203007518795,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":0.0006812030075187946,"sell_reason":"roi","initial_stop_loss_abs":0.013590000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.013590000000000001,"stop_loss_ratio":0.1,"min_rate":0.0151,"max_rate":0.015781203007518795,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.42431134269081283,"open_date":"2018-01-16 22:40:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":0.00235676,"close_rate":0.00246308,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010632000000000003,"sell_reason":"roi","initial_stop_loss_abs":0.002121084,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002121084,"stop_loss_ratio":0.1,"min_rate":0.00235676,"max_rate":0.00246308,"is_open":false,"buy_tag":null,"open_timestamp":1516142400000.0,"close_timestamp":1516142700000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01585559988076589,"open_date":"2018-01-16 22:45:00+00:00","close_date":"2018-01-16 23:05:00+00:00","open_rate":0.0630692,"close_rate":0.06464988170426066,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0015806817042606502,"sell_reason":"roi","initial_stop_loss_abs":0.056762280000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.056762280000000005,"stop_loss_ratio":0.1,"min_rate":0.0630692,"max_rate":0.06464988170426066,"is_open":false,"buy_tag":null,"open_timestamp":1516142700000.0,"close_timestamp":1516143900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":45.45454545454545,"open_date":"2018-01-16 22:50:00+00:00","close_date":"2018-01-16 22:55:00+00:00","open_rate":2.2e-05,"close_rate":2.299248120300751e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":9.924812030075114e-07,"sell_reason":"roi","initial_stop_loss_abs":1.98e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":1.98e-05,"stop_loss_ratio":0.1,"min_rate":2.2e-05,"max_rate":2.299248120300751e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516143000000.0,"close_timestamp":1516143300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.10454362685967,"open_date":"2018-01-17 03:30:00+00:00","close_date":"2018-01-17 04:00:00+00:00","open_rate":4.974e-05,"close_rate":5.048796992481203e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.479699248120277e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4766000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4766000000000005e-05,"stop_loss_ratio":0.1,"min_rate":4.974e-05,"max_rate":5.048796992481203e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516159800000.0,"close_timestamp":1516161600000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":14.068655036578503,"open_date":"2018-01-17 03:55:00+00:00","close_date":"2018-01-17 04:15:00+00:00","open_rate":7.108e-05,"close_rate":7.28614536340852e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.7814536340851996e-06,"sell_reason":"roi","initial_stop_loss_abs":6.3972e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.3972e-05,"stop_loss_ratio":0.1,"min_rate":7.108e-05,"max_rate":7.28614536340852e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516161300000.0,"close_timestamp":1516162500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.0231107002542177,"open_date":"2018-01-17 09:35:00+00:00","close_date":"2018-01-17 10:15:00+00:00","open_rate":0.04327,"close_rate":0.04348689223057644,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002168922305764362,"sell_reason":"roi","initial_stop_loss_abs":0.038943000000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.038943000000000005,"stop_loss_ratio":0.1,"min_rate":0.04327,"max_rate":0.04348689223057644,"is_open":false,"buy_tag":null,"open_timestamp":1516181700000.0,"close_timestamp":1516184100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.012007204322593,"open_date":"2018-01-17 10:20:00+00:00","close_date":"2018-01-17 17:00:00+00:00","open_rate":4.997e-05,"close_rate":5.022047619047618e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":400,"profit_ratio":-0.0,"profit_abs":2.504761904761831e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4973e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4973e-05,"stop_loss_ratio":0.1,"min_rate":4.997e-05,"max_rate":5.022047619047618e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516184400000.0,"close_timestamp":1516208400000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014626687444363738,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:25:00+00:00","open_rate":0.06836818,"close_rate":0.06871087764411027,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00034269764411026804,"sell_reason":"roi","initial_stop_loss_abs":0.061531362,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061531362,"stop_loss_ratio":0.1,"min_rate":0.06836818,"max_rate":0.06871087764411027,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516188300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.548209366391184,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:10:00+00:00","open_rate":3.63e-05,"close_rate":3.648195488721804e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.8195488721804031e-07,"sell_reason":"roi","initial_stop_loss_abs":3.2670000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2670000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.63e-05,"max_rate":3.648195488721804e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516187400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03558718861209965,"open_date":"2018-01-17 12:30:00+00:00","close_date":"2018-01-17 22:05:00+00:00","open_rate":0.0281,"close_rate":0.02824085213032581,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":575,"profit_ratio":-0.0,"profit_abs":0.0001408521303258095,"sell_reason":"roi","initial_stop_loss_abs":0.02529,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.02529,"stop_loss_ratio":0.1,"min_rate":0.0281,"max_rate":0.02824085213032581,"is_open":false,"buy_tag":null,"open_timestamp":1516192200000.0,"close_timestamp":1516226700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011559355963546878,"open_date":"2018-01-17 12:35:00+00:00","close_date":"2018-01-17 16:55:00+00:00","open_rate":0.08651001,"close_rate":0.08694364413533832,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":-0.0,"profit_abs":0.00043363413533832607,"sell_reason":"roi","initial_stop_loss_abs":0.077859009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077859009,"stop_loss_ratio":0.1,"min_rate":0.08651001,"max_rate":0.08694364413533832,"is_open":false,"buy_tag":null,"open_timestamp":1516192500000.0,"close_timestamp":1516208100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.752529735487308,"open_date":"2018-01-18 05:00:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":5.633e-05,"close_rate":5.6612355889724306e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.8235588972430847e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0697e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0697e-05,"stop_loss_ratio":0.1,"min_rate":5.633e-05,"max_rate":5.6612355889724306e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516251600000.0,"close_timestamp":1516254900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01430923457900944,"open_date":"2018-01-18 05:20:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":0.06988494,"close_rate":0.07093584135338346,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0010509013533834544,"sell_reason":"roi","initial_stop_loss_abs":0.06289644600000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06289644600000001,"stop_loss_ratio":0.1,"min_rate":0.06988494,"max_rate":0.07093584135338346,"is_open":false,"buy_tag":null,"open_timestamp":1516252800000.0,"close_timestamp":1516254900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.034265103697024,"open_date":"2018-01-18 07:35:00+00:00","close_date":"2018-01-18 08:15:00+00:00","open_rate":5.545e-05,"close_rate":5.572794486215538e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.779448621553787e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9905e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9905e-05,"stop_loss_ratio":0.1,"min_rate":5.545e-05,"max_rate":5.572794486215538e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516260900000.0,"close_timestamp":1516263300000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06121723118136401,"open_date":"2018-01-18 09:00:00+00:00","close_date":"2018-01-18 09:40:00+00:00","open_rate":0.01633527,"close_rate":0.016417151052631574,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":8.188105263157511e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014701743,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014701743,"stop_loss_ratio":0.1,"min_rate":0.01633527,"max_rate":0.016417151052631574,"is_open":false,"buy_tag":null,"open_timestamp":1516266000000.0,"close_timestamp":1516268400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3707356136045141,"open_date":"2018-01-18 16:40:00+00:00","close_date":"2018-01-18 17:20:00+00:00","open_rate":0.00269734,"close_rate":0.002710860501253133,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.3520501253133123e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002427606,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002427606,"stop_loss_ratio":0.1,"min_rate":0.00269734,"max_rate":0.002710860501253133,"is_open":false,"buy_tag":null,"open_timestamp":1516293600000.0,"close_timestamp":1516296000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.346368715083802,"open_date":"2018-01-18 18:05:00+00:00","close_date":"2018-01-18 18:30:00+00:00","open_rate":4.475e-05,"close_rate":4.587155388471177e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1215538847117757e-06,"sell_reason":"roi","initial_stop_loss_abs":4.0274999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.0274999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.475e-05,"max_rate":4.587155388471177e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516298700000.0,"close_timestamp":1516300200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":35.842293906810035,"open_date":"2018-01-18 18:25:00+00:00","close_date":"2018-01-18 18:55:00+00:00","open_rate":2.79e-05,"close_rate":2.8319548872180444e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.1954887218044365e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5110000000000002e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5110000000000002e-05,"stop_loss_ratio":0.1,"min_rate":2.79e-05,"max_rate":2.8319548872180444e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516299900000.0,"close_timestamp":1516301700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022525942001105574,"open_date":"2018-01-18 20:10:00+00:00","close_date":"2018-01-18 20:50:00+00:00","open_rate":0.04439326,"close_rate":0.04461578260651629,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00022252260651629135,"sell_reason":"roi","initial_stop_loss_abs":0.039953933999999997,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039953933999999997,"stop_loss_ratio":0.1,"min_rate":0.04439326,"max_rate":0.04461578260651629,"is_open":false,"buy_tag":null,"open_timestamp":1516306200000.0,"close_timestamp":1516308600000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.271714922048996,"open_date":"2018-01-18 21:30:00+00:00","close_date":"2018-01-19 00:35:00+00:00","open_rate":4.49e-05,"close_rate":4.51250626566416e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":185,"profit_ratio":0.0,"profit_abs":2.2506265664159932e-07,"sell_reason":"roi","initial_stop_loss_abs":4.041e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.041e-05,"stop_loss_ratio":0.1,"min_rate":4.49e-05,"max_rate":4.51250626566416e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516311000000.0,"close_timestamp":1516322100000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03502626970227671,"open_date":"2018-01-18 21:55:00+00:00","close_date":"2018-01-19 05:05:00+00:00","open_rate":0.02855,"close_rate":0.028693107769423555,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":430,"profit_ratio":-0.0,"profit_abs":0.00014310776942355607,"sell_reason":"roi","initial_stop_loss_abs":0.025695,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025695,"stop_loss_ratio":0.1,"min_rate":0.02855,"max_rate":0.028693107769423555,"is_open":false,"buy_tag":null,"open_timestamp":1516312500000.0,"close_timestamp":1516338300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.25327812284334,"open_date":"2018-01-18 22:10:00+00:00","close_date":"2018-01-18 22:50:00+00:00","open_rate":5.796e-05,"close_rate":5.8250526315789473e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.905263157894727e-07,"sell_reason":"roi","initial_stop_loss_abs":5.2164000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2164000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.796e-05,"max_rate":5.8250526315789473e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516313400000.0,"close_timestamp":1516315800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02303975994413319,"open_date":"2018-01-18 23:50:00+00:00","close_date":"2018-01-19 00:30:00+00:00","open_rate":0.04340323,"close_rate":0.04362079005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0002175600501253122,"sell_reason":"roi","initial_stop_loss_abs":0.039062907,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039062907,"stop_loss_ratio":0.1,"min_rate":0.04340323,"max_rate":0.04362079005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516319400000.0,"close_timestamp":1516321800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02244943545282195,"open_date":"2018-01-19 16:45:00+00:00","close_date":"2018-01-19 17:35:00+00:00","open_rate":0.04454455,"close_rate":0.04476783095238095,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0002232809523809512,"sell_reason":"roi","initial_stop_loss_abs":0.040090095000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040090095000000006,"stop_loss_ratio":0.1,"min_rate":0.04454455,"max_rate":0.04476783095238095,"is_open":false,"buy_tag":null,"open_timestamp":1516380300000.0,"close_timestamp":1516383300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.793594306049823,"open_date":"2018-01-19 17:15:00+00:00","close_date":"2018-01-19 19:55:00+00:00","open_rate":5.62e-05,"close_rate":5.648170426065162e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":160,"profit_ratio":-0.0,"profit_abs":2.817042606516199e-07,"sell_reason":"roi","initial_stop_loss_abs":5.058e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.058e-05,"stop_loss_ratio":0.1,"min_rate":5.62e-05,"max_rate":5.648170426065162e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382100000.0,"close_timestamp":1516391700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.046784973496194,"open_date":"2018-01-19 17:20:00+00:00","close_date":"2018-01-19 20:15:00+00:00","open_rate":4.339e-05,"close_rate":4.360749373433584e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.174937343358337e-07,"sell_reason":"roi","initial_stop_loss_abs":3.9051e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.9051e-05,"stop_loss_ratio":0.1,"min_rate":4.339e-05,"max_rate":4.360749373433584e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382400000.0,"close_timestamp":1516392900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.910802775024777,"open_date":"2018-01-20 04:45:00+00:00","close_date":"2018-01-20 17:35:00+00:00","open_rate":0.0001009,"close_rate":0.00010140576441102755,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":770,"profit_ratio":0.0,"profit_abs":5.057644110275549e-07,"sell_reason":"roi","initial_stop_loss_abs":9.081e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.081e-05,"stop_loss_ratio":0.1,"min_rate":0.0001009,"max_rate":0.00010140576441102755,"is_open":false,"buy_tag":null,"open_timestamp":1516423500000.0,"close_timestamp":1516469700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3696789338459548,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 15:15:00+00:00","open_rate":0.00270505,"close_rate":0.002718609147869674,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":625,"profit_ratio":-0.0,"profit_abs":1.3559147869673764e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002434545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002434545,"stop_loss_ratio":0.1,"min_rate":0.00270505,"max_rate":0.002718609147869674,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516461300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.033333311111125925,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 07:00:00+00:00","open_rate":0.03000002,"close_rate":0.030150396040100245,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":0.00015037604010024672,"sell_reason":"roi","initial_stop_loss_abs":0.027000018,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027000018,"stop_loss_ratio":0.1,"min_rate":0.03000002,"max_rate":0.030150396040100245,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516431600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.315018315018317,"open_date":"2018-01-20 09:00:00+00:00","close_date":"2018-01-20 09:40:00+00:00","open_rate":5.46e-05,"close_rate":5.4873684210526304e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.736842105263053e-07,"sell_reason":"roi","initial_stop_loss_abs":4.914e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.914e-05,"stop_loss_ratio":0.1,"min_rate":5.46e-05,"max_rate":5.4873684210526304e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516438800000.0,"close_timestamp":1516441200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03244412634781012,"open_date":"2018-01-20 18:25:00+00:00","close_date":"2018-01-25 03:50:00+00:00","open_rate":0.03082222,"close_rate":0.027739998,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":6325,"profit_ratio":-0.10448878,"profit_abs":-0.0030822220000000025,"sell_reason":"stop_loss","initial_stop_loss_abs":0.027739998000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027739998000000002,"stop_loss_ratio":0.1,"min_rate":0.027739998,"max_rate":0.03082222,"is_open":false,"buy_tag":null,"open_timestamp":1516472700000.0,"close_timestamp":1516852200000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011148273260677065,"open_date":"2018-01-20 22:25:00+00:00","close_date":"2018-01-20 23:15:00+00:00","open_rate":0.08969999,"close_rate":0.09014961401002504,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":0.00044962401002504593,"sell_reason":"roi","initial_stop_loss_abs":0.080729991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080729991,"stop_loss_ratio":0.1,"min_rate":0.08969999,"max_rate":0.09014961401002504,"is_open":false,"buy_tag":null,"open_timestamp":1516487100000.0,"close_timestamp":1516490100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06125570520324337,"open_date":"2018-01-21 02:50:00+00:00","close_date":"2018-01-21 14:30:00+00:00","open_rate":0.01632501,"close_rate":0.01640683962406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":700,"profit_ratio":0.0,"profit_abs":8.182962406014932e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014692509000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014692509000000001,"stop_loss_ratio":0.1,"min_rate":0.01632501,"max_rate":0.01640683962406015,"is_open":false,"buy_tag":null,"open_timestamp":1516503000000.0,"close_timestamp":1516545000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01417675579120474,"open_date":"2018-01-21 10:20:00+00:00","close_date":"2018-01-21 11:00:00+00:00","open_rate":0.070538,"close_rate":0.07089157393483708,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00035357393483707866,"sell_reason":"roi","initial_stop_loss_abs":0.0634842,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0634842,"stop_loss_ratio":0.1,"min_rate":0.070538,"max_rate":0.07089157393483708,"is_open":false,"buy_tag":null,"open_timestamp":1516530000000.0,"close_timestamp":1516532400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.864365214110546,"open_date":"2018-01-21 15:50:00+00:00","close_date":"2018-01-21 18:45:00+00:00","open_rate":5.301e-05,"close_rate":5.327571428571427e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.657142857142672e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7709e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7709e-05,"stop_loss_ratio":0.1,"min_rate":5.301e-05,"max_rate":5.327571428571427e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516549800000.0,"close_timestamp":1516560300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.284450063211125,"open_date":"2018-01-21 16:20:00+00:00","close_date":"2018-01-21 17:00:00+00:00","open_rate":3.955e-05,"close_rate":3.9748245614035085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.9824561403508552e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5595e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5595e-05,"stop_loss_ratio":0.1,"min_rate":3.955e-05,"max_rate":3.9748245614035085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516551600000.0,"close_timestamp":1516554000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38683971296493297,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:45:00+00:00","open_rate":0.00258505,"close_rate":0.002623922932330827,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.8872932330826816e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326545,"stop_loss_ratio":0.1,"min_rate":0.00258505,"max_rate":0.002623922932330827,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.621316935690498,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:55:00+00:00","open_rate":3.903e-05,"close_rate":3.922563909774435e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.9563909774435151e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5127e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5127e-05,"stop_loss_ratio":0.1,"min_rate":3.903e-05,"max_rate":3.922563909774435e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.098548510313215,"open_date":"2018-01-22 00:35:00+00:00","close_date":"2018-01-22 10:35:00+00:00","open_rate":5.236e-05,"close_rate":5.262245614035087e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":600,"profit_ratio":-0.0,"profit_abs":2.624561403508717e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7124e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7124e-05,"stop_loss_ratio":0.1,"min_rate":5.236e-05,"max_rate":5.262245614035087e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516581300000.0,"close_timestamp":1516617300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.076650420912717,"open_date":"2018-01-22 01:30:00+00:00","close_date":"2018-01-22 02:10:00+00:00","open_rate":9.028e-05,"close_rate":9.07325313283208e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":4.5253132832080657e-07,"sell_reason":"roi","initial_stop_loss_abs":8.1252e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.1252e-05,"stop_loss_ratio":0.1,"min_rate":9.028e-05,"max_rate":9.07325313283208e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516584600000.0,"close_timestamp":1516587000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3721622627465575,"open_date":"2018-01-22 12:25:00+00:00","close_date":"2018-01-22 14:35:00+00:00","open_rate":0.002687,"close_rate":0.002700468671679198,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":1.3468671679197925e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0024183000000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0024183000000000004,"stop_loss_ratio":0.1,"min_rate":0.002687,"max_rate":0.002700468671679198,"is_open":false,"buy_tag":null,"open_timestamp":1516623900000.0,"close_timestamp":1516631700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.99232245681382,"open_date":"2018-01-22 13:15:00+00:00","close_date":"2018-01-22 13:55:00+00:00","open_rate":4.168e-05,"close_rate":4.188892230576441e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.0892230576441054e-07,"sell_reason":"roi","initial_stop_loss_abs":3.7512e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.7512e-05,"stop_loss_ratio":0.1,"min_rate":4.168e-05,"max_rate":4.188892230576441e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516626900000.0,"close_timestamp":1516629300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.336583153837434,"open_date":"2018-01-22 14:00:00+00:00","close_date":"2018-01-22 14:30:00+00:00","open_rate":8.821e-05,"close_rate":8.953646616541353e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":1.326466165413529e-06,"sell_reason":"roi","initial_stop_loss_abs":7.9389e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.9389e-05,"stop_loss_ratio":0.1,"min_rate":8.821e-05,"max_rate":8.953646616541353e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516629600000.0,"close_timestamp":1516631400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.334880123743233,"open_date":"2018-01-22 15:55:00+00:00","close_date":"2018-01-22 16:40:00+00:00","open_rate":5.172e-05,"close_rate":5.1979248120300745e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.592481203007459e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6548e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6548e-05,"stop_loss_ratio":0.1,"min_rate":5.172e-05,"max_rate":5.1979248120300745e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516636500000.0,"close_timestamp":1516639200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.04692663582287,"open_date":"2018-01-22 16:05:00+00:00","close_date":"2018-01-22 16:25:00+00:00","open_rate":3.026e-05,"close_rate":3.101839598997494e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":7.5839598997494e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7234e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7234e-05,"stop_loss_ratio":0.1,"min_rate":3.026e-05,"max_rate":3.101839598997494e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516637100000.0,"close_timestamp":1516638300000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014156285390713478,"open_date":"2018-01-22 19:50:00+00:00","close_date":"2018-01-23 00:10:00+00:00","open_rate":0.07064,"close_rate":0.07099408521303258,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":0.0,"profit_abs":0.00035408521303258167,"sell_reason":"roi","initial_stop_loss_abs":0.063576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.063576,"stop_loss_ratio":0.1,"min_rate":0.07064,"max_rate":0.07099408521303258,"is_open":false,"buy_tag":null,"open_timestamp":1516650600000.0,"close_timestamp":1516666200000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06080938507725528,"open_date":"2018-01-22 21:25:00+00:00","close_date":"2018-01-22 22:05:00+00:00","open_rate":0.01644483,"close_rate":0.01652726022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.243022556390922e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014800347,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014800347,"stop_loss_ratio":0.1,"min_rate":0.01644483,"max_rate":0.01652726022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516656300000.0,"close_timestamp":1516658700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.08935580697299,"open_date":"2018-01-23 00:05:00+00:00","close_date":"2018-01-23 00:35:00+00:00","open_rate":4.331e-05,"close_rate":4.3961278195488714e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":6.512781954887175e-07,"sell_reason":"roi","initial_stop_loss_abs":3.8979e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.8979e-05,"stop_loss_ratio":0.1,"min_rate":4.331e-05,"max_rate":4.3961278195488714e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516665900000.0,"close_timestamp":1516667700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.250000000000004,"open_date":"2018-01-23 01:50:00+00:00","close_date":"2018-01-23 02:15:00+00:00","open_rate":3.2e-05,"close_rate":3.2802005012531326e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":8.020050125313278e-07,"sell_reason":"roi","initial_stop_loss_abs":2.88e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.88e-05,"stop_loss_ratio":0.1,"min_rate":3.2e-05,"max_rate":3.2802005012531326e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516672200000.0,"close_timestamp":1516673700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010907854156754155,"open_date":"2018-01-23 04:25:00+00:00","close_date":"2018-01-23 05:15:00+00:00","open_rate":0.09167706,"close_rate":0.09213659413533835,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0004595341353383492,"sell_reason":"roi","initial_stop_loss_abs":0.08250935400000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08250935400000001,"stop_loss_ratio":0.1,"min_rate":0.09167706,"max_rate":0.09213659413533835,"is_open":false,"buy_tag":null,"open_timestamp":1516681500000.0,"close_timestamp":1516684500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014440474918339115,"open_date":"2018-01-23 07:35:00+00:00","close_date":"2018-01-23 09:00:00+00:00","open_rate":0.0692498,"close_rate":0.06959691679197995,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":0.0,"profit_abs":0.0003471167919799484,"sell_reason":"roi","initial_stop_loss_abs":0.06232482,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06232482,"stop_loss_ratio":0.1,"min_rate":0.0692498,"max_rate":0.06959691679197995,"is_open":false,"buy_tag":null,"open_timestamp":1516692900000.0,"close_timestamp":1516698000000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.426775612822127,"open_date":"2018-01-23 10:50:00+00:00","close_date":"2018-01-23 13:05:00+00:00","open_rate":3.182e-05,"close_rate":3.197949874686716e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":135,"profit_ratio":0.0,"profit_abs":1.594987468671663e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8638e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8638e-05,"stop_loss_ratio":0.1,"min_rate":3.182e-05,"max_rate":3.197949874686716e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516704600000.0,"close_timestamp":1516712700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024461839530332683,"open_date":"2018-01-23 11:05:00+00:00","close_date":"2018-01-23 16:05:00+00:00","open_rate":0.04088,"close_rate":0.04108491228070175,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":300,"profit_ratio":-0.0,"profit_abs":0.0002049122807017481,"sell_reason":"roi","initial_stop_loss_abs":0.036792,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036792,"stop_loss_ratio":0.1,"min_rate":0.04088,"max_rate":0.04108491228070175,"is_open":false,"buy_tag":null,"open_timestamp":1516705500000.0,"close_timestamp":1516723500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.417475728155342,"open_date":"2018-01-23 14:55:00+00:00","close_date":"2018-01-23 15:35:00+00:00","open_rate":5.15e-05,"close_rate":5.175814536340851e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5814536340851513e-07,"sell_reason":"roi","initial_stop_loss_abs":4.635e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.635e-05,"stop_loss_ratio":0.1,"min_rate":5.15e-05,"max_rate":5.175814536340851e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516719300000.0,"close_timestamp":1516721700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011023294646713328,"open_date":"2018-01-23 16:35:00+00:00","close_date":"2018-01-24 00:05:00+00:00","open_rate":0.09071698,"close_rate":0.09117170170426064,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":450,"profit_ratio":0.0,"profit_abs":0.00045472170426064107,"sell_reason":"roi","initial_stop_loss_abs":0.081645282,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.081645282,"stop_loss_ratio":0.1,"min_rate":0.09071698,"max_rate":0.09117170170426064,"is_open":false,"buy_tag":null,"open_timestamp":1516725300000.0,"close_timestamp":1516752300000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.969309462915604,"open_date":"2018-01-23 17:25:00+00:00","close_date":"2018-01-23 18:45:00+00:00","open_rate":3.128e-05,"close_rate":3.1436791979949865e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":1.5679197994986587e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8152e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8152e-05,"stop_loss_ratio":0.1,"min_rate":3.128e-05,"max_rate":3.1436791979949865e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516728300000.0,"close_timestamp":1516733100000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.465724751439037,"open_date":"2018-01-23 20:15:00+00:00","close_date":"2018-01-23 22:00:00+00:00","open_rate":9.555e-05,"close_rate":9.602894736842104e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":4.789473684210343e-07,"sell_reason":"roi","initial_stop_loss_abs":8.5995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.5995e-05,"stop_loss_ratio":0.1,"min_rate":9.555e-05,"max_rate":9.602894736842104e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516738500000.0,"close_timestamp":1516744800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02450979791426522,"open_date":"2018-01-23 22:30:00+00:00","close_date":"2018-01-23 23:10:00+00:00","open_rate":0.04080001,"close_rate":0.0410045213283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00020451132832080554,"sell_reason":"roi","initial_stop_loss_abs":0.036720009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036720009,"stop_loss_ratio":0.1,"min_rate":0.04080001,"max_rate":0.0410045213283208,"is_open":false,"buy_tag":null,"open_timestamp":1516746600000.0,"close_timestamp":1516749000000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.368584156498162,"open_date":"2018-01-23 23:50:00+00:00","close_date":"2018-01-24 03:35:00+00:00","open_rate":5.163e-05,"close_rate":5.18887969924812e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":225,"profit_ratio":-0.0,"profit_abs":2.587969924812037e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6467e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6467e-05,"stop_loss_ratio":0.1,"min_rate":5.163e-05,"max_rate":5.18887969924812e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516751400000.0,"close_timestamp":1516764900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024747691102289384,"open_date":"2018-01-24 00:20:00+00:00","close_date":"2018-01-24 01:50:00+00:00","open_rate":0.04040781,"close_rate":0.04061035541353383,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":0.0,"profit_abs":0.0002025454135338306,"sell_reason":"roi","initial_stop_loss_abs":0.036367029,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036367029,"stop_loss_ratio":0.1,"min_rate":0.04040781,"max_rate":0.04061035541353383,"is_open":false,"buy_tag":null,"open_timestamp":1516753200000.0,"close_timestamp":1516758600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.485580670303975,"open_date":"2018-01-24 06:45:00+00:00","close_date":"2018-01-24 07:25:00+00:00","open_rate":5.132e-05,"close_rate":5.157724310776942e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5724310776941724e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6188000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6188000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.132e-05,"max_rate":5.157724310776942e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516776300000.0,"close_timestamp":1516778700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.23816852635629,"open_date":"2018-01-24 14:15:00+00:00","close_date":"2018-01-24 14:25:00+00:00","open_rate":5.198e-05,"close_rate":5.432496240601503e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":2.344962406015033e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6782e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6782e-05,"stop_loss_ratio":0.1,"min_rate":5.198e-05,"max_rate":5.432496240601503e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516803300000.0,"close_timestamp":1516803900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.74394237066143,"open_date":"2018-01-24 14:50:00+00:00","close_date":"2018-01-24 16:35:00+00:00","open_rate":3.054e-05,"close_rate":3.069308270676692e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":1.5308270676691466e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7486000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7486000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.054e-05,"max_rate":3.069308270676692e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516805400000.0,"close_timestamp":1516811700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.795638562020944,"open_date":"2018-01-24 15:10:00+00:00","close_date":"2018-01-24 16:15:00+00:00","open_rate":9.263e-05,"close_rate":9.309431077694236e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":0.0,"profit_abs":4.6431077694236234e-07,"sell_reason":"roi","initial_stop_loss_abs":8.3367e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.3367e-05,"stop_loss_ratio":0.1,"min_rate":9.263e-05,"max_rate":9.309431077694236e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516806600000.0,"close_timestamp":1516810500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.13565469713457,"open_date":"2018-01-24 22:40:00+00:00","close_date":"2018-01-24 23:25:00+00:00","open_rate":5.514e-05,"close_rate":5.54163909774436e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.7639097744360576e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9625999999999995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9625999999999995e-05,"stop_loss_ratio":0.1,"min_rate":5.514e-05,"max_rate":5.54163909774436e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516833600000.0,"close_timestamp":1516836300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":20.3210729526519,"open_date":"2018-01-25 00:50:00+00:00","close_date":"2018-01-25 01:30:00+00:00","open_rate":4.921e-05,"close_rate":4.9456666666666664e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.4666666666666543e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4289e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4289e-05,"stop_loss_ratio":0.1,"min_rate":4.921e-05,"max_rate":4.9456666666666664e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516841400000.0,"close_timestamp":1516843800000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38461538461538464,"open_date":"2018-01-25 08:15:00+00:00","close_date":"2018-01-25 12:15:00+00:00","open_rate":0.0026,"close_rate":0.002613032581453634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":240,"profit_ratio":0.0,"profit_abs":1.3032581453634e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00234,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00234,"stop_loss_ratio":0.1,"min_rate":0.0026,"max_rate":0.002613032581453634,"is_open":false,"buy_tag":null,"open_timestamp":1516868100000.0,"close_timestamp":1516882500000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03571593119825878,"open_date":"2018-01-25 10:25:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":0.02799871,"close_rate":0.028139054411027563,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":350,"profit_ratio":-0.0,"profit_abs":0.00014034441102756326,"sell_reason":"roi","initial_stop_loss_abs":0.025198839,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025198839,"stop_loss_ratio":0.1,"min_rate":0.02799871,"max_rate":0.028139054411027563,"is_open":false,"buy_tag":null,"open_timestamp":1516875900000.0,"close_timestamp":1516896900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024516401717913302,"open_date":"2018-01-25 11:00:00+00:00","close_date":"2018-01-25 11:45:00+00:00","open_rate":0.04078902,"close_rate":0.0409934762406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020445624060149575,"sell_reason":"roi","initial_stop_loss_abs":0.036710118,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036710118,"stop_loss_ratio":0.1,"min_rate":0.04078902,"max_rate":0.0409934762406015,"is_open":false,"buy_tag":null,"open_timestamp":1516878000000.0,"close_timestamp":1516880700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.602076124567475,"open_date":"2018-01-25 13:05:00+00:00","close_date":"2018-01-25 13:45:00+00:00","open_rate":2.89e-05,"close_rate":2.904486215538847e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4486215538846723e-07,"sell_reason":"roi","initial_stop_loss_abs":2.601e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.601e-05,"stop_loss_ratio":0.1,"min_rate":2.89e-05,"max_rate":2.904486215538847e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516885500000.0,"close_timestamp":1516887900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02432912439481303,"open_date":"2018-01-25 13:20:00+00:00","close_date":"2018-01-25 14:05:00+00:00","open_rate":0.041103,"close_rate":0.04130903007518797,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020603007518796984,"sell_reason":"roi","initial_stop_loss_abs":0.0369927,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0369927,"stop_loss_ratio":0.1,"min_rate":0.041103,"max_rate":0.04130903007518797,"is_open":false,"buy_tag":null,"open_timestamp":1516886400000.0,"close_timestamp":1516889100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.422991893883566,"open_date":"2018-01-25 15:45:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":5.428e-05,"close_rate":5.509624060150376e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.162406015037611e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8852000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8852000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.428e-05,"max_rate":5.509624060150376e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516895100000.0,"close_timestamp":1516896900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.47063169560399,"open_date":"2018-01-25 17:45:00+00:00","close_date":"2018-01-25 23:15:00+00:00","open_rate":5.414e-05,"close_rate":5.441137844611528e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":330,"profit_ratio":-0.0,"profit_abs":2.713784461152774e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8726e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8726e-05,"stop_loss_ratio":0.1,"min_rate":5.414e-05,"max_rate":5.441137844611528e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516902300000.0,"close_timestamp":1516922100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024150056861308878,"open_date":"2018-01-25 21:15:00+00:00","close_date":"2018-01-25 21:55:00+00:00","open_rate":0.04140777,"close_rate":0.0416153277443609,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002075577443608964,"sell_reason":"roi","initial_stop_loss_abs":0.037266993000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.037266993000000005,"stop_loss_ratio":0.1,"min_rate":0.04140777,"max_rate":0.0416153277443609,"is_open":false,"buy_tag":null,"open_timestamp":1516914900000.0,"close_timestamp":1516917300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3932224183965176,"open_date":"2018-01-26 02:05:00+00:00","close_date":"2018-01-26 02:45:00+00:00","open_rate":0.00254309,"close_rate":0.002555837318295739,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.2747318295739177e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002288781,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002288781,"stop_loss_ratio":0.1,"min_rate":0.00254309,"max_rate":0.002555837318295739,"is_open":false,"buy_tag":null,"open_timestamp":1516932300000.0,"close_timestamp":1516934700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.834849295523455,"open_date":"2018-01-26 02:55:00+00:00","close_date":"2018-01-26 15:10:00+00:00","open_rate":5.607e-05,"close_rate":5.6351052631578935e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":735,"profit_ratio":-0.0,"profit_abs":2.810526315789381e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0463e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0463e-05,"stop_loss_ratio":0.1,"min_rate":5.607e-05,"max_rate":5.6351052631578935e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516935300000.0,"close_timestamp":1516979400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.39400171784748983,"open_date":"2018-01-26 06:10:00+00:00","close_date":"2018-01-26 09:25:00+00:00","open_rate":0.00253806,"close_rate":0.0025507821052631577,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":195,"profit_ratio":0.0,"profit_abs":1.2722105263157733e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002284254,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002284254,"stop_loss_ratio":0.1,"min_rate":0.00253806,"max_rate":0.0025507821052631577,"is_open":false,"buy_tag":null,"open_timestamp":1516947000000.0,"close_timestamp":1516958700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024096385542168672,"open_date":"2018-01-26 07:25:00+00:00","close_date":"2018-01-26 09:55:00+00:00","open_rate":0.0415,"close_rate":0.04170802005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":150,"profit_ratio":-0.0,"profit_abs":0.00020802005012530989,"sell_reason":"roi","initial_stop_loss_abs":0.03735,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03735,"stop_loss_ratio":0.1,"min_rate":0.0415,"max_rate":0.04170802005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516951500000.0,"close_timestamp":1516960500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.793459875963165,"open_date":"2018-01-26 09:55:00+00:00","close_date":"2018-01-26 10:25:00+00:00","open_rate":5.321e-05,"close_rate":5.401015037593984e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.00150375939842e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7889e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7889e-05,"stop_loss_ratio":0.1,"min_rate":5.321e-05,"max_rate":5.401015037593984e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516960500000.0,"close_timestamp":1516962300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.036074437437185386,"open_date":"2018-01-26 16:05:00+00:00","close_date":"2018-01-26 16:45:00+00:00","open_rate":0.02772046,"close_rate":0.02785940967418546,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013894967418546025,"sell_reason":"roi","initial_stop_loss_abs":0.024948414,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024948414,"stop_loss_ratio":0.1,"min_rate":0.02772046,"max_rate":0.02785940967418546,"is_open":false,"buy_tag":null,"open_timestamp":1516982700000.0,"close_timestamp":1516985100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010569326272036914,"open_date":"2018-01-26 23:35:00+00:00","close_date":"2018-01-27 00:15:00+00:00","open_rate":0.09461341,"close_rate":0.09508766268170424,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00047425268170424306,"sell_reason":"roi","initial_stop_loss_abs":0.085152069,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085152069,"stop_loss_ratio":0.1,"min_rate":0.09461341,"max_rate":0.09508766268170424,"is_open":false,"buy_tag":null,"open_timestamp":1517009700000.0,"close_timestamp":1517012100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":17.809439002671414,"open_date":"2018-01-27 00:35:00+00:00","close_date":"2018-01-27 01:30:00+00:00","open_rate":5.615e-05,"close_rate":5.643145363408521e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.814536340852038e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0535e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0535e-05,"stop_loss_ratio":0.1,"min_rate":5.615e-05,"max_rate":5.643145363408521e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013300000.0,"close_timestamp":1517016600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.998560115190784,"open_date":"2018-01-27 00:45:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":5.556e-05,"close_rate":5.144e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4560,"profit_ratio":-0.07877175,"profit_abs":-4.120000000000001e-06,"sell_reason":"force_sell","initial_stop_loss_abs":5.0004000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0004000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.144e-05,"max_rate":5.556e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013900000.0,"close_timestamp":1517287500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492751522789634,"open_date":"2018-01-27 02:30:00+00:00","close_date":"2018-01-27 11:25:00+00:00","open_rate":0.06900001,"close_rate":0.06934587471177944,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":535,"profit_ratio":-0.0,"profit_abs":0.0003458647117794422,"sell_reason":"roi","initial_stop_loss_abs":0.062100009000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.062100009000000005,"stop_loss_ratio":0.1,"min_rate":0.06900001,"max_rate":0.06934587471177944,"is_open":false,"buy_tag":null,"open_timestamp":1517020200000.0,"close_timestamp":1517052300000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010582027378879437,"open_date":"2018-01-27 06:25:00+00:00","close_date":"2018-01-27 07:05:00+00:00","open_rate":0.09449985,"close_rate":0.0949735334586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004736834586466093,"sell_reason":"roi","initial_stop_loss_abs":0.085049865,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085049865,"stop_loss_ratio":0.1,"min_rate":0.09449985,"max_rate":0.0949735334586466,"is_open":false,"buy_tag":null,"open_timestamp":1517034300000.0,"close_timestamp":1517036700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02434885085598385,"open_date":"2018-01-27 09:40:00+00:00","close_date":"2018-01-30 04:40:00+00:00","open_rate":0.0410697,"close_rate":0.03928809,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4020,"profit_ratio":-0.04815133,"profit_abs":-0.001781610000000003,"sell_reason":"force_sell","initial_stop_loss_abs":0.03696273,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03696273,"stop_loss_ratio":0.1,"min_rate":0.03928809,"max_rate":0.0410697,"is_open":false,"buy_tag":null,"open_timestamp":1517046000000.0,"close_timestamp":1517287200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03508771929824561,"open_date":"2018-01-27 11:45:00+00:00","close_date":"2018-01-27 12:30:00+00:00","open_rate":0.0285,"close_rate":0.02864285714285714,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00014285714285713902,"sell_reason":"roi","initial_stop_loss_abs":0.025650000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025650000000000003,"stop_loss_ratio":0.1,"min_rate":0.0285,"max_rate":0.02864285714285714,"is_open":false,"buy_tag":null,"open_timestamp":1517053500000.0,"close_timestamp":1517056200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.034887307020861215,"open_date":"2018-01-27 12:35:00+00:00","close_date":"2018-01-27 15:25:00+00:00","open_rate":0.02866372,"close_rate":0.02880739779448621,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":170,"profit_ratio":-0.0,"profit_abs":0.00014367779448621124,"sell_reason":"roi","initial_stop_loss_abs":0.025797348,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025797348,"stop_loss_ratio":0.1,"min_rate":0.02866372,"max_rate":0.02880739779448621,"is_open":false,"buy_tag":null,"open_timestamp":1517056500000.0,"close_timestamp":1517066700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010484268355332824,"open_date":"2018-01-27 15:50:00+00:00","close_date":"2018-01-27 16:50:00+00:00","open_rate":0.095381,"close_rate":0.09585910025062656,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.00047810025062657024,"sell_reason":"roi","initial_stop_loss_abs":0.0858429,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0858429,"stop_loss_ratio":0.1,"min_rate":0.095381,"max_rate":0.09585910025062656,"is_open":false,"buy_tag":null,"open_timestamp":1517068200000.0,"close_timestamp":1517071800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014794886650455417,"open_date":"2018-01-27 17:05:00+00:00","close_date":"2018-01-27 17:45:00+00:00","open_rate":0.06759092,"close_rate":0.06792972160401002,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00033880160401002224,"sell_reason":"roi","initial_stop_loss_abs":0.060831828,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060831828,"stop_loss_ratio":0.1,"min_rate":0.06759092,"max_rate":0.06792972160401002,"is_open":false,"buy_tag":null,"open_timestamp":1517072700000.0,"close_timestamp":1517075100000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38684569885609726,"open_date":"2018-01-27 23:40:00+00:00","close_date":"2018-01-28 01:05:00+00:00","open_rate":0.00258501,"close_rate":0.002597967443609022,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":-0.0,"profit_abs":1.2957443609021985e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326509,"stop_loss_ratio":0.1,"min_rate":0.00258501,"max_rate":0.002597967443609022,"is_open":false,"buy_tag":null,"open_timestamp":1517096400000.0,"close_timestamp":1517101500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014928710926711672,"open_date":"2018-01-28 02:25:00+00:00","close_date":"2018-01-28 08:10:00+00:00","open_rate":0.06698502,"close_rate":0.0673207845112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":345,"profit_ratio":-0.0,"profit_abs":0.00033576451127818874,"sell_reason":"roi","initial_stop_loss_abs":0.060286518000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060286518000000004,"stop_loss_ratio":0.1,"min_rate":0.06698502,"max_rate":0.0673207845112782,"is_open":false,"buy_tag":null,"open_timestamp":1517106300000.0,"close_timestamp":1517127000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014767187899175548,"open_date":"2018-01-28 10:25:00+00:00","close_date":"2018-01-28 16:30:00+00:00","open_rate":0.0677177,"close_rate":0.06805713709273183,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":365,"profit_ratio":-0.0,"profit_abs":0.0003394370927318202,"sell_reason":"roi","initial_stop_loss_abs":0.06094593000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06094593000000001,"stop_loss_ratio":0.1,"min_rate":0.0677177,"max_rate":0.06805713709273183,"is_open":false,"buy_tag":null,"open_timestamp":1517135100000.0,"close_timestamp":1517157000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":19.175455417066157,"open_date":"2018-01-28 20:35:00+00:00","close_date":"2018-01-28 21:35:00+00:00","open_rate":5.215e-05,"close_rate":5.2411403508771925e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":0.0,"profit_abs":2.6140350877192417e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6935000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6935000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.215e-05,"max_rate":5.2411403508771925e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517171700000.0,"close_timestamp":1517175300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.36521808998243305,"open_date":"2018-01-28 22:00:00+00:00","close_date":"2018-01-28 22:30:00+00:00","open_rate":0.00273809,"close_rate":0.002779264285714285,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.117428571428529e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002464281,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002464281,"stop_loss_ratio":0.1,"min_rate":0.00273809,"max_rate":0.002779264285714285,"is_open":false,"buy_tag":null,"open_timestamp":1517176800000.0,"close_timestamp":1517178600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3641236272539253,"open_date":"2018-01-29 00:00:00+00:00","close_date":"2018-01-29 00:30:00+00:00","open_rate":0.00274632,"close_rate":0.002787618045112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.129804511278194e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002471688,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002471688,"stop_loss_ratio":0.1,"min_rate":0.00274632,"max_rate":0.002787618045112782,"is_open":false,"buy_tag":null,"open_timestamp":1517184000000.0,"close_timestamp":1517185800000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.061634117689115045,"open_date":"2018-01-29 02:15:00+00:00","close_date":"2018-01-29 03:00:00+00:00","open_rate":0.01622478,"close_rate":0.016306107218045113,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":8.132721804511231e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014602302000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014602302000000001,"stop_loss_ratio":0.1,"min_rate":0.01622478,"max_rate":0.016306107218045113,"is_open":false,"buy_tag":null,"open_timestamp":1517192100000.0,"close_timestamp":1517194800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492753623188404,"open_date":"2018-01-29 03:05:00+00:00","close_date":"2018-01-29 03:45:00+00:00","open_rate":0.069,"close_rate":0.06934586466165413,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00034586466165412166,"sell_reason":"roi","initial_stop_loss_abs":0.06210000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06210000000000001,"stop_loss_ratio":0.1,"min_rate":0.069,"max_rate":0.06934586466165413,"is_open":false,"buy_tag":null,"open_timestamp":1517195100000.0,"close_timestamp":1517197500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.42204454597373,"open_date":"2018-01-29 05:20:00+00:00","close_date":"2018-01-29 06:55:00+00:00","open_rate":8.755e-05,"close_rate":8.798884711779448e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":95,"profit_ratio":-0.0,"profit_abs":4.3884711779447504e-07,"sell_reason":"roi","initial_stop_loss_abs":7.879500000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.879500000000001e-05,"stop_loss_ratio":0.1,"min_rate":8.755e-05,"max_rate":8.798884711779448e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517203200000.0,"close_timestamp":1517208900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014650376815016871,"open_date":"2018-01-29 07:00:00+00:00","close_date":"2018-01-29 19:25:00+00:00","open_rate":0.06825763,"close_rate":0.06859977350877192,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":745,"profit_ratio":-0.0,"profit_abs":0.00034214350877191657,"sell_reason":"roi","initial_stop_loss_abs":0.061431867,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061431867,"stop_loss_ratio":0.1,"min_rate":0.06825763,"max_rate":0.06859977350877192,"is_open":false,"buy_tag":null,"open_timestamp":1517209200000.0,"close_timestamp":1517253900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014894490408841846,"open_date":"2018-01-29 19:45:00+00:00","close_date":"2018-01-29 20:25:00+00:00","open_rate":0.06713892,"close_rate":0.06747545593984962,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0003365359398496137,"sell_reason":"roi","initial_stop_loss_abs":0.060425028000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060425028000000006,"stop_loss_ratio":0.1,"min_rate":0.06713892,"max_rate":0.06747545593984962,"is_open":false,"buy_tag":null,"open_timestamp":1517255100000.0,"close_timestamp":1517257500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.193194537721066,"open_date":"2018-01-29 23:30:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":8.934e-05,"close_rate":8.8e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":315,"profit_ratio":-0.0199116,"profit_abs":-1.3399999999999973e-06,"sell_reason":"force_sell","initial_stop_loss_abs":8.0406e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.0406e-05,"stop_loss_ratio":0.1,"min_rate":8.8e-05,"max_rate":8.934e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517268600000.0,"close_timestamp":1517287500000.0}],"locks":[],"best_pair":{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},"worst_pair":{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},"results_per_pair":[{"key":"ETH/BTC","trades":21,"profit_mean":0.0009500057142857142,"profit_mean_pct":0.09500057142857142,"profit_sum":0.01995012,"profit_sum_pct":2.0,"profit_total_abs":0.011505731278195264,"profit_total":1.1505731278195264,"profit_total_pct":115.06,"duration_avg":"2:17:00","wins":21,"draws":0,"losses":0},{"key":"DASH/BTC","trades":16,"profit_mean":0.0018703237499999997,"profit_mean_pct":0.18703237499999997,"profit_sum":0.029925179999999996,"profit_sum_pct":2.99,"profit_total_abs":0.007475052681704161,"profit_total":0.7475052681704161,"profit_total_pct":74.75,"duration_avg":"3:03:00","wins":16,"draws":0,"losses":0},{"key":"ZEC/BTC","trades":21,"profit_mean":-0.00039290904761904774,"profit_mean_pct":-0.03929090476190478,"profit_sum":-0.008251090000000003,"profit_sum_pct":-0.83,"profit_total_abs":0.004452605639097655,"profit_total":0.4452605639097655,"profit_total_pct":44.53,"duration_avg":"4:17:00","wins":20,"draws":0,"losses":1},{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},{"key":"ETC/BTC","trades":20,"profit_mean":0.0022568569999999997,"profit_mean_pct":0.22568569999999996,"profit_sum":0.04513713999999999,"profit_sum_pct":4.51,"profit_total_abs":0.00036538235338345404,"profit_total":0.0365382353383454,"profit_total_pct":3.65,"duration_avg":"1:45:00","wins":19,"draws":0,"losses":1},{"key":"TRX/BTC","trades":15,"profit_mean":0.0023467073333333323,"profit_mean_pct":0.23467073333333321,"profit_sum":0.035200609999999986,"profit_sum_pct":3.52,"profit_total_abs":1.1329523809523682e-05,"profit_total":0.0011329523809523682,"profit_total_pct":0.11,"duration_avg":"2:28:00","wins":13,"draws":0,"losses":2},{"key":"XLM/BTC","trades":21,"profit_mean":0.0026243899999999994,"profit_mean_pct":0.2624389999999999,"profit_sum":0.05511218999999999,"profit_sum_pct":5.51,"profit_total_abs":7.340779448621465e-06,"profit_total":0.0007340779448621465,"profit_total_pct":0.07,"duration_avg":"3:21:00","wins":20,"draws":0,"losses":1},{"key":"ADA/BTC","trades":29,"profit_mean":-0.0011598141379310352,"profit_mean_pct":-0.11598141379310352,"profit_sum":-0.03363461000000002,"profit_sum_pct":-3.36,"profit_total_abs":4.916634085212862e-06,"profit_total":0.0004916634085212862,"profit_total_pct":0.05,"duration_avg":"5:35:00","wins":27,"draws":0,"losses":2},{"key":"NXT/BTC","trades":12,"profit_mean":-0.0012261025000000006,"profit_mean_pct":-0.12261025000000006,"profit_sum":-0.014713230000000008,"profit_sum_pct":-1.47,"profit_total_abs":1.4774411027568458e-06,"profit_total":0.00014774411027568458,"profit_total_pct":0.01,"duration_avg":"0:57:00","wins":11,"draws":0,"losses":1},{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"results_per_buy_tag":[{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"sell_reason_summary":[{"sell_reason":"roi","trades":170,"wins":170,"draws":0,"losses":0,"profit_mean":0.005398268352941177,"profit_mean_pct":0.54,"profit_sum":0.91770562,"profit_sum_pct":91.77,"profit_total_abs":0.031232837493733862,"profit_total":0.30590187333333335,"profit_total_pct":30.59},{"sell_reason":"stop_loss","trades":6,"wins":0,"draws":0,"losses":6,"profit_mean":-0.10448878000000002,"profit_mean_pct":-10.45,"profit_sum":-0.6269326800000001,"profit_sum_pct":-62.69,"profit_total_abs":-0.0033602680000000026,"profit_total":-0.20897756000000003,"profit_total_pct":-20.9},{"sell_reason":"force_sell","trades":3,"wins":0,"draws":0,"losses":3,"profit_mean":-0.04894489333333333,"profit_mean_pct":-4.89,"profit_sum":-0.14683468,"profit_sum_pct":-14.68,"profit_total_abs":-0.001787070000000003,"profit_total":-0.04894489333333333,"profit_total_pct":-4.89}],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":179,"total_volume":0.17900000000000005,"avg_stake_amount":0.0010000000000000002,"profit_mean":0.0008041243575418989,"profit_median":0.0,"profit_total":2.6085499493733857,"profit_total_abs":0.026085499493733857,"backtest_start":"2018-01-10 07:15:00","backtest_start_ts":1515568500000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":"2020-10-01 18:00:00+00:00","backtest_run_end_ts":"2020-10-01 18:01:00+00:00","trades_per_day":9.42,"market_change":1.22,"pairlist":[],"stake_amount":0.001,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":0.01,"dry_run_wallet":0.01,"final_balance":0.03608549949373386,"rejected_signals":0,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timeframe_detail":"","timerange":"","enable_protections":false,"strategy_name":"StrategyTestV2","stoploss":0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":false,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.17955111999999998,"backtest_worst_day":-0.14683468,"backtest_best_day_abs":0.0071570099,"backtest_worst_day_abs":-0.0023093218,"winning_days":19,"draw_days":0,"losing_days":2,"daily_profit":[["2018-01-10",0.0025815306],["2018-01-11",0.0049356655],["2018-01-12",0.0006395218],["2018-01-13",0.0002574589],["2018-01-14",0.0010443828],["2018-01-15",0.0024030209],["2018-01-16",0.0071570099],["2018-01-17",0.001137038],["2018-01-18",0.0013712174],["2018-01-19",0.000584673],["2018-01-20",0.0006143386],["2018-01-21",0.0004749361],["2018-01-22",9.91669e-05],["2018-01-23",0.0015726664],["2018-01-24",0.0006610219],["2018-01-25",-0.0023093218],["2018-01-26",0.0003735204],["2018-01-27",0.0023975191],["2018-01-28",0.0007295947],["2018-01-29",0.0011476082],["2018-01-30",-0.00178707]],"wins":48,"losses":9,"draws":122,"holding_avg":"3:40:00","holding_avg_s":13200.0,"winner_holding_avg":"0:24:00","winner_holding_avg_s":1440.0,"loser_holding_avg":"1 day, 5:57:00","loser_holding_avg_s":107820.0,"max_drawdown":0.21142322000000008,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":0.0030822220000000025,"drawdown_start":"2018-01-25 01:30:00","drawdown_start_ts":1516843800000.0,"drawdown_end":"2018-01-25 03:50:00","drawdown_end_ts":1516852200000.0,"max_drawdown_low":0.02245167355388436,"max_drawdown_high":0.025533895553884363,"csum_min":0.01000434887218045,"csum_max":0.03608683949373386},"TestStrategy":{"trades":[{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.37344398340249,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:20:00+00:00","open_rate":9.64e-05,"close_rate":0.00010074887218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":4.348872180451118e-06,"sell_reason":"roi","initial_stop_loss_abs":8.676e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.676e-05,"stop_loss_ratio":0.1,"min_rate":9.64e-05,"max_rate":0.00010074887218045112,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515568800000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":21.026072329688816,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:30:00+00:00","open_rate":4.756e-05,"close_rate":4.9705563909774425e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":2.1455639097744267e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2804e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2804e-05,"stop_loss_ratio":0.1,"min_rate":4.756e-05,"max_rate":4.9705563909774425e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515569400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.94908655286014,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:35:00+00:00","open_rate":3.339e-05,"close_rate":3.489631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":1.506315789473681e-06,"sell_reason":"roi","initial_stop_loss_abs":3.0050999999999997e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0050999999999997e-05,"stop_loss_ratio":0.1,"min_rate":3.339e-05,"max_rate":3.489631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515569700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.313531353135314,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:40:00+00:00","open_rate":9.696e-05,"close_rate":0.00010133413533834584,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":4.3741353383458455e-06,"sell_reason":"roi","initial_stop_loss_abs":8.7264e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.7264e-05,"stop_loss_ratio":0.1,"min_rate":9.696e-05,"max_rate":0.00010133413533834584,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515570000000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010604453870625663,"open_date":"2018-01-10 07:35:00+00:00","close_date":"2018-01-10 08:35:00+00:00","open_rate":0.0943,"close_rate":0.09477268170426063,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.0004726817042606385,"sell_reason":"roi","initial_stop_loss_abs":0.08487,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08487,"stop_loss_ratio":0.1,"min_rate":0.0943,"max_rate":0.09477268170426063,"is_open":false,"buy_tag":null,"open_timestamp":1515569700000.0,"close_timestamp":1515573300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03677001860930642,"open_date":"2018-01-10 07:40:00+00:00","close_date":"2018-01-10 08:10:00+00:00","open_rate":0.02719607,"close_rate":0.02760503345864661,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00040896345864661204,"sell_reason":"roi","initial_stop_loss_abs":0.024476463,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024476463,"stop_loss_ratio":0.1,"min_rate":0.02719607,"max_rate":0.02760503345864661,"is_open":false,"buy_tag":null,"open_timestamp":1515570000000.0,"close_timestamp":1515571800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021575196463739,"open_date":"2018-01-10 08:15:00+00:00","close_date":"2018-01-10 09:55:00+00:00","open_rate":0.04634952,"close_rate":0.046581848421052625,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":100,"profit_ratio":0.0,"profit_abs":0.0002323284210526272,"sell_reason":"roi","initial_stop_loss_abs":0.041714568,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041714568,"stop_loss_ratio":0.1,"min_rate":0.04634952,"max_rate":0.046581848421052625,"is_open":false,"buy_tag":null,"open_timestamp":1515572100000.0,"close_timestamp":1515578100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.615786040443574,"open_date":"2018-01-10 14:45:00+00:00","close_date":"2018-01-10 15:50:00+00:00","open_rate":3.066e-05,"close_rate":3.081368421052631e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":1.5368421052630647e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7594e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7594e-05,"stop_loss_ratio":0.1,"min_rate":3.066e-05,"max_rate":3.081368421052631e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515595500000.0,"close_timestamp":1515599400000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.05917194776300452,"open_date":"2018-01-10 16:35:00+00:00","close_date":"2018-01-10 17:15:00+00:00","open_rate":0.0168999,"close_rate":0.016984611278195488,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.471127819548868e-05,"sell_reason":"roi","initial_stop_loss_abs":0.01520991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01520991,"stop_loss_ratio":0.1,"min_rate":0.0168999,"max_rate":0.016984611278195488,"is_open":false,"buy_tag":null,"open_timestamp":1515602100000.0,"close_timestamp":1515604500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010949822656672253,"open_date":"2018-01-10 16:40:00+00:00","close_date":"2018-01-10 17:20:00+00:00","open_rate":0.09132568,"close_rate":0.0917834528320802,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004577728320801916,"sell_reason":"roi","initial_stop_loss_abs":0.08219311200000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08219311200000001,"stop_loss_ratio":0.1,"min_rate":0.09132568,"max_rate":0.0917834528320802,"is_open":false,"buy_tag":null,"open_timestamp":1515602400000.0,"close_timestamp":1515604800000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011238476768326556,"open_date":"2018-01-10 18:50:00+00:00","close_date":"2018-01-10 19:45:00+00:00","open_rate":0.08898003,"close_rate":0.08942604518796991,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00044601518796991146,"sell_reason":"roi","initial_stop_loss_abs":0.080082027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080082027,"stop_loss_ratio":0.1,"min_rate":0.08898003,"max_rate":0.08942604518796991,"is_open":false,"buy_tag":null,"open_timestamp":1515610200000.0,"close_timestamp":1515613500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011682232072680307,"open_date":"2018-01-10 22:15:00+00:00","close_date":"2018-01-10 23:00:00+00:00","open_rate":0.08560008,"close_rate":0.08602915308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":0.00042907308270676014,"sell_reason":"roi","initial_stop_loss_abs":0.077040072,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077040072,"stop_loss_ratio":0.1,"min_rate":0.08560008,"max_rate":0.08602915308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515622500000.0,"close_timestamp":1515625200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4014726015023105,"open_date":"2018-01-10 22:50:00+00:00","close_date":"2018-01-10 23:20:00+00:00","open_rate":0.00249083,"close_rate":0.0025282860902255634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.745609022556351e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002241747,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002241747,"stop_loss_ratio":0.1,"min_rate":0.00249083,"max_rate":0.0025282860902255634,"is_open":false,"buy_tag":null,"open_timestamp":1515624600000.0,"close_timestamp":1515626400000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.090668431502316,"open_date":"2018-01-10 23:15:00+00:00","close_date":"2018-01-11 00:15:00+00:00","open_rate":3.022e-05,"close_rate":3.037147869674185e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":1.5147869674185174e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7198e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7198e-05,"stop_loss_ratio":0.1,"min_rate":3.022e-05,"max_rate":3.037147869674185e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515626100000.0,"close_timestamp":1515629700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.41034058268362744,"open_date":"2018-01-10 23:40:00+00:00","close_date":"2018-01-11 00:05:00+00:00","open_rate":0.002437,"close_rate":0.0024980776942355883,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":6.107769423558838e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0021933,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0021933,"stop_loss_ratio":0.1,"min_rate":0.002437,"max_rate":0.0024980776942355883,"is_open":false,"buy_tag":null,"open_timestamp":1515627600000.0,"close_timestamp":1515629100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02095643931654345,"open_date":"2018-01-11 00:00:00+00:00","close_date":"2018-01-11 00:35:00+00:00","open_rate":0.04771803,"close_rate":0.04843559436090225,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0007175643609022495,"sell_reason":"roi","initial_stop_loss_abs":0.042946227000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.042946227000000003,"stop_loss_ratio":0.1,"min_rate":0.04771803,"max_rate":0.04843559436090225,"is_open":false,"buy_tag":null,"open_timestamp":1515628800000.0,"close_timestamp":1515630900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.389756231169542,"open_date":"2018-01-11 03:40:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":3.651e-05,"close_rate":3.2859000000000005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.10448878,"profit_abs":-3.650999999999996e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":3.2859000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2859000000000005e-05,"stop_loss_ratio":0.1,"min_rate":3.2859000000000005e-05,"max_rate":3.651e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515642000000.0,"close_timestamp":1515644700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011332594070446804,"open_date":"2018-01-11 03:55:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":0.08824105,"close_rate":0.08956798308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013269330827067605,"sell_reason":"roi","initial_stop_loss_abs":0.079416945,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079416945,"stop_loss_ratio":0.1,"min_rate":0.08824105,"max_rate":0.08956798308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515642900000.0,"close_timestamp":1515644700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.411522633744856,"open_date":"2018-01-11 04:00:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":0.00243,"close_rate":0.002442180451127819,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":1.2180451127819219e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002187,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002187,"stop_loss_ratio":0.1,"min_rate":0.00243,"max_rate":0.002442180451127819,"is_open":false,"buy_tag":null,"open_timestamp":1515643200000.0,"close_timestamp":1515646200000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022001890402423376,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:55:00+00:00","open_rate":0.04545064,"close_rate":0.046589753784461146,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":0.001139113784461146,"sell_reason":"roi","initial_stop_loss_abs":0.040905576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040905576,"stop_loss_ratio":0.1,"min_rate":0.04545064,"max_rate":0.046589753784461146,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.655990510083036,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":3.372e-05,"close_rate":3.456511278195488e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":8.4511278195488e-07,"sell_reason":"roi","initial_stop_loss_abs":3.0348e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0348e-05,"stop_loss_ratio":0.1,"min_rate":3.372e-05,"max_rate":3.456511278195488e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037821482602118005,"open_date":"2018-01-11 04:55:00+00:00","close_date":"2018-01-11 05:15:00+00:00","open_rate":0.02644,"close_rate":0.02710265664160401,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0006626566416040071,"sell_reason":"roi","initial_stop_loss_abs":0.023796,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023796,"stop_loss_ratio":0.1,"min_rate":0.02644,"max_rate":0.02710265664160401,"is_open":false,"buy_tag":null,"open_timestamp":1515646500000.0,"close_timestamp":1515647700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011348161597821153,"open_date":"2018-01-11 11:20:00+00:00","close_date":"2018-01-11 12:00:00+00:00","open_rate":0.08812,"close_rate":0.08856170426065162,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004417042606516125,"sell_reason":"roi","initial_stop_loss_abs":0.079308,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079308,"stop_loss_ratio":0.1,"min_rate":0.08812,"max_rate":0.08856170426065162,"is_open":false,"buy_tag":null,"open_timestamp":1515669600000.0,"close_timestamp":1515672000000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037263696923919086,"open_date":"2018-01-11 11:35:00+00:00","close_date":"2018-01-11 12:15:00+00:00","open_rate":0.02683577,"close_rate":0.026970285137844607,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013451513784460897,"sell_reason":"roi","initial_stop_loss_abs":0.024152193,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024152193,"stop_loss_ratio":0.1,"min_rate":0.02683577,"max_rate":0.026970285137844607,"is_open":false,"buy_tag":null,"open_timestamp":1515670500000.0,"close_timestamp":1515672900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.329335230737954,"open_date":"2018-01-11 14:00:00+00:00","close_date":"2018-01-11 14:25:00+00:00","open_rate":4.919e-05,"close_rate":5.04228320802005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.232832080200495e-06,"sell_reason":"roi","initial_stop_loss_abs":4.4271000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4271000000000004e-05,"stop_loss_ratio":0.1,"min_rate":4.919e-05,"max_rate":5.04228320802005e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515679200000.0,"close_timestamp":1515680700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.01138317402960718,"open_date":"2018-01-11 19:25:00+00:00","close_date":"2018-01-11 20:35:00+00:00","open_rate":0.08784896,"close_rate":0.08828930566416039,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":70,"profit_ratio":-0.0,"profit_abs":0.0004403456641603881,"sell_reason":"roi","initial_stop_loss_abs":0.079064064,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079064064,"stop_loss_ratio":0.1,"min_rate":0.08784896,"max_rate":0.08828930566416039,"is_open":false,"buy_tag":null,"open_timestamp":1515698700000.0,"close_timestamp":1515702900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.58863858961802,"open_date":"2018-01-11 22:35:00+00:00","close_date":"2018-01-11 23:30:00+00:00","open_rate":5.105e-05,"close_rate":5.130588972431077e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.558897243107704e-07,"sell_reason":"roi","initial_stop_loss_abs":4.5945e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.5945e-05,"stop_loss_ratio":0.1,"min_rate":5.105e-05,"max_rate":5.130588972431077e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515710100000.0,"close_timestamp":1515713400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.252525252525253,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:25:00+00:00","open_rate":3.96e-05,"close_rate":4.019548872180451e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":5.954887218045116e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5640000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5640000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.96e-05,"max_rate":4.019548872180451e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.66204506065858,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:35:00+00:00","open_rate":2.885e-05,"close_rate":2.899461152882205e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4461152882205115e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5965e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5965e-05,"stop_loss_ratio":0.1,"min_rate":2.885e-05,"max_rate":2.899461152882205e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03780718336483932,"open_date":"2018-01-11 23:30:00+00:00","close_date":"2018-01-12 00:05:00+00:00","open_rate":0.02645,"close_rate":0.026847744360902256,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0003977443609022545,"sell_reason":"roi","initial_stop_loss_abs":0.023805000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023805000000000003,"stop_loss_ratio":0.1,"min_rate":0.02645,"max_rate":0.026847744360902256,"is_open":false,"buy_tag":null,"open_timestamp":1515713400000.0,"close_timestamp":1515715500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020833333333333332,"open_date":"2018-01-11 23:55:00+00:00","close_date":"2018-01-12 01:15:00+00:00","open_rate":0.048,"close_rate":0.04824060150375939,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00024060150375938838,"sell_reason":"roi","initial_stop_loss_abs":0.0432,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0432,"stop_loss_ratio":0.1,"min_rate":0.048,"max_rate":0.04824060150375939,"is_open":false,"buy_tag":null,"open_timestamp":1515714900000.0,"close_timestamp":1515719700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.31287297527707,"open_date":"2018-01-12 21:15:00+00:00","close_date":"2018-01-12 21:40:00+00:00","open_rate":4.692e-05,"close_rate":4.809593984962405e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1759398496240516e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2227999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2227999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.692e-05,"max_rate":4.809593984962405e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515791700000.0,"close_timestamp":1515793200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38915654211062944,"open_date":"2018-01-13 00:55:00+00:00","close_date":"2018-01-13 06:20:00+00:00","open_rate":0.00256966,"close_rate":0.0025825405012531327,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":325,"profit_ratio":-0.0,"profit_abs":1.2880501253132587e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002312694,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002312694,"stop_loss_ratio":0.1,"min_rate":0.00256966,"max_rate":0.0025825405012531327,"is_open":false,"buy_tag":null,"open_timestamp":1515804900000.0,"close_timestamp":1515824400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":15.96933886937081,"open_date":"2018-01-13 10:55:00+00:00","close_date":"2018-01-13 11:35:00+00:00","open_rate":6.262e-05,"close_rate":6.293388471177944e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":3.138847117794446e-07,"sell_reason":"roi","initial_stop_loss_abs":5.6358e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.6358e-05,"stop_loss_ratio":0.1,"min_rate":6.262e-05,"max_rate":6.293388471177944e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515840900000.0,"close_timestamp":1515843300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.141649048625794,"open_date":"2018-01-13 13:05:00+00:00","close_date":"2018-01-15 14:10:00+00:00","open_rate":4.73e-05,"close_rate":4.753709273182957e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":2945,"profit_ratio":0.0,"profit_abs":2.3709273182957117e-07,"sell_reason":"roi","initial_stop_loss_abs":4.257e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.257e-05,"stop_loss_ratio":0.1,"min_rate":4.73e-05,"max_rate":4.753709273182957e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515848700000.0,"close_timestamp":1516025400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.49348507339601,"open_date":"2018-01-13 13:30:00+00:00","close_date":"2018-01-13 14:45:00+00:00","open_rate":6.063e-05,"close_rate":6.0933909774436085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":3.039097744360846e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4567e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4567e-05,"stop_loss_ratio":0.1,"min_rate":6.063e-05,"max_rate":6.0933909774436085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515850200000.0,"close_timestamp":1515854700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.023641941887746,"open_date":"2018-01-13 13:40:00+00:00","close_date":"2018-01-13 23:30:00+00:00","open_rate":0.00011082,"close_rate":0.00011137548872180448,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":590,"profit_ratio":-0.0,"profit_abs":5.554887218044781e-07,"sell_reason":"roi","initial_stop_loss_abs":9.9738e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.9738e-05,"stop_loss_ratio":0.1,"min_rate":0.00011082,"max_rate":0.00011137548872180448,"is_open":false,"buy_tag":null,"open_timestamp":1515850800000.0,"close_timestamp":1515886200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.863406408094438,"open_date":"2018-01-13 15:15:00+00:00","close_date":"2018-01-13 15:55:00+00:00","open_rate":5.93e-05,"close_rate":5.9597243107769415e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.9724310776941686e-07,"sell_reason":"roi","initial_stop_loss_abs":5.337e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.337e-05,"stop_loss_ratio":0.1,"min_rate":5.93e-05,"max_rate":5.9597243107769415e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515856500000.0,"close_timestamp":1515858900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020618543947292404,"open_date":"2018-01-13 16:30:00+00:00","close_date":"2018-01-13 17:10:00+00:00","open_rate":0.04850003,"close_rate":0.04874313791979949,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00024310791979949287,"sell_reason":"roi","initial_stop_loss_abs":0.043650027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.043650027,"stop_loss_ratio":0.1,"min_rate":0.04850003,"max_rate":0.04874313791979949,"is_open":false,"buy_tag":null,"open_timestamp":1515861000000.0,"close_timestamp":1515863400000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010178097365511457,"open_date":"2018-01-13 22:05:00+00:00","close_date":"2018-01-14 06:25:00+00:00","open_rate":0.09825019,"close_rate":0.09874267215538848,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":500,"profit_ratio":-0.0,"profit_abs":0.0004924821553884823,"sell_reason":"roi","initial_stop_loss_abs":0.088425171,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.088425171,"stop_loss_ratio":0.1,"min_rate":0.09825019,"max_rate":0.09874267215538848,"is_open":false,"buy_tag":null,"open_timestamp":1515881100000.0,"close_timestamp":1515911100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.616816218012627,"open_date":"2018-01-14 00:20:00+00:00","close_date":"2018-01-14 22:55:00+00:00","open_rate":6.018e-05,"close_rate":6.048165413533834e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":1355,"profit_ratio":0.0,"profit_abs":3.0165413533833987e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4162e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4162e-05,"stop_loss_ratio":0.1,"min_rate":6.018e-05,"max_rate":6.048165413533834e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515889200000.0,"close_timestamp":1515970500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010246952581919518,"open_date":"2018-01-14 12:45:00+00:00","close_date":"2018-01-14 13:25:00+00:00","open_rate":0.09758999,"close_rate":0.0980791628822055,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004891728822054991,"sell_reason":"roi","initial_stop_loss_abs":0.087830991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.087830991,"stop_loss_ratio":0.1,"min_rate":0.09758999,"max_rate":0.0980791628822055,"is_open":false,"buy_tag":null,"open_timestamp":1515933900000.0,"close_timestamp":1515936300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3215434083601286,"open_date":"2018-01-14 15:30:00+00:00","close_date":"2018-01-14 16:00:00+00:00","open_rate":0.00311,"close_rate":0.0031567669172932328,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.676691729323286e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002799,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002799,"stop_loss_ratio":0.1,"min_rate":0.00311,"max_rate":0.0031567669172932328,"is_open":false,"buy_tag":null,"open_timestamp":1515943800000.0,"close_timestamp":1515945600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.32010140812609433,"open_date":"2018-01-14 20:45:00+00:00","close_date":"2018-01-14 22:15:00+00:00","open_rate":0.00312401,"close_rate":0.003139669197994987,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":-0.0,"profit_abs":1.5659197994987058e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002811609,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002811609,"stop_loss_ratio":0.1,"min_rate":0.00312401,"max_rate":0.003139669197994987,"is_open":false,"buy_tag":null,"open_timestamp":1515962700000.0,"close_timestamp":1515968100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.057247866085791646,"open_date":"2018-01-14 23:35:00+00:00","close_date":"2018-01-15 00:30:00+00:00","open_rate":0.0174679,"close_rate":0.017555458395989976,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":8.755839598997492e-05,"sell_reason":"roi","initial_stop_loss_abs":0.015721110000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.015721110000000003,"stop_loss_ratio":0.1,"min_rate":0.0174679,"max_rate":0.017555458395989976,"is_open":false,"buy_tag":null,"open_timestamp":1515972900000.0,"close_timestamp":1515976200000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.013611282991367997,"open_date":"2018-01-14 23:45:00+00:00","close_date":"2018-01-15 00:25:00+00:00","open_rate":0.07346846,"close_rate":0.07383672295739348,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00036826295739347814,"sell_reason":"roi","initial_stop_loss_abs":0.066121614,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.066121614,"stop_loss_ratio":0.1,"min_rate":0.07346846,"max_rate":0.07383672295739348,"is_open":false,"buy_tag":null,"open_timestamp":1515973500000.0,"close_timestamp":1515975900000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010204706410596568,"open_date":"2018-01-15 02:25:00+00:00","close_date":"2018-01-15 03:05:00+00:00","open_rate":0.097994,"close_rate":0.09848519799498744,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004911979949874384,"sell_reason":"roi","initial_stop_loss_abs":0.0881946,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0881946,"stop_loss_ratio":0.1,"min_rate":0.097994,"max_rate":0.09848519799498744,"is_open":false,"buy_tag":null,"open_timestamp":1515983100000.0,"close_timestamp":1515985500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010353038616834042,"open_date":"2018-01-15 07:20:00+00:00","close_date":"2018-01-15 08:00:00+00:00","open_rate":0.09659,"close_rate":0.09707416040100247,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004841604010024786,"sell_reason":"roi","initial_stop_loss_abs":0.086931,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.086931,"stop_loss_ratio":0.1,"min_rate":0.09659,"max_rate":0.09707416040100247,"is_open":false,"buy_tag":null,"open_timestamp":1516000800000.0,"close_timestamp":1516003200000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.013016921998599,"open_date":"2018-01-15 08:20:00+00:00","close_date":"2018-01-15 08:55:00+00:00","open_rate":9.987e-05,"close_rate":0.00010137180451127818,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":1.501804511278178e-06,"sell_reason":"roi","initial_stop_loss_abs":8.9883e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.9883e-05,"stop_loss_ratio":0.1,"min_rate":9.987e-05,"max_rate":0.00010137180451127818,"is_open":false,"buy_tag":null,"open_timestamp":1516004400000.0,"close_timestamp":1516006500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010537752023511832,"open_date":"2018-01-15 12:10:00+00:00","close_date":"2018-01-16 02:50:00+00:00","open_rate":0.0948969,"close_rate":0.09537257368421052,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":880,"profit_ratio":0.0,"profit_abs":0.0004756736842105175,"sell_reason":"roi","initial_stop_loss_abs":0.08540721000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08540721000000001,"stop_loss_ratio":0.1,"min_rate":0.0948969,"max_rate":0.09537257368421052,"is_open":false,"buy_tag":null,"open_timestamp":1516018200000.0,"close_timestamp":1516071000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014084507042253523,"open_date":"2018-01-15 14:10:00+00:00","close_date":"2018-01-15 17:40:00+00:00","open_rate":0.071,"close_rate":0.07135588972431077,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":210,"profit_ratio":-0.0,"profit_abs":0.00035588972431077615,"sell_reason":"roi","initial_stop_loss_abs":0.0639,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0639,"stop_loss_ratio":0.1,"min_rate":0.071,"max_rate":0.07135588972431077,"is_open":false,"buy_tag":null,"open_timestamp":1516025400000.0,"close_timestamp":1516038000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021736763017766978,"open_date":"2018-01-15 14:30:00+00:00","close_date":"2018-01-15 15:10:00+00:00","open_rate":0.04600501,"close_rate":0.046235611553884705,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00023060155388470588,"sell_reason":"roi","initial_stop_loss_abs":0.041404509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041404509,"stop_loss_ratio":0.1,"min_rate":0.04600501,"max_rate":0.046235611553884705,"is_open":false,"buy_tag":null,"open_timestamp":1516026600000.0,"close_timestamp":1516029000000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.595465140919686,"open_date":"2018-01-15 18:10:00+00:00","close_date":"2018-01-15 19:25:00+00:00","open_rate":9.438e-05,"close_rate":9.485308270676693e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":4.7308270676692514e-07,"sell_reason":"roi","initial_stop_loss_abs":8.4942e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.4942e-05,"stop_loss_ratio":0.1,"min_rate":9.438e-05,"max_rate":9.485308270676693e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516039800000.0,"close_timestamp":1516044300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.032894726021471705,"open_date":"2018-01-15 18:35:00+00:00","close_date":"2018-01-15 19:15:00+00:00","open_rate":0.03040001,"close_rate":0.030552391002506264,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0001523810025062626,"sell_reason":"roi","initial_stop_loss_abs":0.027360009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027360009,"stop_loss_ratio":0.1,"min_rate":0.03040001,"max_rate":0.030552391002506264,"is_open":false,"buy_tag":null,"open_timestamp":1516041300000.0,"close_timestamp":1516043700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.13208840157615,"open_date":"2018-01-15 20:25:00+00:00","close_date":"2018-01-16 08:25:00+00:00","open_rate":5.837e-05,"close_rate":5.2533e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":720,"profit_ratio":-0.10448878,"profit_abs":-5.8369999999999985e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":5.2533e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2533e-05,"stop_loss_ratio":0.1,"min_rate":5.2533e-05,"max_rate":5.837e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516047900000.0,"close_timestamp":1516091100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021722130506560085,"open_date":"2018-01-15 20:40:00+00:00","close_date":"2018-01-15 22:00:00+00:00","open_rate":0.046036,"close_rate":0.04626675689223057,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00023075689223057277,"sell_reason":"roi","initial_stop_loss_abs":0.0414324,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0414324,"stop_loss_ratio":0.1,"min_rate":0.046036,"max_rate":0.04626675689223057,"is_open":false,"buy_tag":null,"open_timestamp":1516048800000.0,"close_timestamp":1516053600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.34861425832316545,"open_date":"2018-01-16 00:30:00+00:00","close_date":"2018-01-16 01:10:00+00:00","open_rate":0.0028685,"close_rate":0.0028828784461152877,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4378446115287727e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00258165,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00258165,"stop_loss_ratio":0.1,"min_rate":0.0028685,"max_rate":0.0028828784461152877,"is_open":false,"buy_tag":null,"open_timestamp":1516062600000.0,"close_timestamp":1516065000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014854967241083492,"open_date":"2018-01-16 01:15:00+00:00","close_date":"2018-01-16 02:35:00+00:00","open_rate":0.06731755,"close_rate":0.0676549813283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":0.0,"profit_abs":0.00033743132832080025,"sell_reason":"roi","initial_stop_loss_abs":0.060585795000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060585795000000005,"stop_loss_ratio":0.1,"min_rate":0.06731755,"max_rate":0.0676549813283208,"is_open":false,"buy_tag":null,"open_timestamp":1516065300000.0,"close_timestamp":1516070100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010848794492804754,"open_date":"2018-01-16 07:45:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":0.09217614,"close_rate":0.09263817578947368,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":0.0004620357894736804,"sell_reason":"roi","initial_stop_loss_abs":0.082958526,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082958526,"stop_loss_ratio":0.1,"min_rate":0.09217614,"max_rate":0.09263817578947368,"is_open":false,"buy_tag":null,"open_timestamp":1516088700000.0,"close_timestamp":1516092000000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06060606060606061,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:55:00+00:00","open_rate":0.0165,"close_rate":0.016913533834586467,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.00041353383458646656,"sell_reason":"roi","initial_stop_loss_abs":0.01485,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01485,"stop_loss_ratio":0.1,"min_rate":0.0165,"max_rate":0.016913533834586467,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":12.57387149503332,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":7.953e-05,"close_rate":8.311781954887218e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":3.587819548872171e-06,"sell_reason":"roi","initial_stop_loss_abs":7.157700000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.157700000000001e-05,"stop_loss_ratio":0.1,"min_rate":7.953e-05,"max_rate":8.311781954887218e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022122914915269236,"open_date":"2018-01-16 08:45:00+00:00","close_date":"2018-01-16 09:50:00+00:00","open_rate":0.045202,"close_rate":0.04542857644110275,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":0.00022657644110275071,"sell_reason":"roi","initial_stop_loss_abs":0.0406818,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0406818,"stop_loss_ratio":0.1,"min_rate":0.045202,"max_rate":0.04542857644110275,"is_open":false,"buy_tag":null,"open_timestamp":1516092300000.0,"close_timestamp":1516096200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.054878048780488,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:45:00+00:00","open_rate":5.248e-05,"close_rate":5.326917293233082e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.891729323308177e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7232e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7232e-05,"stop_loss_ratio":0.1,"min_rate":5.248e-05,"max_rate":5.326917293233082e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516095900000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03457434486802627,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:55:00+00:00","open_rate":0.02892318,"close_rate":0.02906815834586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0001449783458646603,"sell_reason":"roi","initial_stop_loss_abs":0.026030862000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.026030862000000002,"stop_loss_ratio":0.1,"min_rate":0.02892318,"max_rate":0.02906815834586466,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516096500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.38735944164405,"open_date":"2018-01-16 09:50:00+00:00","close_date":"2018-01-16 10:10:00+00:00","open_rate":5.158e-05,"close_rate":5.287273182957392e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.2927318295739246e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6422e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6422e-05,"stop_loss_ratio":0.1,"min_rate":5.158e-05,"max_rate":5.287273182957392e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516096200000.0,"close_timestamp":1516097400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035357778286929785,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:35:00+00:00","open_rate":0.02828232,"close_rate":0.02870761804511278,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042529804511277913,"sell_reason":"roi","initial_stop_loss_abs":0.025454088,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025454088,"stop_loss_ratio":0.1,"min_rate":0.02828232,"max_rate":0.02870761804511278,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516098900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022948496230938982,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:40:00+00:00","open_rate":0.04357584,"close_rate":0.044231115789473675,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0006552757894736777,"sell_reason":"roi","initial_stop_loss_abs":0.039218256,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039218256,"stop_loss_ratio":0.1,"min_rate":0.04357584,"max_rate":0.044231115789473675,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516099200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.64975755315181,"open_date":"2018-01-16 13:45:00+00:00","close_date":"2018-01-16 14:20:00+00:00","open_rate":5.362e-05,"close_rate":5.442631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":8.063157894736843e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8258e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8258e-05,"stop_loss_ratio":0.1,"min_rate":5.362e-05,"max_rate":5.442631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516110300000.0,"close_timestamp":1516112400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.86080724254998,"open_date":"2018-01-16 17:30:00+00:00","close_date":"2018-01-16 18:25:00+00:00","open_rate":5.302e-05,"close_rate":5.328576441102756e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.6576441102756397e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7718e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7718e-05,"stop_loss_ratio":0.1,"min_rate":5.302e-05,"max_rate":5.328576441102756e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516123800000.0,"close_timestamp":1516127100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010952903718828448,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:45:00+00:00","open_rate":0.09129999,"close_rate":0.09267292218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013729321804511196,"sell_reason":"roi","initial_stop_loss_abs":0.082169991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082169991,"stop_loss_ratio":0.1,"min_rate":0.09129999,"max_rate":0.09267292218045112,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516128300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":26.26050420168067,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:35:00+00:00","open_rate":3.808e-05,"close_rate":3.903438596491228e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":9.543859649122774e-07,"sell_reason":"roi","initial_stop_loss_abs":3.4272e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.4272e-05,"stop_loss_ratio":0.1,"min_rate":3.808e-05,"max_rate":3.903438596491228e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516127700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035574376772493324,"open_date":"2018-01-16 19:00:00+00:00","close_date":"2018-01-16 19:30:00+00:00","open_rate":0.02811012,"close_rate":0.028532828571428567,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042270857142856846,"sell_reason":"roi","initial_stop_loss_abs":0.025299108,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025299108,"stop_loss_ratio":0.1,"min_rate":0.02811012,"max_rate":0.028532828571428567,"is_open":false,"buy_tag":null,"open_timestamp":1516129200000.0,"close_timestamp":1516131000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.387028357567759,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":0.00258379,"close_rate":0.002325411,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.10448878,"profit_abs":-0.000258379,"sell_reason":"stop_loss","initial_stop_loss_abs":0.002325411,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002325411,"stop_loss_ratio":0.1,"min_rate":0.002325411,"max_rate":0.00258379,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516141500000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":39.07776475185619,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":2.559e-05,"close_rate":2.3031e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.10448878,"profit_abs":-2.5590000000000004e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":2.3031e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.3031e-05,"stop_loss_ratio":0.1,"min_rate":2.3031e-05,"max_rate":2.559e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516142700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":13.123359580052494,"open_date":"2018-01-16 21:35:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":7.62e-05,"close_rate":6.858e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.10448878,"profit_abs":-7.619999999999998e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":6.858e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.858e-05,"stop_loss_ratio":0.1,"min_rate":6.858e-05,"max_rate":7.62e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516138500000.0,"close_timestamp":1516141500000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4350777048780912,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:35:00+00:00","open_rate":0.00229844,"close_rate":0.002402129022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010368902255639091,"sell_reason":"roi","initial_stop_loss_abs":0.0020685960000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0020685960000000002,"stop_loss_ratio":0.1,"min_rate":0.00229844,"max_rate":0.002402129022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06622516556291391,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:40:00+00:00","open_rate":0.0151,"close_rate":0.015781203007518795,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":0.0006812030075187946,"sell_reason":"roi","initial_stop_loss_abs":0.013590000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.013590000000000001,"stop_loss_ratio":0.1,"min_rate":0.0151,"max_rate":0.015781203007518795,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.42431134269081283,"open_date":"2018-01-16 22:40:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":0.00235676,"close_rate":0.00246308,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010632000000000003,"sell_reason":"roi","initial_stop_loss_abs":0.002121084,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002121084,"stop_loss_ratio":0.1,"min_rate":0.00235676,"max_rate":0.00246308,"is_open":false,"buy_tag":null,"open_timestamp":1516142400000.0,"close_timestamp":1516142700000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01585559988076589,"open_date":"2018-01-16 22:45:00+00:00","close_date":"2018-01-16 23:05:00+00:00","open_rate":0.0630692,"close_rate":0.06464988170426066,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0015806817042606502,"sell_reason":"roi","initial_stop_loss_abs":0.056762280000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.056762280000000005,"stop_loss_ratio":0.1,"min_rate":0.0630692,"max_rate":0.06464988170426066,"is_open":false,"buy_tag":null,"open_timestamp":1516142700000.0,"close_timestamp":1516143900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":45.45454545454545,"open_date":"2018-01-16 22:50:00+00:00","close_date":"2018-01-16 22:55:00+00:00","open_rate":2.2e-05,"close_rate":2.299248120300751e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":9.924812030075114e-07,"sell_reason":"roi","initial_stop_loss_abs":1.98e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":1.98e-05,"stop_loss_ratio":0.1,"min_rate":2.2e-05,"max_rate":2.299248120300751e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516143000000.0,"close_timestamp":1516143300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.10454362685967,"open_date":"2018-01-17 03:30:00+00:00","close_date":"2018-01-17 04:00:00+00:00","open_rate":4.974e-05,"close_rate":5.048796992481203e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.479699248120277e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4766000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4766000000000005e-05,"stop_loss_ratio":0.1,"min_rate":4.974e-05,"max_rate":5.048796992481203e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516159800000.0,"close_timestamp":1516161600000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":14.068655036578503,"open_date":"2018-01-17 03:55:00+00:00","close_date":"2018-01-17 04:15:00+00:00","open_rate":7.108e-05,"close_rate":7.28614536340852e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.7814536340851996e-06,"sell_reason":"roi","initial_stop_loss_abs":6.3972e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.3972e-05,"stop_loss_ratio":0.1,"min_rate":7.108e-05,"max_rate":7.28614536340852e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516161300000.0,"close_timestamp":1516162500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.0231107002542177,"open_date":"2018-01-17 09:35:00+00:00","close_date":"2018-01-17 10:15:00+00:00","open_rate":0.04327,"close_rate":0.04348689223057644,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002168922305764362,"sell_reason":"roi","initial_stop_loss_abs":0.038943000000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.038943000000000005,"stop_loss_ratio":0.1,"min_rate":0.04327,"max_rate":0.04348689223057644,"is_open":false,"buy_tag":null,"open_timestamp":1516181700000.0,"close_timestamp":1516184100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.012007204322593,"open_date":"2018-01-17 10:20:00+00:00","close_date":"2018-01-17 17:00:00+00:00","open_rate":4.997e-05,"close_rate":5.022047619047618e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":400,"profit_ratio":-0.0,"profit_abs":2.504761904761831e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4973e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4973e-05,"stop_loss_ratio":0.1,"min_rate":4.997e-05,"max_rate":5.022047619047618e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516184400000.0,"close_timestamp":1516208400000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014626687444363738,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:25:00+00:00","open_rate":0.06836818,"close_rate":0.06871087764411027,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00034269764411026804,"sell_reason":"roi","initial_stop_loss_abs":0.061531362,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061531362,"stop_loss_ratio":0.1,"min_rate":0.06836818,"max_rate":0.06871087764411027,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516188300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.548209366391184,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:10:00+00:00","open_rate":3.63e-05,"close_rate":3.648195488721804e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.8195488721804031e-07,"sell_reason":"roi","initial_stop_loss_abs":3.2670000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2670000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.63e-05,"max_rate":3.648195488721804e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516187400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03558718861209965,"open_date":"2018-01-17 12:30:00+00:00","close_date":"2018-01-17 22:05:00+00:00","open_rate":0.0281,"close_rate":0.02824085213032581,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":575,"profit_ratio":-0.0,"profit_abs":0.0001408521303258095,"sell_reason":"roi","initial_stop_loss_abs":0.02529,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.02529,"stop_loss_ratio":0.1,"min_rate":0.0281,"max_rate":0.02824085213032581,"is_open":false,"buy_tag":null,"open_timestamp":1516192200000.0,"close_timestamp":1516226700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011559355963546878,"open_date":"2018-01-17 12:35:00+00:00","close_date":"2018-01-17 16:55:00+00:00","open_rate":0.08651001,"close_rate":0.08694364413533832,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":-0.0,"profit_abs":0.00043363413533832607,"sell_reason":"roi","initial_stop_loss_abs":0.077859009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077859009,"stop_loss_ratio":0.1,"min_rate":0.08651001,"max_rate":0.08694364413533832,"is_open":false,"buy_tag":null,"open_timestamp":1516192500000.0,"close_timestamp":1516208100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.752529735487308,"open_date":"2018-01-18 05:00:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":5.633e-05,"close_rate":5.6612355889724306e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.8235588972430847e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0697e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0697e-05,"stop_loss_ratio":0.1,"min_rate":5.633e-05,"max_rate":5.6612355889724306e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516251600000.0,"close_timestamp":1516254900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01430923457900944,"open_date":"2018-01-18 05:20:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":0.06988494,"close_rate":0.07093584135338346,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0010509013533834544,"sell_reason":"roi","initial_stop_loss_abs":0.06289644600000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06289644600000001,"stop_loss_ratio":0.1,"min_rate":0.06988494,"max_rate":0.07093584135338346,"is_open":false,"buy_tag":null,"open_timestamp":1516252800000.0,"close_timestamp":1516254900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.034265103697024,"open_date":"2018-01-18 07:35:00+00:00","close_date":"2018-01-18 08:15:00+00:00","open_rate":5.545e-05,"close_rate":5.572794486215538e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.779448621553787e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9905e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9905e-05,"stop_loss_ratio":0.1,"min_rate":5.545e-05,"max_rate":5.572794486215538e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516260900000.0,"close_timestamp":1516263300000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06121723118136401,"open_date":"2018-01-18 09:00:00+00:00","close_date":"2018-01-18 09:40:00+00:00","open_rate":0.01633527,"close_rate":0.016417151052631574,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":8.188105263157511e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014701743,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014701743,"stop_loss_ratio":0.1,"min_rate":0.01633527,"max_rate":0.016417151052631574,"is_open":false,"buy_tag":null,"open_timestamp":1516266000000.0,"close_timestamp":1516268400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3707356136045141,"open_date":"2018-01-18 16:40:00+00:00","close_date":"2018-01-18 17:20:00+00:00","open_rate":0.00269734,"close_rate":0.002710860501253133,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.3520501253133123e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002427606,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002427606,"stop_loss_ratio":0.1,"min_rate":0.00269734,"max_rate":0.002710860501253133,"is_open":false,"buy_tag":null,"open_timestamp":1516293600000.0,"close_timestamp":1516296000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.346368715083802,"open_date":"2018-01-18 18:05:00+00:00","close_date":"2018-01-18 18:30:00+00:00","open_rate":4.475e-05,"close_rate":4.587155388471177e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1215538847117757e-06,"sell_reason":"roi","initial_stop_loss_abs":4.0274999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.0274999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.475e-05,"max_rate":4.587155388471177e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516298700000.0,"close_timestamp":1516300200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":35.842293906810035,"open_date":"2018-01-18 18:25:00+00:00","close_date":"2018-01-18 18:55:00+00:00","open_rate":2.79e-05,"close_rate":2.8319548872180444e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.1954887218044365e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5110000000000002e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5110000000000002e-05,"stop_loss_ratio":0.1,"min_rate":2.79e-05,"max_rate":2.8319548872180444e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516299900000.0,"close_timestamp":1516301700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022525942001105574,"open_date":"2018-01-18 20:10:00+00:00","close_date":"2018-01-18 20:50:00+00:00","open_rate":0.04439326,"close_rate":0.04461578260651629,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00022252260651629135,"sell_reason":"roi","initial_stop_loss_abs":0.039953933999999997,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039953933999999997,"stop_loss_ratio":0.1,"min_rate":0.04439326,"max_rate":0.04461578260651629,"is_open":false,"buy_tag":null,"open_timestamp":1516306200000.0,"close_timestamp":1516308600000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.271714922048996,"open_date":"2018-01-18 21:30:00+00:00","close_date":"2018-01-19 00:35:00+00:00","open_rate":4.49e-05,"close_rate":4.51250626566416e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":185,"profit_ratio":0.0,"profit_abs":2.2506265664159932e-07,"sell_reason":"roi","initial_stop_loss_abs":4.041e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.041e-05,"stop_loss_ratio":0.1,"min_rate":4.49e-05,"max_rate":4.51250626566416e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516311000000.0,"close_timestamp":1516322100000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03502626970227671,"open_date":"2018-01-18 21:55:00+00:00","close_date":"2018-01-19 05:05:00+00:00","open_rate":0.02855,"close_rate":0.028693107769423555,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":430,"profit_ratio":-0.0,"profit_abs":0.00014310776942355607,"sell_reason":"roi","initial_stop_loss_abs":0.025695,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025695,"stop_loss_ratio":0.1,"min_rate":0.02855,"max_rate":0.028693107769423555,"is_open":false,"buy_tag":null,"open_timestamp":1516312500000.0,"close_timestamp":1516338300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.25327812284334,"open_date":"2018-01-18 22:10:00+00:00","close_date":"2018-01-18 22:50:00+00:00","open_rate":5.796e-05,"close_rate":5.8250526315789473e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.905263157894727e-07,"sell_reason":"roi","initial_stop_loss_abs":5.2164000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2164000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.796e-05,"max_rate":5.8250526315789473e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516313400000.0,"close_timestamp":1516315800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02303975994413319,"open_date":"2018-01-18 23:50:00+00:00","close_date":"2018-01-19 00:30:00+00:00","open_rate":0.04340323,"close_rate":0.04362079005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0002175600501253122,"sell_reason":"roi","initial_stop_loss_abs":0.039062907,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039062907,"stop_loss_ratio":0.1,"min_rate":0.04340323,"max_rate":0.04362079005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516319400000.0,"close_timestamp":1516321800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02244943545282195,"open_date":"2018-01-19 16:45:00+00:00","close_date":"2018-01-19 17:35:00+00:00","open_rate":0.04454455,"close_rate":0.04476783095238095,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0002232809523809512,"sell_reason":"roi","initial_stop_loss_abs":0.040090095000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040090095000000006,"stop_loss_ratio":0.1,"min_rate":0.04454455,"max_rate":0.04476783095238095,"is_open":false,"buy_tag":null,"open_timestamp":1516380300000.0,"close_timestamp":1516383300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.793594306049823,"open_date":"2018-01-19 17:15:00+00:00","close_date":"2018-01-19 19:55:00+00:00","open_rate":5.62e-05,"close_rate":5.648170426065162e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":160,"profit_ratio":-0.0,"profit_abs":2.817042606516199e-07,"sell_reason":"roi","initial_stop_loss_abs":5.058e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.058e-05,"stop_loss_ratio":0.1,"min_rate":5.62e-05,"max_rate":5.648170426065162e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382100000.0,"close_timestamp":1516391700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.046784973496194,"open_date":"2018-01-19 17:20:00+00:00","close_date":"2018-01-19 20:15:00+00:00","open_rate":4.339e-05,"close_rate":4.360749373433584e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.174937343358337e-07,"sell_reason":"roi","initial_stop_loss_abs":3.9051e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.9051e-05,"stop_loss_ratio":0.1,"min_rate":4.339e-05,"max_rate":4.360749373433584e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382400000.0,"close_timestamp":1516392900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.910802775024777,"open_date":"2018-01-20 04:45:00+00:00","close_date":"2018-01-20 17:35:00+00:00","open_rate":0.0001009,"close_rate":0.00010140576441102755,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":770,"profit_ratio":0.0,"profit_abs":5.057644110275549e-07,"sell_reason":"roi","initial_stop_loss_abs":9.081e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.081e-05,"stop_loss_ratio":0.1,"min_rate":0.0001009,"max_rate":0.00010140576441102755,"is_open":false,"buy_tag":null,"open_timestamp":1516423500000.0,"close_timestamp":1516469700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3696789338459548,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 15:15:00+00:00","open_rate":0.00270505,"close_rate":0.002718609147869674,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":625,"profit_ratio":-0.0,"profit_abs":1.3559147869673764e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002434545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002434545,"stop_loss_ratio":0.1,"min_rate":0.00270505,"max_rate":0.002718609147869674,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516461300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.033333311111125925,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 07:00:00+00:00","open_rate":0.03000002,"close_rate":0.030150396040100245,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":0.00015037604010024672,"sell_reason":"roi","initial_stop_loss_abs":0.027000018,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027000018,"stop_loss_ratio":0.1,"min_rate":0.03000002,"max_rate":0.030150396040100245,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516431600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.315018315018317,"open_date":"2018-01-20 09:00:00+00:00","close_date":"2018-01-20 09:40:00+00:00","open_rate":5.46e-05,"close_rate":5.4873684210526304e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.736842105263053e-07,"sell_reason":"roi","initial_stop_loss_abs":4.914e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.914e-05,"stop_loss_ratio":0.1,"min_rate":5.46e-05,"max_rate":5.4873684210526304e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516438800000.0,"close_timestamp":1516441200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03244412634781012,"open_date":"2018-01-20 18:25:00+00:00","close_date":"2018-01-25 03:50:00+00:00","open_rate":0.03082222,"close_rate":0.027739998,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":6325,"profit_ratio":-0.10448878,"profit_abs":-0.0030822220000000025,"sell_reason":"stop_loss","initial_stop_loss_abs":0.027739998000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027739998000000002,"stop_loss_ratio":0.1,"min_rate":0.027739998,"max_rate":0.03082222,"is_open":false,"buy_tag":null,"open_timestamp":1516472700000.0,"close_timestamp":1516852200000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011148273260677065,"open_date":"2018-01-20 22:25:00+00:00","close_date":"2018-01-20 23:15:00+00:00","open_rate":0.08969999,"close_rate":0.09014961401002504,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":0.00044962401002504593,"sell_reason":"roi","initial_stop_loss_abs":0.080729991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080729991,"stop_loss_ratio":0.1,"min_rate":0.08969999,"max_rate":0.09014961401002504,"is_open":false,"buy_tag":null,"open_timestamp":1516487100000.0,"close_timestamp":1516490100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06125570520324337,"open_date":"2018-01-21 02:50:00+00:00","close_date":"2018-01-21 14:30:00+00:00","open_rate":0.01632501,"close_rate":0.01640683962406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":700,"profit_ratio":0.0,"profit_abs":8.182962406014932e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014692509000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014692509000000001,"stop_loss_ratio":0.1,"min_rate":0.01632501,"max_rate":0.01640683962406015,"is_open":false,"buy_tag":null,"open_timestamp":1516503000000.0,"close_timestamp":1516545000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01417675579120474,"open_date":"2018-01-21 10:20:00+00:00","close_date":"2018-01-21 11:00:00+00:00","open_rate":0.070538,"close_rate":0.07089157393483708,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00035357393483707866,"sell_reason":"roi","initial_stop_loss_abs":0.0634842,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0634842,"stop_loss_ratio":0.1,"min_rate":0.070538,"max_rate":0.07089157393483708,"is_open":false,"buy_tag":null,"open_timestamp":1516530000000.0,"close_timestamp":1516532400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.864365214110546,"open_date":"2018-01-21 15:50:00+00:00","close_date":"2018-01-21 18:45:00+00:00","open_rate":5.301e-05,"close_rate":5.327571428571427e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.657142857142672e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7709e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7709e-05,"stop_loss_ratio":0.1,"min_rate":5.301e-05,"max_rate":5.327571428571427e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516549800000.0,"close_timestamp":1516560300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.284450063211125,"open_date":"2018-01-21 16:20:00+00:00","close_date":"2018-01-21 17:00:00+00:00","open_rate":3.955e-05,"close_rate":3.9748245614035085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.9824561403508552e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5595e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5595e-05,"stop_loss_ratio":0.1,"min_rate":3.955e-05,"max_rate":3.9748245614035085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516551600000.0,"close_timestamp":1516554000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38683971296493297,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:45:00+00:00","open_rate":0.00258505,"close_rate":0.002623922932330827,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.8872932330826816e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326545,"stop_loss_ratio":0.1,"min_rate":0.00258505,"max_rate":0.002623922932330827,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.621316935690498,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:55:00+00:00","open_rate":3.903e-05,"close_rate":3.922563909774435e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.9563909774435151e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5127e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5127e-05,"stop_loss_ratio":0.1,"min_rate":3.903e-05,"max_rate":3.922563909774435e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.098548510313215,"open_date":"2018-01-22 00:35:00+00:00","close_date":"2018-01-22 10:35:00+00:00","open_rate":5.236e-05,"close_rate":5.262245614035087e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":600,"profit_ratio":-0.0,"profit_abs":2.624561403508717e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7124e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7124e-05,"stop_loss_ratio":0.1,"min_rate":5.236e-05,"max_rate":5.262245614035087e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516581300000.0,"close_timestamp":1516617300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.076650420912717,"open_date":"2018-01-22 01:30:00+00:00","close_date":"2018-01-22 02:10:00+00:00","open_rate":9.028e-05,"close_rate":9.07325313283208e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":4.5253132832080657e-07,"sell_reason":"roi","initial_stop_loss_abs":8.1252e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.1252e-05,"stop_loss_ratio":0.1,"min_rate":9.028e-05,"max_rate":9.07325313283208e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516584600000.0,"close_timestamp":1516587000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3721622627465575,"open_date":"2018-01-22 12:25:00+00:00","close_date":"2018-01-22 14:35:00+00:00","open_rate":0.002687,"close_rate":0.002700468671679198,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":1.3468671679197925e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0024183000000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0024183000000000004,"stop_loss_ratio":0.1,"min_rate":0.002687,"max_rate":0.002700468671679198,"is_open":false,"buy_tag":null,"open_timestamp":1516623900000.0,"close_timestamp":1516631700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.99232245681382,"open_date":"2018-01-22 13:15:00+00:00","close_date":"2018-01-22 13:55:00+00:00","open_rate":4.168e-05,"close_rate":4.188892230576441e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.0892230576441054e-07,"sell_reason":"roi","initial_stop_loss_abs":3.7512e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.7512e-05,"stop_loss_ratio":0.1,"min_rate":4.168e-05,"max_rate":4.188892230576441e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516626900000.0,"close_timestamp":1516629300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.336583153837434,"open_date":"2018-01-22 14:00:00+00:00","close_date":"2018-01-22 14:30:00+00:00","open_rate":8.821e-05,"close_rate":8.953646616541353e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":1.326466165413529e-06,"sell_reason":"roi","initial_stop_loss_abs":7.9389e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.9389e-05,"stop_loss_ratio":0.1,"min_rate":8.821e-05,"max_rate":8.953646616541353e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516629600000.0,"close_timestamp":1516631400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.334880123743233,"open_date":"2018-01-22 15:55:00+00:00","close_date":"2018-01-22 16:40:00+00:00","open_rate":5.172e-05,"close_rate":5.1979248120300745e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.592481203007459e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6548e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6548e-05,"stop_loss_ratio":0.1,"min_rate":5.172e-05,"max_rate":5.1979248120300745e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516636500000.0,"close_timestamp":1516639200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.04692663582287,"open_date":"2018-01-22 16:05:00+00:00","close_date":"2018-01-22 16:25:00+00:00","open_rate":3.026e-05,"close_rate":3.101839598997494e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":7.5839598997494e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7234e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7234e-05,"stop_loss_ratio":0.1,"min_rate":3.026e-05,"max_rate":3.101839598997494e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516637100000.0,"close_timestamp":1516638300000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014156285390713478,"open_date":"2018-01-22 19:50:00+00:00","close_date":"2018-01-23 00:10:00+00:00","open_rate":0.07064,"close_rate":0.07099408521303258,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":0.0,"profit_abs":0.00035408521303258167,"sell_reason":"roi","initial_stop_loss_abs":0.063576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.063576,"stop_loss_ratio":0.1,"min_rate":0.07064,"max_rate":0.07099408521303258,"is_open":false,"buy_tag":null,"open_timestamp":1516650600000.0,"close_timestamp":1516666200000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06080938507725528,"open_date":"2018-01-22 21:25:00+00:00","close_date":"2018-01-22 22:05:00+00:00","open_rate":0.01644483,"close_rate":0.01652726022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.243022556390922e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014800347,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014800347,"stop_loss_ratio":0.1,"min_rate":0.01644483,"max_rate":0.01652726022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516656300000.0,"close_timestamp":1516658700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.08935580697299,"open_date":"2018-01-23 00:05:00+00:00","close_date":"2018-01-23 00:35:00+00:00","open_rate":4.331e-05,"close_rate":4.3961278195488714e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":6.512781954887175e-07,"sell_reason":"roi","initial_stop_loss_abs":3.8979e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.8979e-05,"stop_loss_ratio":0.1,"min_rate":4.331e-05,"max_rate":4.3961278195488714e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516665900000.0,"close_timestamp":1516667700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.250000000000004,"open_date":"2018-01-23 01:50:00+00:00","close_date":"2018-01-23 02:15:00+00:00","open_rate":3.2e-05,"close_rate":3.2802005012531326e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":8.020050125313278e-07,"sell_reason":"roi","initial_stop_loss_abs":2.88e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.88e-05,"stop_loss_ratio":0.1,"min_rate":3.2e-05,"max_rate":3.2802005012531326e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516672200000.0,"close_timestamp":1516673700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010907854156754155,"open_date":"2018-01-23 04:25:00+00:00","close_date":"2018-01-23 05:15:00+00:00","open_rate":0.09167706,"close_rate":0.09213659413533835,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0004595341353383492,"sell_reason":"roi","initial_stop_loss_abs":0.08250935400000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08250935400000001,"stop_loss_ratio":0.1,"min_rate":0.09167706,"max_rate":0.09213659413533835,"is_open":false,"buy_tag":null,"open_timestamp":1516681500000.0,"close_timestamp":1516684500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014440474918339115,"open_date":"2018-01-23 07:35:00+00:00","close_date":"2018-01-23 09:00:00+00:00","open_rate":0.0692498,"close_rate":0.06959691679197995,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":0.0,"profit_abs":0.0003471167919799484,"sell_reason":"roi","initial_stop_loss_abs":0.06232482,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06232482,"stop_loss_ratio":0.1,"min_rate":0.0692498,"max_rate":0.06959691679197995,"is_open":false,"buy_tag":null,"open_timestamp":1516692900000.0,"close_timestamp":1516698000000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.426775612822127,"open_date":"2018-01-23 10:50:00+00:00","close_date":"2018-01-23 13:05:00+00:00","open_rate":3.182e-05,"close_rate":3.197949874686716e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":135,"profit_ratio":0.0,"profit_abs":1.594987468671663e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8638e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8638e-05,"stop_loss_ratio":0.1,"min_rate":3.182e-05,"max_rate":3.197949874686716e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516704600000.0,"close_timestamp":1516712700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024461839530332683,"open_date":"2018-01-23 11:05:00+00:00","close_date":"2018-01-23 16:05:00+00:00","open_rate":0.04088,"close_rate":0.04108491228070175,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":300,"profit_ratio":-0.0,"profit_abs":0.0002049122807017481,"sell_reason":"roi","initial_stop_loss_abs":0.036792,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036792,"stop_loss_ratio":0.1,"min_rate":0.04088,"max_rate":0.04108491228070175,"is_open":false,"buy_tag":null,"open_timestamp":1516705500000.0,"close_timestamp":1516723500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.417475728155342,"open_date":"2018-01-23 14:55:00+00:00","close_date":"2018-01-23 15:35:00+00:00","open_rate":5.15e-05,"close_rate":5.175814536340851e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5814536340851513e-07,"sell_reason":"roi","initial_stop_loss_abs":4.635e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.635e-05,"stop_loss_ratio":0.1,"min_rate":5.15e-05,"max_rate":5.175814536340851e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516719300000.0,"close_timestamp":1516721700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011023294646713328,"open_date":"2018-01-23 16:35:00+00:00","close_date":"2018-01-24 00:05:00+00:00","open_rate":0.09071698,"close_rate":0.09117170170426064,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":450,"profit_ratio":0.0,"profit_abs":0.00045472170426064107,"sell_reason":"roi","initial_stop_loss_abs":0.081645282,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.081645282,"stop_loss_ratio":0.1,"min_rate":0.09071698,"max_rate":0.09117170170426064,"is_open":false,"buy_tag":null,"open_timestamp":1516725300000.0,"close_timestamp":1516752300000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.969309462915604,"open_date":"2018-01-23 17:25:00+00:00","close_date":"2018-01-23 18:45:00+00:00","open_rate":3.128e-05,"close_rate":3.1436791979949865e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":1.5679197994986587e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8152e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8152e-05,"stop_loss_ratio":0.1,"min_rate":3.128e-05,"max_rate":3.1436791979949865e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516728300000.0,"close_timestamp":1516733100000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.465724751439037,"open_date":"2018-01-23 20:15:00+00:00","close_date":"2018-01-23 22:00:00+00:00","open_rate":9.555e-05,"close_rate":9.602894736842104e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":4.789473684210343e-07,"sell_reason":"roi","initial_stop_loss_abs":8.5995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.5995e-05,"stop_loss_ratio":0.1,"min_rate":9.555e-05,"max_rate":9.602894736842104e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516738500000.0,"close_timestamp":1516744800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02450979791426522,"open_date":"2018-01-23 22:30:00+00:00","close_date":"2018-01-23 23:10:00+00:00","open_rate":0.04080001,"close_rate":0.0410045213283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00020451132832080554,"sell_reason":"roi","initial_stop_loss_abs":0.036720009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036720009,"stop_loss_ratio":0.1,"min_rate":0.04080001,"max_rate":0.0410045213283208,"is_open":false,"buy_tag":null,"open_timestamp":1516746600000.0,"close_timestamp":1516749000000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.368584156498162,"open_date":"2018-01-23 23:50:00+00:00","close_date":"2018-01-24 03:35:00+00:00","open_rate":5.163e-05,"close_rate":5.18887969924812e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":225,"profit_ratio":-0.0,"profit_abs":2.587969924812037e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6467e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6467e-05,"stop_loss_ratio":0.1,"min_rate":5.163e-05,"max_rate":5.18887969924812e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516751400000.0,"close_timestamp":1516764900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024747691102289384,"open_date":"2018-01-24 00:20:00+00:00","close_date":"2018-01-24 01:50:00+00:00","open_rate":0.04040781,"close_rate":0.04061035541353383,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":0.0,"profit_abs":0.0002025454135338306,"sell_reason":"roi","initial_stop_loss_abs":0.036367029,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036367029,"stop_loss_ratio":0.1,"min_rate":0.04040781,"max_rate":0.04061035541353383,"is_open":false,"buy_tag":null,"open_timestamp":1516753200000.0,"close_timestamp":1516758600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.485580670303975,"open_date":"2018-01-24 06:45:00+00:00","close_date":"2018-01-24 07:25:00+00:00","open_rate":5.132e-05,"close_rate":5.157724310776942e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5724310776941724e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6188000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6188000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.132e-05,"max_rate":5.157724310776942e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516776300000.0,"close_timestamp":1516778700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.23816852635629,"open_date":"2018-01-24 14:15:00+00:00","close_date":"2018-01-24 14:25:00+00:00","open_rate":5.198e-05,"close_rate":5.432496240601503e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":2.344962406015033e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6782e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6782e-05,"stop_loss_ratio":0.1,"min_rate":5.198e-05,"max_rate":5.432496240601503e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516803300000.0,"close_timestamp":1516803900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.74394237066143,"open_date":"2018-01-24 14:50:00+00:00","close_date":"2018-01-24 16:35:00+00:00","open_rate":3.054e-05,"close_rate":3.069308270676692e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":1.5308270676691466e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7486000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7486000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.054e-05,"max_rate":3.069308270676692e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516805400000.0,"close_timestamp":1516811700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.795638562020944,"open_date":"2018-01-24 15:10:00+00:00","close_date":"2018-01-24 16:15:00+00:00","open_rate":9.263e-05,"close_rate":9.309431077694236e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":0.0,"profit_abs":4.6431077694236234e-07,"sell_reason":"roi","initial_stop_loss_abs":8.3367e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.3367e-05,"stop_loss_ratio":0.1,"min_rate":9.263e-05,"max_rate":9.309431077694236e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516806600000.0,"close_timestamp":1516810500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.13565469713457,"open_date":"2018-01-24 22:40:00+00:00","close_date":"2018-01-24 23:25:00+00:00","open_rate":5.514e-05,"close_rate":5.54163909774436e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.7639097744360576e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9625999999999995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9625999999999995e-05,"stop_loss_ratio":0.1,"min_rate":5.514e-05,"max_rate":5.54163909774436e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516833600000.0,"close_timestamp":1516836300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":20.3210729526519,"open_date":"2018-01-25 00:50:00+00:00","close_date":"2018-01-25 01:30:00+00:00","open_rate":4.921e-05,"close_rate":4.9456666666666664e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.4666666666666543e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4289e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4289e-05,"stop_loss_ratio":0.1,"min_rate":4.921e-05,"max_rate":4.9456666666666664e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516841400000.0,"close_timestamp":1516843800000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38461538461538464,"open_date":"2018-01-25 08:15:00+00:00","close_date":"2018-01-25 12:15:00+00:00","open_rate":0.0026,"close_rate":0.002613032581453634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":240,"profit_ratio":0.0,"profit_abs":1.3032581453634e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00234,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00234,"stop_loss_ratio":0.1,"min_rate":0.0026,"max_rate":0.002613032581453634,"is_open":false,"buy_tag":null,"open_timestamp":1516868100000.0,"close_timestamp":1516882500000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03571593119825878,"open_date":"2018-01-25 10:25:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":0.02799871,"close_rate":0.028139054411027563,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":350,"profit_ratio":-0.0,"profit_abs":0.00014034441102756326,"sell_reason":"roi","initial_stop_loss_abs":0.025198839,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025198839,"stop_loss_ratio":0.1,"min_rate":0.02799871,"max_rate":0.028139054411027563,"is_open":false,"buy_tag":null,"open_timestamp":1516875900000.0,"close_timestamp":1516896900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024516401717913302,"open_date":"2018-01-25 11:00:00+00:00","close_date":"2018-01-25 11:45:00+00:00","open_rate":0.04078902,"close_rate":0.0409934762406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020445624060149575,"sell_reason":"roi","initial_stop_loss_abs":0.036710118,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036710118,"stop_loss_ratio":0.1,"min_rate":0.04078902,"max_rate":0.0409934762406015,"is_open":false,"buy_tag":null,"open_timestamp":1516878000000.0,"close_timestamp":1516880700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.602076124567475,"open_date":"2018-01-25 13:05:00+00:00","close_date":"2018-01-25 13:45:00+00:00","open_rate":2.89e-05,"close_rate":2.904486215538847e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4486215538846723e-07,"sell_reason":"roi","initial_stop_loss_abs":2.601e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.601e-05,"stop_loss_ratio":0.1,"min_rate":2.89e-05,"max_rate":2.904486215538847e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516885500000.0,"close_timestamp":1516887900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02432912439481303,"open_date":"2018-01-25 13:20:00+00:00","close_date":"2018-01-25 14:05:00+00:00","open_rate":0.041103,"close_rate":0.04130903007518797,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020603007518796984,"sell_reason":"roi","initial_stop_loss_abs":0.0369927,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0369927,"stop_loss_ratio":0.1,"min_rate":0.041103,"max_rate":0.04130903007518797,"is_open":false,"buy_tag":null,"open_timestamp":1516886400000.0,"close_timestamp":1516889100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.422991893883566,"open_date":"2018-01-25 15:45:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":5.428e-05,"close_rate":5.509624060150376e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.162406015037611e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8852000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8852000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.428e-05,"max_rate":5.509624060150376e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516895100000.0,"close_timestamp":1516896900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.47063169560399,"open_date":"2018-01-25 17:45:00+00:00","close_date":"2018-01-25 23:15:00+00:00","open_rate":5.414e-05,"close_rate":5.441137844611528e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":330,"profit_ratio":-0.0,"profit_abs":2.713784461152774e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8726e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8726e-05,"stop_loss_ratio":0.1,"min_rate":5.414e-05,"max_rate":5.441137844611528e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516902300000.0,"close_timestamp":1516922100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024150056861308878,"open_date":"2018-01-25 21:15:00+00:00","close_date":"2018-01-25 21:55:00+00:00","open_rate":0.04140777,"close_rate":0.0416153277443609,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002075577443608964,"sell_reason":"roi","initial_stop_loss_abs":0.037266993000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.037266993000000005,"stop_loss_ratio":0.1,"min_rate":0.04140777,"max_rate":0.0416153277443609,"is_open":false,"buy_tag":null,"open_timestamp":1516914900000.0,"close_timestamp":1516917300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3932224183965176,"open_date":"2018-01-26 02:05:00+00:00","close_date":"2018-01-26 02:45:00+00:00","open_rate":0.00254309,"close_rate":0.002555837318295739,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.2747318295739177e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002288781,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002288781,"stop_loss_ratio":0.1,"min_rate":0.00254309,"max_rate":0.002555837318295739,"is_open":false,"buy_tag":null,"open_timestamp":1516932300000.0,"close_timestamp":1516934700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.834849295523455,"open_date":"2018-01-26 02:55:00+00:00","close_date":"2018-01-26 15:10:00+00:00","open_rate":5.607e-05,"close_rate":5.6351052631578935e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":735,"profit_ratio":-0.0,"profit_abs":2.810526315789381e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0463e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0463e-05,"stop_loss_ratio":0.1,"min_rate":5.607e-05,"max_rate":5.6351052631578935e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516935300000.0,"close_timestamp":1516979400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.39400171784748983,"open_date":"2018-01-26 06:10:00+00:00","close_date":"2018-01-26 09:25:00+00:00","open_rate":0.00253806,"close_rate":0.0025507821052631577,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":195,"profit_ratio":0.0,"profit_abs":1.2722105263157733e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002284254,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002284254,"stop_loss_ratio":0.1,"min_rate":0.00253806,"max_rate":0.0025507821052631577,"is_open":false,"buy_tag":null,"open_timestamp":1516947000000.0,"close_timestamp":1516958700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024096385542168672,"open_date":"2018-01-26 07:25:00+00:00","close_date":"2018-01-26 09:55:00+00:00","open_rate":0.0415,"close_rate":0.04170802005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":150,"profit_ratio":-0.0,"profit_abs":0.00020802005012530989,"sell_reason":"roi","initial_stop_loss_abs":0.03735,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03735,"stop_loss_ratio":0.1,"min_rate":0.0415,"max_rate":0.04170802005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516951500000.0,"close_timestamp":1516960500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.793459875963165,"open_date":"2018-01-26 09:55:00+00:00","close_date":"2018-01-26 10:25:00+00:00","open_rate":5.321e-05,"close_rate":5.401015037593984e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.00150375939842e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7889e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7889e-05,"stop_loss_ratio":0.1,"min_rate":5.321e-05,"max_rate":5.401015037593984e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516960500000.0,"close_timestamp":1516962300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.036074437437185386,"open_date":"2018-01-26 16:05:00+00:00","close_date":"2018-01-26 16:45:00+00:00","open_rate":0.02772046,"close_rate":0.02785940967418546,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013894967418546025,"sell_reason":"roi","initial_stop_loss_abs":0.024948414,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024948414,"stop_loss_ratio":0.1,"min_rate":0.02772046,"max_rate":0.02785940967418546,"is_open":false,"buy_tag":null,"open_timestamp":1516982700000.0,"close_timestamp":1516985100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010569326272036914,"open_date":"2018-01-26 23:35:00+00:00","close_date":"2018-01-27 00:15:00+00:00","open_rate":0.09461341,"close_rate":0.09508766268170424,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00047425268170424306,"sell_reason":"roi","initial_stop_loss_abs":0.085152069,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085152069,"stop_loss_ratio":0.1,"min_rate":0.09461341,"max_rate":0.09508766268170424,"is_open":false,"buy_tag":null,"open_timestamp":1517009700000.0,"close_timestamp":1517012100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":17.809439002671414,"open_date":"2018-01-27 00:35:00+00:00","close_date":"2018-01-27 01:30:00+00:00","open_rate":5.615e-05,"close_rate":5.643145363408521e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.814536340852038e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0535e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0535e-05,"stop_loss_ratio":0.1,"min_rate":5.615e-05,"max_rate":5.643145363408521e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013300000.0,"close_timestamp":1517016600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.998560115190784,"open_date":"2018-01-27 00:45:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":5.556e-05,"close_rate":5.144e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4560,"profit_ratio":-0.07877175,"profit_abs":-4.120000000000001e-06,"sell_reason":"force_sell","initial_stop_loss_abs":5.0004000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0004000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.144e-05,"max_rate":5.556e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013900000.0,"close_timestamp":1517287500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492751522789634,"open_date":"2018-01-27 02:30:00+00:00","close_date":"2018-01-27 11:25:00+00:00","open_rate":0.06900001,"close_rate":0.06934587471177944,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":535,"profit_ratio":-0.0,"profit_abs":0.0003458647117794422,"sell_reason":"roi","initial_stop_loss_abs":0.062100009000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.062100009000000005,"stop_loss_ratio":0.1,"min_rate":0.06900001,"max_rate":0.06934587471177944,"is_open":false,"buy_tag":null,"open_timestamp":1517020200000.0,"close_timestamp":1517052300000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010582027378879437,"open_date":"2018-01-27 06:25:00+00:00","close_date":"2018-01-27 07:05:00+00:00","open_rate":0.09449985,"close_rate":0.0949735334586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004736834586466093,"sell_reason":"roi","initial_stop_loss_abs":0.085049865,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085049865,"stop_loss_ratio":0.1,"min_rate":0.09449985,"max_rate":0.0949735334586466,"is_open":false,"buy_tag":null,"open_timestamp":1517034300000.0,"close_timestamp":1517036700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02434885085598385,"open_date":"2018-01-27 09:40:00+00:00","close_date":"2018-01-30 04:40:00+00:00","open_rate":0.0410697,"close_rate":0.03928809,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4020,"profit_ratio":-0.04815133,"profit_abs":-0.001781610000000003,"sell_reason":"force_sell","initial_stop_loss_abs":0.03696273,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03696273,"stop_loss_ratio":0.1,"min_rate":0.03928809,"max_rate":0.0410697,"is_open":false,"buy_tag":null,"open_timestamp":1517046000000.0,"close_timestamp":1517287200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03508771929824561,"open_date":"2018-01-27 11:45:00+00:00","close_date":"2018-01-27 12:30:00+00:00","open_rate":0.0285,"close_rate":0.02864285714285714,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00014285714285713902,"sell_reason":"roi","initial_stop_loss_abs":0.025650000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025650000000000003,"stop_loss_ratio":0.1,"min_rate":0.0285,"max_rate":0.02864285714285714,"is_open":false,"buy_tag":null,"open_timestamp":1517053500000.0,"close_timestamp":1517056200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.034887307020861215,"open_date":"2018-01-27 12:35:00+00:00","close_date":"2018-01-27 15:25:00+00:00","open_rate":0.02866372,"close_rate":0.02880739779448621,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":170,"profit_ratio":-0.0,"profit_abs":0.00014367779448621124,"sell_reason":"roi","initial_stop_loss_abs":0.025797348,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025797348,"stop_loss_ratio":0.1,"min_rate":0.02866372,"max_rate":0.02880739779448621,"is_open":false,"buy_tag":null,"open_timestamp":1517056500000.0,"close_timestamp":1517066700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010484268355332824,"open_date":"2018-01-27 15:50:00+00:00","close_date":"2018-01-27 16:50:00+00:00","open_rate":0.095381,"close_rate":0.09585910025062656,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.00047810025062657024,"sell_reason":"roi","initial_stop_loss_abs":0.0858429,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0858429,"stop_loss_ratio":0.1,"min_rate":0.095381,"max_rate":0.09585910025062656,"is_open":false,"buy_tag":null,"open_timestamp":1517068200000.0,"close_timestamp":1517071800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014794886650455417,"open_date":"2018-01-27 17:05:00+00:00","close_date":"2018-01-27 17:45:00+00:00","open_rate":0.06759092,"close_rate":0.06792972160401002,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00033880160401002224,"sell_reason":"roi","initial_stop_loss_abs":0.060831828,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060831828,"stop_loss_ratio":0.1,"min_rate":0.06759092,"max_rate":0.06792972160401002,"is_open":false,"buy_tag":null,"open_timestamp":1517072700000.0,"close_timestamp":1517075100000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38684569885609726,"open_date":"2018-01-27 23:40:00+00:00","close_date":"2018-01-28 01:05:00+00:00","open_rate":0.00258501,"close_rate":0.002597967443609022,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":-0.0,"profit_abs":1.2957443609021985e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326509,"stop_loss_ratio":0.1,"min_rate":0.00258501,"max_rate":0.002597967443609022,"is_open":false,"buy_tag":null,"open_timestamp":1517096400000.0,"close_timestamp":1517101500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014928710926711672,"open_date":"2018-01-28 02:25:00+00:00","close_date":"2018-01-28 08:10:00+00:00","open_rate":0.06698502,"close_rate":0.0673207845112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":345,"profit_ratio":-0.0,"profit_abs":0.00033576451127818874,"sell_reason":"roi","initial_stop_loss_abs":0.060286518000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060286518000000004,"stop_loss_ratio":0.1,"min_rate":0.06698502,"max_rate":0.0673207845112782,"is_open":false,"buy_tag":null,"open_timestamp":1517106300000.0,"close_timestamp":1517127000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014767187899175548,"open_date":"2018-01-28 10:25:00+00:00","close_date":"2018-01-28 16:30:00+00:00","open_rate":0.0677177,"close_rate":0.06805713709273183,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":365,"profit_ratio":-0.0,"profit_abs":0.0003394370927318202,"sell_reason":"roi","initial_stop_loss_abs":0.06094593000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06094593000000001,"stop_loss_ratio":0.1,"min_rate":0.0677177,"max_rate":0.06805713709273183,"is_open":false,"buy_tag":null,"open_timestamp":1517135100000.0,"close_timestamp":1517157000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":19.175455417066157,"open_date":"2018-01-28 20:35:00+00:00","close_date":"2018-01-28 21:35:00+00:00","open_rate":5.215e-05,"close_rate":5.2411403508771925e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":0.0,"profit_abs":2.6140350877192417e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6935000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6935000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.215e-05,"max_rate":5.2411403508771925e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517171700000.0,"close_timestamp":1517175300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.36521808998243305,"open_date":"2018-01-28 22:00:00+00:00","close_date":"2018-01-28 22:30:00+00:00","open_rate":0.00273809,"close_rate":0.002779264285714285,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.117428571428529e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002464281,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002464281,"stop_loss_ratio":0.1,"min_rate":0.00273809,"max_rate":0.002779264285714285,"is_open":false,"buy_tag":null,"open_timestamp":1517176800000.0,"close_timestamp":1517178600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3641236272539253,"open_date":"2018-01-29 00:00:00+00:00","close_date":"2018-01-29 00:30:00+00:00","open_rate":0.00274632,"close_rate":0.002787618045112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.129804511278194e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002471688,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002471688,"stop_loss_ratio":0.1,"min_rate":0.00274632,"max_rate":0.002787618045112782,"is_open":false,"buy_tag":null,"open_timestamp":1517184000000.0,"close_timestamp":1517185800000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.061634117689115045,"open_date":"2018-01-29 02:15:00+00:00","close_date":"2018-01-29 03:00:00+00:00","open_rate":0.01622478,"close_rate":0.016306107218045113,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":8.132721804511231e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014602302000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014602302000000001,"stop_loss_ratio":0.1,"min_rate":0.01622478,"max_rate":0.016306107218045113,"is_open":false,"buy_tag":null,"open_timestamp":1517192100000.0,"close_timestamp":1517194800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492753623188404,"open_date":"2018-01-29 03:05:00+00:00","close_date":"2018-01-29 03:45:00+00:00","open_rate":0.069,"close_rate":0.06934586466165413,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00034586466165412166,"sell_reason":"roi","initial_stop_loss_abs":0.06210000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06210000000000001,"stop_loss_ratio":0.1,"min_rate":0.069,"max_rate":0.06934586466165413,"is_open":false,"buy_tag":null,"open_timestamp":1517195100000.0,"close_timestamp":1517197500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.42204454597373,"open_date":"2018-01-29 05:20:00+00:00","close_date":"2018-01-29 06:55:00+00:00","open_rate":8.755e-05,"close_rate":8.798884711779448e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":95,"profit_ratio":-0.0,"profit_abs":4.3884711779447504e-07,"sell_reason":"roi","initial_stop_loss_abs":7.879500000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.879500000000001e-05,"stop_loss_ratio":0.1,"min_rate":8.755e-05,"max_rate":8.798884711779448e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517203200000.0,"close_timestamp":1517208900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014650376815016871,"open_date":"2018-01-29 07:00:00+00:00","close_date":"2018-01-29 19:25:00+00:00","open_rate":0.06825763,"close_rate":0.06859977350877192,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":745,"profit_ratio":-0.0,"profit_abs":0.00034214350877191657,"sell_reason":"roi","initial_stop_loss_abs":0.061431867,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061431867,"stop_loss_ratio":0.1,"min_rate":0.06825763,"max_rate":0.06859977350877192,"is_open":false,"buy_tag":null,"open_timestamp":1517209200000.0,"close_timestamp":1517253900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014894490408841846,"open_date":"2018-01-29 19:45:00+00:00","close_date":"2018-01-29 20:25:00+00:00","open_rate":0.06713892,"close_rate":0.06747545593984962,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0003365359398496137,"sell_reason":"roi","initial_stop_loss_abs":0.060425028000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060425028000000006,"stop_loss_ratio":0.1,"min_rate":0.06713892,"max_rate":0.06747545593984962,"is_open":false,"buy_tag":null,"open_timestamp":1517255100000.0,"close_timestamp":1517257500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.193194537721066,"open_date":"2018-01-29 23:30:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":8.934e-05,"close_rate":8.8e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":315,"profit_ratio":-0.0199116,"profit_abs":-1.3399999999999973e-06,"sell_reason":"force_sell","initial_stop_loss_abs":8.0406e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.0406e-05,"stop_loss_ratio":0.1,"min_rate":8.8e-05,"max_rate":8.934e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517268600000.0,"close_timestamp":1517287500000.0}],"locks":[],"best_pair":{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},"worst_pair":{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},"results_per_pair":[{"key":"ETH/BTC","trades":21,"profit_mean":0.0009500057142857142,"profit_mean_pct":0.09500057142857142,"profit_sum":0.01995012,"profit_sum_pct":2.0,"profit_total_abs":0.011505731278195264,"profit_total":1.1505731278195264,"profit_total_pct":115.06,"duration_avg":"2:17:00","wins":21,"draws":0,"losses":0},{"key":"DASH/BTC","trades":16,"profit_mean":0.0018703237499999997,"profit_mean_pct":0.18703237499999997,"profit_sum":0.029925179999999996,"profit_sum_pct":2.99,"profit_total_abs":0.007475052681704161,"profit_total":0.7475052681704161,"profit_total_pct":74.75,"duration_avg":"3:03:00","wins":16,"draws":0,"losses":0},{"key":"ZEC/BTC","trades":21,"profit_mean":-0.00039290904761904774,"profit_mean_pct":-0.03929090476190478,"profit_sum":-0.008251090000000003,"profit_sum_pct":-0.83,"profit_total_abs":0.004452605639097655,"profit_total":0.4452605639097655,"profit_total_pct":44.53,"duration_avg":"4:17:00","wins":20,"draws":0,"losses":1},{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},{"key":"ETC/BTC","trades":20,"profit_mean":0.0022568569999999997,"profit_mean_pct":0.22568569999999996,"profit_sum":0.04513713999999999,"profit_sum_pct":4.51,"profit_total_abs":0.00036538235338345404,"profit_total":0.0365382353383454,"profit_total_pct":3.65,"duration_avg":"1:45:00","wins":19,"draws":0,"losses":1},{"key":"TRX/BTC","trades":15,"profit_mean":0.0023467073333333323,"profit_mean_pct":0.23467073333333321,"profit_sum":0.035200609999999986,"profit_sum_pct":3.52,"profit_total_abs":1.1329523809523682e-05,"profit_total":0.0011329523809523682,"profit_total_pct":0.11,"duration_avg":"2:28:00","wins":13,"draws":0,"losses":2},{"key":"XLM/BTC","trades":21,"profit_mean":0.0026243899999999994,"profit_mean_pct":0.2624389999999999,"profit_sum":0.05511218999999999,"profit_sum_pct":5.51,"profit_total_abs":7.340779448621465e-06,"profit_total":0.0007340779448621465,"profit_total_pct":0.07,"duration_avg":"3:21:00","wins":20,"draws":0,"losses":1},{"key":"ADA/BTC","trades":29,"profit_mean":-0.0011598141379310352,"profit_mean_pct":-0.11598141379310352,"profit_sum":-0.03363461000000002,"profit_sum_pct":-3.36,"profit_total_abs":4.916634085212862e-06,"profit_total":0.0004916634085212862,"profit_total_pct":0.05,"duration_avg":"5:35:00","wins":27,"draws":0,"losses":2},{"key":"NXT/BTC","trades":12,"profit_mean":-0.0012261025000000006,"profit_mean_pct":-0.12261025000000006,"profit_sum":-0.014713230000000008,"profit_sum_pct":-1.47,"profit_total_abs":1.4774411027568458e-06,"profit_total":0.00014774411027568458,"profit_total_pct":0.01,"duration_avg":"0:57:00","wins":11,"draws":0,"losses":1},{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"results_per_buy_tag":[{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"sell_reason_summary":[{"sell_reason":"roi","trades":170,"wins":170,"draws":0,"losses":0,"profit_mean":0.005398268352941177,"profit_mean_pct":0.54,"profit_sum":0.91770562,"profit_sum_pct":91.77,"profit_total_abs":0.031232837493733862,"profit_total":0.30590187333333335,"profit_total_pct":30.59},{"sell_reason":"stop_loss","trades":6,"wins":0,"draws":0,"losses":6,"profit_mean":-0.10448878000000002,"profit_mean_pct":-10.45,"profit_sum":-0.6269326800000001,"profit_sum_pct":-62.69,"profit_total_abs":-0.0033602680000000026,"profit_total":-0.20897756000000003,"profit_total_pct":-20.9},{"sell_reason":"force_sell","trades":3,"wins":0,"draws":0,"losses":3,"profit_mean":-0.04894489333333333,"profit_mean_pct":-4.89,"profit_sum":-0.14683468,"profit_sum_pct":-14.68,"profit_total_abs":-0.001787070000000003,"profit_total":-0.04894489333333333,"profit_total_pct":-4.89}],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":179,"total_volume":0.17900000000000005,"avg_stake_amount":0.0010000000000000002,"profit_mean":0.0008041243575418989,"profit_median":0.0,"profit_total":2.6085499493733857,"profit_total_abs":0.026085499493733857,"backtest_start":"2018-01-10 07:15:00","backtest_start_ts":1515568500000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":"2020-10-01 18:00:00+00:00","backtest_run_end_ts":"2020-10-01 18:01:00+00:00","trades_per_day":9.42,"market_change":1.22,"pairlist":[],"stake_amount":0.001,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":0.01,"dry_run_wallet":0.01,"final_balance":0.03608549949373386,"rejected_signals":0,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timeframe_detail":"","timerange":"","enable_protections":false,"strategy_name":"StrategyTestV2","stoploss":0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":false,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.17955111999999998,"backtest_worst_day":-0.14683468,"backtest_best_day_abs":0.0071570099,"backtest_worst_day_abs":-0.0023093218,"winning_days":19,"draw_days":0,"losing_days":2,"daily_profit":[["2018-01-10",0.0025815306],["2018-01-11",0.0049356655],["2018-01-12",0.0006395218],["2018-01-13",0.0002574589],["2018-01-14",0.0010443828],["2018-01-15",0.0024030209],["2018-01-16",0.0071570099],["2018-01-17",0.001137038],["2018-01-18",0.0013712174],["2018-01-19",0.000584673],["2018-01-20",0.0006143386],["2018-01-21",0.0004749361],["2018-01-22",9.91669e-05],["2018-01-23",0.0015726664],["2018-01-24",0.0006610219],["2018-01-25",-0.0023093218],["2018-01-26",0.0003735204],["2018-01-27",0.0023975191],["2018-01-28",0.0007295947],["2018-01-29",0.0011476082],["2018-01-30",-0.00178707]],"wins":48,"losses":9,"draws":122,"holding_avg":"3:40:00","holding_avg_s":13200.0,"winner_holding_avg":"0:24:00","winner_holding_avg_s":1440.0,"loser_holding_avg":"1 day, 5:57:00","loser_holding_avg_s":107820.0,"max_drawdown":0.21142322000000008,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":0.0030822220000000025,"drawdown_start":"2018-01-25 01:30:00","drawdown_start_ts":1516843800000.0,"drawdown_end":"2018-01-25 03:50:00","drawdown_end_ts":1516852200000.0,"max_drawdown_low":0.02245167355388436,"max_drawdown_high":0.025533895553884363,"csum_min":0.01000434887218045,"csum_max":0.03608683949373386}},"strategy_comparison":[{"key":"StrategyTestV2","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":"0.00308222"},{"key":"TestStrategy","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":"0.00308222"}]}
\ No newline at end of file
+{"strategy":{"StrategyTestV2":{"trades":[{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.37344398340249,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:20:00+00:00","open_rate":9.64e-05,"close_rate":0.00010074887218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":4.348872180451118e-06,"sell_reason":"roi","initial_stop_loss_abs":8.676e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.676e-05,"stop_loss_ratio":0.1,"min_rate":9.64e-05,"max_rate":0.00010074887218045112,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515568800000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":21.026072329688816,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:30:00+00:00","open_rate":4.756e-05,"close_rate":4.9705563909774425e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":2.1455639097744267e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2804e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2804e-05,"stop_loss_ratio":0.1,"min_rate":4.756e-05,"max_rate":4.9705563909774425e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515569400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.94908655286014,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:35:00+00:00","open_rate":3.339e-05,"close_rate":3.489631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":1.506315789473681e-06,"sell_reason":"roi","initial_stop_loss_abs":3.0050999999999997e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0050999999999997e-05,"stop_loss_ratio":0.1,"min_rate":3.339e-05,"max_rate":3.489631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515569700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.313531353135314,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:40:00+00:00","open_rate":9.696e-05,"close_rate":0.00010133413533834584,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":4.3741353383458455e-06,"sell_reason":"roi","initial_stop_loss_abs":8.7264e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.7264e-05,"stop_loss_ratio":0.1,"min_rate":9.696e-05,"max_rate":0.00010133413533834584,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515570000000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010604453870625663,"open_date":"2018-01-10 07:35:00+00:00","close_date":"2018-01-10 08:35:00+00:00","open_rate":0.0943,"close_rate":0.09477268170426063,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.0004726817042606385,"sell_reason":"roi","initial_stop_loss_abs":0.08487,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08487,"stop_loss_ratio":0.1,"min_rate":0.0943,"max_rate":0.09477268170426063,"is_open":false,"buy_tag":null,"open_timestamp":1515569700000.0,"close_timestamp":1515573300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03677001860930642,"open_date":"2018-01-10 07:40:00+00:00","close_date":"2018-01-10 08:10:00+00:00","open_rate":0.02719607,"close_rate":0.02760503345864661,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00040896345864661204,"sell_reason":"roi","initial_stop_loss_abs":0.024476463,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024476463,"stop_loss_ratio":0.1,"min_rate":0.02719607,"max_rate":0.02760503345864661,"is_open":false,"buy_tag":null,"open_timestamp":1515570000000.0,"close_timestamp":1515571800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021575196463739,"open_date":"2018-01-10 08:15:00+00:00","close_date":"2018-01-10 09:55:00+00:00","open_rate":0.04634952,"close_rate":0.046581848421052625,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":100,"profit_ratio":0.0,"profit_abs":0.0002323284210526272,"sell_reason":"roi","initial_stop_loss_abs":0.041714568,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041714568,"stop_loss_ratio":0.1,"min_rate":0.04634952,"max_rate":0.046581848421052625,"is_open":false,"buy_tag":null,"open_timestamp":1515572100000.0,"close_timestamp":1515578100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.615786040443574,"open_date":"2018-01-10 14:45:00+00:00","close_date":"2018-01-10 15:50:00+00:00","open_rate":3.066e-05,"close_rate":3.081368421052631e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":1.5368421052630647e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7594e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7594e-05,"stop_loss_ratio":0.1,"min_rate":3.066e-05,"max_rate":3.081368421052631e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515595500000.0,"close_timestamp":1515599400000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.05917194776300452,"open_date":"2018-01-10 16:35:00+00:00","close_date":"2018-01-10 17:15:00+00:00","open_rate":0.0168999,"close_rate":0.016984611278195488,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.471127819548868e-05,"sell_reason":"roi","initial_stop_loss_abs":0.01520991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01520991,"stop_loss_ratio":0.1,"min_rate":0.0168999,"max_rate":0.016984611278195488,"is_open":false,"buy_tag":null,"open_timestamp":1515602100000.0,"close_timestamp":1515604500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010949822656672253,"open_date":"2018-01-10 16:40:00+00:00","close_date":"2018-01-10 17:20:00+00:00","open_rate":0.09132568,"close_rate":0.0917834528320802,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004577728320801916,"sell_reason":"roi","initial_stop_loss_abs":0.08219311200000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08219311200000001,"stop_loss_ratio":0.1,"min_rate":0.09132568,"max_rate":0.0917834528320802,"is_open":false,"buy_tag":null,"open_timestamp":1515602400000.0,"close_timestamp":1515604800000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011238476768326556,"open_date":"2018-01-10 18:50:00+00:00","close_date":"2018-01-10 19:45:00+00:00","open_rate":0.08898003,"close_rate":0.08942604518796991,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00044601518796991146,"sell_reason":"roi","initial_stop_loss_abs":0.080082027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080082027,"stop_loss_ratio":0.1,"min_rate":0.08898003,"max_rate":0.08942604518796991,"is_open":false,"buy_tag":null,"open_timestamp":1515610200000.0,"close_timestamp":1515613500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011682232072680307,"open_date":"2018-01-10 22:15:00+00:00","close_date":"2018-01-10 23:00:00+00:00","open_rate":0.08560008,"close_rate":0.08602915308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":0.00042907308270676014,"sell_reason":"roi","initial_stop_loss_abs":0.077040072,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077040072,"stop_loss_ratio":0.1,"min_rate":0.08560008,"max_rate":0.08602915308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515622500000.0,"close_timestamp":1515625200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4014726015023105,"open_date":"2018-01-10 22:50:00+00:00","close_date":"2018-01-10 23:20:00+00:00","open_rate":0.00249083,"close_rate":0.0025282860902255634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.745609022556351e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002241747,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002241747,"stop_loss_ratio":0.1,"min_rate":0.00249083,"max_rate":0.0025282860902255634,"is_open":false,"buy_tag":null,"open_timestamp":1515624600000.0,"close_timestamp":1515626400000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.090668431502316,"open_date":"2018-01-10 23:15:00+00:00","close_date":"2018-01-11 00:15:00+00:00","open_rate":3.022e-05,"close_rate":3.037147869674185e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":1.5147869674185174e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7198e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7198e-05,"stop_loss_ratio":0.1,"min_rate":3.022e-05,"max_rate":3.037147869674185e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515626100000.0,"close_timestamp":1515629700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.41034058268362744,"open_date":"2018-01-10 23:40:00+00:00","close_date":"2018-01-11 00:05:00+00:00","open_rate":0.002437,"close_rate":0.0024980776942355883,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":6.107769423558838e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0021933,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0021933,"stop_loss_ratio":0.1,"min_rate":0.002437,"max_rate":0.0024980776942355883,"is_open":false,"buy_tag":null,"open_timestamp":1515627600000.0,"close_timestamp":1515629100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02095643931654345,"open_date":"2018-01-11 00:00:00+00:00","close_date":"2018-01-11 00:35:00+00:00","open_rate":0.04771803,"close_rate":0.04843559436090225,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0007175643609022495,"sell_reason":"roi","initial_stop_loss_abs":0.042946227000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.042946227000000003,"stop_loss_ratio":0.1,"min_rate":0.04771803,"max_rate":0.04843559436090225,"is_open":false,"buy_tag":null,"open_timestamp":1515628800000.0,"close_timestamp":1515630900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.389756231169542,"open_date":"2018-01-11 03:40:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":3.651e-05,"close_rate":3.2859000000000005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.10448878,"profit_abs":-3.650999999999996e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":3.2859000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2859000000000005e-05,"stop_loss_ratio":0.1,"min_rate":3.2859000000000005e-05,"max_rate":3.651e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515642000000.0,"close_timestamp":1515644700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011332594070446804,"open_date":"2018-01-11 03:55:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":0.08824105,"close_rate":0.08956798308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013269330827067605,"sell_reason":"roi","initial_stop_loss_abs":0.079416945,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079416945,"stop_loss_ratio":0.1,"min_rate":0.08824105,"max_rate":0.08956798308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515642900000.0,"close_timestamp":1515644700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.411522633744856,"open_date":"2018-01-11 04:00:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":0.00243,"close_rate":0.002442180451127819,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":1.2180451127819219e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002187,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002187,"stop_loss_ratio":0.1,"min_rate":0.00243,"max_rate":0.002442180451127819,"is_open":false,"buy_tag":null,"open_timestamp":1515643200000.0,"close_timestamp":1515646200000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022001890402423376,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:55:00+00:00","open_rate":0.04545064,"close_rate":0.046589753784461146,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":0.001139113784461146,"sell_reason":"roi","initial_stop_loss_abs":0.040905576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040905576,"stop_loss_ratio":0.1,"min_rate":0.04545064,"max_rate":0.046589753784461146,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.655990510083036,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":3.372e-05,"close_rate":3.456511278195488e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":8.4511278195488e-07,"sell_reason":"roi","initial_stop_loss_abs":3.0348e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0348e-05,"stop_loss_ratio":0.1,"min_rate":3.372e-05,"max_rate":3.456511278195488e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037821482602118005,"open_date":"2018-01-11 04:55:00+00:00","close_date":"2018-01-11 05:15:00+00:00","open_rate":0.02644,"close_rate":0.02710265664160401,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0006626566416040071,"sell_reason":"roi","initial_stop_loss_abs":0.023796,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023796,"stop_loss_ratio":0.1,"min_rate":0.02644,"max_rate":0.02710265664160401,"is_open":false,"buy_tag":null,"open_timestamp":1515646500000.0,"close_timestamp":1515647700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011348161597821153,"open_date":"2018-01-11 11:20:00+00:00","close_date":"2018-01-11 12:00:00+00:00","open_rate":0.08812,"close_rate":0.08856170426065162,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004417042606516125,"sell_reason":"roi","initial_stop_loss_abs":0.079308,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079308,"stop_loss_ratio":0.1,"min_rate":0.08812,"max_rate":0.08856170426065162,"is_open":false,"buy_tag":null,"open_timestamp":1515669600000.0,"close_timestamp":1515672000000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037263696923919086,"open_date":"2018-01-11 11:35:00+00:00","close_date":"2018-01-11 12:15:00+00:00","open_rate":0.02683577,"close_rate":0.026970285137844607,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013451513784460897,"sell_reason":"roi","initial_stop_loss_abs":0.024152193,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024152193,"stop_loss_ratio":0.1,"min_rate":0.02683577,"max_rate":0.026970285137844607,"is_open":false,"buy_tag":null,"open_timestamp":1515670500000.0,"close_timestamp":1515672900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.329335230737954,"open_date":"2018-01-11 14:00:00+00:00","close_date":"2018-01-11 14:25:00+00:00","open_rate":4.919e-05,"close_rate":5.04228320802005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.232832080200495e-06,"sell_reason":"roi","initial_stop_loss_abs":4.4271000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4271000000000004e-05,"stop_loss_ratio":0.1,"min_rate":4.919e-05,"max_rate":5.04228320802005e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515679200000.0,"close_timestamp":1515680700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.01138317402960718,"open_date":"2018-01-11 19:25:00+00:00","close_date":"2018-01-11 20:35:00+00:00","open_rate":0.08784896,"close_rate":0.08828930566416039,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":70,"profit_ratio":-0.0,"profit_abs":0.0004403456641603881,"sell_reason":"roi","initial_stop_loss_abs":0.079064064,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079064064,"stop_loss_ratio":0.1,"min_rate":0.08784896,"max_rate":0.08828930566416039,"is_open":false,"buy_tag":null,"open_timestamp":1515698700000.0,"close_timestamp":1515702900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.58863858961802,"open_date":"2018-01-11 22:35:00+00:00","close_date":"2018-01-11 23:30:00+00:00","open_rate":5.105e-05,"close_rate":5.130588972431077e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.558897243107704e-07,"sell_reason":"roi","initial_stop_loss_abs":4.5945e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.5945e-05,"stop_loss_ratio":0.1,"min_rate":5.105e-05,"max_rate":5.130588972431077e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515710100000.0,"close_timestamp":1515713400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.252525252525253,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:25:00+00:00","open_rate":3.96e-05,"close_rate":4.019548872180451e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":5.954887218045116e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5640000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5640000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.96e-05,"max_rate":4.019548872180451e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.66204506065858,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:35:00+00:00","open_rate":2.885e-05,"close_rate":2.899461152882205e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4461152882205115e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5965e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5965e-05,"stop_loss_ratio":0.1,"min_rate":2.885e-05,"max_rate":2.899461152882205e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03780718336483932,"open_date":"2018-01-11 23:30:00+00:00","close_date":"2018-01-12 00:05:00+00:00","open_rate":0.02645,"close_rate":0.026847744360902256,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0003977443609022545,"sell_reason":"roi","initial_stop_loss_abs":0.023805000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023805000000000003,"stop_loss_ratio":0.1,"min_rate":0.02645,"max_rate":0.026847744360902256,"is_open":false,"buy_tag":null,"open_timestamp":1515713400000.0,"close_timestamp":1515715500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020833333333333332,"open_date":"2018-01-11 23:55:00+00:00","close_date":"2018-01-12 01:15:00+00:00","open_rate":0.048,"close_rate":0.04824060150375939,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00024060150375938838,"sell_reason":"roi","initial_stop_loss_abs":0.0432,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0432,"stop_loss_ratio":0.1,"min_rate":0.048,"max_rate":0.04824060150375939,"is_open":false,"buy_tag":null,"open_timestamp":1515714900000.0,"close_timestamp":1515719700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.31287297527707,"open_date":"2018-01-12 21:15:00+00:00","close_date":"2018-01-12 21:40:00+00:00","open_rate":4.692e-05,"close_rate":4.809593984962405e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1759398496240516e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2227999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2227999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.692e-05,"max_rate":4.809593984962405e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515791700000.0,"close_timestamp":1515793200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38915654211062944,"open_date":"2018-01-13 00:55:00+00:00","close_date":"2018-01-13 06:20:00+00:00","open_rate":0.00256966,"close_rate":0.0025825405012531327,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":325,"profit_ratio":-0.0,"profit_abs":1.2880501253132587e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002312694,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002312694,"stop_loss_ratio":0.1,"min_rate":0.00256966,"max_rate":0.0025825405012531327,"is_open":false,"buy_tag":null,"open_timestamp":1515804900000.0,"close_timestamp":1515824400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":15.96933886937081,"open_date":"2018-01-13 10:55:00+00:00","close_date":"2018-01-13 11:35:00+00:00","open_rate":6.262e-05,"close_rate":6.293388471177944e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":3.138847117794446e-07,"sell_reason":"roi","initial_stop_loss_abs":5.6358e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.6358e-05,"stop_loss_ratio":0.1,"min_rate":6.262e-05,"max_rate":6.293388471177944e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515840900000.0,"close_timestamp":1515843300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.141649048625794,"open_date":"2018-01-13 13:05:00+00:00","close_date":"2018-01-15 14:10:00+00:00","open_rate":4.73e-05,"close_rate":4.753709273182957e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":2945,"profit_ratio":0.0,"profit_abs":2.3709273182957117e-07,"sell_reason":"roi","initial_stop_loss_abs":4.257e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.257e-05,"stop_loss_ratio":0.1,"min_rate":4.73e-05,"max_rate":4.753709273182957e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515848700000.0,"close_timestamp":1516025400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.49348507339601,"open_date":"2018-01-13 13:30:00+00:00","close_date":"2018-01-13 14:45:00+00:00","open_rate":6.063e-05,"close_rate":6.0933909774436085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":3.039097744360846e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4567e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4567e-05,"stop_loss_ratio":0.1,"min_rate":6.063e-05,"max_rate":6.0933909774436085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515850200000.0,"close_timestamp":1515854700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.023641941887746,"open_date":"2018-01-13 13:40:00+00:00","close_date":"2018-01-13 23:30:00+00:00","open_rate":0.00011082,"close_rate":0.00011137548872180448,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":590,"profit_ratio":-0.0,"profit_abs":5.554887218044781e-07,"sell_reason":"roi","initial_stop_loss_abs":9.9738e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.9738e-05,"stop_loss_ratio":0.1,"min_rate":0.00011082,"max_rate":0.00011137548872180448,"is_open":false,"buy_tag":null,"open_timestamp":1515850800000.0,"close_timestamp":1515886200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.863406408094438,"open_date":"2018-01-13 15:15:00+00:00","close_date":"2018-01-13 15:55:00+00:00","open_rate":5.93e-05,"close_rate":5.9597243107769415e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.9724310776941686e-07,"sell_reason":"roi","initial_stop_loss_abs":5.337e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.337e-05,"stop_loss_ratio":0.1,"min_rate":5.93e-05,"max_rate":5.9597243107769415e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515856500000.0,"close_timestamp":1515858900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020618543947292404,"open_date":"2018-01-13 16:30:00+00:00","close_date":"2018-01-13 17:10:00+00:00","open_rate":0.04850003,"close_rate":0.04874313791979949,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00024310791979949287,"sell_reason":"roi","initial_stop_loss_abs":0.043650027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.043650027,"stop_loss_ratio":0.1,"min_rate":0.04850003,"max_rate":0.04874313791979949,"is_open":false,"buy_tag":null,"open_timestamp":1515861000000.0,"close_timestamp":1515863400000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010178097365511457,"open_date":"2018-01-13 22:05:00+00:00","close_date":"2018-01-14 06:25:00+00:00","open_rate":0.09825019,"close_rate":0.09874267215538848,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":500,"profit_ratio":-0.0,"profit_abs":0.0004924821553884823,"sell_reason":"roi","initial_stop_loss_abs":0.088425171,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.088425171,"stop_loss_ratio":0.1,"min_rate":0.09825019,"max_rate":0.09874267215538848,"is_open":false,"buy_tag":null,"open_timestamp":1515881100000.0,"close_timestamp":1515911100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.616816218012627,"open_date":"2018-01-14 00:20:00+00:00","close_date":"2018-01-14 22:55:00+00:00","open_rate":6.018e-05,"close_rate":6.048165413533834e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":1355,"profit_ratio":0.0,"profit_abs":3.0165413533833987e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4162e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4162e-05,"stop_loss_ratio":0.1,"min_rate":6.018e-05,"max_rate":6.048165413533834e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515889200000.0,"close_timestamp":1515970500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010246952581919518,"open_date":"2018-01-14 12:45:00+00:00","close_date":"2018-01-14 13:25:00+00:00","open_rate":0.09758999,"close_rate":0.0980791628822055,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004891728822054991,"sell_reason":"roi","initial_stop_loss_abs":0.087830991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.087830991,"stop_loss_ratio":0.1,"min_rate":0.09758999,"max_rate":0.0980791628822055,"is_open":false,"buy_tag":null,"open_timestamp":1515933900000.0,"close_timestamp":1515936300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3215434083601286,"open_date":"2018-01-14 15:30:00+00:00","close_date":"2018-01-14 16:00:00+00:00","open_rate":0.00311,"close_rate":0.0031567669172932328,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.676691729323286e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002799,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002799,"stop_loss_ratio":0.1,"min_rate":0.00311,"max_rate":0.0031567669172932328,"is_open":false,"buy_tag":null,"open_timestamp":1515943800000.0,"close_timestamp":1515945600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.32010140812609433,"open_date":"2018-01-14 20:45:00+00:00","close_date":"2018-01-14 22:15:00+00:00","open_rate":0.00312401,"close_rate":0.003139669197994987,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":-0.0,"profit_abs":1.5659197994987058e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002811609,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002811609,"stop_loss_ratio":0.1,"min_rate":0.00312401,"max_rate":0.003139669197994987,"is_open":false,"buy_tag":null,"open_timestamp":1515962700000.0,"close_timestamp":1515968100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.057247866085791646,"open_date":"2018-01-14 23:35:00+00:00","close_date":"2018-01-15 00:30:00+00:00","open_rate":0.0174679,"close_rate":0.017555458395989976,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":8.755839598997492e-05,"sell_reason":"roi","initial_stop_loss_abs":0.015721110000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.015721110000000003,"stop_loss_ratio":0.1,"min_rate":0.0174679,"max_rate":0.017555458395989976,"is_open":false,"buy_tag":null,"open_timestamp":1515972900000.0,"close_timestamp":1515976200000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.013611282991367997,"open_date":"2018-01-14 23:45:00+00:00","close_date":"2018-01-15 00:25:00+00:00","open_rate":0.07346846,"close_rate":0.07383672295739348,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00036826295739347814,"sell_reason":"roi","initial_stop_loss_abs":0.066121614,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.066121614,"stop_loss_ratio":0.1,"min_rate":0.07346846,"max_rate":0.07383672295739348,"is_open":false,"buy_tag":null,"open_timestamp":1515973500000.0,"close_timestamp":1515975900000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010204706410596568,"open_date":"2018-01-15 02:25:00+00:00","close_date":"2018-01-15 03:05:00+00:00","open_rate":0.097994,"close_rate":0.09848519799498744,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004911979949874384,"sell_reason":"roi","initial_stop_loss_abs":0.0881946,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0881946,"stop_loss_ratio":0.1,"min_rate":0.097994,"max_rate":0.09848519799498744,"is_open":false,"buy_tag":null,"open_timestamp":1515983100000.0,"close_timestamp":1515985500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010353038616834042,"open_date":"2018-01-15 07:20:00+00:00","close_date":"2018-01-15 08:00:00+00:00","open_rate":0.09659,"close_rate":0.09707416040100247,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004841604010024786,"sell_reason":"roi","initial_stop_loss_abs":0.086931,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.086931,"stop_loss_ratio":0.1,"min_rate":0.09659,"max_rate":0.09707416040100247,"is_open":false,"buy_tag":null,"open_timestamp":1516000800000.0,"close_timestamp":1516003200000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.013016921998599,"open_date":"2018-01-15 08:20:00+00:00","close_date":"2018-01-15 08:55:00+00:00","open_rate":9.987e-05,"close_rate":0.00010137180451127818,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":1.501804511278178e-06,"sell_reason":"roi","initial_stop_loss_abs":8.9883e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.9883e-05,"stop_loss_ratio":0.1,"min_rate":9.987e-05,"max_rate":0.00010137180451127818,"is_open":false,"buy_tag":null,"open_timestamp":1516004400000.0,"close_timestamp":1516006500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010537752023511832,"open_date":"2018-01-15 12:10:00+00:00","close_date":"2018-01-16 02:50:00+00:00","open_rate":0.0948969,"close_rate":0.09537257368421052,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":880,"profit_ratio":0.0,"profit_abs":0.0004756736842105175,"sell_reason":"roi","initial_stop_loss_abs":0.08540721000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08540721000000001,"stop_loss_ratio":0.1,"min_rate":0.0948969,"max_rate":0.09537257368421052,"is_open":false,"buy_tag":null,"open_timestamp":1516018200000.0,"close_timestamp":1516071000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014084507042253523,"open_date":"2018-01-15 14:10:00+00:00","close_date":"2018-01-15 17:40:00+00:00","open_rate":0.071,"close_rate":0.07135588972431077,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":210,"profit_ratio":-0.0,"profit_abs":0.00035588972431077615,"sell_reason":"roi","initial_stop_loss_abs":0.0639,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0639,"stop_loss_ratio":0.1,"min_rate":0.071,"max_rate":0.07135588972431077,"is_open":false,"buy_tag":null,"open_timestamp":1516025400000.0,"close_timestamp":1516038000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021736763017766978,"open_date":"2018-01-15 14:30:00+00:00","close_date":"2018-01-15 15:10:00+00:00","open_rate":0.04600501,"close_rate":0.046235611553884705,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00023060155388470588,"sell_reason":"roi","initial_stop_loss_abs":0.041404509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041404509,"stop_loss_ratio":0.1,"min_rate":0.04600501,"max_rate":0.046235611553884705,"is_open":false,"buy_tag":null,"open_timestamp":1516026600000.0,"close_timestamp":1516029000000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.595465140919686,"open_date":"2018-01-15 18:10:00+00:00","close_date":"2018-01-15 19:25:00+00:00","open_rate":9.438e-05,"close_rate":9.485308270676693e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":4.7308270676692514e-07,"sell_reason":"roi","initial_stop_loss_abs":8.4942e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.4942e-05,"stop_loss_ratio":0.1,"min_rate":9.438e-05,"max_rate":9.485308270676693e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516039800000.0,"close_timestamp":1516044300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.032894726021471705,"open_date":"2018-01-15 18:35:00+00:00","close_date":"2018-01-15 19:15:00+00:00","open_rate":0.03040001,"close_rate":0.030552391002506264,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0001523810025062626,"sell_reason":"roi","initial_stop_loss_abs":0.027360009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027360009,"stop_loss_ratio":0.1,"min_rate":0.03040001,"max_rate":0.030552391002506264,"is_open":false,"buy_tag":null,"open_timestamp":1516041300000.0,"close_timestamp":1516043700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.13208840157615,"open_date":"2018-01-15 20:25:00+00:00","close_date":"2018-01-16 08:25:00+00:00","open_rate":5.837e-05,"close_rate":5.2533e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":720,"profit_ratio":-0.10448878,"profit_abs":-5.8369999999999985e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":5.2533e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2533e-05,"stop_loss_ratio":0.1,"min_rate":5.2533e-05,"max_rate":5.837e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516047900000.0,"close_timestamp":1516091100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021722130506560085,"open_date":"2018-01-15 20:40:00+00:00","close_date":"2018-01-15 22:00:00+00:00","open_rate":0.046036,"close_rate":0.04626675689223057,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00023075689223057277,"sell_reason":"roi","initial_stop_loss_abs":0.0414324,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0414324,"stop_loss_ratio":0.1,"min_rate":0.046036,"max_rate":0.04626675689223057,"is_open":false,"buy_tag":null,"open_timestamp":1516048800000.0,"close_timestamp":1516053600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.34861425832316545,"open_date":"2018-01-16 00:30:00+00:00","close_date":"2018-01-16 01:10:00+00:00","open_rate":0.0028685,"close_rate":0.0028828784461152877,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4378446115287727e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00258165,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00258165,"stop_loss_ratio":0.1,"min_rate":0.0028685,"max_rate":0.0028828784461152877,"is_open":false,"buy_tag":null,"open_timestamp":1516062600000.0,"close_timestamp":1516065000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014854967241083492,"open_date":"2018-01-16 01:15:00+00:00","close_date":"2018-01-16 02:35:00+00:00","open_rate":0.06731755,"close_rate":0.0676549813283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":0.0,"profit_abs":0.00033743132832080025,"sell_reason":"roi","initial_stop_loss_abs":0.060585795000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060585795000000005,"stop_loss_ratio":0.1,"min_rate":0.06731755,"max_rate":0.0676549813283208,"is_open":false,"buy_tag":null,"open_timestamp":1516065300000.0,"close_timestamp":1516070100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010848794492804754,"open_date":"2018-01-16 07:45:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":0.09217614,"close_rate":0.09263817578947368,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":0.0004620357894736804,"sell_reason":"roi","initial_stop_loss_abs":0.082958526,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082958526,"stop_loss_ratio":0.1,"min_rate":0.09217614,"max_rate":0.09263817578947368,"is_open":false,"buy_tag":null,"open_timestamp":1516088700000.0,"close_timestamp":1516092000000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06060606060606061,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:55:00+00:00","open_rate":0.0165,"close_rate":0.016913533834586467,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.00041353383458646656,"sell_reason":"roi","initial_stop_loss_abs":0.01485,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01485,"stop_loss_ratio":0.1,"min_rate":0.0165,"max_rate":0.016913533834586467,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":12.57387149503332,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":7.953e-05,"close_rate":8.311781954887218e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":3.587819548872171e-06,"sell_reason":"roi","initial_stop_loss_abs":7.157700000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.157700000000001e-05,"stop_loss_ratio":0.1,"min_rate":7.953e-05,"max_rate":8.311781954887218e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022122914915269236,"open_date":"2018-01-16 08:45:00+00:00","close_date":"2018-01-16 09:50:00+00:00","open_rate":0.045202,"close_rate":0.04542857644110275,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":0.00022657644110275071,"sell_reason":"roi","initial_stop_loss_abs":0.0406818,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0406818,"stop_loss_ratio":0.1,"min_rate":0.045202,"max_rate":0.04542857644110275,"is_open":false,"buy_tag":null,"open_timestamp":1516092300000.0,"close_timestamp":1516096200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.054878048780488,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:45:00+00:00","open_rate":5.248e-05,"close_rate":5.326917293233082e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.891729323308177e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7232e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7232e-05,"stop_loss_ratio":0.1,"min_rate":5.248e-05,"max_rate":5.326917293233082e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516095900000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03457434486802627,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:55:00+00:00","open_rate":0.02892318,"close_rate":0.02906815834586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0001449783458646603,"sell_reason":"roi","initial_stop_loss_abs":0.026030862000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.026030862000000002,"stop_loss_ratio":0.1,"min_rate":0.02892318,"max_rate":0.02906815834586466,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516096500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.38735944164405,"open_date":"2018-01-16 09:50:00+00:00","close_date":"2018-01-16 10:10:00+00:00","open_rate":5.158e-05,"close_rate":5.287273182957392e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.2927318295739246e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6422e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6422e-05,"stop_loss_ratio":0.1,"min_rate":5.158e-05,"max_rate":5.287273182957392e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516096200000.0,"close_timestamp":1516097400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035357778286929785,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:35:00+00:00","open_rate":0.02828232,"close_rate":0.02870761804511278,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042529804511277913,"sell_reason":"roi","initial_stop_loss_abs":0.025454088,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025454088,"stop_loss_ratio":0.1,"min_rate":0.02828232,"max_rate":0.02870761804511278,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516098900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022948496230938982,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:40:00+00:00","open_rate":0.04357584,"close_rate":0.044231115789473675,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0006552757894736777,"sell_reason":"roi","initial_stop_loss_abs":0.039218256,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039218256,"stop_loss_ratio":0.1,"min_rate":0.04357584,"max_rate":0.044231115789473675,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516099200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.64975755315181,"open_date":"2018-01-16 13:45:00+00:00","close_date":"2018-01-16 14:20:00+00:00","open_rate":5.362e-05,"close_rate":5.442631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":8.063157894736843e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8258e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8258e-05,"stop_loss_ratio":0.1,"min_rate":5.362e-05,"max_rate":5.442631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516110300000.0,"close_timestamp":1516112400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.86080724254998,"open_date":"2018-01-16 17:30:00+00:00","close_date":"2018-01-16 18:25:00+00:00","open_rate":5.302e-05,"close_rate":5.328576441102756e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.6576441102756397e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7718e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7718e-05,"stop_loss_ratio":0.1,"min_rate":5.302e-05,"max_rate":5.328576441102756e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516123800000.0,"close_timestamp":1516127100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010952903718828448,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:45:00+00:00","open_rate":0.09129999,"close_rate":0.09267292218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013729321804511196,"sell_reason":"roi","initial_stop_loss_abs":0.082169991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082169991,"stop_loss_ratio":0.1,"min_rate":0.09129999,"max_rate":0.09267292218045112,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516128300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":26.26050420168067,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:35:00+00:00","open_rate":3.808e-05,"close_rate":3.903438596491228e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":9.543859649122774e-07,"sell_reason":"roi","initial_stop_loss_abs":3.4272e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.4272e-05,"stop_loss_ratio":0.1,"min_rate":3.808e-05,"max_rate":3.903438596491228e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516127700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035574376772493324,"open_date":"2018-01-16 19:00:00+00:00","close_date":"2018-01-16 19:30:00+00:00","open_rate":0.02811012,"close_rate":0.028532828571428567,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042270857142856846,"sell_reason":"roi","initial_stop_loss_abs":0.025299108,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025299108,"stop_loss_ratio":0.1,"min_rate":0.02811012,"max_rate":0.028532828571428567,"is_open":false,"buy_tag":null,"open_timestamp":1516129200000.0,"close_timestamp":1516131000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.387028357567759,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":0.00258379,"close_rate":0.002325411,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.10448878,"profit_abs":-0.000258379,"sell_reason":"stop_loss","initial_stop_loss_abs":0.002325411,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002325411,"stop_loss_ratio":0.1,"min_rate":0.002325411,"max_rate":0.00258379,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516141500000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":39.07776475185619,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":2.559e-05,"close_rate":2.3031e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.10448878,"profit_abs":-2.5590000000000004e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":2.3031e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.3031e-05,"stop_loss_ratio":0.1,"min_rate":2.3031e-05,"max_rate":2.559e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516142700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":13.123359580052494,"open_date":"2018-01-16 21:35:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":7.62e-05,"close_rate":6.858e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.10448878,"profit_abs":-7.619999999999998e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":6.858e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.858e-05,"stop_loss_ratio":0.1,"min_rate":6.858e-05,"max_rate":7.62e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516138500000.0,"close_timestamp":1516141500000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4350777048780912,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:35:00+00:00","open_rate":0.00229844,"close_rate":0.002402129022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010368902255639091,"sell_reason":"roi","initial_stop_loss_abs":0.0020685960000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0020685960000000002,"stop_loss_ratio":0.1,"min_rate":0.00229844,"max_rate":0.002402129022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06622516556291391,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:40:00+00:00","open_rate":0.0151,"close_rate":0.015781203007518795,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":0.0006812030075187946,"sell_reason":"roi","initial_stop_loss_abs":0.013590000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.013590000000000001,"stop_loss_ratio":0.1,"min_rate":0.0151,"max_rate":0.015781203007518795,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.42431134269081283,"open_date":"2018-01-16 22:40:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":0.00235676,"close_rate":0.00246308,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010632000000000003,"sell_reason":"roi","initial_stop_loss_abs":0.002121084,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002121084,"stop_loss_ratio":0.1,"min_rate":0.00235676,"max_rate":0.00246308,"is_open":false,"buy_tag":null,"open_timestamp":1516142400000.0,"close_timestamp":1516142700000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01585559988076589,"open_date":"2018-01-16 22:45:00+00:00","close_date":"2018-01-16 23:05:00+00:00","open_rate":0.0630692,"close_rate":0.06464988170426066,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0015806817042606502,"sell_reason":"roi","initial_stop_loss_abs":0.056762280000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.056762280000000005,"stop_loss_ratio":0.1,"min_rate":0.0630692,"max_rate":0.06464988170426066,"is_open":false,"buy_tag":null,"open_timestamp":1516142700000.0,"close_timestamp":1516143900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":45.45454545454545,"open_date":"2018-01-16 22:50:00+00:00","close_date":"2018-01-16 22:55:00+00:00","open_rate":2.2e-05,"close_rate":2.299248120300751e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":9.924812030075114e-07,"sell_reason":"roi","initial_stop_loss_abs":1.98e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":1.98e-05,"stop_loss_ratio":0.1,"min_rate":2.2e-05,"max_rate":2.299248120300751e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516143000000.0,"close_timestamp":1516143300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.10454362685967,"open_date":"2018-01-17 03:30:00+00:00","close_date":"2018-01-17 04:00:00+00:00","open_rate":4.974e-05,"close_rate":5.048796992481203e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.479699248120277e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4766000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4766000000000005e-05,"stop_loss_ratio":0.1,"min_rate":4.974e-05,"max_rate":5.048796992481203e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516159800000.0,"close_timestamp":1516161600000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":14.068655036578503,"open_date":"2018-01-17 03:55:00+00:00","close_date":"2018-01-17 04:15:00+00:00","open_rate":7.108e-05,"close_rate":7.28614536340852e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.7814536340851996e-06,"sell_reason":"roi","initial_stop_loss_abs":6.3972e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.3972e-05,"stop_loss_ratio":0.1,"min_rate":7.108e-05,"max_rate":7.28614536340852e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516161300000.0,"close_timestamp":1516162500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.0231107002542177,"open_date":"2018-01-17 09:35:00+00:00","close_date":"2018-01-17 10:15:00+00:00","open_rate":0.04327,"close_rate":0.04348689223057644,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002168922305764362,"sell_reason":"roi","initial_stop_loss_abs":0.038943000000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.038943000000000005,"stop_loss_ratio":0.1,"min_rate":0.04327,"max_rate":0.04348689223057644,"is_open":false,"buy_tag":null,"open_timestamp":1516181700000.0,"close_timestamp":1516184100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.012007204322593,"open_date":"2018-01-17 10:20:00+00:00","close_date":"2018-01-17 17:00:00+00:00","open_rate":4.997e-05,"close_rate":5.022047619047618e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":400,"profit_ratio":-0.0,"profit_abs":2.504761904761831e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4973e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4973e-05,"stop_loss_ratio":0.1,"min_rate":4.997e-05,"max_rate":5.022047619047618e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516184400000.0,"close_timestamp":1516208400000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014626687444363738,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:25:00+00:00","open_rate":0.06836818,"close_rate":0.06871087764411027,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00034269764411026804,"sell_reason":"roi","initial_stop_loss_abs":0.061531362,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061531362,"stop_loss_ratio":0.1,"min_rate":0.06836818,"max_rate":0.06871087764411027,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516188300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.548209366391184,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:10:00+00:00","open_rate":3.63e-05,"close_rate":3.648195488721804e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.8195488721804031e-07,"sell_reason":"roi","initial_stop_loss_abs":3.2670000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2670000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.63e-05,"max_rate":3.648195488721804e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516187400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03558718861209965,"open_date":"2018-01-17 12:30:00+00:00","close_date":"2018-01-17 22:05:00+00:00","open_rate":0.0281,"close_rate":0.02824085213032581,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":575,"profit_ratio":-0.0,"profit_abs":0.0001408521303258095,"sell_reason":"roi","initial_stop_loss_abs":0.02529,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.02529,"stop_loss_ratio":0.1,"min_rate":0.0281,"max_rate":0.02824085213032581,"is_open":false,"buy_tag":null,"open_timestamp":1516192200000.0,"close_timestamp":1516226700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011559355963546878,"open_date":"2018-01-17 12:35:00+00:00","close_date":"2018-01-17 16:55:00+00:00","open_rate":0.08651001,"close_rate":0.08694364413533832,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":-0.0,"profit_abs":0.00043363413533832607,"sell_reason":"roi","initial_stop_loss_abs":0.077859009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077859009,"stop_loss_ratio":0.1,"min_rate":0.08651001,"max_rate":0.08694364413533832,"is_open":false,"buy_tag":null,"open_timestamp":1516192500000.0,"close_timestamp":1516208100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.752529735487308,"open_date":"2018-01-18 05:00:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":5.633e-05,"close_rate":5.6612355889724306e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.8235588972430847e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0697e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0697e-05,"stop_loss_ratio":0.1,"min_rate":5.633e-05,"max_rate":5.6612355889724306e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516251600000.0,"close_timestamp":1516254900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01430923457900944,"open_date":"2018-01-18 05:20:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":0.06988494,"close_rate":0.07093584135338346,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0010509013533834544,"sell_reason":"roi","initial_stop_loss_abs":0.06289644600000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06289644600000001,"stop_loss_ratio":0.1,"min_rate":0.06988494,"max_rate":0.07093584135338346,"is_open":false,"buy_tag":null,"open_timestamp":1516252800000.0,"close_timestamp":1516254900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.034265103697024,"open_date":"2018-01-18 07:35:00+00:00","close_date":"2018-01-18 08:15:00+00:00","open_rate":5.545e-05,"close_rate":5.572794486215538e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.779448621553787e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9905e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9905e-05,"stop_loss_ratio":0.1,"min_rate":5.545e-05,"max_rate":5.572794486215538e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516260900000.0,"close_timestamp":1516263300000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06121723118136401,"open_date":"2018-01-18 09:00:00+00:00","close_date":"2018-01-18 09:40:00+00:00","open_rate":0.01633527,"close_rate":0.016417151052631574,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":8.188105263157511e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014701743,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014701743,"stop_loss_ratio":0.1,"min_rate":0.01633527,"max_rate":0.016417151052631574,"is_open":false,"buy_tag":null,"open_timestamp":1516266000000.0,"close_timestamp":1516268400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3707356136045141,"open_date":"2018-01-18 16:40:00+00:00","close_date":"2018-01-18 17:20:00+00:00","open_rate":0.00269734,"close_rate":0.002710860501253133,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.3520501253133123e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002427606,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002427606,"stop_loss_ratio":0.1,"min_rate":0.00269734,"max_rate":0.002710860501253133,"is_open":false,"buy_tag":null,"open_timestamp":1516293600000.0,"close_timestamp":1516296000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.346368715083802,"open_date":"2018-01-18 18:05:00+00:00","close_date":"2018-01-18 18:30:00+00:00","open_rate":4.475e-05,"close_rate":4.587155388471177e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1215538847117757e-06,"sell_reason":"roi","initial_stop_loss_abs":4.0274999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.0274999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.475e-05,"max_rate":4.587155388471177e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516298700000.0,"close_timestamp":1516300200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":35.842293906810035,"open_date":"2018-01-18 18:25:00+00:00","close_date":"2018-01-18 18:55:00+00:00","open_rate":2.79e-05,"close_rate":2.8319548872180444e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.1954887218044365e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5110000000000002e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5110000000000002e-05,"stop_loss_ratio":0.1,"min_rate":2.79e-05,"max_rate":2.8319548872180444e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516299900000.0,"close_timestamp":1516301700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022525942001105574,"open_date":"2018-01-18 20:10:00+00:00","close_date":"2018-01-18 20:50:00+00:00","open_rate":0.04439326,"close_rate":0.04461578260651629,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00022252260651629135,"sell_reason":"roi","initial_stop_loss_abs":0.039953933999999997,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039953933999999997,"stop_loss_ratio":0.1,"min_rate":0.04439326,"max_rate":0.04461578260651629,"is_open":false,"buy_tag":null,"open_timestamp":1516306200000.0,"close_timestamp":1516308600000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.271714922048996,"open_date":"2018-01-18 21:30:00+00:00","close_date":"2018-01-19 00:35:00+00:00","open_rate":4.49e-05,"close_rate":4.51250626566416e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":185,"profit_ratio":0.0,"profit_abs":2.2506265664159932e-07,"sell_reason":"roi","initial_stop_loss_abs":4.041e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.041e-05,"stop_loss_ratio":0.1,"min_rate":4.49e-05,"max_rate":4.51250626566416e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516311000000.0,"close_timestamp":1516322100000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03502626970227671,"open_date":"2018-01-18 21:55:00+00:00","close_date":"2018-01-19 05:05:00+00:00","open_rate":0.02855,"close_rate":0.028693107769423555,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":430,"profit_ratio":-0.0,"profit_abs":0.00014310776942355607,"sell_reason":"roi","initial_stop_loss_abs":0.025695,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025695,"stop_loss_ratio":0.1,"min_rate":0.02855,"max_rate":0.028693107769423555,"is_open":false,"buy_tag":null,"open_timestamp":1516312500000.0,"close_timestamp":1516338300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.25327812284334,"open_date":"2018-01-18 22:10:00+00:00","close_date":"2018-01-18 22:50:00+00:00","open_rate":5.796e-05,"close_rate":5.8250526315789473e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.905263157894727e-07,"sell_reason":"roi","initial_stop_loss_abs":5.2164000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2164000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.796e-05,"max_rate":5.8250526315789473e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516313400000.0,"close_timestamp":1516315800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02303975994413319,"open_date":"2018-01-18 23:50:00+00:00","close_date":"2018-01-19 00:30:00+00:00","open_rate":0.04340323,"close_rate":0.04362079005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0002175600501253122,"sell_reason":"roi","initial_stop_loss_abs":0.039062907,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039062907,"stop_loss_ratio":0.1,"min_rate":0.04340323,"max_rate":0.04362079005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516319400000.0,"close_timestamp":1516321800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02244943545282195,"open_date":"2018-01-19 16:45:00+00:00","close_date":"2018-01-19 17:35:00+00:00","open_rate":0.04454455,"close_rate":0.04476783095238095,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0002232809523809512,"sell_reason":"roi","initial_stop_loss_abs":0.040090095000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040090095000000006,"stop_loss_ratio":0.1,"min_rate":0.04454455,"max_rate":0.04476783095238095,"is_open":false,"buy_tag":null,"open_timestamp":1516380300000.0,"close_timestamp":1516383300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.793594306049823,"open_date":"2018-01-19 17:15:00+00:00","close_date":"2018-01-19 19:55:00+00:00","open_rate":5.62e-05,"close_rate":5.648170426065162e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":160,"profit_ratio":-0.0,"profit_abs":2.817042606516199e-07,"sell_reason":"roi","initial_stop_loss_abs":5.058e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.058e-05,"stop_loss_ratio":0.1,"min_rate":5.62e-05,"max_rate":5.648170426065162e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382100000.0,"close_timestamp":1516391700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.046784973496194,"open_date":"2018-01-19 17:20:00+00:00","close_date":"2018-01-19 20:15:00+00:00","open_rate":4.339e-05,"close_rate":4.360749373433584e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.174937343358337e-07,"sell_reason":"roi","initial_stop_loss_abs":3.9051e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.9051e-05,"stop_loss_ratio":0.1,"min_rate":4.339e-05,"max_rate":4.360749373433584e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382400000.0,"close_timestamp":1516392900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.910802775024777,"open_date":"2018-01-20 04:45:00+00:00","close_date":"2018-01-20 17:35:00+00:00","open_rate":0.0001009,"close_rate":0.00010140576441102755,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":770,"profit_ratio":0.0,"profit_abs":5.057644110275549e-07,"sell_reason":"roi","initial_stop_loss_abs":9.081e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.081e-05,"stop_loss_ratio":0.1,"min_rate":0.0001009,"max_rate":0.00010140576441102755,"is_open":false,"buy_tag":null,"open_timestamp":1516423500000.0,"close_timestamp":1516469700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3696789338459548,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 15:15:00+00:00","open_rate":0.00270505,"close_rate":0.002718609147869674,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":625,"profit_ratio":-0.0,"profit_abs":1.3559147869673764e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002434545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002434545,"stop_loss_ratio":0.1,"min_rate":0.00270505,"max_rate":0.002718609147869674,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516461300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.033333311111125925,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 07:00:00+00:00","open_rate":0.03000002,"close_rate":0.030150396040100245,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":0.00015037604010024672,"sell_reason":"roi","initial_stop_loss_abs":0.027000018,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027000018,"stop_loss_ratio":0.1,"min_rate":0.03000002,"max_rate":0.030150396040100245,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516431600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.315018315018317,"open_date":"2018-01-20 09:00:00+00:00","close_date":"2018-01-20 09:40:00+00:00","open_rate":5.46e-05,"close_rate":5.4873684210526304e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.736842105263053e-07,"sell_reason":"roi","initial_stop_loss_abs":4.914e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.914e-05,"stop_loss_ratio":0.1,"min_rate":5.46e-05,"max_rate":5.4873684210526304e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516438800000.0,"close_timestamp":1516441200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03244412634781012,"open_date":"2018-01-20 18:25:00+00:00","close_date":"2018-01-25 03:50:00+00:00","open_rate":0.03082222,"close_rate":0.027739998,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":6325,"profit_ratio":-0.10448878,"profit_abs":-0.0030822220000000025,"sell_reason":"stop_loss","initial_stop_loss_abs":0.027739998000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027739998000000002,"stop_loss_ratio":0.1,"min_rate":0.027739998,"max_rate":0.03082222,"is_open":false,"buy_tag":null,"open_timestamp":1516472700000.0,"close_timestamp":1516852200000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011148273260677065,"open_date":"2018-01-20 22:25:00+00:00","close_date":"2018-01-20 23:15:00+00:00","open_rate":0.08969999,"close_rate":0.09014961401002504,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":0.00044962401002504593,"sell_reason":"roi","initial_stop_loss_abs":0.080729991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080729991,"stop_loss_ratio":0.1,"min_rate":0.08969999,"max_rate":0.09014961401002504,"is_open":false,"buy_tag":null,"open_timestamp":1516487100000.0,"close_timestamp":1516490100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06125570520324337,"open_date":"2018-01-21 02:50:00+00:00","close_date":"2018-01-21 14:30:00+00:00","open_rate":0.01632501,"close_rate":0.01640683962406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":700,"profit_ratio":0.0,"profit_abs":8.182962406014932e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014692509000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014692509000000001,"stop_loss_ratio":0.1,"min_rate":0.01632501,"max_rate":0.01640683962406015,"is_open":false,"buy_tag":null,"open_timestamp":1516503000000.0,"close_timestamp":1516545000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01417675579120474,"open_date":"2018-01-21 10:20:00+00:00","close_date":"2018-01-21 11:00:00+00:00","open_rate":0.070538,"close_rate":0.07089157393483708,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00035357393483707866,"sell_reason":"roi","initial_stop_loss_abs":0.0634842,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0634842,"stop_loss_ratio":0.1,"min_rate":0.070538,"max_rate":0.07089157393483708,"is_open":false,"buy_tag":null,"open_timestamp":1516530000000.0,"close_timestamp":1516532400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.864365214110546,"open_date":"2018-01-21 15:50:00+00:00","close_date":"2018-01-21 18:45:00+00:00","open_rate":5.301e-05,"close_rate":5.327571428571427e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.657142857142672e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7709e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7709e-05,"stop_loss_ratio":0.1,"min_rate":5.301e-05,"max_rate":5.327571428571427e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516549800000.0,"close_timestamp":1516560300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.284450063211125,"open_date":"2018-01-21 16:20:00+00:00","close_date":"2018-01-21 17:00:00+00:00","open_rate":3.955e-05,"close_rate":3.9748245614035085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.9824561403508552e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5595e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5595e-05,"stop_loss_ratio":0.1,"min_rate":3.955e-05,"max_rate":3.9748245614035085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516551600000.0,"close_timestamp":1516554000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38683971296493297,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:45:00+00:00","open_rate":0.00258505,"close_rate":0.002623922932330827,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.8872932330826816e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326545,"stop_loss_ratio":0.1,"min_rate":0.00258505,"max_rate":0.002623922932330827,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.621316935690498,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:55:00+00:00","open_rate":3.903e-05,"close_rate":3.922563909774435e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.9563909774435151e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5127e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5127e-05,"stop_loss_ratio":0.1,"min_rate":3.903e-05,"max_rate":3.922563909774435e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.098548510313215,"open_date":"2018-01-22 00:35:00+00:00","close_date":"2018-01-22 10:35:00+00:00","open_rate":5.236e-05,"close_rate":5.262245614035087e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":600,"profit_ratio":-0.0,"profit_abs":2.624561403508717e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7124e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7124e-05,"stop_loss_ratio":0.1,"min_rate":5.236e-05,"max_rate":5.262245614035087e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516581300000.0,"close_timestamp":1516617300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.076650420912717,"open_date":"2018-01-22 01:30:00+00:00","close_date":"2018-01-22 02:10:00+00:00","open_rate":9.028e-05,"close_rate":9.07325313283208e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":4.5253132832080657e-07,"sell_reason":"roi","initial_stop_loss_abs":8.1252e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.1252e-05,"stop_loss_ratio":0.1,"min_rate":9.028e-05,"max_rate":9.07325313283208e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516584600000.0,"close_timestamp":1516587000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3721622627465575,"open_date":"2018-01-22 12:25:00+00:00","close_date":"2018-01-22 14:35:00+00:00","open_rate":0.002687,"close_rate":0.002700468671679198,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":1.3468671679197925e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0024183000000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0024183000000000004,"stop_loss_ratio":0.1,"min_rate":0.002687,"max_rate":0.002700468671679198,"is_open":false,"buy_tag":null,"open_timestamp":1516623900000.0,"close_timestamp":1516631700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.99232245681382,"open_date":"2018-01-22 13:15:00+00:00","close_date":"2018-01-22 13:55:00+00:00","open_rate":4.168e-05,"close_rate":4.188892230576441e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.0892230576441054e-07,"sell_reason":"roi","initial_stop_loss_abs":3.7512e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.7512e-05,"stop_loss_ratio":0.1,"min_rate":4.168e-05,"max_rate":4.188892230576441e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516626900000.0,"close_timestamp":1516629300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.336583153837434,"open_date":"2018-01-22 14:00:00+00:00","close_date":"2018-01-22 14:30:00+00:00","open_rate":8.821e-05,"close_rate":8.953646616541353e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":1.326466165413529e-06,"sell_reason":"roi","initial_stop_loss_abs":7.9389e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.9389e-05,"stop_loss_ratio":0.1,"min_rate":8.821e-05,"max_rate":8.953646616541353e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516629600000.0,"close_timestamp":1516631400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.334880123743233,"open_date":"2018-01-22 15:55:00+00:00","close_date":"2018-01-22 16:40:00+00:00","open_rate":5.172e-05,"close_rate":5.1979248120300745e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.592481203007459e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6548e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6548e-05,"stop_loss_ratio":0.1,"min_rate":5.172e-05,"max_rate":5.1979248120300745e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516636500000.0,"close_timestamp":1516639200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.04692663582287,"open_date":"2018-01-22 16:05:00+00:00","close_date":"2018-01-22 16:25:00+00:00","open_rate":3.026e-05,"close_rate":3.101839598997494e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":7.5839598997494e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7234e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7234e-05,"stop_loss_ratio":0.1,"min_rate":3.026e-05,"max_rate":3.101839598997494e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516637100000.0,"close_timestamp":1516638300000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014156285390713478,"open_date":"2018-01-22 19:50:00+00:00","close_date":"2018-01-23 00:10:00+00:00","open_rate":0.07064,"close_rate":0.07099408521303258,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":0.0,"profit_abs":0.00035408521303258167,"sell_reason":"roi","initial_stop_loss_abs":0.063576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.063576,"stop_loss_ratio":0.1,"min_rate":0.07064,"max_rate":0.07099408521303258,"is_open":false,"buy_tag":null,"open_timestamp":1516650600000.0,"close_timestamp":1516666200000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06080938507725528,"open_date":"2018-01-22 21:25:00+00:00","close_date":"2018-01-22 22:05:00+00:00","open_rate":0.01644483,"close_rate":0.01652726022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.243022556390922e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014800347,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014800347,"stop_loss_ratio":0.1,"min_rate":0.01644483,"max_rate":0.01652726022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516656300000.0,"close_timestamp":1516658700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.08935580697299,"open_date":"2018-01-23 00:05:00+00:00","close_date":"2018-01-23 00:35:00+00:00","open_rate":4.331e-05,"close_rate":4.3961278195488714e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":6.512781954887175e-07,"sell_reason":"roi","initial_stop_loss_abs":3.8979e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.8979e-05,"stop_loss_ratio":0.1,"min_rate":4.331e-05,"max_rate":4.3961278195488714e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516665900000.0,"close_timestamp":1516667700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.250000000000004,"open_date":"2018-01-23 01:50:00+00:00","close_date":"2018-01-23 02:15:00+00:00","open_rate":3.2e-05,"close_rate":3.2802005012531326e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":8.020050125313278e-07,"sell_reason":"roi","initial_stop_loss_abs":2.88e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.88e-05,"stop_loss_ratio":0.1,"min_rate":3.2e-05,"max_rate":3.2802005012531326e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516672200000.0,"close_timestamp":1516673700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010907854156754155,"open_date":"2018-01-23 04:25:00+00:00","close_date":"2018-01-23 05:15:00+00:00","open_rate":0.09167706,"close_rate":0.09213659413533835,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0004595341353383492,"sell_reason":"roi","initial_stop_loss_abs":0.08250935400000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08250935400000001,"stop_loss_ratio":0.1,"min_rate":0.09167706,"max_rate":0.09213659413533835,"is_open":false,"buy_tag":null,"open_timestamp":1516681500000.0,"close_timestamp":1516684500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014440474918339115,"open_date":"2018-01-23 07:35:00+00:00","close_date":"2018-01-23 09:00:00+00:00","open_rate":0.0692498,"close_rate":0.06959691679197995,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":0.0,"profit_abs":0.0003471167919799484,"sell_reason":"roi","initial_stop_loss_abs":0.06232482,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06232482,"stop_loss_ratio":0.1,"min_rate":0.0692498,"max_rate":0.06959691679197995,"is_open":false,"buy_tag":null,"open_timestamp":1516692900000.0,"close_timestamp":1516698000000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.426775612822127,"open_date":"2018-01-23 10:50:00+00:00","close_date":"2018-01-23 13:05:00+00:00","open_rate":3.182e-05,"close_rate":3.197949874686716e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":135,"profit_ratio":0.0,"profit_abs":1.594987468671663e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8638e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8638e-05,"stop_loss_ratio":0.1,"min_rate":3.182e-05,"max_rate":3.197949874686716e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516704600000.0,"close_timestamp":1516712700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024461839530332683,"open_date":"2018-01-23 11:05:00+00:00","close_date":"2018-01-23 16:05:00+00:00","open_rate":0.04088,"close_rate":0.04108491228070175,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":300,"profit_ratio":-0.0,"profit_abs":0.0002049122807017481,"sell_reason":"roi","initial_stop_loss_abs":0.036792,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036792,"stop_loss_ratio":0.1,"min_rate":0.04088,"max_rate":0.04108491228070175,"is_open":false,"buy_tag":null,"open_timestamp":1516705500000.0,"close_timestamp":1516723500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.417475728155342,"open_date":"2018-01-23 14:55:00+00:00","close_date":"2018-01-23 15:35:00+00:00","open_rate":5.15e-05,"close_rate":5.175814536340851e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5814536340851513e-07,"sell_reason":"roi","initial_stop_loss_abs":4.635e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.635e-05,"stop_loss_ratio":0.1,"min_rate":5.15e-05,"max_rate":5.175814536340851e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516719300000.0,"close_timestamp":1516721700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011023294646713328,"open_date":"2018-01-23 16:35:00+00:00","close_date":"2018-01-24 00:05:00+00:00","open_rate":0.09071698,"close_rate":0.09117170170426064,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":450,"profit_ratio":0.0,"profit_abs":0.00045472170426064107,"sell_reason":"roi","initial_stop_loss_abs":0.081645282,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.081645282,"stop_loss_ratio":0.1,"min_rate":0.09071698,"max_rate":0.09117170170426064,"is_open":false,"buy_tag":null,"open_timestamp":1516725300000.0,"close_timestamp":1516752300000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.969309462915604,"open_date":"2018-01-23 17:25:00+00:00","close_date":"2018-01-23 18:45:00+00:00","open_rate":3.128e-05,"close_rate":3.1436791979949865e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":1.5679197994986587e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8152e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8152e-05,"stop_loss_ratio":0.1,"min_rate":3.128e-05,"max_rate":3.1436791979949865e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516728300000.0,"close_timestamp":1516733100000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.465724751439037,"open_date":"2018-01-23 20:15:00+00:00","close_date":"2018-01-23 22:00:00+00:00","open_rate":9.555e-05,"close_rate":9.602894736842104e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":4.789473684210343e-07,"sell_reason":"roi","initial_stop_loss_abs":8.5995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.5995e-05,"stop_loss_ratio":0.1,"min_rate":9.555e-05,"max_rate":9.602894736842104e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516738500000.0,"close_timestamp":1516744800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02450979791426522,"open_date":"2018-01-23 22:30:00+00:00","close_date":"2018-01-23 23:10:00+00:00","open_rate":0.04080001,"close_rate":0.0410045213283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00020451132832080554,"sell_reason":"roi","initial_stop_loss_abs":0.036720009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036720009,"stop_loss_ratio":0.1,"min_rate":0.04080001,"max_rate":0.0410045213283208,"is_open":false,"buy_tag":null,"open_timestamp":1516746600000.0,"close_timestamp":1516749000000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.368584156498162,"open_date":"2018-01-23 23:50:00+00:00","close_date":"2018-01-24 03:35:00+00:00","open_rate":5.163e-05,"close_rate":5.18887969924812e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":225,"profit_ratio":-0.0,"profit_abs":2.587969924812037e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6467e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6467e-05,"stop_loss_ratio":0.1,"min_rate":5.163e-05,"max_rate":5.18887969924812e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516751400000.0,"close_timestamp":1516764900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024747691102289384,"open_date":"2018-01-24 00:20:00+00:00","close_date":"2018-01-24 01:50:00+00:00","open_rate":0.04040781,"close_rate":0.04061035541353383,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":0.0,"profit_abs":0.0002025454135338306,"sell_reason":"roi","initial_stop_loss_abs":0.036367029,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036367029,"stop_loss_ratio":0.1,"min_rate":0.04040781,"max_rate":0.04061035541353383,"is_open":false,"buy_tag":null,"open_timestamp":1516753200000.0,"close_timestamp":1516758600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.485580670303975,"open_date":"2018-01-24 06:45:00+00:00","close_date":"2018-01-24 07:25:00+00:00","open_rate":5.132e-05,"close_rate":5.157724310776942e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5724310776941724e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6188000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6188000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.132e-05,"max_rate":5.157724310776942e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516776300000.0,"close_timestamp":1516778700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.23816852635629,"open_date":"2018-01-24 14:15:00+00:00","close_date":"2018-01-24 14:25:00+00:00","open_rate":5.198e-05,"close_rate":5.432496240601503e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":2.344962406015033e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6782e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6782e-05,"stop_loss_ratio":0.1,"min_rate":5.198e-05,"max_rate":5.432496240601503e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516803300000.0,"close_timestamp":1516803900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.74394237066143,"open_date":"2018-01-24 14:50:00+00:00","close_date":"2018-01-24 16:35:00+00:00","open_rate":3.054e-05,"close_rate":3.069308270676692e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":1.5308270676691466e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7486000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7486000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.054e-05,"max_rate":3.069308270676692e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516805400000.0,"close_timestamp":1516811700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.795638562020944,"open_date":"2018-01-24 15:10:00+00:00","close_date":"2018-01-24 16:15:00+00:00","open_rate":9.263e-05,"close_rate":9.309431077694236e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":0.0,"profit_abs":4.6431077694236234e-07,"sell_reason":"roi","initial_stop_loss_abs":8.3367e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.3367e-05,"stop_loss_ratio":0.1,"min_rate":9.263e-05,"max_rate":9.309431077694236e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516806600000.0,"close_timestamp":1516810500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.13565469713457,"open_date":"2018-01-24 22:40:00+00:00","close_date":"2018-01-24 23:25:00+00:00","open_rate":5.514e-05,"close_rate":5.54163909774436e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.7639097744360576e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9625999999999995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9625999999999995e-05,"stop_loss_ratio":0.1,"min_rate":5.514e-05,"max_rate":5.54163909774436e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516833600000.0,"close_timestamp":1516836300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":20.3210729526519,"open_date":"2018-01-25 00:50:00+00:00","close_date":"2018-01-25 01:30:00+00:00","open_rate":4.921e-05,"close_rate":4.9456666666666664e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.4666666666666543e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4289e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4289e-05,"stop_loss_ratio":0.1,"min_rate":4.921e-05,"max_rate":4.9456666666666664e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516841400000.0,"close_timestamp":1516843800000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38461538461538464,"open_date":"2018-01-25 08:15:00+00:00","close_date":"2018-01-25 12:15:00+00:00","open_rate":0.0026,"close_rate":0.002613032581453634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":240,"profit_ratio":0.0,"profit_abs":1.3032581453634e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00234,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00234,"stop_loss_ratio":0.1,"min_rate":0.0026,"max_rate":0.002613032581453634,"is_open":false,"buy_tag":null,"open_timestamp":1516868100000.0,"close_timestamp":1516882500000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03571593119825878,"open_date":"2018-01-25 10:25:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":0.02799871,"close_rate":0.028139054411027563,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":350,"profit_ratio":-0.0,"profit_abs":0.00014034441102756326,"sell_reason":"roi","initial_stop_loss_abs":0.025198839,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025198839,"stop_loss_ratio":0.1,"min_rate":0.02799871,"max_rate":0.028139054411027563,"is_open":false,"buy_tag":null,"open_timestamp":1516875900000.0,"close_timestamp":1516896900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024516401717913302,"open_date":"2018-01-25 11:00:00+00:00","close_date":"2018-01-25 11:45:00+00:00","open_rate":0.04078902,"close_rate":0.0409934762406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020445624060149575,"sell_reason":"roi","initial_stop_loss_abs":0.036710118,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036710118,"stop_loss_ratio":0.1,"min_rate":0.04078902,"max_rate":0.0409934762406015,"is_open":false,"buy_tag":null,"open_timestamp":1516878000000.0,"close_timestamp":1516880700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.602076124567475,"open_date":"2018-01-25 13:05:00+00:00","close_date":"2018-01-25 13:45:00+00:00","open_rate":2.89e-05,"close_rate":2.904486215538847e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4486215538846723e-07,"sell_reason":"roi","initial_stop_loss_abs":2.601e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.601e-05,"stop_loss_ratio":0.1,"min_rate":2.89e-05,"max_rate":2.904486215538847e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516885500000.0,"close_timestamp":1516887900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02432912439481303,"open_date":"2018-01-25 13:20:00+00:00","close_date":"2018-01-25 14:05:00+00:00","open_rate":0.041103,"close_rate":0.04130903007518797,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020603007518796984,"sell_reason":"roi","initial_stop_loss_abs":0.0369927,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0369927,"stop_loss_ratio":0.1,"min_rate":0.041103,"max_rate":0.04130903007518797,"is_open":false,"buy_tag":null,"open_timestamp":1516886400000.0,"close_timestamp":1516889100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.422991893883566,"open_date":"2018-01-25 15:45:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":5.428e-05,"close_rate":5.509624060150376e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.162406015037611e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8852000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8852000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.428e-05,"max_rate":5.509624060150376e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516895100000.0,"close_timestamp":1516896900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.47063169560399,"open_date":"2018-01-25 17:45:00+00:00","close_date":"2018-01-25 23:15:00+00:00","open_rate":5.414e-05,"close_rate":5.441137844611528e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":330,"profit_ratio":-0.0,"profit_abs":2.713784461152774e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8726e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8726e-05,"stop_loss_ratio":0.1,"min_rate":5.414e-05,"max_rate":5.441137844611528e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516902300000.0,"close_timestamp":1516922100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024150056861308878,"open_date":"2018-01-25 21:15:00+00:00","close_date":"2018-01-25 21:55:00+00:00","open_rate":0.04140777,"close_rate":0.0416153277443609,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002075577443608964,"sell_reason":"roi","initial_stop_loss_abs":0.037266993000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.037266993000000005,"stop_loss_ratio":0.1,"min_rate":0.04140777,"max_rate":0.0416153277443609,"is_open":false,"buy_tag":null,"open_timestamp":1516914900000.0,"close_timestamp":1516917300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3932224183965176,"open_date":"2018-01-26 02:05:00+00:00","close_date":"2018-01-26 02:45:00+00:00","open_rate":0.00254309,"close_rate":0.002555837318295739,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.2747318295739177e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002288781,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002288781,"stop_loss_ratio":0.1,"min_rate":0.00254309,"max_rate":0.002555837318295739,"is_open":false,"buy_tag":null,"open_timestamp":1516932300000.0,"close_timestamp":1516934700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.834849295523455,"open_date":"2018-01-26 02:55:00+00:00","close_date":"2018-01-26 15:10:00+00:00","open_rate":5.607e-05,"close_rate":5.6351052631578935e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":735,"profit_ratio":-0.0,"profit_abs":2.810526315789381e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0463e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0463e-05,"stop_loss_ratio":0.1,"min_rate":5.607e-05,"max_rate":5.6351052631578935e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516935300000.0,"close_timestamp":1516979400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.39400171784748983,"open_date":"2018-01-26 06:10:00+00:00","close_date":"2018-01-26 09:25:00+00:00","open_rate":0.00253806,"close_rate":0.0025507821052631577,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":195,"profit_ratio":0.0,"profit_abs":1.2722105263157733e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002284254,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002284254,"stop_loss_ratio":0.1,"min_rate":0.00253806,"max_rate":0.0025507821052631577,"is_open":false,"buy_tag":null,"open_timestamp":1516947000000.0,"close_timestamp":1516958700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024096385542168672,"open_date":"2018-01-26 07:25:00+00:00","close_date":"2018-01-26 09:55:00+00:00","open_rate":0.0415,"close_rate":0.04170802005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":150,"profit_ratio":-0.0,"profit_abs":0.00020802005012530989,"sell_reason":"roi","initial_stop_loss_abs":0.03735,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03735,"stop_loss_ratio":0.1,"min_rate":0.0415,"max_rate":0.04170802005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516951500000.0,"close_timestamp":1516960500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.793459875963165,"open_date":"2018-01-26 09:55:00+00:00","close_date":"2018-01-26 10:25:00+00:00","open_rate":5.321e-05,"close_rate":5.401015037593984e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.00150375939842e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7889e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7889e-05,"stop_loss_ratio":0.1,"min_rate":5.321e-05,"max_rate":5.401015037593984e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516960500000.0,"close_timestamp":1516962300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.036074437437185386,"open_date":"2018-01-26 16:05:00+00:00","close_date":"2018-01-26 16:45:00+00:00","open_rate":0.02772046,"close_rate":0.02785940967418546,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013894967418546025,"sell_reason":"roi","initial_stop_loss_abs":0.024948414,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024948414,"stop_loss_ratio":0.1,"min_rate":0.02772046,"max_rate":0.02785940967418546,"is_open":false,"buy_tag":null,"open_timestamp":1516982700000.0,"close_timestamp":1516985100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010569326272036914,"open_date":"2018-01-26 23:35:00+00:00","close_date":"2018-01-27 00:15:00+00:00","open_rate":0.09461341,"close_rate":0.09508766268170424,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00047425268170424306,"sell_reason":"roi","initial_stop_loss_abs":0.085152069,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085152069,"stop_loss_ratio":0.1,"min_rate":0.09461341,"max_rate":0.09508766268170424,"is_open":false,"buy_tag":null,"open_timestamp":1517009700000.0,"close_timestamp":1517012100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":17.809439002671414,"open_date":"2018-01-27 00:35:00+00:00","close_date":"2018-01-27 01:30:00+00:00","open_rate":5.615e-05,"close_rate":5.643145363408521e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.814536340852038e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0535e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0535e-05,"stop_loss_ratio":0.1,"min_rate":5.615e-05,"max_rate":5.643145363408521e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013300000.0,"close_timestamp":1517016600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.998560115190784,"open_date":"2018-01-27 00:45:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":5.556e-05,"close_rate":5.144e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4560,"profit_ratio":-0.07877175,"profit_abs":-4.120000000000001e-06,"sell_reason":"force_sell","initial_stop_loss_abs":5.0004000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0004000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.144e-05,"max_rate":5.556e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013900000.0,"close_timestamp":1517287500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492751522789634,"open_date":"2018-01-27 02:30:00+00:00","close_date":"2018-01-27 11:25:00+00:00","open_rate":0.06900001,"close_rate":0.06934587471177944,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":535,"profit_ratio":-0.0,"profit_abs":0.0003458647117794422,"sell_reason":"roi","initial_stop_loss_abs":0.062100009000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.062100009000000005,"stop_loss_ratio":0.1,"min_rate":0.06900001,"max_rate":0.06934587471177944,"is_open":false,"buy_tag":null,"open_timestamp":1517020200000.0,"close_timestamp":1517052300000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010582027378879437,"open_date":"2018-01-27 06:25:00+00:00","close_date":"2018-01-27 07:05:00+00:00","open_rate":0.09449985,"close_rate":0.0949735334586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004736834586466093,"sell_reason":"roi","initial_stop_loss_abs":0.085049865,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085049865,"stop_loss_ratio":0.1,"min_rate":0.09449985,"max_rate":0.0949735334586466,"is_open":false,"buy_tag":null,"open_timestamp":1517034300000.0,"close_timestamp":1517036700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02434885085598385,"open_date":"2018-01-27 09:40:00+00:00","close_date":"2018-01-30 04:40:00+00:00","open_rate":0.0410697,"close_rate":0.03928809,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4020,"profit_ratio":-0.04815133,"profit_abs":-0.001781610000000003,"sell_reason":"force_sell","initial_stop_loss_abs":0.03696273,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03696273,"stop_loss_ratio":0.1,"min_rate":0.03928809,"max_rate":0.0410697,"is_open":false,"buy_tag":null,"open_timestamp":1517046000000.0,"close_timestamp":1517287200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03508771929824561,"open_date":"2018-01-27 11:45:00+00:00","close_date":"2018-01-27 12:30:00+00:00","open_rate":0.0285,"close_rate":0.02864285714285714,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00014285714285713902,"sell_reason":"roi","initial_stop_loss_abs":0.025650000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025650000000000003,"stop_loss_ratio":0.1,"min_rate":0.0285,"max_rate":0.02864285714285714,"is_open":false,"buy_tag":null,"open_timestamp":1517053500000.0,"close_timestamp":1517056200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.034887307020861215,"open_date":"2018-01-27 12:35:00+00:00","close_date":"2018-01-27 15:25:00+00:00","open_rate":0.02866372,"close_rate":0.02880739779448621,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":170,"profit_ratio":-0.0,"profit_abs":0.00014367779448621124,"sell_reason":"roi","initial_stop_loss_abs":0.025797348,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025797348,"stop_loss_ratio":0.1,"min_rate":0.02866372,"max_rate":0.02880739779448621,"is_open":false,"buy_tag":null,"open_timestamp":1517056500000.0,"close_timestamp":1517066700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010484268355332824,"open_date":"2018-01-27 15:50:00+00:00","close_date":"2018-01-27 16:50:00+00:00","open_rate":0.095381,"close_rate":0.09585910025062656,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.00047810025062657024,"sell_reason":"roi","initial_stop_loss_abs":0.0858429,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0858429,"stop_loss_ratio":0.1,"min_rate":0.095381,"max_rate":0.09585910025062656,"is_open":false,"buy_tag":null,"open_timestamp":1517068200000.0,"close_timestamp":1517071800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014794886650455417,"open_date":"2018-01-27 17:05:00+00:00","close_date":"2018-01-27 17:45:00+00:00","open_rate":0.06759092,"close_rate":0.06792972160401002,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00033880160401002224,"sell_reason":"roi","initial_stop_loss_abs":0.060831828,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060831828,"stop_loss_ratio":0.1,"min_rate":0.06759092,"max_rate":0.06792972160401002,"is_open":false,"buy_tag":null,"open_timestamp":1517072700000.0,"close_timestamp":1517075100000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38684569885609726,"open_date":"2018-01-27 23:40:00+00:00","close_date":"2018-01-28 01:05:00+00:00","open_rate":0.00258501,"close_rate":0.002597967443609022,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":-0.0,"profit_abs":1.2957443609021985e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326509,"stop_loss_ratio":0.1,"min_rate":0.00258501,"max_rate":0.002597967443609022,"is_open":false,"buy_tag":null,"open_timestamp":1517096400000.0,"close_timestamp":1517101500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014928710926711672,"open_date":"2018-01-28 02:25:00+00:00","close_date":"2018-01-28 08:10:00+00:00","open_rate":0.06698502,"close_rate":0.0673207845112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":345,"profit_ratio":-0.0,"profit_abs":0.00033576451127818874,"sell_reason":"roi","initial_stop_loss_abs":0.060286518000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060286518000000004,"stop_loss_ratio":0.1,"min_rate":0.06698502,"max_rate":0.0673207845112782,"is_open":false,"buy_tag":null,"open_timestamp":1517106300000.0,"close_timestamp":1517127000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014767187899175548,"open_date":"2018-01-28 10:25:00+00:00","close_date":"2018-01-28 16:30:00+00:00","open_rate":0.0677177,"close_rate":0.06805713709273183,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":365,"profit_ratio":-0.0,"profit_abs":0.0003394370927318202,"sell_reason":"roi","initial_stop_loss_abs":0.06094593000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06094593000000001,"stop_loss_ratio":0.1,"min_rate":0.0677177,"max_rate":0.06805713709273183,"is_open":false,"buy_tag":null,"open_timestamp":1517135100000.0,"close_timestamp":1517157000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":19.175455417066157,"open_date":"2018-01-28 20:35:00+00:00","close_date":"2018-01-28 21:35:00+00:00","open_rate":5.215e-05,"close_rate":5.2411403508771925e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":0.0,"profit_abs":2.6140350877192417e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6935000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6935000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.215e-05,"max_rate":5.2411403508771925e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517171700000.0,"close_timestamp":1517175300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.36521808998243305,"open_date":"2018-01-28 22:00:00+00:00","close_date":"2018-01-28 22:30:00+00:00","open_rate":0.00273809,"close_rate":0.002779264285714285,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.117428571428529e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002464281,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002464281,"stop_loss_ratio":0.1,"min_rate":0.00273809,"max_rate":0.002779264285714285,"is_open":false,"buy_tag":null,"open_timestamp":1517176800000.0,"close_timestamp":1517178600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3641236272539253,"open_date":"2018-01-29 00:00:00+00:00","close_date":"2018-01-29 00:30:00+00:00","open_rate":0.00274632,"close_rate":0.002787618045112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.129804511278194e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002471688,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002471688,"stop_loss_ratio":0.1,"min_rate":0.00274632,"max_rate":0.002787618045112782,"is_open":false,"buy_tag":null,"open_timestamp":1517184000000.0,"close_timestamp":1517185800000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.061634117689115045,"open_date":"2018-01-29 02:15:00+00:00","close_date":"2018-01-29 03:00:00+00:00","open_rate":0.01622478,"close_rate":0.016306107218045113,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":8.132721804511231e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014602302000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014602302000000001,"stop_loss_ratio":0.1,"min_rate":0.01622478,"max_rate":0.016306107218045113,"is_open":false,"buy_tag":null,"open_timestamp":1517192100000.0,"close_timestamp":1517194800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492753623188404,"open_date":"2018-01-29 03:05:00+00:00","close_date":"2018-01-29 03:45:00+00:00","open_rate":0.069,"close_rate":0.06934586466165413,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00034586466165412166,"sell_reason":"roi","initial_stop_loss_abs":0.06210000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06210000000000001,"stop_loss_ratio":0.1,"min_rate":0.069,"max_rate":0.06934586466165413,"is_open":false,"buy_tag":null,"open_timestamp":1517195100000.0,"close_timestamp":1517197500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.42204454597373,"open_date":"2018-01-29 05:20:00+00:00","close_date":"2018-01-29 06:55:00+00:00","open_rate":8.755e-05,"close_rate":8.798884711779448e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":95,"profit_ratio":-0.0,"profit_abs":4.3884711779447504e-07,"sell_reason":"roi","initial_stop_loss_abs":7.879500000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.879500000000001e-05,"stop_loss_ratio":0.1,"min_rate":8.755e-05,"max_rate":8.798884711779448e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517203200000.0,"close_timestamp":1517208900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014650376815016871,"open_date":"2018-01-29 07:00:00+00:00","close_date":"2018-01-29 19:25:00+00:00","open_rate":0.06825763,"close_rate":0.06859977350877192,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":745,"profit_ratio":-0.0,"profit_abs":0.00034214350877191657,"sell_reason":"roi","initial_stop_loss_abs":0.061431867,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061431867,"stop_loss_ratio":0.1,"min_rate":0.06825763,"max_rate":0.06859977350877192,"is_open":false,"buy_tag":null,"open_timestamp":1517209200000.0,"close_timestamp":1517253900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014894490408841846,"open_date":"2018-01-29 19:45:00+00:00","close_date":"2018-01-29 20:25:00+00:00","open_rate":0.06713892,"close_rate":0.06747545593984962,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0003365359398496137,"sell_reason":"roi","initial_stop_loss_abs":0.060425028000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060425028000000006,"stop_loss_ratio":0.1,"min_rate":0.06713892,"max_rate":0.06747545593984962,"is_open":false,"buy_tag":null,"open_timestamp":1517255100000.0,"close_timestamp":1517257500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.193194537721066,"open_date":"2018-01-29 23:30:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":8.934e-05,"close_rate":8.8e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":315,"profit_ratio":-0.0199116,"profit_abs":-1.3399999999999973e-06,"sell_reason":"force_sell","initial_stop_loss_abs":8.0406e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.0406e-05,"stop_loss_ratio":0.1,"min_rate":8.8e-05,"max_rate":8.934e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517268600000.0,"close_timestamp":1517287500000.0}],"locks":[],"best_pair":{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},"worst_pair":{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},"results_per_pair":[{"key":"ETH/BTC","trades":21,"profit_mean":0.0009500057142857142,"profit_mean_pct":0.09500057142857142,"profit_sum":0.01995012,"profit_sum_pct":2.0,"profit_total_abs":0.011505731278195264,"profit_total":1.1505731278195264,"profit_total_pct":115.06,"duration_avg":"2:17:00","wins":21,"draws":0,"losses":0},{"key":"DASH/BTC","trades":16,"profit_mean":0.0018703237499999997,"profit_mean_pct":0.18703237499999997,"profit_sum":0.029925179999999996,"profit_sum_pct":2.99,"profit_total_abs":0.007475052681704161,"profit_total":0.7475052681704161,"profit_total_pct":74.75,"duration_avg":"3:03:00","wins":16,"draws":0,"losses":0},{"key":"ZEC/BTC","trades":21,"profit_mean":-0.00039290904761904774,"profit_mean_pct":-0.03929090476190478,"profit_sum":-0.008251090000000003,"profit_sum_pct":-0.83,"profit_total_abs":0.004452605639097655,"profit_total":0.4452605639097655,"profit_total_pct":44.53,"duration_avg":"4:17:00","wins":20,"draws":0,"losses":1},{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},{"key":"ETC/BTC","trades":20,"profit_mean":0.0022568569999999997,"profit_mean_pct":0.22568569999999996,"profit_sum":0.04513713999999999,"profit_sum_pct":4.51,"profit_total_abs":0.00036538235338345404,"profit_total":0.0365382353383454,"profit_total_pct":3.65,"duration_avg":"1:45:00","wins":19,"draws":0,"losses":1},{"key":"TRX/BTC","trades":15,"profit_mean":0.0023467073333333323,"profit_mean_pct":0.23467073333333321,"profit_sum":0.035200609999999986,"profit_sum_pct":3.52,"profit_total_abs":1.1329523809523682e-05,"profit_total":0.0011329523809523682,"profit_total_pct":0.11,"duration_avg":"2:28:00","wins":13,"draws":0,"losses":2},{"key":"XLM/BTC","trades":21,"profit_mean":0.0026243899999999994,"profit_mean_pct":0.2624389999999999,"profit_sum":0.05511218999999999,"profit_sum_pct":5.51,"profit_total_abs":7.340779448621465e-06,"profit_total":0.0007340779448621465,"profit_total_pct":0.07,"duration_avg":"3:21:00","wins":20,"draws":0,"losses":1},{"key":"ADA/BTC","trades":29,"profit_mean":-0.0011598141379310352,"profit_mean_pct":-0.11598141379310352,"profit_sum":-0.03363461000000002,"profit_sum_pct":-3.36,"profit_total_abs":4.916634085212862e-06,"profit_total":0.0004916634085212862,"profit_total_pct":0.05,"duration_avg":"5:35:00","wins":27,"draws":0,"losses":2},{"key":"NXT/BTC","trades":12,"profit_mean":-0.0012261025000000006,"profit_mean_pct":-0.12261025000000006,"profit_sum":-0.014713230000000008,"profit_sum_pct":-1.47,"profit_total_abs":1.4774411027568458e-06,"profit_total":0.00014774411027568458,"profit_total_pct":0.01,"duration_avg":"0:57:00","wins":11,"draws":0,"losses":1},{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"results_per_enter_tag":[{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"sell_reason_summary":[{"sell_reason":"roi","trades":170,"wins":170,"draws":0,"losses":0,"profit_mean":0.005398268352941177,"profit_mean_pct":0.54,"profit_sum":0.91770562,"profit_sum_pct":91.77,"profit_total_abs":0.031232837493733862,"profit_total":0.30590187333333335,"profit_total_pct":30.59},{"sell_reason":"stop_loss","trades":6,"wins":0,"draws":0,"losses":6,"profit_mean":-0.10448878000000002,"profit_mean_pct":-10.45,"profit_sum":-0.6269326800000001,"profit_sum_pct":-62.69,"profit_total_abs":-0.0033602680000000026,"profit_total":-0.20897756000000003,"profit_total_pct":-20.9},{"sell_reason":"force_sell","trades":3,"wins":0,"draws":0,"losses":3,"profit_mean":-0.04894489333333333,"profit_mean_pct":-4.89,"profit_sum":-0.14683468,"profit_sum_pct":-14.68,"profit_total_abs":-0.001787070000000003,"profit_total":-0.04894489333333333,"profit_total_pct":-4.89}],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":179,"total_volume":0.17900000000000005,"avg_stake_amount":0.0010000000000000002,"profit_mean":0.0008041243575418989,"profit_median":0.0,"profit_total":2.6085499493733857,"profit_total_abs":0.026085499493733857,"backtest_start":"2018-01-10 07:15:00","backtest_start_ts":1515568500000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":"2020-10-01 18:00:00+00:00","backtest_run_end_ts":"2020-10-01 18:01:00+00:00","trades_per_day":9.42,"market_change":1.22,"pairlist":[],"stake_amount":0.001,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":0.01,"dry_run_wallet":0.01,"final_balance":0.03608549949373386,"rejected_signals":0,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timeframe_detail":"","timerange":"","enable_protections":false,"strategy_name":"StrategyTestV2","stoploss":0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":false,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.17955111999999998,"backtest_worst_day":-0.14683468,"backtest_best_day_abs":0.0071570099,"backtest_worst_day_abs":-0.0023093218,"winning_days":19,"draw_days":0,"losing_days":2,"daily_profit":[["2018-01-10",0.0025815306],["2018-01-11",0.0049356655],["2018-01-12",0.0006395218],["2018-01-13",0.0002574589],["2018-01-14",0.0010443828],["2018-01-15",0.0024030209],["2018-01-16",0.0071570099],["2018-01-17",0.001137038],["2018-01-18",0.0013712174],["2018-01-19",0.000584673],["2018-01-20",0.0006143386],["2018-01-21",0.0004749361],["2018-01-22",9.91669e-05],["2018-01-23",0.0015726664],["2018-01-24",0.0006610219],["2018-01-25",-0.0023093218],["2018-01-26",0.0003735204],["2018-01-27",0.0023975191],["2018-01-28",0.0007295947],["2018-01-29",0.0011476082],["2018-01-30",-0.00178707]],"wins":48,"losses":9,"draws":122,"holding_avg":"3:40:00","holding_avg_s":13200.0,"winner_holding_avg":"0:24:00","winner_holding_avg_s":1440.0,"loser_holding_avg":"1 day, 5:57:00","loser_holding_avg_s":107820.0,"max_drawdown":0.21142322000000008,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":0.0030822220000000025,"drawdown_start":"2018-01-25 01:30:00","drawdown_start_ts":1516843800000.0,"drawdown_end":"2018-01-25 03:50:00","drawdown_end_ts":1516852200000.0,"max_drawdown_low":0.02245167355388436,"max_drawdown_high":0.025533895553884363,"csum_min":0.01000434887218045,"csum_max":0.03608683949373386},"TestStrategy":{"trades":[{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.37344398340249,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:20:00+00:00","open_rate":9.64e-05,"close_rate":0.00010074887218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":4.348872180451118e-06,"sell_reason":"roi","initial_stop_loss_abs":8.676e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.676e-05,"stop_loss_ratio":0.1,"min_rate":9.64e-05,"max_rate":0.00010074887218045112,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515568800000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":21.026072329688816,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:30:00+00:00","open_rate":4.756e-05,"close_rate":4.9705563909774425e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":2.1455639097744267e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2804e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2804e-05,"stop_loss_ratio":0.1,"min_rate":4.756e-05,"max_rate":4.9705563909774425e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515569400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.94908655286014,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:35:00+00:00","open_rate":3.339e-05,"close_rate":3.489631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":1.506315789473681e-06,"sell_reason":"roi","initial_stop_loss_abs":3.0050999999999997e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0050999999999997e-05,"stop_loss_ratio":0.1,"min_rate":3.339e-05,"max_rate":3.489631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515569700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.313531353135314,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:40:00+00:00","open_rate":9.696e-05,"close_rate":0.00010133413533834584,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":4.3741353383458455e-06,"sell_reason":"roi","initial_stop_loss_abs":8.7264e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.7264e-05,"stop_loss_ratio":0.1,"min_rate":9.696e-05,"max_rate":0.00010133413533834584,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515570000000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010604453870625663,"open_date":"2018-01-10 07:35:00+00:00","close_date":"2018-01-10 08:35:00+00:00","open_rate":0.0943,"close_rate":0.09477268170426063,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.0004726817042606385,"sell_reason":"roi","initial_stop_loss_abs":0.08487,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08487,"stop_loss_ratio":0.1,"min_rate":0.0943,"max_rate":0.09477268170426063,"is_open":false,"buy_tag":null,"open_timestamp":1515569700000.0,"close_timestamp":1515573300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03677001860930642,"open_date":"2018-01-10 07:40:00+00:00","close_date":"2018-01-10 08:10:00+00:00","open_rate":0.02719607,"close_rate":0.02760503345864661,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00040896345864661204,"sell_reason":"roi","initial_stop_loss_abs":0.024476463,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024476463,"stop_loss_ratio":0.1,"min_rate":0.02719607,"max_rate":0.02760503345864661,"is_open":false,"buy_tag":null,"open_timestamp":1515570000000.0,"close_timestamp":1515571800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021575196463739,"open_date":"2018-01-10 08:15:00+00:00","close_date":"2018-01-10 09:55:00+00:00","open_rate":0.04634952,"close_rate":0.046581848421052625,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":100,"profit_ratio":0.0,"profit_abs":0.0002323284210526272,"sell_reason":"roi","initial_stop_loss_abs":0.041714568,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041714568,"stop_loss_ratio":0.1,"min_rate":0.04634952,"max_rate":0.046581848421052625,"is_open":false,"buy_tag":null,"open_timestamp":1515572100000.0,"close_timestamp":1515578100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.615786040443574,"open_date":"2018-01-10 14:45:00+00:00","close_date":"2018-01-10 15:50:00+00:00","open_rate":3.066e-05,"close_rate":3.081368421052631e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":1.5368421052630647e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7594e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7594e-05,"stop_loss_ratio":0.1,"min_rate":3.066e-05,"max_rate":3.081368421052631e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515595500000.0,"close_timestamp":1515599400000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.05917194776300452,"open_date":"2018-01-10 16:35:00+00:00","close_date":"2018-01-10 17:15:00+00:00","open_rate":0.0168999,"close_rate":0.016984611278195488,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.471127819548868e-05,"sell_reason":"roi","initial_stop_loss_abs":0.01520991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01520991,"stop_loss_ratio":0.1,"min_rate":0.0168999,"max_rate":0.016984611278195488,"is_open":false,"buy_tag":null,"open_timestamp":1515602100000.0,"close_timestamp":1515604500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010949822656672253,"open_date":"2018-01-10 16:40:00+00:00","close_date":"2018-01-10 17:20:00+00:00","open_rate":0.09132568,"close_rate":0.0917834528320802,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004577728320801916,"sell_reason":"roi","initial_stop_loss_abs":0.08219311200000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08219311200000001,"stop_loss_ratio":0.1,"min_rate":0.09132568,"max_rate":0.0917834528320802,"is_open":false,"buy_tag":null,"open_timestamp":1515602400000.0,"close_timestamp":1515604800000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011238476768326556,"open_date":"2018-01-10 18:50:00+00:00","close_date":"2018-01-10 19:45:00+00:00","open_rate":0.08898003,"close_rate":0.08942604518796991,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00044601518796991146,"sell_reason":"roi","initial_stop_loss_abs":0.080082027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080082027,"stop_loss_ratio":0.1,"min_rate":0.08898003,"max_rate":0.08942604518796991,"is_open":false,"buy_tag":null,"open_timestamp":1515610200000.0,"close_timestamp":1515613500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011682232072680307,"open_date":"2018-01-10 22:15:00+00:00","close_date":"2018-01-10 23:00:00+00:00","open_rate":0.08560008,"close_rate":0.08602915308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":0.00042907308270676014,"sell_reason":"roi","initial_stop_loss_abs":0.077040072,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077040072,"stop_loss_ratio":0.1,"min_rate":0.08560008,"max_rate":0.08602915308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515622500000.0,"close_timestamp":1515625200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4014726015023105,"open_date":"2018-01-10 22:50:00+00:00","close_date":"2018-01-10 23:20:00+00:00","open_rate":0.00249083,"close_rate":0.0025282860902255634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.745609022556351e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002241747,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002241747,"stop_loss_ratio":0.1,"min_rate":0.00249083,"max_rate":0.0025282860902255634,"is_open":false,"buy_tag":null,"open_timestamp":1515624600000.0,"close_timestamp":1515626400000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.090668431502316,"open_date":"2018-01-10 23:15:00+00:00","close_date":"2018-01-11 00:15:00+00:00","open_rate":3.022e-05,"close_rate":3.037147869674185e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":1.5147869674185174e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7198e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7198e-05,"stop_loss_ratio":0.1,"min_rate":3.022e-05,"max_rate":3.037147869674185e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515626100000.0,"close_timestamp":1515629700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.41034058268362744,"open_date":"2018-01-10 23:40:00+00:00","close_date":"2018-01-11 00:05:00+00:00","open_rate":0.002437,"close_rate":0.0024980776942355883,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":6.107769423558838e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0021933,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0021933,"stop_loss_ratio":0.1,"min_rate":0.002437,"max_rate":0.0024980776942355883,"is_open":false,"buy_tag":null,"open_timestamp":1515627600000.0,"close_timestamp":1515629100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02095643931654345,"open_date":"2018-01-11 00:00:00+00:00","close_date":"2018-01-11 00:35:00+00:00","open_rate":0.04771803,"close_rate":0.04843559436090225,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0007175643609022495,"sell_reason":"roi","initial_stop_loss_abs":0.042946227000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.042946227000000003,"stop_loss_ratio":0.1,"min_rate":0.04771803,"max_rate":0.04843559436090225,"is_open":false,"buy_tag":null,"open_timestamp":1515628800000.0,"close_timestamp":1515630900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.389756231169542,"open_date":"2018-01-11 03:40:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":3.651e-05,"close_rate":3.2859000000000005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.10448878,"profit_abs":-3.650999999999996e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":3.2859000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2859000000000005e-05,"stop_loss_ratio":0.1,"min_rate":3.2859000000000005e-05,"max_rate":3.651e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515642000000.0,"close_timestamp":1515644700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011332594070446804,"open_date":"2018-01-11 03:55:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":0.08824105,"close_rate":0.08956798308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013269330827067605,"sell_reason":"roi","initial_stop_loss_abs":0.079416945,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079416945,"stop_loss_ratio":0.1,"min_rate":0.08824105,"max_rate":0.08956798308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515642900000.0,"close_timestamp":1515644700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.411522633744856,"open_date":"2018-01-11 04:00:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":0.00243,"close_rate":0.002442180451127819,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":1.2180451127819219e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002187,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002187,"stop_loss_ratio":0.1,"min_rate":0.00243,"max_rate":0.002442180451127819,"is_open":false,"buy_tag":null,"open_timestamp":1515643200000.0,"close_timestamp":1515646200000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022001890402423376,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:55:00+00:00","open_rate":0.04545064,"close_rate":0.046589753784461146,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":0.001139113784461146,"sell_reason":"roi","initial_stop_loss_abs":0.040905576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040905576,"stop_loss_ratio":0.1,"min_rate":0.04545064,"max_rate":0.046589753784461146,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.655990510083036,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":3.372e-05,"close_rate":3.456511278195488e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":8.4511278195488e-07,"sell_reason":"roi","initial_stop_loss_abs":3.0348e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0348e-05,"stop_loss_ratio":0.1,"min_rate":3.372e-05,"max_rate":3.456511278195488e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037821482602118005,"open_date":"2018-01-11 04:55:00+00:00","close_date":"2018-01-11 05:15:00+00:00","open_rate":0.02644,"close_rate":0.02710265664160401,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0006626566416040071,"sell_reason":"roi","initial_stop_loss_abs":0.023796,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023796,"stop_loss_ratio":0.1,"min_rate":0.02644,"max_rate":0.02710265664160401,"is_open":false,"buy_tag":null,"open_timestamp":1515646500000.0,"close_timestamp":1515647700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011348161597821153,"open_date":"2018-01-11 11:20:00+00:00","close_date":"2018-01-11 12:00:00+00:00","open_rate":0.08812,"close_rate":0.08856170426065162,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004417042606516125,"sell_reason":"roi","initial_stop_loss_abs":0.079308,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079308,"stop_loss_ratio":0.1,"min_rate":0.08812,"max_rate":0.08856170426065162,"is_open":false,"buy_tag":null,"open_timestamp":1515669600000.0,"close_timestamp":1515672000000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037263696923919086,"open_date":"2018-01-11 11:35:00+00:00","close_date":"2018-01-11 12:15:00+00:00","open_rate":0.02683577,"close_rate":0.026970285137844607,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013451513784460897,"sell_reason":"roi","initial_stop_loss_abs":0.024152193,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024152193,"stop_loss_ratio":0.1,"min_rate":0.02683577,"max_rate":0.026970285137844607,"is_open":false,"buy_tag":null,"open_timestamp":1515670500000.0,"close_timestamp":1515672900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.329335230737954,"open_date":"2018-01-11 14:00:00+00:00","close_date":"2018-01-11 14:25:00+00:00","open_rate":4.919e-05,"close_rate":5.04228320802005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.232832080200495e-06,"sell_reason":"roi","initial_stop_loss_abs":4.4271000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4271000000000004e-05,"stop_loss_ratio":0.1,"min_rate":4.919e-05,"max_rate":5.04228320802005e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515679200000.0,"close_timestamp":1515680700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.01138317402960718,"open_date":"2018-01-11 19:25:00+00:00","close_date":"2018-01-11 20:35:00+00:00","open_rate":0.08784896,"close_rate":0.08828930566416039,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":70,"profit_ratio":-0.0,"profit_abs":0.0004403456641603881,"sell_reason":"roi","initial_stop_loss_abs":0.079064064,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079064064,"stop_loss_ratio":0.1,"min_rate":0.08784896,"max_rate":0.08828930566416039,"is_open":false,"buy_tag":null,"open_timestamp":1515698700000.0,"close_timestamp":1515702900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.58863858961802,"open_date":"2018-01-11 22:35:00+00:00","close_date":"2018-01-11 23:30:00+00:00","open_rate":5.105e-05,"close_rate":5.130588972431077e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.558897243107704e-07,"sell_reason":"roi","initial_stop_loss_abs":4.5945e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.5945e-05,"stop_loss_ratio":0.1,"min_rate":5.105e-05,"max_rate":5.130588972431077e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515710100000.0,"close_timestamp":1515713400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.252525252525253,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:25:00+00:00","open_rate":3.96e-05,"close_rate":4.019548872180451e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":5.954887218045116e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5640000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5640000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.96e-05,"max_rate":4.019548872180451e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.66204506065858,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:35:00+00:00","open_rate":2.885e-05,"close_rate":2.899461152882205e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4461152882205115e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5965e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5965e-05,"stop_loss_ratio":0.1,"min_rate":2.885e-05,"max_rate":2.899461152882205e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03780718336483932,"open_date":"2018-01-11 23:30:00+00:00","close_date":"2018-01-12 00:05:00+00:00","open_rate":0.02645,"close_rate":0.026847744360902256,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0003977443609022545,"sell_reason":"roi","initial_stop_loss_abs":0.023805000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023805000000000003,"stop_loss_ratio":0.1,"min_rate":0.02645,"max_rate":0.026847744360902256,"is_open":false,"buy_tag":null,"open_timestamp":1515713400000.0,"close_timestamp":1515715500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020833333333333332,"open_date":"2018-01-11 23:55:00+00:00","close_date":"2018-01-12 01:15:00+00:00","open_rate":0.048,"close_rate":0.04824060150375939,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00024060150375938838,"sell_reason":"roi","initial_stop_loss_abs":0.0432,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0432,"stop_loss_ratio":0.1,"min_rate":0.048,"max_rate":0.04824060150375939,"is_open":false,"buy_tag":null,"open_timestamp":1515714900000.0,"close_timestamp":1515719700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.31287297527707,"open_date":"2018-01-12 21:15:00+00:00","close_date":"2018-01-12 21:40:00+00:00","open_rate":4.692e-05,"close_rate":4.809593984962405e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1759398496240516e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2227999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2227999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.692e-05,"max_rate":4.809593984962405e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515791700000.0,"close_timestamp":1515793200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38915654211062944,"open_date":"2018-01-13 00:55:00+00:00","close_date":"2018-01-13 06:20:00+00:00","open_rate":0.00256966,"close_rate":0.0025825405012531327,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":325,"profit_ratio":-0.0,"profit_abs":1.2880501253132587e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002312694,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002312694,"stop_loss_ratio":0.1,"min_rate":0.00256966,"max_rate":0.0025825405012531327,"is_open":false,"buy_tag":null,"open_timestamp":1515804900000.0,"close_timestamp":1515824400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":15.96933886937081,"open_date":"2018-01-13 10:55:00+00:00","close_date":"2018-01-13 11:35:00+00:00","open_rate":6.262e-05,"close_rate":6.293388471177944e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":3.138847117794446e-07,"sell_reason":"roi","initial_stop_loss_abs":5.6358e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.6358e-05,"stop_loss_ratio":0.1,"min_rate":6.262e-05,"max_rate":6.293388471177944e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515840900000.0,"close_timestamp":1515843300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.141649048625794,"open_date":"2018-01-13 13:05:00+00:00","close_date":"2018-01-15 14:10:00+00:00","open_rate":4.73e-05,"close_rate":4.753709273182957e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":2945,"profit_ratio":0.0,"profit_abs":2.3709273182957117e-07,"sell_reason":"roi","initial_stop_loss_abs":4.257e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.257e-05,"stop_loss_ratio":0.1,"min_rate":4.73e-05,"max_rate":4.753709273182957e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515848700000.0,"close_timestamp":1516025400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.49348507339601,"open_date":"2018-01-13 13:30:00+00:00","close_date":"2018-01-13 14:45:00+00:00","open_rate":6.063e-05,"close_rate":6.0933909774436085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":3.039097744360846e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4567e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4567e-05,"stop_loss_ratio":0.1,"min_rate":6.063e-05,"max_rate":6.0933909774436085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515850200000.0,"close_timestamp":1515854700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.023641941887746,"open_date":"2018-01-13 13:40:00+00:00","close_date":"2018-01-13 23:30:00+00:00","open_rate":0.00011082,"close_rate":0.00011137548872180448,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":590,"profit_ratio":-0.0,"profit_abs":5.554887218044781e-07,"sell_reason":"roi","initial_stop_loss_abs":9.9738e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.9738e-05,"stop_loss_ratio":0.1,"min_rate":0.00011082,"max_rate":0.00011137548872180448,"is_open":false,"buy_tag":null,"open_timestamp":1515850800000.0,"close_timestamp":1515886200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.863406408094438,"open_date":"2018-01-13 15:15:00+00:00","close_date":"2018-01-13 15:55:00+00:00","open_rate":5.93e-05,"close_rate":5.9597243107769415e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.9724310776941686e-07,"sell_reason":"roi","initial_stop_loss_abs":5.337e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.337e-05,"stop_loss_ratio":0.1,"min_rate":5.93e-05,"max_rate":5.9597243107769415e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515856500000.0,"close_timestamp":1515858900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020618543947292404,"open_date":"2018-01-13 16:30:00+00:00","close_date":"2018-01-13 17:10:00+00:00","open_rate":0.04850003,"close_rate":0.04874313791979949,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00024310791979949287,"sell_reason":"roi","initial_stop_loss_abs":0.043650027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.043650027,"stop_loss_ratio":0.1,"min_rate":0.04850003,"max_rate":0.04874313791979949,"is_open":false,"buy_tag":null,"open_timestamp":1515861000000.0,"close_timestamp":1515863400000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010178097365511457,"open_date":"2018-01-13 22:05:00+00:00","close_date":"2018-01-14 06:25:00+00:00","open_rate":0.09825019,"close_rate":0.09874267215538848,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":500,"profit_ratio":-0.0,"profit_abs":0.0004924821553884823,"sell_reason":"roi","initial_stop_loss_abs":0.088425171,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.088425171,"stop_loss_ratio":0.1,"min_rate":0.09825019,"max_rate":0.09874267215538848,"is_open":false,"buy_tag":null,"open_timestamp":1515881100000.0,"close_timestamp":1515911100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.616816218012627,"open_date":"2018-01-14 00:20:00+00:00","close_date":"2018-01-14 22:55:00+00:00","open_rate":6.018e-05,"close_rate":6.048165413533834e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":1355,"profit_ratio":0.0,"profit_abs":3.0165413533833987e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4162e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4162e-05,"stop_loss_ratio":0.1,"min_rate":6.018e-05,"max_rate":6.048165413533834e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515889200000.0,"close_timestamp":1515970500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010246952581919518,"open_date":"2018-01-14 12:45:00+00:00","close_date":"2018-01-14 13:25:00+00:00","open_rate":0.09758999,"close_rate":0.0980791628822055,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004891728822054991,"sell_reason":"roi","initial_stop_loss_abs":0.087830991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.087830991,"stop_loss_ratio":0.1,"min_rate":0.09758999,"max_rate":0.0980791628822055,"is_open":false,"buy_tag":null,"open_timestamp":1515933900000.0,"close_timestamp":1515936300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3215434083601286,"open_date":"2018-01-14 15:30:00+00:00","close_date":"2018-01-14 16:00:00+00:00","open_rate":0.00311,"close_rate":0.0031567669172932328,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.676691729323286e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002799,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002799,"stop_loss_ratio":0.1,"min_rate":0.00311,"max_rate":0.0031567669172932328,"is_open":false,"buy_tag":null,"open_timestamp":1515943800000.0,"close_timestamp":1515945600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.32010140812609433,"open_date":"2018-01-14 20:45:00+00:00","close_date":"2018-01-14 22:15:00+00:00","open_rate":0.00312401,"close_rate":0.003139669197994987,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":-0.0,"profit_abs":1.5659197994987058e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002811609,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002811609,"stop_loss_ratio":0.1,"min_rate":0.00312401,"max_rate":0.003139669197994987,"is_open":false,"buy_tag":null,"open_timestamp":1515962700000.0,"close_timestamp":1515968100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.057247866085791646,"open_date":"2018-01-14 23:35:00+00:00","close_date":"2018-01-15 00:30:00+00:00","open_rate":0.0174679,"close_rate":0.017555458395989976,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":8.755839598997492e-05,"sell_reason":"roi","initial_stop_loss_abs":0.015721110000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.015721110000000003,"stop_loss_ratio":0.1,"min_rate":0.0174679,"max_rate":0.017555458395989976,"is_open":false,"buy_tag":null,"open_timestamp":1515972900000.0,"close_timestamp":1515976200000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.013611282991367997,"open_date":"2018-01-14 23:45:00+00:00","close_date":"2018-01-15 00:25:00+00:00","open_rate":0.07346846,"close_rate":0.07383672295739348,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00036826295739347814,"sell_reason":"roi","initial_stop_loss_abs":0.066121614,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.066121614,"stop_loss_ratio":0.1,"min_rate":0.07346846,"max_rate":0.07383672295739348,"is_open":false,"buy_tag":null,"open_timestamp":1515973500000.0,"close_timestamp":1515975900000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010204706410596568,"open_date":"2018-01-15 02:25:00+00:00","close_date":"2018-01-15 03:05:00+00:00","open_rate":0.097994,"close_rate":0.09848519799498744,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004911979949874384,"sell_reason":"roi","initial_stop_loss_abs":0.0881946,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0881946,"stop_loss_ratio":0.1,"min_rate":0.097994,"max_rate":0.09848519799498744,"is_open":false,"buy_tag":null,"open_timestamp":1515983100000.0,"close_timestamp":1515985500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010353038616834042,"open_date":"2018-01-15 07:20:00+00:00","close_date":"2018-01-15 08:00:00+00:00","open_rate":0.09659,"close_rate":0.09707416040100247,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004841604010024786,"sell_reason":"roi","initial_stop_loss_abs":0.086931,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.086931,"stop_loss_ratio":0.1,"min_rate":0.09659,"max_rate":0.09707416040100247,"is_open":false,"buy_tag":null,"open_timestamp":1516000800000.0,"close_timestamp":1516003200000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.013016921998599,"open_date":"2018-01-15 08:20:00+00:00","close_date":"2018-01-15 08:55:00+00:00","open_rate":9.987e-05,"close_rate":0.00010137180451127818,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":1.501804511278178e-06,"sell_reason":"roi","initial_stop_loss_abs":8.9883e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.9883e-05,"stop_loss_ratio":0.1,"min_rate":9.987e-05,"max_rate":0.00010137180451127818,"is_open":false,"buy_tag":null,"open_timestamp":1516004400000.0,"close_timestamp":1516006500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010537752023511832,"open_date":"2018-01-15 12:10:00+00:00","close_date":"2018-01-16 02:50:00+00:00","open_rate":0.0948969,"close_rate":0.09537257368421052,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":880,"profit_ratio":0.0,"profit_abs":0.0004756736842105175,"sell_reason":"roi","initial_stop_loss_abs":0.08540721000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08540721000000001,"stop_loss_ratio":0.1,"min_rate":0.0948969,"max_rate":0.09537257368421052,"is_open":false,"buy_tag":null,"open_timestamp":1516018200000.0,"close_timestamp":1516071000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014084507042253523,"open_date":"2018-01-15 14:10:00+00:00","close_date":"2018-01-15 17:40:00+00:00","open_rate":0.071,"close_rate":0.07135588972431077,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":210,"profit_ratio":-0.0,"profit_abs":0.00035588972431077615,"sell_reason":"roi","initial_stop_loss_abs":0.0639,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0639,"stop_loss_ratio":0.1,"min_rate":0.071,"max_rate":0.07135588972431077,"is_open":false,"buy_tag":null,"open_timestamp":1516025400000.0,"close_timestamp":1516038000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021736763017766978,"open_date":"2018-01-15 14:30:00+00:00","close_date":"2018-01-15 15:10:00+00:00","open_rate":0.04600501,"close_rate":0.046235611553884705,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00023060155388470588,"sell_reason":"roi","initial_stop_loss_abs":0.041404509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041404509,"stop_loss_ratio":0.1,"min_rate":0.04600501,"max_rate":0.046235611553884705,"is_open":false,"buy_tag":null,"open_timestamp":1516026600000.0,"close_timestamp":1516029000000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.595465140919686,"open_date":"2018-01-15 18:10:00+00:00","close_date":"2018-01-15 19:25:00+00:00","open_rate":9.438e-05,"close_rate":9.485308270676693e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":4.7308270676692514e-07,"sell_reason":"roi","initial_stop_loss_abs":8.4942e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.4942e-05,"stop_loss_ratio":0.1,"min_rate":9.438e-05,"max_rate":9.485308270676693e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516039800000.0,"close_timestamp":1516044300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.032894726021471705,"open_date":"2018-01-15 18:35:00+00:00","close_date":"2018-01-15 19:15:00+00:00","open_rate":0.03040001,"close_rate":0.030552391002506264,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0001523810025062626,"sell_reason":"roi","initial_stop_loss_abs":0.027360009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027360009,"stop_loss_ratio":0.1,"min_rate":0.03040001,"max_rate":0.030552391002506264,"is_open":false,"buy_tag":null,"open_timestamp":1516041300000.0,"close_timestamp":1516043700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.13208840157615,"open_date":"2018-01-15 20:25:00+00:00","close_date":"2018-01-16 08:25:00+00:00","open_rate":5.837e-05,"close_rate":5.2533e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":720,"profit_ratio":-0.10448878,"profit_abs":-5.8369999999999985e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":5.2533e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2533e-05,"stop_loss_ratio":0.1,"min_rate":5.2533e-05,"max_rate":5.837e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516047900000.0,"close_timestamp":1516091100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021722130506560085,"open_date":"2018-01-15 20:40:00+00:00","close_date":"2018-01-15 22:00:00+00:00","open_rate":0.046036,"close_rate":0.04626675689223057,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00023075689223057277,"sell_reason":"roi","initial_stop_loss_abs":0.0414324,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0414324,"stop_loss_ratio":0.1,"min_rate":0.046036,"max_rate":0.04626675689223057,"is_open":false,"buy_tag":null,"open_timestamp":1516048800000.0,"close_timestamp":1516053600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.34861425832316545,"open_date":"2018-01-16 00:30:00+00:00","close_date":"2018-01-16 01:10:00+00:00","open_rate":0.0028685,"close_rate":0.0028828784461152877,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4378446115287727e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00258165,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00258165,"stop_loss_ratio":0.1,"min_rate":0.0028685,"max_rate":0.0028828784461152877,"is_open":false,"buy_tag":null,"open_timestamp":1516062600000.0,"close_timestamp":1516065000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014854967241083492,"open_date":"2018-01-16 01:15:00+00:00","close_date":"2018-01-16 02:35:00+00:00","open_rate":0.06731755,"close_rate":0.0676549813283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":0.0,"profit_abs":0.00033743132832080025,"sell_reason":"roi","initial_stop_loss_abs":0.060585795000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060585795000000005,"stop_loss_ratio":0.1,"min_rate":0.06731755,"max_rate":0.0676549813283208,"is_open":false,"buy_tag":null,"open_timestamp":1516065300000.0,"close_timestamp":1516070100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010848794492804754,"open_date":"2018-01-16 07:45:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":0.09217614,"close_rate":0.09263817578947368,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":0.0004620357894736804,"sell_reason":"roi","initial_stop_loss_abs":0.082958526,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082958526,"stop_loss_ratio":0.1,"min_rate":0.09217614,"max_rate":0.09263817578947368,"is_open":false,"buy_tag":null,"open_timestamp":1516088700000.0,"close_timestamp":1516092000000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06060606060606061,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:55:00+00:00","open_rate":0.0165,"close_rate":0.016913533834586467,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.00041353383458646656,"sell_reason":"roi","initial_stop_loss_abs":0.01485,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01485,"stop_loss_ratio":0.1,"min_rate":0.0165,"max_rate":0.016913533834586467,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":12.57387149503332,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":7.953e-05,"close_rate":8.311781954887218e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":3.587819548872171e-06,"sell_reason":"roi","initial_stop_loss_abs":7.157700000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.157700000000001e-05,"stop_loss_ratio":0.1,"min_rate":7.953e-05,"max_rate":8.311781954887218e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022122914915269236,"open_date":"2018-01-16 08:45:00+00:00","close_date":"2018-01-16 09:50:00+00:00","open_rate":0.045202,"close_rate":0.04542857644110275,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":0.00022657644110275071,"sell_reason":"roi","initial_stop_loss_abs":0.0406818,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0406818,"stop_loss_ratio":0.1,"min_rate":0.045202,"max_rate":0.04542857644110275,"is_open":false,"buy_tag":null,"open_timestamp":1516092300000.0,"close_timestamp":1516096200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.054878048780488,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:45:00+00:00","open_rate":5.248e-05,"close_rate":5.326917293233082e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.891729323308177e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7232e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7232e-05,"stop_loss_ratio":0.1,"min_rate":5.248e-05,"max_rate":5.326917293233082e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516095900000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03457434486802627,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:55:00+00:00","open_rate":0.02892318,"close_rate":0.02906815834586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0001449783458646603,"sell_reason":"roi","initial_stop_loss_abs":0.026030862000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.026030862000000002,"stop_loss_ratio":0.1,"min_rate":0.02892318,"max_rate":0.02906815834586466,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516096500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.38735944164405,"open_date":"2018-01-16 09:50:00+00:00","close_date":"2018-01-16 10:10:00+00:00","open_rate":5.158e-05,"close_rate":5.287273182957392e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.2927318295739246e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6422e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6422e-05,"stop_loss_ratio":0.1,"min_rate":5.158e-05,"max_rate":5.287273182957392e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516096200000.0,"close_timestamp":1516097400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035357778286929785,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:35:00+00:00","open_rate":0.02828232,"close_rate":0.02870761804511278,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042529804511277913,"sell_reason":"roi","initial_stop_loss_abs":0.025454088,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025454088,"stop_loss_ratio":0.1,"min_rate":0.02828232,"max_rate":0.02870761804511278,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516098900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022948496230938982,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:40:00+00:00","open_rate":0.04357584,"close_rate":0.044231115789473675,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0006552757894736777,"sell_reason":"roi","initial_stop_loss_abs":0.039218256,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039218256,"stop_loss_ratio":0.1,"min_rate":0.04357584,"max_rate":0.044231115789473675,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516099200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.64975755315181,"open_date":"2018-01-16 13:45:00+00:00","close_date":"2018-01-16 14:20:00+00:00","open_rate":5.362e-05,"close_rate":5.442631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":8.063157894736843e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8258e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8258e-05,"stop_loss_ratio":0.1,"min_rate":5.362e-05,"max_rate":5.442631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516110300000.0,"close_timestamp":1516112400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.86080724254998,"open_date":"2018-01-16 17:30:00+00:00","close_date":"2018-01-16 18:25:00+00:00","open_rate":5.302e-05,"close_rate":5.328576441102756e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.6576441102756397e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7718e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7718e-05,"stop_loss_ratio":0.1,"min_rate":5.302e-05,"max_rate":5.328576441102756e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516123800000.0,"close_timestamp":1516127100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010952903718828448,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:45:00+00:00","open_rate":0.09129999,"close_rate":0.09267292218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013729321804511196,"sell_reason":"roi","initial_stop_loss_abs":0.082169991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082169991,"stop_loss_ratio":0.1,"min_rate":0.09129999,"max_rate":0.09267292218045112,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516128300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":26.26050420168067,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:35:00+00:00","open_rate":3.808e-05,"close_rate":3.903438596491228e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":9.543859649122774e-07,"sell_reason":"roi","initial_stop_loss_abs":3.4272e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.4272e-05,"stop_loss_ratio":0.1,"min_rate":3.808e-05,"max_rate":3.903438596491228e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516127700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035574376772493324,"open_date":"2018-01-16 19:00:00+00:00","close_date":"2018-01-16 19:30:00+00:00","open_rate":0.02811012,"close_rate":0.028532828571428567,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042270857142856846,"sell_reason":"roi","initial_stop_loss_abs":0.025299108,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025299108,"stop_loss_ratio":0.1,"min_rate":0.02811012,"max_rate":0.028532828571428567,"is_open":false,"buy_tag":null,"open_timestamp":1516129200000.0,"close_timestamp":1516131000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.387028357567759,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":0.00258379,"close_rate":0.002325411,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.10448878,"profit_abs":-0.000258379,"sell_reason":"stop_loss","initial_stop_loss_abs":0.002325411,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002325411,"stop_loss_ratio":0.1,"min_rate":0.002325411,"max_rate":0.00258379,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516141500000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":39.07776475185619,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":2.559e-05,"close_rate":2.3031e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.10448878,"profit_abs":-2.5590000000000004e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":2.3031e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.3031e-05,"stop_loss_ratio":0.1,"min_rate":2.3031e-05,"max_rate":2.559e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516142700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":13.123359580052494,"open_date":"2018-01-16 21:35:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":7.62e-05,"close_rate":6.858e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.10448878,"profit_abs":-7.619999999999998e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":6.858e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.858e-05,"stop_loss_ratio":0.1,"min_rate":6.858e-05,"max_rate":7.62e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516138500000.0,"close_timestamp":1516141500000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4350777048780912,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:35:00+00:00","open_rate":0.00229844,"close_rate":0.002402129022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010368902255639091,"sell_reason":"roi","initial_stop_loss_abs":0.0020685960000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0020685960000000002,"stop_loss_ratio":0.1,"min_rate":0.00229844,"max_rate":0.002402129022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06622516556291391,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:40:00+00:00","open_rate":0.0151,"close_rate":0.015781203007518795,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":0.0006812030075187946,"sell_reason":"roi","initial_stop_loss_abs":0.013590000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.013590000000000001,"stop_loss_ratio":0.1,"min_rate":0.0151,"max_rate":0.015781203007518795,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.42431134269081283,"open_date":"2018-01-16 22:40:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":0.00235676,"close_rate":0.00246308,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010632000000000003,"sell_reason":"roi","initial_stop_loss_abs":0.002121084,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002121084,"stop_loss_ratio":0.1,"min_rate":0.00235676,"max_rate":0.00246308,"is_open":false,"buy_tag":null,"open_timestamp":1516142400000.0,"close_timestamp":1516142700000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01585559988076589,"open_date":"2018-01-16 22:45:00+00:00","close_date":"2018-01-16 23:05:00+00:00","open_rate":0.0630692,"close_rate":0.06464988170426066,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0015806817042606502,"sell_reason":"roi","initial_stop_loss_abs":0.056762280000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.056762280000000005,"stop_loss_ratio":0.1,"min_rate":0.0630692,"max_rate":0.06464988170426066,"is_open":false,"buy_tag":null,"open_timestamp":1516142700000.0,"close_timestamp":1516143900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":45.45454545454545,"open_date":"2018-01-16 22:50:00+00:00","close_date":"2018-01-16 22:55:00+00:00","open_rate":2.2e-05,"close_rate":2.299248120300751e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":9.924812030075114e-07,"sell_reason":"roi","initial_stop_loss_abs":1.98e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":1.98e-05,"stop_loss_ratio":0.1,"min_rate":2.2e-05,"max_rate":2.299248120300751e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516143000000.0,"close_timestamp":1516143300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.10454362685967,"open_date":"2018-01-17 03:30:00+00:00","close_date":"2018-01-17 04:00:00+00:00","open_rate":4.974e-05,"close_rate":5.048796992481203e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.479699248120277e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4766000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4766000000000005e-05,"stop_loss_ratio":0.1,"min_rate":4.974e-05,"max_rate":5.048796992481203e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516159800000.0,"close_timestamp":1516161600000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":14.068655036578503,"open_date":"2018-01-17 03:55:00+00:00","close_date":"2018-01-17 04:15:00+00:00","open_rate":7.108e-05,"close_rate":7.28614536340852e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.7814536340851996e-06,"sell_reason":"roi","initial_stop_loss_abs":6.3972e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.3972e-05,"stop_loss_ratio":0.1,"min_rate":7.108e-05,"max_rate":7.28614536340852e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516161300000.0,"close_timestamp":1516162500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.0231107002542177,"open_date":"2018-01-17 09:35:00+00:00","close_date":"2018-01-17 10:15:00+00:00","open_rate":0.04327,"close_rate":0.04348689223057644,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002168922305764362,"sell_reason":"roi","initial_stop_loss_abs":0.038943000000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.038943000000000005,"stop_loss_ratio":0.1,"min_rate":0.04327,"max_rate":0.04348689223057644,"is_open":false,"buy_tag":null,"open_timestamp":1516181700000.0,"close_timestamp":1516184100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.012007204322593,"open_date":"2018-01-17 10:20:00+00:00","close_date":"2018-01-17 17:00:00+00:00","open_rate":4.997e-05,"close_rate":5.022047619047618e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":400,"profit_ratio":-0.0,"profit_abs":2.504761904761831e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4973e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4973e-05,"stop_loss_ratio":0.1,"min_rate":4.997e-05,"max_rate":5.022047619047618e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516184400000.0,"close_timestamp":1516208400000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014626687444363738,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:25:00+00:00","open_rate":0.06836818,"close_rate":0.06871087764411027,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00034269764411026804,"sell_reason":"roi","initial_stop_loss_abs":0.061531362,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061531362,"stop_loss_ratio":0.1,"min_rate":0.06836818,"max_rate":0.06871087764411027,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516188300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.548209366391184,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:10:00+00:00","open_rate":3.63e-05,"close_rate":3.648195488721804e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.8195488721804031e-07,"sell_reason":"roi","initial_stop_loss_abs":3.2670000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2670000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.63e-05,"max_rate":3.648195488721804e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516187400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03558718861209965,"open_date":"2018-01-17 12:30:00+00:00","close_date":"2018-01-17 22:05:00+00:00","open_rate":0.0281,"close_rate":0.02824085213032581,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":575,"profit_ratio":-0.0,"profit_abs":0.0001408521303258095,"sell_reason":"roi","initial_stop_loss_abs":0.02529,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.02529,"stop_loss_ratio":0.1,"min_rate":0.0281,"max_rate":0.02824085213032581,"is_open":false,"buy_tag":null,"open_timestamp":1516192200000.0,"close_timestamp":1516226700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011559355963546878,"open_date":"2018-01-17 12:35:00+00:00","close_date":"2018-01-17 16:55:00+00:00","open_rate":0.08651001,"close_rate":0.08694364413533832,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":-0.0,"profit_abs":0.00043363413533832607,"sell_reason":"roi","initial_stop_loss_abs":0.077859009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077859009,"stop_loss_ratio":0.1,"min_rate":0.08651001,"max_rate":0.08694364413533832,"is_open":false,"buy_tag":null,"open_timestamp":1516192500000.0,"close_timestamp":1516208100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.752529735487308,"open_date":"2018-01-18 05:00:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":5.633e-05,"close_rate":5.6612355889724306e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.8235588972430847e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0697e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0697e-05,"stop_loss_ratio":0.1,"min_rate":5.633e-05,"max_rate":5.6612355889724306e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516251600000.0,"close_timestamp":1516254900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01430923457900944,"open_date":"2018-01-18 05:20:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":0.06988494,"close_rate":0.07093584135338346,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0010509013533834544,"sell_reason":"roi","initial_stop_loss_abs":0.06289644600000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06289644600000001,"stop_loss_ratio":0.1,"min_rate":0.06988494,"max_rate":0.07093584135338346,"is_open":false,"buy_tag":null,"open_timestamp":1516252800000.0,"close_timestamp":1516254900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.034265103697024,"open_date":"2018-01-18 07:35:00+00:00","close_date":"2018-01-18 08:15:00+00:00","open_rate":5.545e-05,"close_rate":5.572794486215538e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.779448621553787e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9905e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9905e-05,"stop_loss_ratio":0.1,"min_rate":5.545e-05,"max_rate":5.572794486215538e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516260900000.0,"close_timestamp":1516263300000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06121723118136401,"open_date":"2018-01-18 09:00:00+00:00","close_date":"2018-01-18 09:40:00+00:00","open_rate":0.01633527,"close_rate":0.016417151052631574,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":8.188105263157511e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014701743,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014701743,"stop_loss_ratio":0.1,"min_rate":0.01633527,"max_rate":0.016417151052631574,"is_open":false,"buy_tag":null,"open_timestamp":1516266000000.0,"close_timestamp":1516268400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3707356136045141,"open_date":"2018-01-18 16:40:00+00:00","close_date":"2018-01-18 17:20:00+00:00","open_rate":0.00269734,"close_rate":0.002710860501253133,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.3520501253133123e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002427606,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002427606,"stop_loss_ratio":0.1,"min_rate":0.00269734,"max_rate":0.002710860501253133,"is_open":false,"buy_tag":null,"open_timestamp":1516293600000.0,"close_timestamp":1516296000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.346368715083802,"open_date":"2018-01-18 18:05:00+00:00","close_date":"2018-01-18 18:30:00+00:00","open_rate":4.475e-05,"close_rate":4.587155388471177e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1215538847117757e-06,"sell_reason":"roi","initial_stop_loss_abs":4.0274999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.0274999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.475e-05,"max_rate":4.587155388471177e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516298700000.0,"close_timestamp":1516300200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":35.842293906810035,"open_date":"2018-01-18 18:25:00+00:00","close_date":"2018-01-18 18:55:00+00:00","open_rate":2.79e-05,"close_rate":2.8319548872180444e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.1954887218044365e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5110000000000002e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5110000000000002e-05,"stop_loss_ratio":0.1,"min_rate":2.79e-05,"max_rate":2.8319548872180444e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516299900000.0,"close_timestamp":1516301700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022525942001105574,"open_date":"2018-01-18 20:10:00+00:00","close_date":"2018-01-18 20:50:00+00:00","open_rate":0.04439326,"close_rate":0.04461578260651629,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00022252260651629135,"sell_reason":"roi","initial_stop_loss_abs":0.039953933999999997,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039953933999999997,"stop_loss_ratio":0.1,"min_rate":0.04439326,"max_rate":0.04461578260651629,"is_open":false,"buy_tag":null,"open_timestamp":1516306200000.0,"close_timestamp":1516308600000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.271714922048996,"open_date":"2018-01-18 21:30:00+00:00","close_date":"2018-01-19 00:35:00+00:00","open_rate":4.49e-05,"close_rate":4.51250626566416e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":185,"profit_ratio":0.0,"profit_abs":2.2506265664159932e-07,"sell_reason":"roi","initial_stop_loss_abs":4.041e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.041e-05,"stop_loss_ratio":0.1,"min_rate":4.49e-05,"max_rate":4.51250626566416e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516311000000.0,"close_timestamp":1516322100000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03502626970227671,"open_date":"2018-01-18 21:55:00+00:00","close_date":"2018-01-19 05:05:00+00:00","open_rate":0.02855,"close_rate":0.028693107769423555,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":430,"profit_ratio":-0.0,"profit_abs":0.00014310776942355607,"sell_reason":"roi","initial_stop_loss_abs":0.025695,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025695,"stop_loss_ratio":0.1,"min_rate":0.02855,"max_rate":0.028693107769423555,"is_open":false,"buy_tag":null,"open_timestamp":1516312500000.0,"close_timestamp":1516338300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.25327812284334,"open_date":"2018-01-18 22:10:00+00:00","close_date":"2018-01-18 22:50:00+00:00","open_rate":5.796e-05,"close_rate":5.8250526315789473e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.905263157894727e-07,"sell_reason":"roi","initial_stop_loss_abs":5.2164000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2164000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.796e-05,"max_rate":5.8250526315789473e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516313400000.0,"close_timestamp":1516315800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02303975994413319,"open_date":"2018-01-18 23:50:00+00:00","close_date":"2018-01-19 00:30:00+00:00","open_rate":0.04340323,"close_rate":0.04362079005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0002175600501253122,"sell_reason":"roi","initial_stop_loss_abs":0.039062907,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039062907,"stop_loss_ratio":0.1,"min_rate":0.04340323,"max_rate":0.04362079005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516319400000.0,"close_timestamp":1516321800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02244943545282195,"open_date":"2018-01-19 16:45:00+00:00","close_date":"2018-01-19 17:35:00+00:00","open_rate":0.04454455,"close_rate":0.04476783095238095,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0002232809523809512,"sell_reason":"roi","initial_stop_loss_abs":0.040090095000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040090095000000006,"stop_loss_ratio":0.1,"min_rate":0.04454455,"max_rate":0.04476783095238095,"is_open":false,"buy_tag":null,"open_timestamp":1516380300000.0,"close_timestamp":1516383300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.793594306049823,"open_date":"2018-01-19 17:15:00+00:00","close_date":"2018-01-19 19:55:00+00:00","open_rate":5.62e-05,"close_rate":5.648170426065162e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":160,"profit_ratio":-0.0,"profit_abs":2.817042606516199e-07,"sell_reason":"roi","initial_stop_loss_abs":5.058e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.058e-05,"stop_loss_ratio":0.1,"min_rate":5.62e-05,"max_rate":5.648170426065162e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382100000.0,"close_timestamp":1516391700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.046784973496194,"open_date":"2018-01-19 17:20:00+00:00","close_date":"2018-01-19 20:15:00+00:00","open_rate":4.339e-05,"close_rate":4.360749373433584e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.174937343358337e-07,"sell_reason":"roi","initial_stop_loss_abs":3.9051e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.9051e-05,"stop_loss_ratio":0.1,"min_rate":4.339e-05,"max_rate":4.360749373433584e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382400000.0,"close_timestamp":1516392900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.910802775024777,"open_date":"2018-01-20 04:45:00+00:00","close_date":"2018-01-20 17:35:00+00:00","open_rate":0.0001009,"close_rate":0.00010140576441102755,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":770,"profit_ratio":0.0,"profit_abs":5.057644110275549e-07,"sell_reason":"roi","initial_stop_loss_abs":9.081e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.081e-05,"stop_loss_ratio":0.1,"min_rate":0.0001009,"max_rate":0.00010140576441102755,"is_open":false,"buy_tag":null,"open_timestamp":1516423500000.0,"close_timestamp":1516469700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3696789338459548,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 15:15:00+00:00","open_rate":0.00270505,"close_rate":0.002718609147869674,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":625,"profit_ratio":-0.0,"profit_abs":1.3559147869673764e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002434545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002434545,"stop_loss_ratio":0.1,"min_rate":0.00270505,"max_rate":0.002718609147869674,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516461300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.033333311111125925,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 07:00:00+00:00","open_rate":0.03000002,"close_rate":0.030150396040100245,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":0.00015037604010024672,"sell_reason":"roi","initial_stop_loss_abs":0.027000018,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027000018,"stop_loss_ratio":0.1,"min_rate":0.03000002,"max_rate":0.030150396040100245,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516431600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.315018315018317,"open_date":"2018-01-20 09:00:00+00:00","close_date":"2018-01-20 09:40:00+00:00","open_rate":5.46e-05,"close_rate":5.4873684210526304e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.736842105263053e-07,"sell_reason":"roi","initial_stop_loss_abs":4.914e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.914e-05,"stop_loss_ratio":0.1,"min_rate":5.46e-05,"max_rate":5.4873684210526304e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516438800000.0,"close_timestamp":1516441200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03244412634781012,"open_date":"2018-01-20 18:25:00+00:00","close_date":"2018-01-25 03:50:00+00:00","open_rate":0.03082222,"close_rate":0.027739998,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":6325,"profit_ratio":-0.10448878,"profit_abs":-0.0030822220000000025,"sell_reason":"stop_loss","initial_stop_loss_abs":0.027739998000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027739998000000002,"stop_loss_ratio":0.1,"min_rate":0.027739998,"max_rate":0.03082222,"is_open":false,"buy_tag":null,"open_timestamp":1516472700000.0,"close_timestamp":1516852200000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011148273260677065,"open_date":"2018-01-20 22:25:00+00:00","close_date":"2018-01-20 23:15:00+00:00","open_rate":0.08969999,"close_rate":0.09014961401002504,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":0.00044962401002504593,"sell_reason":"roi","initial_stop_loss_abs":0.080729991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080729991,"stop_loss_ratio":0.1,"min_rate":0.08969999,"max_rate":0.09014961401002504,"is_open":false,"buy_tag":null,"open_timestamp":1516487100000.0,"close_timestamp":1516490100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06125570520324337,"open_date":"2018-01-21 02:50:00+00:00","close_date":"2018-01-21 14:30:00+00:00","open_rate":0.01632501,"close_rate":0.01640683962406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":700,"profit_ratio":0.0,"profit_abs":8.182962406014932e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014692509000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014692509000000001,"stop_loss_ratio":0.1,"min_rate":0.01632501,"max_rate":0.01640683962406015,"is_open":false,"buy_tag":null,"open_timestamp":1516503000000.0,"close_timestamp":1516545000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01417675579120474,"open_date":"2018-01-21 10:20:00+00:00","close_date":"2018-01-21 11:00:00+00:00","open_rate":0.070538,"close_rate":0.07089157393483708,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00035357393483707866,"sell_reason":"roi","initial_stop_loss_abs":0.0634842,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0634842,"stop_loss_ratio":0.1,"min_rate":0.070538,"max_rate":0.07089157393483708,"is_open":false,"buy_tag":null,"open_timestamp":1516530000000.0,"close_timestamp":1516532400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.864365214110546,"open_date":"2018-01-21 15:50:00+00:00","close_date":"2018-01-21 18:45:00+00:00","open_rate":5.301e-05,"close_rate":5.327571428571427e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.657142857142672e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7709e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7709e-05,"stop_loss_ratio":0.1,"min_rate":5.301e-05,"max_rate":5.327571428571427e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516549800000.0,"close_timestamp":1516560300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.284450063211125,"open_date":"2018-01-21 16:20:00+00:00","close_date":"2018-01-21 17:00:00+00:00","open_rate":3.955e-05,"close_rate":3.9748245614035085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.9824561403508552e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5595e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5595e-05,"stop_loss_ratio":0.1,"min_rate":3.955e-05,"max_rate":3.9748245614035085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516551600000.0,"close_timestamp":1516554000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38683971296493297,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:45:00+00:00","open_rate":0.00258505,"close_rate":0.002623922932330827,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.8872932330826816e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326545,"stop_loss_ratio":0.1,"min_rate":0.00258505,"max_rate":0.002623922932330827,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.621316935690498,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:55:00+00:00","open_rate":3.903e-05,"close_rate":3.922563909774435e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.9563909774435151e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5127e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5127e-05,"stop_loss_ratio":0.1,"min_rate":3.903e-05,"max_rate":3.922563909774435e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.098548510313215,"open_date":"2018-01-22 00:35:00+00:00","close_date":"2018-01-22 10:35:00+00:00","open_rate":5.236e-05,"close_rate":5.262245614035087e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":600,"profit_ratio":-0.0,"profit_abs":2.624561403508717e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7124e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7124e-05,"stop_loss_ratio":0.1,"min_rate":5.236e-05,"max_rate":5.262245614035087e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516581300000.0,"close_timestamp":1516617300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.076650420912717,"open_date":"2018-01-22 01:30:00+00:00","close_date":"2018-01-22 02:10:00+00:00","open_rate":9.028e-05,"close_rate":9.07325313283208e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":4.5253132832080657e-07,"sell_reason":"roi","initial_stop_loss_abs":8.1252e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.1252e-05,"stop_loss_ratio":0.1,"min_rate":9.028e-05,"max_rate":9.07325313283208e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516584600000.0,"close_timestamp":1516587000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3721622627465575,"open_date":"2018-01-22 12:25:00+00:00","close_date":"2018-01-22 14:35:00+00:00","open_rate":0.002687,"close_rate":0.002700468671679198,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":1.3468671679197925e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0024183000000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0024183000000000004,"stop_loss_ratio":0.1,"min_rate":0.002687,"max_rate":0.002700468671679198,"is_open":false,"buy_tag":null,"open_timestamp":1516623900000.0,"close_timestamp":1516631700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.99232245681382,"open_date":"2018-01-22 13:15:00+00:00","close_date":"2018-01-22 13:55:00+00:00","open_rate":4.168e-05,"close_rate":4.188892230576441e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.0892230576441054e-07,"sell_reason":"roi","initial_stop_loss_abs":3.7512e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.7512e-05,"stop_loss_ratio":0.1,"min_rate":4.168e-05,"max_rate":4.188892230576441e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516626900000.0,"close_timestamp":1516629300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.336583153837434,"open_date":"2018-01-22 14:00:00+00:00","close_date":"2018-01-22 14:30:00+00:00","open_rate":8.821e-05,"close_rate":8.953646616541353e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":1.326466165413529e-06,"sell_reason":"roi","initial_stop_loss_abs":7.9389e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.9389e-05,"stop_loss_ratio":0.1,"min_rate":8.821e-05,"max_rate":8.953646616541353e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516629600000.0,"close_timestamp":1516631400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.334880123743233,"open_date":"2018-01-22 15:55:00+00:00","close_date":"2018-01-22 16:40:00+00:00","open_rate":5.172e-05,"close_rate":5.1979248120300745e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.592481203007459e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6548e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6548e-05,"stop_loss_ratio":0.1,"min_rate":5.172e-05,"max_rate":5.1979248120300745e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516636500000.0,"close_timestamp":1516639200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.04692663582287,"open_date":"2018-01-22 16:05:00+00:00","close_date":"2018-01-22 16:25:00+00:00","open_rate":3.026e-05,"close_rate":3.101839598997494e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":7.5839598997494e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7234e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7234e-05,"stop_loss_ratio":0.1,"min_rate":3.026e-05,"max_rate":3.101839598997494e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516637100000.0,"close_timestamp":1516638300000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014156285390713478,"open_date":"2018-01-22 19:50:00+00:00","close_date":"2018-01-23 00:10:00+00:00","open_rate":0.07064,"close_rate":0.07099408521303258,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":0.0,"profit_abs":0.00035408521303258167,"sell_reason":"roi","initial_stop_loss_abs":0.063576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.063576,"stop_loss_ratio":0.1,"min_rate":0.07064,"max_rate":0.07099408521303258,"is_open":false,"buy_tag":null,"open_timestamp":1516650600000.0,"close_timestamp":1516666200000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06080938507725528,"open_date":"2018-01-22 21:25:00+00:00","close_date":"2018-01-22 22:05:00+00:00","open_rate":0.01644483,"close_rate":0.01652726022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.243022556390922e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014800347,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014800347,"stop_loss_ratio":0.1,"min_rate":0.01644483,"max_rate":0.01652726022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516656300000.0,"close_timestamp":1516658700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.08935580697299,"open_date":"2018-01-23 00:05:00+00:00","close_date":"2018-01-23 00:35:00+00:00","open_rate":4.331e-05,"close_rate":4.3961278195488714e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":6.512781954887175e-07,"sell_reason":"roi","initial_stop_loss_abs":3.8979e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.8979e-05,"stop_loss_ratio":0.1,"min_rate":4.331e-05,"max_rate":4.3961278195488714e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516665900000.0,"close_timestamp":1516667700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.250000000000004,"open_date":"2018-01-23 01:50:00+00:00","close_date":"2018-01-23 02:15:00+00:00","open_rate":3.2e-05,"close_rate":3.2802005012531326e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":8.020050125313278e-07,"sell_reason":"roi","initial_stop_loss_abs":2.88e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.88e-05,"stop_loss_ratio":0.1,"min_rate":3.2e-05,"max_rate":3.2802005012531326e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516672200000.0,"close_timestamp":1516673700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010907854156754155,"open_date":"2018-01-23 04:25:00+00:00","close_date":"2018-01-23 05:15:00+00:00","open_rate":0.09167706,"close_rate":0.09213659413533835,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0004595341353383492,"sell_reason":"roi","initial_stop_loss_abs":0.08250935400000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08250935400000001,"stop_loss_ratio":0.1,"min_rate":0.09167706,"max_rate":0.09213659413533835,"is_open":false,"buy_tag":null,"open_timestamp":1516681500000.0,"close_timestamp":1516684500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014440474918339115,"open_date":"2018-01-23 07:35:00+00:00","close_date":"2018-01-23 09:00:00+00:00","open_rate":0.0692498,"close_rate":0.06959691679197995,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":0.0,"profit_abs":0.0003471167919799484,"sell_reason":"roi","initial_stop_loss_abs":0.06232482,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06232482,"stop_loss_ratio":0.1,"min_rate":0.0692498,"max_rate":0.06959691679197995,"is_open":false,"buy_tag":null,"open_timestamp":1516692900000.0,"close_timestamp":1516698000000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.426775612822127,"open_date":"2018-01-23 10:50:00+00:00","close_date":"2018-01-23 13:05:00+00:00","open_rate":3.182e-05,"close_rate":3.197949874686716e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":135,"profit_ratio":0.0,"profit_abs":1.594987468671663e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8638e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8638e-05,"stop_loss_ratio":0.1,"min_rate":3.182e-05,"max_rate":3.197949874686716e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516704600000.0,"close_timestamp":1516712700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024461839530332683,"open_date":"2018-01-23 11:05:00+00:00","close_date":"2018-01-23 16:05:00+00:00","open_rate":0.04088,"close_rate":0.04108491228070175,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":300,"profit_ratio":-0.0,"profit_abs":0.0002049122807017481,"sell_reason":"roi","initial_stop_loss_abs":0.036792,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036792,"stop_loss_ratio":0.1,"min_rate":0.04088,"max_rate":0.04108491228070175,"is_open":false,"buy_tag":null,"open_timestamp":1516705500000.0,"close_timestamp":1516723500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.417475728155342,"open_date":"2018-01-23 14:55:00+00:00","close_date":"2018-01-23 15:35:00+00:00","open_rate":5.15e-05,"close_rate":5.175814536340851e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5814536340851513e-07,"sell_reason":"roi","initial_stop_loss_abs":4.635e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.635e-05,"stop_loss_ratio":0.1,"min_rate":5.15e-05,"max_rate":5.175814536340851e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516719300000.0,"close_timestamp":1516721700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011023294646713328,"open_date":"2018-01-23 16:35:00+00:00","close_date":"2018-01-24 00:05:00+00:00","open_rate":0.09071698,"close_rate":0.09117170170426064,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":450,"profit_ratio":0.0,"profit_abs":0.00045472170426064107,"sell_reason":"roi","initial_stop_loss_abs":0.081645282,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.081645282,"stop_loss_ratio":0.1,"min_rate":0.09071698,"max_rate":0.09117170170426064,"is_open":false,"buy_tag":null,"open_timestamp":1516725300000.0,"close_timestamp":1516752300000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.969309462915604,"open_date":"2018-01-23 17:25:00+00:00","close_date":"2018-01-23 18:45:00+00:00","open_rate":3.128e-05,"close_rate":3.1436791979949865e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":1.5679197994986587e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8152e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8152e-05,"stop_loss_ratio":0.1,"min_rate":3.128e-05,"max_rate":3.1436791979949865e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516728300000.0,"close_timestamp":1516733100000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.465724751439037,"open_date":"2018-01-23 20:15:00+00:00","close_date":"2018-01-23 22:00:00+00:00","open_rate":9.555e-05,"close_rate":9.602894736842104e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":4.789473684210343e-07,"sell_reason":"roi","initial_stop_loss_abs":8.5995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.5995e-05,"stop_loss_ratio":0.1,"min_rate":9.555e-05,"max_rate":9.602894736842104e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516738500000.0,"close_timestamp":1516744800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02450979791426522,"open_date":"2018-01-23 22:30:00+00:00","close_date":"2018-01-23 23:10:00+00:00","open_rate":0.04080001,"close_rate":0.0410045213283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00020451132832080554,"sell_reason":"roi","initial_stop_loss_abs":0.036720009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036720009,"stop_loss_ratio":0.1,"min_rate":0.04080001,"max_rate":0.0410045213283208,"is_open":false,"buy_tag":null,"open_timestamp":1516746600000.0,"close_timestamp":1516749000000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.368584156498162,"open_date":"2018-01-23 23:50:00+00:00","close_date":"2018-01-24 03:35:00+00:00","open_rate":5.163e-05,"close_rate":5.18887969924812e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":225,"profit_ratio":-0.0,"profit_abs":2.587969924812037e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6467e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6467e-05,"stop_loss_ratio":0.1,"min_rate":5.163e-05,"max_rate":5.18887969924812e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516751400000.0,"close_timestamp":1516764900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024747691102289384,"open_date":"2018-01-24 00:20:00+00:00","close_date":"2018-01-24 01:50:00+00:00","open_rate":0.04040781,"close_rate":0.04061035541353383,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":0.0,"profit_abs":0.0002025454135338306,"sell_reason":"roi","initial_stop_loss_abs":0.036367029,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036367029,"stop_loss_ratio":0.1,"min_rate":0.04040781,"max_rate":0.04061035541353383,"is_open":false,"buy_tag":null,"open_timestamp":1516753200000.0,"close_timestamp":1516758600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.485580670303975,"open_date":"2018-01-24 06:45:00+00:00","close_date":"2018-01-24 07:25:00+00:00","open_rate":5.132e-05,"close_rate":5.157724310776942e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5724310776941724e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6188000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6188000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.132e-05,"max_rate":5.157724310776942e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516776300000.0,"close_timestamp":1516778700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.23816852635629,"open_date":"2018-01-24 14:15:00+00:00","close_date":"2018-01-24 14:25:00+00:00","open_rate":5.198e-05,"close_rate":5.432496240601503e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":2.344962406015033e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6782e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6782e-05,"stop_loss_ratio":0.1,"min_rate":5.198e-05,"max_rate":5.432496240601503e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516803300000.0,"close_timestamp":1516803900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.74394237066143,"open_date":"2018-01-24 14:50:00+00:00","close_date":"2018-01-24 16:35:00+00:00","open_rate":3.054e-05,"close_rate":3.069308270676692e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":1.5308270676691466e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7486000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7486000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.054e-05,"max_rate":3.069308270676692e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516805400000.0,"close_timestamp":1516811700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.795638562020944,"open_date":"2018-01-24 15:10:00+00:00","close_date":"2018-01-24 16:15:00+00:00","open_rate":9.263e-05,"close_rate":9.309431077694236e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":0.0,"profit_abs":4.6431077694236234e-07,"sell_reason":"roi","initial_stop_loss_abs":8.3367e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.3367e-05,"stop_loss_ratio":0.1,"min_rate":9.263e-05,"max_rate":9.309431077694236e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516806600000.0,"close_timestamp":1516810500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.13565469713457,"open_date":"2018-01-24 22:40:00+00:00","close_date":"2018-01-24 23:25:00+00:00","open_rate":5.514e-05,"close_rate":5.54163909774436e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.7639097744360576e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9625999999999995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9625999999999995e-05,"stop_loss_ratio":0.1,"min_rate":5.514e-05,"max_rate":5.54163909774436e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516833600000.0,"close_timestamp":1516836300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":20.3210729526519,"open_date":"2018-01-25 00:50:00+00:00","close_date":"2018-01-25 01:30:00+00:00","open_rate":4.921e-05,"close_rate":4.9456666666666664e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.4666666666666543e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4289e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4289e-05,"stop_loss_ratio":0.1,"min_rate":4.921e-05,"max_rate":4.9456666666666664e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516841400000.0,"close_timestamp":1516843800000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38461538461538464,"open_date":"2018-01-25 08:15:00+00:00","close_date":"2018-01-25 12:15:00+00:00","open_rate":0.0026,"close_rate":0.002613032581453634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":240,"profit_ratio":0.0,"profit_abs":1.3032581453634e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00234,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00234,"stop_loss_ratio":0.1,"min_rate":0.0026,"max_rate":0.002613032581453634,"is_open":false,"buy_tag":null,"open_timestamp":1516868100000.0,"close_timestamp":1516882500000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03571593119825878,"open_date":"2018-01-25 10:25:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":0.02799871,"close_rate":0.028139054411027563,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":350,"profit_ratio":-0.0,"profit_abs":0.00014034441102756326,"sell_reason":"roi","initial_stop_loss_abs":0.025198839,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025198839,"stop_loss_ratio":0.1,"min_rate":0.02799871,"max_rate":0.028139054411027563,"is_open":false,"buy_tag":null,"open_timestamp":1516875900000.0,"close_timestamp":1516896900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024516401717913302,"open_date":"2018-01-25 11:00:00+00:00","close_date":"2018-01-25 11:45:00+00:00","open_rate":0.04078902,"close_rate":0.0409934762406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020445624060149575,"sell_reason":"roi","initial_stop_loss_abs":0.036710118,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036710118,"stop_loss_ratio":0.1,"min_rate":0.04078902,"max_rate":0.0409934762406015,"is_open":false,"buy_tag":null,"open_timestamp":1516878000000.0,"close_timestamp":1516880700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.602076124567475,"open_date":"2018-01-25 13:05:00+00:00","close_date":"2018-01-25 13:45:00+00:00","open_rate":2.89e-05,"close_rate":2.904486215538847e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4486215538846723e-07,"sell_reason":"roi","initial_stop_loss_abs":2.601e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.601e-05,"stop_loss_ratio":0.1,"min_rate":2.89e-05,"max_rate":2.904486215538847e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516885500000.0,"close_timestamp":1516887900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02432912439481303,"open_date":"2018-01-25 13:20:00+00:00","close_date":"2018-01-25 14:05:00+00:00","open_rate":0.041103,"close_rate":0.04130903007518797,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020603007518796984,"sell_reason":"roi","initial_stop_loss_abs":0.0369927,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0369927,"stop_loss_ratio":0.1,"min_rate":0.041103,"max_rate":0.04130903007518797,"is_open":false,"buy_tag":null,"open_timestamp":1516886400000.0,"close_timestamp":1516889100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.422991893883566,"open_date":"2018-01-25 15:45:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":5.428e-05,"close_rate":5.509624060150376e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.162406015037611e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8852000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8852000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.428e-05,"max_rate":5.509624060150376e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516895100000.0,"close_timestamp":1516896900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.47063169560399,"open_date":"2018-01-25 17:45:00+00:00","close_date":"2018-01-25 23:15:00+00:00","open_rate":5.414e-05,"close_rate":5.441137844611528e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":330,"profit_ratio":-0.0,"profit_abs":2.713784461152774e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8726e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8726e-05,"stop_loss_ratio":0.1,"min_rate":5.414e-05,"max_rate":5.441137844611528e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516902300000.0,"close_timestamp":1516922100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024150056861308878,"open_date":"2018-01-25 21:15:00+00:00","close_date":"2018-01-25 21:55:00+00:00","open_rate":0.04140777,"close_rate":0.0416153277443609,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002075577443608964,"sell_reason":"roi","initial_stop_loss_abs":0.037266993000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.037266993000000005,"stop_loss_ratio":0.1,"min_rate":0.04140777,"max_rate":0.0416153277443609,"is_open":false,"buy_tag":null,"open_timestamp":1516914900000.0,"close_timestamp":1516917300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3932224183965176,"open_date":"2018-01-26 02:05:00+00:00","close_date":"2018-01-26 02:45:00+00:00","open_rate":0.00254309,"close_rate":0.002555837318295739,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.2747318295739177e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002288781,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002288781,"stop_loss_ratio":0.1,"min_rate":0.00254309,"max_rate":0.002555837318295739,"is_open":false,"buy_tag":null,"open_timestamp":1516932300000.0,"close_timestamp":1516934700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.834849295523455,"open_date":"2018-01-26 02:55:00+00:00","close_date":"2018-01-26 15:10:00+00:00","open_rate":5.607e-05,"close_rate":5.6351052631578935e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":735,"profit_ratio":-0.0,"profit_abs":2.810526315789381e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0463e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0463e-05,"stop_loss_ratio":0.1,"min_rate":5.607e-05,"max_rate":5.6351052631578935e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516935300000.0,"close_timestamp":1516979400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.39400171784748983,"open_date":"2018-01-26 06:10:00+00:00","close_date":"2018-01-26 09:25:00+00:00","open_rate":0.00253806,"close_rate":0.0025507821052631577,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":195,"profit_ratio":0.0,"profit_abs":1.2722105263157733e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002284254,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002284254,"stop_loss_ratio":0.1,"min_rate":0.00253806,"max_rate":0.0025507821052631577,"is_open":false,"buy_tag":null,"open_timestamp":1516947000000.0,"close_timestamp":1516958700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024096385542168672,"open_date":"2018-01-26 07:25:00+00:00","close_date":"2018-01-26 09:55:00+00:00","open_rate":0.0415,"close_rate":0.04170802005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":150,"profit_ratio":-0.0,"profit_abs":0.00020802005012530989,"sell_reason":"roi","initial_stop_loss_abs":0.03735,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03735,"stop_loss_ratio":0.1,"min_rate":0.0415,"max_rate":0.04170802005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516951500000.0,"close_timestamp":1516960500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.793459875963165,"open_date":"2018-01-26 09:55:00+00:00","close_date":"2018-01-26 10:25:00+00:00","open_rate":5.321e-05,"close_rate":5.401015037593984e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.00150375939842e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7889e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7889e-05,"stop_loss_ratio":0.1,"min_rate":5.321e-05,"max_rate":5.401015037593984e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516960500000.0,"close_timestamp":1516962300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.036074437437185386,"open_date":"2018-01-26 16:05:00+00:00","close_date":"2018-01-26 16:45:00+00:00","open_rate":0.02772046,"close_rate":0.02785940967418546,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013894967418546025,"sell_reason":"roi","initial_stop_loss_abs":0.024948414,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024948414,"stop_loss_ratio":0.1,"min_rate":0.02772046,"max_rate":0.02785940967418546,"is_open":false,"buy_tag":null,"open_timestamp":1516982700000.0,"close_timestamp":1516985100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010569326272036914,"open_date":"2018-01-26 23:35:00+00:00","close_date":"2018-01-27 00:15:00+00:00","open_rate":0.09461341,"close_rate":0.09508766268170424,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00047425268170424306,"sell_reason":"roi","initial_stop_loss_abs":0.085152069,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085152069,"stop_loss_ratio":0.1,"min_rate":0.09461341,"max_rate":0.09508766268170424,"is_open":false,"buy_tag":null,"open_timestamp":1517009700000.0,"close_timestamp":1517012100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":17.809439002671414,"open_date":"2018-01-27 00:35:00+00:00","close_date":"2018-01-27 01:30:00+00:00","open_rate":5.615e-05,"close_rate":5.643145363408521e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.814536340852038e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0535e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0535e-05,"stop_loss_ratio":0.1,"min_rate":5.615e-05,"max_rate":5.643145363408521e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013300000.0,"close_timestamp":1517016600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.998560115190784,"open_date":"2018-01-27 00:45:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":5.556e-05,"close_rate":5.144e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4560,"profit_ratio":-0.07877175,"profit_abs":-4.120000000000001e-06,"sell_reason":"force_sell","initial_stop_loss_abs":5.0004000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0004000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.144e-05,"max_rate":5.556e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013900000.0,"close_timestamp":1517287500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492751522789634,"open_date":"2018-01-27 02:30:00+00:00","close_date":"2018-01-27 11:25:00+00:00","open_rate":0.06900001,"close_rate":0.06934587471177944,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":535,"profit_ratio":-0.0,"profit_abs":0.0003458647117794422,"sell_reason":"roi","initial_stop_loss_abs":0.062100009000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.062100009000000005,"stop_loss_ratio":0.1,"min_rate":0.06900001,"max_rate":0.06934587471177944,"is_open":false,"buy_tag":null,"open_timestamp":1517020200000.0,"close_timestamp":1517052300000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010582027378879437,"open_date":"2018-01-27 06:25:00+00:00","close_date":"2018-01-27 07:05:00+00:00","open_rate":0.09449985,"close_rate":0.0949735334586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004736834586466093,"sell_reason":"roi","initial_stop_loss_abs":0.085049865,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085049865,"stop_loss_ratio":0.1,"min_rate":0.09449985,"max_rate":0.0949735334586466,"is_open":false,"buy_tag":null,"open_timestamp":1517034300000.0,"close_timestamp":1517036700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02434885085598385,"open_date":"2018-01-27 09:40:00+00:00","close_date":"2018-01-30 04:40:00+00:00","open_rate":0.0410697,"close_rate":0.03928809,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4020,"profit_ratio":-0.04815133,"profit_abs":-0.001781610000000003,"sell_reason":"force_sell","initial_stop_loss_abs":0.03696273,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03696273,"stop_loss_ratio":0.1,"min_rate":0.03928809,"max_rate":0.0410697,"is_open":false,"buy_tag":null,"open_timestamp":1517046000000.0,"close_timestamp":1517287200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03508771929824561,"open_date":"2018-01-27 11:45:00+00:00","close_date":"2018-01-27 12:30:00+00:00","open_rate":0.0285,"close_rate":0.02864285714285714,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00014285714285713902,"sell_reason":"roi","initial_stop_loss_abs":0.025650000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025650000000000003,"stop_loss_ratio":0.1,"min_rate":0.0285,"max_rate":0.02864285714285714,"is_open":false,"buy_tag":null,"open_timestamp":1517053500000.0,"close_timestamp":1517056200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.034887307020861215,"open_date":"2018-01-27 12:35:00+00:00","close_date":"2018-01-27 15:25:00+00:00","open_rate":0.02866372,"close_rate":0.02880739779448621,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":170,"profit_ratio":-0.0,"profit_abs":0.00014367779448621124,"sell_reason":"roi","initial_stop_loss_abs":0.025797348,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025797348,"stop_loss_ratio":0.1,"min_rate":0.02866372,"max_rate":0.02880739779448621,"is_open":false,"buy_tag":null,"open_timestamp":1517056500000.0,"close_timestamp":1517066700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010484268355332824,"open_date":"2018-01-27 15:50:00+00:00","close_date":"2018-01-27 16:50:00+00:00","open_rate":0.095381,"close_rate":0.09585910025062656,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.00047810025062657024,"sell_reason":"roi","initial_stop_loss_abs":0.0858429,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0858429,"stop_loss_ratio":0.1,"min_rate":0.095381,"max_rate":0.09585910025062656,"is_open":false,"buy_tag":null,"open_timestamp":1517068200000.0,"close_timestamp":1517071800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014794886650455417,"open_date":"2018-01-27 17:05:00+00:00","close_date":"2018-01-27 17:45:00+00:00","open_rate":0.06759092,"close_rate":0.06792972160401002,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00033880160401002224,"sell_reason":"roi","initial_stop_loss_abs":0.060831828,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060831828,"stop_loss_ratio":0.1,"min_rate":0.06759092,"max_rate":0.06792972160401002,"is_open":false,"buy_tag":null,"open_timestamp":1517072700000.0,"close_timestamp":1517075100000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38684569885609726,"open_date":"2018-01-27 23:40:00+00:00","close_date":"2018-01-28 01:05:00+00:00","open_rate":0.00258501,"close_rate":0.002597967443609022,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":-0.0,"profit_abs":1.2957443609021985e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326509,"stop_loss_ratio":0.1,"min_rate":0.00258501,"max_rate":0.002597967443609022,"is_open":false,"buy_tag":null,"open_timestamp":1517096400000.0,"close_timestamp":1517101500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014928710926711672,"open_date":"2018-01-28 02:25:00+00:00","close_date":"2018-01-28 08:10:00+00:00","open_rate":0.06698502,"close_rate":0.0673207845112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":345,"profit_ratio":-0.0,"profit_abs":0.00033576451127818874,"sell_reason":"roi","initial_stop_loss_abs":0.060286518000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060286518000000004,"stop_loss_ratio":0.1,"min_rate":0.06698502,"max_rate":0.0673207845112782,"is_open":false,"buy_tag":null,"open_timestamp":1517106300000.0,"close_timestamp":1517127000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014767187899175548,"open_date":"2018-01-28 10:25:00+00:00","close_date":"2018-01-28 16:30:00+00:00","open_rate":0.0677177,"close_rate":0.06805713709273183,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":365,"profit_ratio":-0.0,"profit_abs":0.0003394370927318202,"sell_reason":"roi","initial_stop_loss_abs":0.06094593000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06094593000000001,"stop_loss_ratio":0.1,"min_rate":0.0677177,"max_rate":0.06805713709273183,"is_open":false,"buy_tag":null,"open_timestamp":1517135100000.0,"close_timestamp":1517157000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":19.175455417066157,"open_date":"2018-01-28 20:35:00+00:00","close_date":"2018-01-28 21:35:00+00:00","open_rate":5.215e-05,"close_rate":5.2411403508771925e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":0.0,"profit_abs":2.6140350877192417e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6935000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6935000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.215e-05,"max_rate":5.2411403508771925e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517171700000.0,"close_timestamp":1517175300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.36521808998243305,"open_date":"2018-01-28 22:00:00+00:00","close_date":"2018-01-28 22:30:00+00:00","open_rate":0.00273809,"close_rate":0.002779264285714285,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.117428571428529e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002464281,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002464281,"stop_loss_ratio":0.1,"min_rate":0.00273809,"max_rate":0.002779264285714285,"is_open":false,"buy_tag":null,"open_timestamp":1517176800000.0,"close_timestamp":1517178600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3641236272539253,"open_date":"2018-01-29 00:00:00+00:00","close_date":"2018-01-29 00:30:00+00:00","open_rate":0.00274632,"close_rate":0.002787618045112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.129804511278194e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002471688,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002471688,"stop_loss_ratio":0.1,"min_rate":0.00274632,"max_rate":0.002787618045112782,"is_open":false,"buy_tag":null,"open_timestamp":1517184000000.0,"close_timestamp":1517185800000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.061634117689115045,"open_date":"2018-01-29 02:15:00+00:00","close_date":"2018-01-29 03:00:00+00:00","open_rate":0.01622478,"close_rate":0.016306107218045113,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":8.132721804511231e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014602302000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014602302000000001,"stop_loss_ratio":0.1,"min_rate":0.01622478,"max_rate":0.016306107218045113,"is_open":false,"buy_tag":null,"open_timestamp":1517192100000.0,"close_timestamp":1517194800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492753623188404,"open_date":"2018-01-29 03:05:00+00:00","close_date":"2018-01-29 03:45:00+00:00","open_rate":0.069,"close_rate":0.06934586466165413,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00034586466165412166,"sell_reason":"roi","initial_stop_loss_abs":0.06210000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06210000000000001,"stop_loss_ratio":0.1,"min_rate":0.069,"max_rate":0.06934586466165413,"is_open":false,"buy_tag":null,"open_timestamp":1517195100000.0,"close_timestamp":1517197500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.42204454597373,"open_date":"2018-01-29 05:20:00+00:00","close_date":"2018-01-29 06:55:00+00:00","open_rate":8.755e-05,"close_rate":8.798884711779448e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":95,"profit_ratio":-0.0,"profit_abs":4.3884711779447504e-07,"sell_reason":"roi","initial_stop_loss_abs":7.879500000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.879500000000001e-05,"stop_loss_ratio":0.1,"min_rate":8.755e-05,"max_rate":8.798884711779448e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517203200000.0,"close_timestamp":1517208900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014650376815016871,"open_date":"2018-01-29 07:00:00+00:00","close_date":"2018-01-29 19:25:00+00:00","open_rate":0.06825763,"close_rate":0.06859977350877192,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":745,"profit_ratio":-0.0,"profit_abs":0.00034214350877191657,"sell_reason":"roi","initial_stop_loss_abs":0.061431867,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061431867,"stop_loss_ratio":0.1,"min_rate":0.06825763,"max_rate":0.06859977350877192,"is_open":false,"buy_tag":null,"open_timestamp":1517209200000.0,"close_timestamp":1517253900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014894490408841846,"open_date":"2018-01-29 19:45:00+00:00","close_date":"2018-01-29 20:25:00+00:00","open_rate":0.06713892,"close_rate":0.06747545593984962,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0003365359398496137,"sell_reason":"roi","initial_stop_loss_abs":0.060425028000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060425028000000006,"stop_loss_ratio":0.1,"min_rate":0.06713892,"max_rate":0.06747545593984962,"is_open":false,"buy_tag":null,"open_timestamp":1517255100000.0,"close_timestamp":1517257500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.193194537721066,"open_date":"2018-01-29 23:30:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":8.934e-05,"close_rate":8.8e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":315,"profit_ratio":-0.0199116,"profit_abs":-1.3399999999999973e-06,"sell_reason":"force_sell","initial_stop_loss_abs":8.0406e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.0406e-05,"stop_loss_ratio":0.1,"min_rate":8.8e-05,"max_rate":8.934e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517268600000.0,"close_timestamp":1517287500000.0}],"locks":[],"best_pair":{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},"worst_pair":{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},"results_per_pair":[{"key":"ETH/BTC","trades":21,"profit_mean":0.0009500057142857142,"profit_mean_pct":0.09500057142857142,"profit_sum":0.01995012,"profit_sum_pct":2.0,"profit_total_abs":0.011505731278195264,"profit_total":1.1505731278195264,"profit_total_pct":115.06,"duration_avg":"2:17:00","wins":21,"draws":0,"losses":0},{"key":"DASH/BTC","trades":16,"profit_mean":0.0018703237499999997,"profit_mean_pct":0.18703237499999997,"profit_sum":0.029925179999999996,"profit_sum_pct":2.99,"profit_total_abs":0.007475052681704161,"profit_total":0.7475052681704161,"profit_total_pct":74.75,"duration_avg":"3:03:00","wins":16,"draws":0,"losses":0},{"key":"ZEC/BTC","trades":21,"profit_mean":-0.00039290904761904774,"profit_mean_pct":-0.03929090476190478,"profit_sum":-0.008251090000000003,"profit_sum_pct":-0.83,"profit_total_abs":0.004452605639097655,"profit_total":0.4452605639097655,"profit_total_pct":44.53,"duration_avg":"4:17:00","wins":20,"draws":0,"losses":1},{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},{"key":"ETC/BTC","trades":20,"profit_mean":0.0022568569999999997,"profit_mean_pct":0.22568569999999996,"profit_sum":0.04513713999999999,"profit_sum_pct":4.51,"profit_total_abs":0.00036538235338345404,"profit_total":0.0365382353383454,"profit_total_pct":3.65,"duration_avg":"1:45:00","wins":19,"draws":0,"losses":1},{"key":"TRX/BTC","trades":15,"profit_mean":0.0023467073333333323,"profit_mean_pct":0.23467073333333321,"profit_sum":0.035200609999999986,"profit_sum_pct":3.52,"profit_total_abs":1.1329523809523682e-05,"profit_total":0.0011329523809523682,"profit_total_pct":0.11,"duration_avg":"2:28:00","wins":13,"draws":0,"losses":2},{"key":"XLM/BTC","trades":21,"profit_mean":0.0026243899999999994,"profit_mean_pct":0.2624389999999999,"profit_sum":0.05511218999999999,"profit_sum_pct":5.51,"profit_total_abs":7.340779448621465e-06,"profit_total":0.0007340779448621465,"profit_total_pct":0.07,"duration_avg":"3:21:00","wins":20,"draws":0,"losses":1},{"key":"ADA/BTC","trades":29,"profit_mean":-0.0011598141379310352,"profit_mean_pct":-0.11598141379310352,"profit_sum":-0.03363461000000002,"profit_sum_pct":-3.36,"profit_total_abs":4.916634085212862e-06,"profit_total":0.0004916634085212862,"profit_total_pct":0.05,"duration_avg":"5:35:00","wins":27,"draws":0,"losses":2},{"key":"NXT/BTC","trades":12,"profit_mean":-0.0012261025000000006,"profit_mean_pct":-0.12261025000000006,"profit_sum":-0.014713230000000008,"profit_sum_pct":-1.47,"profit_total_abs":1.4774411027568458e-06,"profit_total":0.00014774411027568458,"profit_total_pct":0.01,"duration_avg":"0:57:00","wins":11,"draws":0,"losses":1},{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"results_per_enter_tag":[{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"sell_reason_summary":[{"sell_reason":"roi","trades":170,"wins":170,"draws":0,"losses":0,"profit_mean":0.005398268352941177,"profit_mean_pct":0.54,"profit_sum":0.91770562,"profit_sum_pct":91.77,"profit_total_abs":0.031232837493733862,"profit_total":0.30590187333333335,"profit_total_pct":30.59},{"sell_reason":"stop_loss","trades":6,"wins":0,"draws":0,"losses":6,"profit_mean":-0.10448878000000002,"profit_mean_pct":-10.45,"profit_sum":-0.6269326800000001,"profit_sum_pct":-62.69,"profit_total_abs":-0.0033602680000000026,"profit_total":-0.20897756000000003,"profit_total_pct":-20.9},{"sell_reason":"force_sell","trades":3,"wins":0,"draws":0,"losses":3,"profit_mean":-0.04894489333333333,"profit_mean_pct":-4.89,"profit_sum":-0.14683468,"profit_sum_pct":-14.68,"profit_total_abs":-0.001787070000000003,"profit_total":-0.04894489333333333,"profit_total_pct":-4.89}],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":179,"total_volume":0.17900000000000005,"avg_stake_amount":0.0010000000000000002,"profit_mean":0.0008041243575418989,"profit_median":0.0,"profit_total":2.6085499493733857,"profit_total_abs":0.026085499493733857,"backtest_start":"2018-01-10 07:15:00","backtest_start_ts":1515568500000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":"2020-10-01 18:00:00+00:00","backtest_run_end_ts":"2020-10-01 18:01:00+00:00","trades_per_day":9.42,"market_change":1.22,"pairlist":[],"stake_amount":0.001,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":0.01,"dry_run_wallet":0.01,"final_balance":0.03608549949373386,"rejected_signals":0,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timeframe_detail":"","timerange":"","enable_protections":false,"strategy_name":"StrategyTestV2","stoploss":0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":false,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.17955111999999998,"backtest_worst_day":-0.14683468,"backtest_best_day_abs":0.0071570099,"backtest_worst_day_abs":-0.0023093218,"winning_days":19,"draw_days":0,"losing_days":2,"daily_profit":[["2018-01-10",0.0025815306],["2018-01-11",0.0049356655],["2018-01-12",0.0006395218],["2018-01-13",0.0002574589],["2018-01-14",0.0010443828],["2018-01-15",0.0024030209],["2018-01-16",0.0071570099],["2018-01-17",0.001137038],["2018-01-18",0.0013712174],["2018-01-19",0.000584673],["2018-01-20",0.0006143386],["2018-01-21",0.0004749361],["2018-01-22",9.91669e-05],["2018-01-23",0.0015726664],["2018-01-24",0.0006610219],["2018-01-25",-0.0023093218],["2018-01-26",0.0003735204],["2018-01-27",0.0023975191],["2018-01-28",0.0007295947],["2018-01-29",0.0011476082],["2018-01-30",-0.00178707]],"wins":48,"losses":9,"draws":122,"holding_avg":"3:40:00","holding_avg_s":13200.0,"winner_holding_avg":"0:24:00","winner_holding_avg_s":1440.0,"loser_holding_avg":"1 day, 5:57:00","loser_holding_avg_s":107820.0,"max_drawdown":0.21142322000000008,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":0.0030822220000000025,"drawdown_start":"2018-01-25 01:30:00","drawdown_start_ts":1516843800000.0,"drawdown_end":"2018-01-25 03:50:00","drawdown_end_ts":1516852200000.0,"max_drawdown_low":0.02245167355388436,"max_drawdown_high":0.025533895553884363,"csum_min":0.01000434887218045,"csum_max":0.03608683949373386}},"strategy_comparison":[{"key":"StrategyTestV2","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":"0.00308222"},{"key":"TestStrategy","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":"0.00308222"}]}
diff --git a/tests/testdata/backtest-result_new.json b/tests/testdata/backtest-result_new.json
index f0d58cba4..1d72cd479 100644
--- a/tests/testdata/backtest-result_new.json
+++ b/tests/testdata/backtest-result_new.json
@@ -1 +1 @@
-{"strategy":{"StrategyTestV2":{"trades":[{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.37344398340249,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:20:00+00:00","open_rate":9.64e-05,"close_rate":0.00010074887218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":4.348872180451118e-06,"sell_reason":"roi","initial_stop_loss_abs":8.676e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.676e-05,"stop_loss_ratio":0.1,"min_rate":9.64e-05,"max_rate":0.00010074887218045112,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515568800000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":21.026072329688816,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:30:00+00:00","open_rate":4.756e-05,"close_rate":4.9705563909774425e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":2.1455639097744267e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2804e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2804e-05,"stop_loss_ratio":0.1,"min_rate":4.756e-05,"max_rate":4.9705563909774425e-05,"is_open":false,"buy_tag":"buy_tag","open_timestamp":1515568500000.0,"close_timestamp":1515569400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.94908655286014,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:35:00+00:00","open_rate":3.339e-05,"close_rate":3.489631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":1.506315789473681e-06,"sell_reason":"roi","initial_stop_loss_abs":3.0050999999999997e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0050999999999997e-05,"stop_loss_ratio":0.1,"min_rate":3.339e-05,"max_rate":3.489631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515569700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.313531353135314,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:40:00+00:00","open_rate":9.696e-05,"close_rate":0.00010133413533834584,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":4.3741353383458455e-06,"sell_reason":"roi","initial_stop_loss_abs":8.7264e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.7264e-05,"stop_loss_ratio":0.1,"min_rate":9.696e-05,"max_rate":0.00010133413533834584,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515570000000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010604453870625663,"open_date":"2018-01-10 07:35:00+00:00","close_date":"2018-01-10 08:35:00+00:00","open_rate":0.0943,"close_rate":0.09477268170426063,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.0004726817042606385,"sell_reason":"roi","initial_stop_loss_abs":0.08487,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08487,"stop_loss_ratio":0.1,"min_rate":0.0943,"max_rate":0.09477268170426063,"is_open":false,"buy_tag":null,"open_timestamp":1515569700000.0,"close_timestamp":1515573300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03677001860930642,"open_date":"2018-01-10 07:40:00+00:00","close_date":"2018-01-10 08:10:00+00:00","open_rate":0.02719607,"close_rate":0.02760503345864661,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00040896345864661204,"sell_reason":"roi","initial_stop_loss_abs":0.024476463,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024476463,"stop_loss_ratio":0.1,"min_rate":0.02719607,"max_rate":0.02760503345864661,"is_open":false,"buy_tag":null,"open_timestamp":1515570000000.0,"close_timestamp":1515571800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021575196463739,"open_date":"2018-01-10 08:15:00+00:00","close_date":"2018-01-10 09:55:00+00:00","open_rate":0.04634952,"close_rate":0.046581848421052625,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":100,"profit_ratio":0.0,"profit_abs":0.0002323284210526272,"sell_reason":"roi","initial_stop_loss_abs":0.041714568,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041714568,"stop_loss_ratio":0.1,"min_rate":0.04634952,"max_rate":0.046581848421052625,"is_open":false,"buy_tag":null,"open_timestamp":1515572100000.0,"close_timestamp":1515578100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.615786040443574,"open_date":"2018-01-10 14:45:00+00:00","close_date":"2018-01-10 15:50:00+00:00","open_rate":3.066e-05,"close_rate":3.081368421052631e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":1.5368421052630647e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7594e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7594e-05,"stop_loss_ratio":0.1,"min_rate":3.066e-05,"max_rate":3.081368421052631e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515595500000.0,"close_timestamp":1515599400000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.05917194776300452,"open_date":"2018-01-10 16:35:00+00:00","close_date":"2018-01-10 17:15:00+00:00","open_rate":0.0168999,"close_rate":0.016984611278195488,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.471127819548868e-05,"sell_reason":"roi","initial_stop_loss_abs":0.01520991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01520991,"stop_loss_ratio":0.1,"min_rate":0.0168999,"max_rate":0.016984611278195488,"is_open":false,"buy_tag":null,"open_timestamp":1515602100000.0,"close_timestamp":1515604500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010949822656672253,"open_date":"2018-01-10 16:40:00+00:00","close_date":"2018-01-10 17:20:00+00:00","open_rate":0.09132568,"close_rate":0.0917834528320802,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004577728320801916,"sell_reason":"roi","initial_stop_loss_abs":0.08219311200000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08219311200000001,"stop_loss_ratio":0.1,"min_rate":0.09132568,"max_rate":0.0917834528320802,"is_open":false,"buy_tag":null,"open_timestamp":1515602400000.0,"close_timestamp":1515604800000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011238476768326556,"open_date":"2018-01-10 18:50:00+00:00","close_date":"2018-01-10 19:45:00+00:00","open_rate":0.08898003,"close_rate":0.08942604518796991,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00044601518796991146,"sell_reason":"roi","initial_stop_loss_abs":0.080082027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080082027,"stop_loss_ratio":0.1,"min_rate":0.08898003,"max_rate":0.08942604518796991,"is_open":false,"buy_tag":null,"open_timestamp":1515610200000.0,"close_timestamp":1515613500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011682232072680307,"open_date":"2018-01-10 22:15:00+00:00","close_date":"2018-01-10 23:00:00+00:00","open_rate":0.08560008,"close_rate":0.08602915308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":0.00042907308270676014,"sell_reason":"roi","initial_stop_loss_abs":0.077040072,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077040072,"stop_loss_ratio":0.1,"min_rate":0.08560008,"max_rate":0.08602915308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515622500000.0,"close_timestamp":1515625200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4014726015023105,"open_date":"2018-01-10 22:50:00+00:00","close_date":"2018-01-10 23:20:00+00:00","open_rate":0.00249083,"close_rate":0.0025282860902255634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.745609022556351e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002241747,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002241747,"stop_loss_ratio":0.1,"min_rate":0.00249083,"max_rate":0.0025282860902255634,"is_open":false,"buy_tag":null,"open_timestamp":1515624600000.0,"close_timestamp":1515626400000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.090668431502316,"open_date":"2018-01-10 23:15:00+00:00","close_date":"2018-01-11 00:15:00+00:00","open_rate":3.022e-05,"close_rate":3.037147869674185e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":1.5147869674185174e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7198e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7198e-05,"stop_loss_ratio":0.1,"min_rate":3.022e-05,"max_rate":3.037147869674185e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515626100000.0,"close_timestamp":1515629700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.41034058268362744,"open_date":"2018-01-10 23:40:00+00:00","close_date":"2018-01-11 00:05:00+00:00","open_rate":0.002437,"close_rate":0.0024980776942355883,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":6.107769423558838e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0021933,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0021933,"stop_loss_ratio":0.1,"min_rate":0.002437,"max_rate":0.0024980776942355883,"is_open":false,"buy_tag":null,"open_timestamp":1515627600000.0,"close_timestamp":1515629100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02095643931654345,"open_date":"2018-01-11 00:00:00+00:00","close_date":"2018-01-11 00:35:00+00:00","open_rate":0.04771803,"close_rate":0.04843559436090225,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0007175643609022495,"sell_reason":"roi","initial_stop_loss_abs":0.042946227000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.042946227000000003,"stop_loss_ratio":0.1,"min_rate":0.04771803,"max_rate":0.04843559436090225,"is_open":false,"buy_tag":null,"open_timestamp":1515628800000.0,"close_timestamp":1515630900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.389756231169542,"open_date":"2018-01-11 03:40:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":3.651e-05,"close_rate":3.2859000000000005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.10448878,"profit_abs":-3.650999999999996e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":3.2859000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2859000000000005e-05,"stop_loss_ratio":0.1,"min_rate":3.2859000000000005e-05,"max_rate":3.651e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515642000000.0,"close_timestamp":1515644700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011332594070446804,"open_date":"2018-01-11 03:55:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":0.08824105,"close_rate":0.08956798308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013269330827067605,"sell_reason":"roi","initial_stop_loss_abs":0.079416945,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079416945,"stop_loss_ratio":0.1,"min_rate":0.08824105,"max_rate":0.08956798308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515642900000.0,"close_timestamp":1515644700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.411522633744856,"open_date":"2018-01-11 04:00:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":0.00243,"close_rate":0.002442180451127819,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":1.2180451127819219e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002187,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002187,"stop_loss_ratio":0.1,"min_rate":0.00243,"max_rate":0.002442180451127819,"is_open":false,"buy_tag":null,"open_timestamp":1515643200000.0,"close_timestamp":1515646200000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022001890402423376,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:55:00+00:00","open_rate":0.04545064,"close_rate":0.046589753784461146,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":0.001139113784461146,"sell_reason":"roi","initial_stop_loss_abs":0.040905576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040905576,"stop_loss_ratio":0.1,"min_rate":0.04545064,"max_rate":0.046589753784461146,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.655990510083036,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":3.372e-05,"close_rate":3.456511278195488e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":8.4511278195488e-07,"sell_reason":"roi","initial_stop_loss_abs":3.0348e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0348e-05,"stop_loss_ratio":0.1,"min_rate":3.372e-05,"max_rate":3.456511278195488e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037821482602118005,"open_date":"2018-01-11 04:55:00+00:00","close_date":"2018-01-11 05:15:00+00:00","open_rate":0.02644,"close_rate":0.02710265664160401,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0006626566416040071,"sell_reason":"roi","initial_stop_loss_abs":0.023796,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023796,"stop_loss_ratio":0.1,"min_rate":0.02644,"max_rate":0.02710265664160401,"is_open":false,"buy_tag":null,"open_timestamp":1515646500000.0,"close_timestamp":1515647700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011348161597821153,"open_date":"2018-01-11 11:20:00+00:00","close_date":"2018-01-11 12:00:00+00:00","open_rate":0.08812,"close_rate":0.08856170426065162,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004417042606516125,"sell_reason":"roi","initial_stop_loss_abs":0.079308,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079308,"stop_loss_ratio":0.1,"min_rate":0.08812,"max_rate":0.08856170426065162,"is_open":false,"buy_tag":null,"open_timestamp":1515669600000.0,"close_timestamp":1515672000000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037263696923919086,"open_date":"2018-01-11 11:35:00+00:00","close_date":"2018-01-11 12:15:00+00:00","open_rate":0.02683577,"close_rate":0.026970285137844607,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013451513784460897,"sell_reason":"roi","initial_stop_loss_abs":0.024152193,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024152193,"stop_loss_ratio":0.1,"min_rate":0.02683577,"max_rate":0.026970285137844607,"is_open":false,"buy_tag":null,"open_timestamp":1515670500000.0,"close_timestamp":1515672900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.329335230737954,"open_date":"2018-01-11 14:00:00+00:00","close_date":"2018-01-11 14:25:00+00:00","open_rate":4.919e-05,"close_rate":5.04228320802005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.232832080200495e-06,"sell_reason":"roi","initial_stop_loss_abs":4.4271000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4271000000000004e-05,"stop_loss_ratio":0.1,"min_rate":4.919e-05,"max_rate":5.04228320802005e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515679200000.0,"close_timestamp":1515680700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.01138317402960718,"open_date":"2018-01-11 19:25:00+00:00","close_date":"2018-01-11 20:35:00+00:00","open_rate":0.08784896,"close_rate":0.08828930566416039,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":70,"profit_ratio":-0.0,"profit_abs":0.0004403456641603881,"sell_reason":"roi","initial_stop_loss_abs":0.079064064,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079064064,"stop_loss_ratio":0.1,"min_rate":0.08784896,"max_rate":0.08828930566416039,"is_open":false,"buy_tag":null,"open_timestamp":1515698700000.0,"close_timestamp":1515702900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.58863858961802,"open_date":"2018-01-11 22:35:00+00:00","close_date":"2018-01-11 23:30:00+00:00","open_rate":5.105e-05,"close_rate":5.130588972431077e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.558897243107704e-07,"sell_reason":"roi","initial_stop_loss_abs":4.5945e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.5945e-05,"stop_loss_ratio":0.1,"min_rate":5.105e-05,"max_rate":5.130588972431077e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515710100000.0,"close_timestamp":1515713400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.252525252525253,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:25:00+00:00","open_rate":3.96e-05,"close_rate":4.019548872180451e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":5.954887218045116e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5640000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5640000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.96e-05,"max_rate":4.019548872180451e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.66204506065858,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:35:00+00:00","open_rate":2.885e-05,"close_rate":2.899461152882205e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4461152882205115e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5965e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5965e-05,"stop_loss_ratio":0.1,"min_rate":2.885e-05,"max_rate":2.899461152882205e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03780718336483932,"open_date":"2018-01-11 23:30:00+00:00","close_date":"2018-01-12 00:05:00+00:00","open_rate":0.02645,"close_rate":0.026847744360902256,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0003977443609022545,"sell_reason":"roi","initial_stop_loss_abs":0.023805000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023805000000000003,"stop_loss_ratio":0.1,"min_rate":0.02645,"max_rate":0.026847744360902256,"is_open":false,"buy_tag":null,"open_timestamp":1515713400000.0,"close_timestamp":1515715500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020833333333333332,"open_date":"2018-01-11 23:55:00+00:00","close_date":"2018-01-12 01:15:00+00:00","open_rate":0.048,"close_rate":0.04824060150375939,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00024060150375938838,"sell_reason":"roi","initial_stop_loss_abs":0.0432,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0432,"stop_loss_ratio":0.1,"min_rate":0.048,"max_rate":0.04824060150375939,"is_open":false,"buy_tag":null,"open_timestamp":1515714900000.0,"close_timestamp":1515719700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.31287297527707,"open_date":"2018-01-12 21:15:00+00:00","close_date":"2018-01-12 21:40:00+00:00","open_rate":4.692e-05,"close_rate":4.809593984962405e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1759398496240516e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2227999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2227999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.692e-05,"max_rate":4.809593984962405e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515791700000.0,"close_timestamp":1515793200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38915654211062944,"open_date":"2018-01-13 00:55:00+00:00","close_date":"2018-01-13 06:20:00+00:00","open_rate":0.00256966,"close_rate":0.0025825405012531327,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":325,"profit_ratio":-0.0,"profit_abs":1.2880501253132587e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002312694,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002312694,"stop_loss_ratio":0.1,"min_rate":0.00256966,"max_rate":0.0025825405012531327,"is_open":false,"buy_tag":null,"open_timestamp":1515804900000.0,"close_timestamp":1515824400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":15.96933886937081,"open_date":"2018-01-13 10:55:00+00:00","close_date":"2018-01-13 11:35:00+00:00","open_rate":6.262e-05,"close_rate":6.293388471177944e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":3.138847117794446e-07,"sell_reason":"roi","initial_stop_loss_abs":5.6358e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.6358e-05,"stop_loss_ratio":0.1,"min_rate":6.262e-05,"max_rate":6.293388471177944e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515840900000.0,"close_timestamp":1515843300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.141649048625794,"open_date":"2018-01-13 13:05:00+00:00","close_date":"2018-01-15 14:10:00+00:00","open_rate":4.73e-05,"close_rate":4.753709273182957e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":2945,"profit_ratio":0.0,"profit_abs":2.3709273182957117e-07,"sell_reason":"roi","initial_stop_loss_abs":4.257e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.257e-05,"stop_loss_ratio":0.1,"min_rate":4.73e-05,"max_rate":4.753709273182957e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515848700000.0,"close_timestamp":1516025400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.49348507339601,"open_date":"2018-01-13 13:30:00+00:00","close_date":"2018-01-13 14:45:00+00:00","open_rate":6.063e-05,"close_rate":6.0933909774436085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":3.039097744360846e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4567e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4567e-05,"stop_loss_ratio":0.1,"min_rate":6.063e-05,"max_rate":6.0933909774436085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515850200000.0,"close_timestamp":1515854700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.023641941887746,"open_date":"2018-01-13 13:40:00+00:00","close_date":"2018-01-13 23:30:00+00:00","open_rate":0.00011082,"close_rate":0.00011137548872180448,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":590,"profit_ratio":-0.0,"profit_abs":5.554887218044781e-07,"sell_reason":"roi","initial_stop_loss_abs":9.9738e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.9738e-05,"stop_loss_ratio":0.1,"min_rate":0.00011082,"max_rate":0.00011137548872180448,"is_open":false,"buy_tag":null,"open_timestamp":1515850800000.0,"close_timestamp":1515886200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.863406408094438,"open_date":"2018-01-13 15:15:00+00:00","close_date":"2018-01-13 15:55:00+00:00","open_rate":5.93e-05,"close_rate":5.9597243107769415e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.9724310776941686e-07,"sell_reason":"roi","initial_stop_loss_abs":5.337e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.337e-05,"stop_loss_ratio":0.1,"min_rate":5.93e-05,"max_rate":5.9597243107769415e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515856500000.0,"close_timestamp":1515858900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020618543947292404,"open_date":"2018-01-13 16:30:00+00:00","close_date":"2018-01-13 17:10:00+00:00","open_rate":0.04850003,"close_rate":0.04874313791979949,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00024310791979949287,"sell_reason":"roi","initial_stop_loss_abs":0.043650027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.043650027,"stop_loss_ratio":0.1,"min_rate":0.04850003,"max_rate":0.04874313791979949,"is_open":false,"buy_tag":null,"open_timestamp":1515861000000.0,"close_timestamp":1515863400000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010178097365511457,"open_date":"2018-01-13 22:05:00+00:00","close_date":"2018-01-14 06:25:00+00:00","open_rate":0.09825019,"close_rate":0.09874267215538848,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":500,"profit_ratio":-0.0,"profit_abs":0.0004924821553884823,"sell_reason":"roi","initial_stop_loss_abs":0.088425171,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.088425171,"stop_loss_ratio":0.1,"min_rate":0.09825019,"max_rate":0.09874267215538848,"is_open":false,"buy_tag":null,"open_timestamp":1515881100000.0,"close_timestamp":1515911100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.616816218012627,"open_date":"2018-01-14 00:20:00+00:00","close_date":"2018-01-14 22:55:00+00:00","open_rate":6.018e-05,"close_rate":6.048165413533834e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":1355,"profit_ratio":0.0,"profit_abs":3.0165413533833987e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4162e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4162e-05,"stop_loss_ratio":0.1,"min_rate":6.018e-05,"max_rate":6.048165413533834e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515889200000.0,"close_timestamp":1515970500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010246952581919518,"open_date":"2018-01-14 12:45:00+00:00","close_date":"2018-01-14 13:25:00+00:00","open_rate":0.09758999,"close_rate":0.0980791628822055,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004891728822054991,"sell_reason":"roi","initial_stop_loss_abs":0.087830991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.087830991,"stop_loss_ratio":0.1,"min_rate":0.09758999,"max_rate":0.0980791628822055,"is_open":false,"buy_tag":null,"open_timestamp":1515933900000.0,"close_timestamp":1515936300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3215434083601286,"open_date":"2018-01-14 15:30:00+00:00","close_date":"2018-01-14 16:00:00+00:00","open_rate":0.00311,"close_rate":0.0031567669172932328,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.676691729323286e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002799,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002799,"stop_loss_ratio":0.1,"min_rate":0.00311,"max_rate":0.0031567669172932328,"is_open":false,"buy_tag":null,"open_timestamp":1515943800000.0,"close_timestamp":1515945600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.32010140812609433,"open_date":"2018-01-14 20:45:00+00:00","close_date":"2018-01-14 22:15:00+00:00","open_rate":0.00312401,"close_rate":0.003139669197994987,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":-0.0,"profit_abs":1.5659197994987058e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002811609,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002811609,"stop_loss_ratio":0.1,"min_rate":0.00312401,"max_rate":0.003139669197994987,"is_open":false,"buy_tag":null,"open_timestamp":1515962700000.0,"close_timestamp":1515968100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.057247866085791646,"open_date":"2018-01-14 23:35:00+00:00","close_date":"2018-01-15 00:30:00+00:00","open_rate":0.0174679,"close_rate":0.017555458395989976,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":8.755839598997492e-05,"sell_reason":"roi","initial_stop_loss_abs":0.015721110000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.015721110000000003,"stop_loss_ratio":0.1,"min_rate":0.0174679,"max_rate":0.017555458395989976,"is_open":false,"buy_tag":null,"open_timestamp":1515972900000.0,"close_timestamp":1515976200000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.013611282991367997,"open_date":"2018-01-14 23:45:00+00:00","close_date":"2018-01-15 00:25:00+00:00","open_rate":0.07346846,"close_rate":0.07383672295739348,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00036826295739347814,"sell_reason":"roi","initial_stop_loss_abs":0.066121614,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.066121614,"stop_loss_ratio":0.1,"min_rate":0.07346846,"max_rate":0.07383672295739348,"is_open":false,"buy_tag":null,"open_timestamp":1515973500000.0,"close_timestamp":1515975900000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010204706410596568,"open_date":"2018-01-15 02:25:00+00:00","close_date":"2018-01-15 03:05:00+00:00","open_rate":0.097994,"close_rate":0.09848519799498744,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004911979949874384,"sell_reason":"roi","initial_stop_loss_abs":0.0881946,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0881946,"stop_loss_ratio":0.1,"min_rate":0.097994,"max_rate":0.09848519799498744,"is_open":false,"buy_tag":null,"open_timestamp":1515983100000.0,"close_timestamp":1515985500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010353038616834042,"open_date":"2018-01-15 07:20:00+00:00","close_date":"2018-01-15 08:00:00+00:00","open_rate":0.09659,"close_rate":0.09707416040100247,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004841604010024786,"sell_reason":"roi","initial_stop_loss_abs":0.086931,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.086931,"stop_loss_ratio":0.1,"min_rate":0.09659,"max_rate":0.09707416040100247,"is_open":false,"buy_tag":null,"open_timestamp":1516000800000.0,"close_timestamp":1516003200000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.013016921998599,"open_date":"2018-01-15 08:20:00+00:00","close_date":"2018-01-15 08:55:00+00:00","open_rate":9.987e-05,"close_rate":0.00010137180451127818,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":1.501804511278178e-06,"sell_reason":"roi","initial_stop_loss_abs":8.9883e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.9883e-05,"stop_loss_ratio":0.1,"min_rate":9.987e-05,"max_rate":0.00010137180451127818,"is_open":false,"buy_tag":null,"open_timestamp":1516004400000.0,"close_timestamp":1516006500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010537752023511832,"open_date":"2018-01-15 12:10:00+00:00","close_date":"2018-01-16 02:50:00+00:00","open_rate":0.0948969,"close_rate":0.09537257368421052,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":880,"profit_ratio":0.0,"profit_abs":0.0004756736842105175,"sell_reason":"roi","initial_stop_loss_abs":0.08540721000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08540721000000001,"stop_loss_ratio":0.1,"min_rate":0.0948969,"max_rate":0.09537257368421052,"is_open":false,"buy_tag":null,"open_timestamp":1516018200000.0,"close_timestamp":1516071000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014084507042253523,"open_date":"2018-01-15 14:10:00+00:00","close_date":"2018-01-15 17:40:00+00:00","open_rate":0.071,"close_rate":0.07135588972431077,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":210,"profit_ratio":-0.0,"profit_abs":0.00035588972431077615,"sell_reason":"roi","initial_stop_loss_abs":0.0639,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0639,"stop_loss_ratio":0.1,"min_rate":0.071,"max_rate":0.07135588972431077,"is_open":false,"buy_tag":null,"open_timestamp":1516025400000.0,"close_timestamp":1516038000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021736763017766978,"open_date":"2018-01-15 14:30:00+00:00","close_date":"2018-01-15 15:10:00+00:00","open_rate":0.04600501,"close_rate":0.046235611553884705,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00023060155388470588,"sell_reason":"roi","initial_stop_loss_abs":0.041404509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041404509,"stop_loss_ratio":0.1,"min_rate":0.04600501,"max_rate":0.046235611553884705,"is_open":false,"buy_tag":null,"open_timestamp":1516026600000.0,"close_timestamp":1516029000000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.595465140919686,"open_date":"2018-01-15 18:10:00+00:00","close_date":"2018-01-15 19:25:00+00:00","open_rate":9.438e-05,"close_rate":9.485308270676693e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":4.7308270676692514e-07,"sell_reason":"roi","initial_stop_loss_abs":8.4942e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.4942e-05,"stop_loss_ratio":0.1,"min_rate":9.438e-05,"max_rate":9.485308270676693e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516039800000.0,"close_timestamp":1516044300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.032894726021471705,"open_date":"2018-01-15 18:35:00+00:00","close_date":"2018-01-15 19:15:00+00:00","open_rate":0.03040001,"close_rate":0.030552391002506264,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0001523810025062626,"sell_reason":"roi","initial_stop_loss_abs":0.027360009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027360009,"stop_loss_ratio":0.1,"min_rate":0.03040001,"max_rate":0.030552391002506264,"is_open":false,"buy_tag":null,"open_timestamp":1516041300000.0,"close_timestamp":1516043700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.13208840157615,"open_date":"2018-01-15 20:25:00+00:00","close_date":"2018-01-16 08:25:00+00:00","open_rate":5.837e-05,"close_rate":5.2533e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":720,"profit_ratio":-0.10448878,"profit_abs":-5.8369999999999985e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":5.2533e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2533e-05,"stop_loss_ratio":0.1,"min_rate":5.2533e-05,"max_rate":5.837e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516047900000.0,"close_timestamp":1516091100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021722130506560085,"open_date":"2018-01-15 20:40:00+00:00","close_date":"2018-01-15 22:00:00+00:00","open_rate":0.046036,"close_rate":0.04626675689223057,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00023075689223057277,"sell_reason":"roi","initial_stop_loss_abs":0.0414324,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0414324,"stop_loss_ratio":0.1,"min_rate":0.046036,"max_rate":0.04626675689223057,"is_open":false,"buy_tag":null,"open_timestamp":1516048800000.0,"close_timestamp":1516053600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.34861425832316545,"open_date":"2018-01-16 00:30:00+00:00","close_date":"2018-01-16 01:10:00+00:00","open_rate":0.0028685,"close_rate":0.0028828784461152877,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4378446115287727e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00258165,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00258165,"stop_loss_ratio":0.1,"min_rate":0.0028685,"max_rate":0.0028828784461152877,"is_open":false,"buy_tag":null,"open_timestamp":1516062600000.0,"close_timestamp":1516065000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014854967241083492,"open_date":"2018-01-16 01:15:00+00:00","close_date":"2018-01-16 02:35:00+00:00","open_rate":0.06731755,"close_rate":0.0676549813283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":0.0,"profit_abs":0.00033743132832080025,"sell_reason":"roi","initial_stop_loss_abs":0.060585795000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060585795000000005,"stop_loss_ratio":0.1,"min_rate":0.06731755,"max_rate":0.0676549813283208,"is_open":false,"buy_tag":null,"open_timestamp":1516065300000.0,"close_timestamp":1516070100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010848794492804754,"open_date":"2018-01-16 07:45:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":0.09217614,"close_rate":0.09263817578947368,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":0.0004620357894736804,"sell_reason":"roi","initial_stop_loss_abs":0.082958526,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082958526,"stop_loss_ratio":0.1,"min_rate":0.09217614,"max_rate":0.09263817578947368,"is_open":false,"buy_tag":null,"open_timestamp":1516088700000.0,"close_timestamp":1516092000000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06060606060606061,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:55:00+00:00","open_rate":0.0165,"close_rate":0.016913533834586467,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.00041353383458646656,"sell_reason":"roi","initial_stop_loss_abs":0.01485,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01485,"stop_loss_ratio":0.1,"min_rate":0.0165,"max_rate":0.016913533834586467,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":12.57387149503332,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":7.953e-05,"close_rate":8.311781954887218e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":3.587819548872171e-06,"sell_reason":"roi","initial_stop_loss_abs":7.157700000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.157700000000001e-05,"stop_loss_ratio":0.1,"min_rate":7.953e-05,"max_rate":8.311781954887218e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022122914915269236,"open_date":"2018-01-16 08:45:00+00:00","close_date":"2018-01-16 09:50:00+00:00","open_rate":0.045202,"close_rate":0.04542857644110275,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":0.00022657644110275071,"sell_reason":"roi","initial_stop_loss_abs":0.0406818,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0406818,"stop_loss_ratio":0.1,"min_rate":0.045202,"max_rate":0.04542857644110275,"is_open":false,"buy_tag":null,"open_timestamp":1516092300000.0,"close_timestamp":1516096200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.054878048780488,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:45:00+00:00","open_rate":5.248e-05,"close_rate":5.326917293233082e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.891729323308177e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7232e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7232e-05,"stop_loss_ratio":0.1,"min_rate":5.248e-05,"max_rate":5.326917293233082e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516095900000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03457434486802627,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:55:00+00:00","open_rate":0.02892318,"close_rate":0.02906815834586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0001449783458646603,"sell_reason":"roi","initial_stop_loss_abs":0.026030862000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.026030862000000002,"stop_loss_ratio":0.1,"min_rate":0.02892318,"max_rate":0.02906815834586466,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516096500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.38735944164405,"open_date":"2018-01-16 09:50:00+00:00","close_date":"2018-01-16 10:10:00+00:00","open_rate":5.158e-05,"close_rate":5.287273182957392e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.2927318295739246e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6422e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6422e-05,"stop_loss_ratio":0.1,"min_rate":5.158e-05,"max_rate":5.287273182957392e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516096200000.0,"close_timestamp":1516097400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035357778286929785,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:35:00+00:00","open_rate":0.02828232,"close_rate":0.02870761804511278,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042529804511277913,"sell_reason":"roi","initial_stop_loss_abs":0.025454088,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025454088,"stop_loss_ratio":0.1,"min_rate":0.02828232,"max_rate":0.02870761804511278,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516098900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022948496230938982,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:40:00+00:00","open_rate":0.04357584,"close_rate":0.044231115789473675,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0006552757894736777,"sell_reason":"roi","initial_stop_loss_abs":0.039218256,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039218256,"stop_loss_ratio":0.1,"min_rate":0.04357584,"max_rate":0.044231115789473675,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516099200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.64975755315181,"open_date":"2018-01-16 13:45:00+00:00","close_date":"2018-01-16 14:20:00+00:00","open_rate":5.362e-05,"close_rate":5.442631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":8.063157894736843e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8258e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8258e-05,"stop_loss_ratio":0.1,"min_rate":5.362e-05,"max_rate":5.442631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516110300000.0,"close_timestamp":1516112400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.86080724254998,"open_date":"2018-01-16 17:30:00+00:00","close_date":"2018-01-16 18:25:00+00:00","open_rate":5.302e-05,"close_rate":5.328576441102756e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.6576441102756397e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7718e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7718e-05,"stop_loss_ratio":0.1,"min_rate":5.302e-05,"max_rate":5.328576441102756e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516123800000.0,"close_timestamp":1516127100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010952903718828448,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:45:00+00:00","open_rate":0.09129999,"close_rate":0.09267292218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013729321804511196,"sell_reason":"roi","initial_stop_loss_abs":0.082169991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082169991,"stop_loss_ratio":0.1,"min_rate":0.09129999,"max_rate":0.09267292218045112,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516128300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":26.26050420168067,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:35:00+00:00","open_rate":3.808e-05,"close_rate":3.903438596491228e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":9.543859649122774e-07,"sell_reason":"roi","initial_stop_loss_abs":3.4272e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.4272e-05,"stop_loss_ratio":0.1,"min_rate":3.808e-05,"max_rate":3.903438596491228e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516127700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035574376772493324,"open_date":"2018-01-16 19:00:00+00:00","close_date":"2018-01-16 19:30:00+00:00","open_rate":0.02811012,"close_rate":0.028532828571428567,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042270857142856846,"sell_reason":"roi","initial_stop_loss_abs":0.025299108,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025299108,"stop_loss_ratio":0.1,"min_rate":0.02811012,"max_rate":0.028532828571428567,"is_open":false,"buy_tag":null,"open_timestamp":1516129200000.0,"close_timestamp":1516131000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.387028357567759,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":0.00258379,"close_rate":0.002325411,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.10448878,"profit_abs":-0.000258379,"sell_reason":"stop_loss","initial_stop_loss_abs":0.002325411,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002325411,"stop_loss_ratio":0.1,"min_rate":0.002325411,"max_rate":0.00258379,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516141500000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":39.07776475185619,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":2.559e-05,"close_rate":2.3031e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.10448878,"profit_abs":-2.5590000000000004e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":2.3031e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.3031e-05,"stop_loss_ratio":0.1,"min_rate":2.3031e-05,"max_rate":2.559e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516142700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":13.123359580052494,"open_date":"2018-01-16 21:35:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":7.62e-05,"close_rate":6.858e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.10448878,"profit_abs":-7.619999999999998e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":6.858e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.858e-05,"stop_loss_ratio":0.1,"min_rate":6.858e-05,"max_rate":7.62e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516138500000.0,"close_timestamp":1516141500000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4350777048780912,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:35:00+00:00","open_rate":0.00229844,"close_rate":0.002402129022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010368902255639091,"sell_reason":"roi","initial_stop_loss_abs":0.0020685960000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0020685960000000002,"stop_loss_ratio":0.1,"min_rate":0.00229844,"max_rate":0.002402129022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06622516556291391,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:40:00+00:00","open_rate":0.0151,"close_rate":0.015781203007518795,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":0.0006812030075187946,"sell_reason":"roi","initial_stop_loss_abs":0.013590000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.013590000000000001,"stop_loss_ratio":0.1,"min_rate":0.0151,"max_rate":0.015781203007518795,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.42431134269081283,"open_date":"2018-01-16 22:40:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":0.00235676,"close_rate":0.00246308,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010632000000000003,"sell_reason":"roi","initial_stop_loss_abs":0.002121084,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002121084,"stop_loss_ratio":0.1,"min_rate":0.00235676,"max_rate":0.00246308,"is_open":false,"buy_tag":null,"open_timestamp":1516142400000.0,"close_timestamp":1516142700000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01585559988076589,"open_date":"2018-01-16 22:45:00+00:00","close_date":"2018-01-16 23:05:00+00:00","open_rate":0.0630692,"close_rate":0.06464988170426066,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0015806817042606502,"sell_reason":"roi","initial_stop_loss_abs":0.056762280000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.056762280000000005,"stop_loss_ratio":0.1,"min_rate":0.0630692,"max_rate":0.06464988170426066,"is_open":false,"buy_tag":null,"open_timestamp":1516142700000.0,"close_timestamp":1516143900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":45.45454545454545,"open_date":"2018-01-16 22:50:00+00:00","close_date":"2018-01-16 22:55:00+00:00","open_rate":2.2e-05,"close_rate":2.299248120300751e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":9.924812030075114e-07,"sell_reason":"roi","initial_stop_loss_abs":1.98e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":1.98e-05,"stop_loss_ratio":0.1,"min_rate":2.2e-05,"max_rate":2.299248120300751e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516143000000.0,"close_timestamp":1516143300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.10454362685967,"open_date":"2018-01-17 03:30:00+00:00","close_date":"2018-01-17 04:00:00+00:00","open_rate":4.974e-05,"close_rate":5.048796992481203e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.479699248120277e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4766000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4766000000000005e-05,"stop_loss_ratio":0.1,"min_rate":4.974e-05,"max_rate":5.048796992481203e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516159800000.0,"close_timestamp":1516161600000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":14.068655036578503,"open_date":"2018-01-17 03:55:00+00:00","close_date":"2018-01-17 04:15:00+00:00","open_rate":7.108e-05,"close_rate":7.28614536340852e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.7814536340851996e-06,"sell_reason":"roi","initial_stop_loss_abs":6.3972e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.3972e-05,"stop_loss_ratio":0.1,"min_rate":7.108e-05,"max_rate":7.28614536340852e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516161300000.0,"close_timestamp":1516162500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.0231107002542177,"open_date":"2018-01-17 09:35:00+00:00","close_date":"2018-01-17 10:15:00+00:00","open_rate":0.04327,"close_rate":0.04348689223057644,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002168922305764362,"sell_reason":"roi","initial_stop_loss_abs":0.038943000000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.038943000000000005,"stop_loss_ratio":0.1,"min_rate":0.04327,"max_rate":0.04348689223057644,"is_open":false,"buy_tag":null,"open_timestamp":1516181700000.0,"close_timestamp":1516184100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.012007204322593,"open_date":"2018-01-17 10:20:00+00:00","close_date":"2018-01-17 17:00:00+00:00","open_rate":4.997e-05,"close_rate":5.022047619047618e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":400,"profit_ratio":-0.0,"profit_abs":2.504761904761831e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4973e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4973e-05,"stop_loss_ratio":0.1,"min_rate":4.997e-05,"max_rate":5.022047619047618e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516184400000.0,"close_timestamp":1516208400000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014626687444363738,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:25:00+00:00","open_rate":0.06836818,"close_rate":0.06871087764411027,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00034269764411026804,"sell_reason":"roi","initial_stop_loss_abs":0.061531362,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061531362,"stop_loss_ratio":0.1,"min_rate":0.06836818,"max_rate":0.06871087764411027,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516188300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.548209366391184,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:10:00+00:00","open_rate":3.63e-05,"close_rate":3.648195488721804e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.8195488721804031e-07,"sell_reason":"roi","initial_stop_loss_abs":3.2670000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2670000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.63e-05,"max_rate":3.648195488721804e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516187400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03558718861209965,"open_date":"2018-01-17 12:30:00+00:00","close_date":"2018-01-17 22:05:00+00:00","open_rate":0.0281,"close_rate":0.02824085213032581,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":575,"profit_ratio":-0.0,"profit_abs":0.0001408521303258095,"sell_reason":"roi","initial_stop_loss_abs":0.02529,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.02529,"stop_loss_ratio":0.1,"min_rate":0.0281,"max_rate":0.02824085213032581,"is_open":false,"buy_tag":null,"open_timestamp":1516192200000.0,"close_timestamp":1516226700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011559355963546878,"open_date":"2018-01-17 12:35:00+00:00","close_date":"2018-01-17 16:55:00+00:00","open_rate":0.08651001,"close_rate":0.08694364413533832,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":-0.0,"profit_abs":0.00043363413533832607,"sell_reason":"roi","initial_stop_loss_abs":0.077859009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077859009,"stop_loss_ratio":0.1,"min_rate":0.08651001,"max_rate":0.08694364413533832,"is_open":false,"buy_tag":null,"open_timestamp":1516192500000.0,"close_timestamp":1516208100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.752529735487308,"open_date":"2018-01-18 05:00:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":5.633e-05,"close_rate":5.6612355889724306e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.8235588972430847e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0697e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0697e-05,"stop_loss_ratio":0.1,"min_rate":5.633e-05,"max_rate":5.6612355889724306e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516251600000.0,"close_timestamp":1516254900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01430923457900944,"open_date":"2018-01-18 05:20:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":0.06988494,"close_rate":0.07093584135338346,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0010509013533834544,"sell_reason":"roi","initial_stop_loss_abs":0.06289644600000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06289644600000001,"stop_loss_ratio":0.1,"min_rate":0.06988494,"max_rate":0.07093584135338346,"is_open":false,"buy_tag":null,"open_timestamp":1516252800000.0,"close_timestamp":1516254900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.034265103697024,"open_date":"2018-01-18 07:35:00+00:00","close_date":"2018-01-18 08:15:00+00:00","open_rate":5.545e-05,"close_rate":5.572794486215538e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.779448621553787e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9905e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9905e-05,"stop_loss_ratio":0.1,"min_rate":5.545e-05,"max_rate":5.572794486215538e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516260900000.0,"close_timestamp":1516263300000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06121723118136401,"open_date":"2018-01-18 09:00:00+00:00","close_date":"2018-01-18 09:40:00+00:00","open_rate":0.01633527,"close_rate":0.016417151052631574,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":8.188105263157511e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014701743,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014701743,"stop_loss_ratio":0.1,"min_rate":0.01633527,"max_rate":0.016417151052631574,"is_open":false,"buy_tag":null,"open_timestamp":1516266000000.0,"close_timestamp":1516268400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3707356136045141,"open_date":"2018-01-18 16:40:00+00:00","close_date":"2018-01-18 17:20:00+00:00","open_rate":0.00269734,"close_rate":0.002710860501253133,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.3520501253133123e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002427606,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002427606,"stop_loss_ratio":0.1,"min_rate":0.00269734,"max_rate":0.002710860501253133,"is_open":false,"buy_tag":null,"open_timestamp":1516293600000.0,"close_timestamp":1516296000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.346368715083802,"open_date":"2018-01-18 18:05:00+00:00","close_date":"2018-01-18 18:30:00+00:00","open_rate":4.475e-05,"close_rate":4.587155388471177e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1215538847117757e-06,"sell_reason":"roi","initial_stop_loss_abs":4.0274999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.0274999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.475e-05,"max_rate":4.587155388471177e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516298700000.0,"close_timestamp":1516300200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":35.842293906810035,"open_date":"2018-01-18 18:25:00+00:00","close_date":"2018-01-18 18:55:00+00:00","open_rate":2.79e-05,"close_rate":2.8319548872180444e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.1954887218044365e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5110000000000002e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5110000000000002e-05,"stop_loss_ratio":0.1,"min_rate":2.79e-05,"max_rate":2.8319548872180444e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516299900000.0,"close_timestamp":1516301700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022525942001105574,"open_date":"2018-01-18 20:10:00+00:00","close_date":"2018-01-18 20:50:00+00:00","open_rate":0.04439326,"close_rate":0.04461578260651629,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00022252260651629135,"sell_reason":"roi","initial_stop_loss_abs":0.039953933999999997,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039953933999999997,"stop_loss_ratio":0.1,"min_rate":0.04439326,"max_rate":0.04461578260651629,"is_open":false,"buy_tag":null,"open_timestamp":1516306200000.0,"close_timestamp":1516308600000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.271714922048996,"open_date":"2018-01-18 21:30:00+00:00","close_date":"2018-01-19 00:35:00+00:00","open_rate":4.49e-05,"close_rate":4.51250626566416e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":185,"profit_ratio":0.0,"profit_abs":2.2506265664159932e-07,"sell_reason":"roi","initial_stop_loss_abs":4.041e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.041e-05,"stop_loss_ratio":0.1,"min_rate":4.49e-05,"max_rate":4.51250626566416e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516311000000.0,"close_timestamp":1516322100000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03502626970227671,"open_date":"2018-01-18 21:55:00+00:00","close_date":"2018-01-19 05:05:00+00:00","open_rate":0.02855,"close_rate":0.028693107769423555,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":430,"profit_ratio":-0.0,"profit_abs":0.00014310776942355607,"sell_reason":"roi","initial_stop_loss_abs":0.025695,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025695,"stop_loss_ratio":0.1,"min_rate":0.02855,"max_rate":0.028693107769423555,"is_open":false,"buy_tag":null,"open_timestamp":1516312500000.0,"close_timestamp":1516338300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.25327812284334,"open_date":"2018-01-18 22:10:00+00:00","close_date":"2018-01-18 22:50:00+00:00","open_rate":5.796e-05,"close_rate":5.8250526315789473e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.905263157894727e-07,"sell_reason":"roi","initial_stop_loss_abs":5.2164000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2164000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.796e-05,"max_rate":5.8250526315789473e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516313400000.0,"close_timestamp":1516315800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02303975994413319,"open_date":"2018-01-18 23:50:00+00:00","close_date":"2018-01-19 00:30:00+00:00","open_rate":0.04340323,"close_rate":0.04362079005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0002175600501253122,"sell_reason":"roi","initial_stop_loss_abs":0.039062907,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039062907,"stop_loss_ratio":0.1,"min_rate":0.04340323,"max_rate":0.04362079005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516319400000.0,"close_timestamp":1516321800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02244943545282195,"open_date":"2018-01-19 16:45:00+00:00","close_date":"2018-01-19 17:35:00+00:00","open_rate":0.04454455,"close_rate":0.04476783095238095,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0002232809523809512,"sell_reason":"roi","initial_stop_loss_abs":0.040090095000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040090095000000006,"stop_loss_ratio":0.1,"min_rate":0.04454455,"max_rate":0.04476783095238095,"is_open":false,"buy_tag":null,"open_timestamp":1516380300000.0,"close_timestamp":1516383300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.793594306049823,"open_date":"2018-01-19 17:15:00+00:00","close_date":"2018-01-19 19:55:00+00:00","open_rate":5.62e-05,"close_rate":5.648170426065162e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":160,"profit_ratio":-0.0,"profit_abs":2.817042606516199e-07,"sell_reason":"roi","initial_stop_loss_abs":5.058e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.058e-05,"stop_loss_ratio":0.1,"min_rate":5.62e-05,"max_rate":5.648170426065162e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382100000.0,"close_timestamp":1516391700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.046784973496194,"open_date":"2018-01-19 17:20:00+00:00","close_date":"2018-01-19 20:15:00+00:00","open_rate":4.339e-05,"close_rate":4.360749373433584e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.174937343358337e-07,"sell_reason":"roi","initial_stop_loss_abs":3.9051e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.9051e-05,"stop_loss_ratio":0.1,"min_rate":4.339e-05,"max_rate":4.360749373433584e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382400000.0,"close_timestamp":1516392900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.910802775024777,"open_date":"2018-01-20 04:45:00+00:00","close_date":"2018-01-20 17:35:00+00:00","open_rate":0.0001009,"close_rate":0.00010140576441102755,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":770,"profit_ratio":0.0,"profit_abs":5.057644110275549e-07,"sell_reason":"roi","initial_stop_loss_abs":9.081e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.081e-05,"stop_loss_ratio":0.1,"min_rate":0.0001009,"max_rate":0.00010140576441102755,"is_open":false,"buy_tag":null,"open_timestamp":1516423500000.0,"close_timestamp":1516469700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3696789338459548,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 15:15:00+00:00","open_rate":0.00270505,"close_rate":0.002718609147869674,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":625,"profit_ratio":-0.0,"profit_abs":1.3559147869673764e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002434545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002434545,"stop_loss_ratio":0.1,"min_rate":0.00270505,"max_rate":0.002718609147869674,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516461300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.033333311111125925,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 07:00:00+00:00","open_rate":0.03000002,"close_rate":0.030150396040100245,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":0.00015037604010024672,"sell_reason":"roi","initial_stop_loss_abs":0.027000018,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027000018,"stop_loss_ratio":0.1,"min_rate":0.03000002,"max_rate":0.030150396040100245,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516431600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.315018315018317,"open_date":"2018-01-20 09:00:00+00:00","close_date":"2018-01-20 09:40:00+00:00","open_rate":5.46e-05,"close_rate":5.4873684210526304e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.736842105263053e-07,"sell_reason":"roi","initial_stop_loss_abs":4.914e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.914e-05,"stop_loss_ratio":0.1,"min_rate":5.46e-05,"max_rate":5.4873684210526304e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516438800000.0,"close_timestamp":1516441200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03244412634781012,"open_date":"2018-01-20 18:25:00+00:00","close_date":"2018-01-25 03:50:00+00:00","open_rate":0.03082222,"close_rate":0.027739998,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":6325,"profit_ratio":-0.10448878,"profit_abs":-0.0030822220000000025,"sell_reason":"stop_loss","initial_stop_loss_abs":0.027739998000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027739998000000002,"stop_loss_ratio":0.1,"min_rate":0.027739998,"max_rate":0.03082222,"is_open":false,"buy_tag":null,"open_timestamp":1516472700000.0,"close_timestamp":1516852200000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011148273260677065,"open_date":"2018-01-20 22:25:00+00:00","close_date":"2018-01-20 23:15:00+00:00","open_rate":0.08969999,"close_rate":0.09014961401002504,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":0.00044962401002504593,"sell_reason":"roi","initial_stop_loss_abs":0.080729991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080729991,"stop_loss_ratio":0.1,"min_rate":0.08969999,"max_rate":0.09014961401002504,"is_open":false,"buy_tag":null,"open_timestamp":1516487100000.0,"close_timestamp":1516490100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06125570520324337,"open_date":"2018-01-21 02:50:00+00:00","close_date":"2018-01-21 14:30:00+00:00","open_rate":0.01632501,"close_rate":0.01640683962406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":700,"profit_ratio":0.0,"profit_abs":8.182962406014932e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014692509000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014692509000000001,"stop_loss_ratio":0.1,"min_rate":0.01632501,"max_rate":0.01640683962406015,"is_open":false,"buy_tag":null,"open_timestamp":1516503000000.0,"close_timestamp":1516545000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01417675579120474,"open_date":"2018-01-21 10:20:00+00:00","close_date":"2018-01-21 11:00:00+00:00","open_rate":0.070538,"close_rate":0.07089157393483708,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00035357393483707866,"sell_reason":"roi","initial_stop_loss_abs":0.0634842,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0634842,"stop_loss_ratio":0.1,"min_rate":0.070538,"max_rate":0.07089157393483708,"is_open":false,"buy_tag":null,"open_timestamp":1516530000000.0,"close_timestamp":1516532400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.864365214110546,"open_date":"2018-01-21 15:50:00+00:00","close_date":"2018-01-21 18:45:00+00:00","open_rate":5.301e-05,"close_rate":5.327571428571427e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.657142857142672e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7709e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7709e-05,"stop_loss_ratio":0.1,"min_rate":5.301e-05,"max_rate":5.327571428571427e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516549800000.0,"close_timestamp":1516560300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.284450063211125,"open_date":"2018-01-21 16:20:00+00:00","close_date":"2018-01-21 17:00:00+00:00","open_rate":3.955e-05,"close_rate":3.9748245614035085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.9824561403508552e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5595e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5595e-05,"stop_loss_ratio":0.1,"min_rate":3.955e-05,"max_rate":3.9748245614035085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516551600000.0,"close_timestamp":1516554000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38683971296493297,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:45:00+00:00","open_rate":0.00258505,"close_rate":0.002623922932330827,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.8872932330826816e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326545,"stop_loss_ratio":0.1,"min_rate":0.00258505,"max_rate":0.002623922932330827,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.621316935690498,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:55:00+00:00","open_rate":3.903e-05,"close_rate":3.922563909774435e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.9563909774435151e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5127e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5127e-05,"stop_loss_ratio":0.1,"min_rate":3.903e-05,"max_rate":3.922563909774435e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.098548510313215,"open_date":"2018-01-22 00:35:00+00:00","close_date":"2018-01-22 10:35:00+00:00","open_rate":5.236e-05,"close_rate":5.262245614035087e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":600,"profit_ratio":-0.0,"profit_abs":2.624561403508717e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7124e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7124e-05,"stop_loss_ratio":0.1,"min_rate":5.236e-05,"max_rate":5.262245614035087e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516581300000.0,"close_timestamp":1516617300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.076650420912717,"open_date":"2018-01-22 01:30:00+00:00","close_date":"2018-01-22 02:10:00+00:00","open_rate":9.028e-05,"close_rate":9.07325313283208e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":4.5253132832080657e-07,"sell_reason":"roi","initial_stop_loss_abs":8.1252e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.1252e-05,"stop_loss_ratio":0.1,"min_rate":9.028e-05,"max_rate":9.07325313283208e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516584600000.0,"close_timestamp":1516587000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3721622627465575,"open_date":"2018-01-22 12:25:00+00:00","close_date":"2018-01-22 14:35:00+00:00","open_rate":0.002687,"close_rate":0.002700468671679198,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":1.3468671679197925e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0024183000000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0024183000000000004,"stop_loss_ratio":0.1,"min_rate":0.002687,"max_rate":0.002700468671679198,"is_open":false,"buy_tag":null,"open_timestamp":1516623900000.0,"close_timestamp":1516631700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.99232245681382,"open_date":"2018-01-22 13:15:00+00:00","close_date":"2018-01-22 13:55:00+00:00","open_rate":4.168e-05,"close_rate":4.188892230576441e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.0892230576441054e-07,"sell_reason":"roi","initial_stop_loss_abs":3.7512e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.7512e-05,"stop_loss_ratio":0.1,"min_rate":4.168e-05,"max_rate":4.188892230576441e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516626900000.0,"close_timestamp":1516629300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.336583153837434,"open_date":"2018-01-22 14:00:00+00:00","close_date":"2018-01-22 14:30:00+00:00","open_rate":8.821e-05,"close_rate":8.953646616541353e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":1.326466165413529e-06,"sell_reason":"roi","initial_stop_loss_abs":7.9389e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.9389e-05,"stop_loss_ratio":0.1,"min_rate":8.821e-05,"max_rate":8.953646616541353e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516629600000.0,"close_timestamp":1516631400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.334880123743233,"open_date":"2018-01-22 15:55:00+00:00","close_date":"2018-01-22 16:40:00+00:00","open_rate":5.172e-05,"close_rate":5.1979248120300745e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.592481203007459e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6548e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6548e-05,"stop_loss_ratio":0.1,"min_rate":5.172e-05,"max_rate":5.1979248120300745e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516636500000.0,"close_timestamp":1516639200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.04692663582287,"open_date":"2018-01-22 16:05:00+00:00","close_date":"2018-01-22 16:25:00+00:00","open_rate":3.026e-05,"close_rate":3.101839598997494e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":7.5839598997494e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7234e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7234e-05,"stop_loss_ratio":0.1,"min_rate":3.026e-05,"max_rate":3.101839598997494e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516637100000.0,"close_timestamp":1516638300000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014156285390713478,"open_date":"2018-01-22 19:50:00+00:00","close_date":"2018-01-23 00:10:00+00:00","open_rate":0.07064,"close_rate":0.07099408521303258,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":0.0,"profit_abs":0.00035408521303258167,"sell_reason":"roi","initial_stop_loss_abs":0.063576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.063576,"stop_loss_ratio":0.1,"min_rate":0.07064,"max_rate":0.07099408521303258,"is_open":false,"buy_tag":null,"open_timestamp":1516650600000.0,"close_timestamp":1516666200000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06080938507725528,"open_date":"2018-01-22 21:25:00+00:00","close_date":"2018-01-22 22:05:00+00:00","open_rate":0.01644483,"close_rate":0.01652726022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.243022556390922e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014800347,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014800347,"stop_loss_ratio":0.1,"min_rate":0.01644483,"max_rate":0.01652726022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516656300000.0,"close_timestamp":1516658700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.08935580697299,"open_date":"2018-01-23 00:05:00+00:00","close_date":"2018-01-23 00:35:00+00:00","open_rate":4.331e-05,"close_rate":4.3961278195488714e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":6.512781954887175e-07,"sell_reason":"roi","initial_stop_loss_abs":3.8979e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.8979e-05,"stop_loss_ratio":0.1,"min_rate":4.331e-05,"max_rate":4.3961278195488714e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516665900000.0,"close_timestamp":1516667700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.250000000000004,"open_date":"2018-01-23 01:50:00+00:00","close_date":"2018-01-23 02:15:00+00:00","open_rate":3.2e-05,"close_rate":3.2802005012531326e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":8.020050125313278e-07,"sell_reason":"roi","initial_stop_loss_abs":2.88e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.88e-05,"stop_loss_ratio":0.1,"min_rate":3.2e-05,"max_rate":3.2802005012531326e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516672200000.0,"close_timestamp":1516673700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010907854156754155,"open_date":"2018-01-23 04:25:00+00:00","close_date":"2018-01-23 05:15:00+00:00","open_rate":0.09167706,"close_rate":0.09213659413533835,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0004595341353383492,"sell_reason":"roi","initial_stop_loss_abs":0.08250935400000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08250935400000001,"stop_loss_ratio":0.1,"min_rate":0.09167706,"max_rate":0.09213659413533835,"is_open":false,"buy_tag":null,"open_timestamp":1516681500000.0,"close_timestamp":1516684500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014440474918339115,"open_date":"2018-01-23 07:35:00+00:00","close_date":"2018-01-23 09:00:00+00:00","open_rate":0.0692498,"close_rate":0.06959691679197995,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":0.0,"profit_abs":0.0003471167919799484,"sell_reason":"roi","initial_stop_loss_abs":0.06232482,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06232482,"stop_loss_ratio":0.1,"min_rate":0.0692498,"max_rate":0.06959691679197995,"is_open":false,"buy_tag":null,"open_timestamp":1516692900000.0,"close_timestamp":1516698000000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.426775612822127,"open_date":"2018-01-23 10:50:00+00:00","close_date":"2018-01-23 13:05:00+00:00","open_rate":3.182e-05,"close_rate":3.197949874686716e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":135,"profit_ratio":0.0,"profit_abs":1.594987468671663e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8638e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8638e-05,"stop_loss_ratio":0.1,"min_rate":3.182e-05,"max_rate":3.197949874686716e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516704600000.0,"close_timestamp":1516712700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024461839530332683,"open_date":"2018-01-23 11:05:00+00:00","close_date":"2018-01-23 16:05:00+00:00","open_rate":0.04088,"close_rate":0.04108491228070175,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":300,"profit_ratio":-0.0,"profit_abs":0.0002049122807017481,"sell_reason":"roi","initial_stop_loss_abs":0.036792,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036792,"stop_loss_ratio":0.1,"min_rate":0.04088,"max_rate":0.04108491228070175,"is_open":false,"buy_tag":null,"open_timestamp":1516705500000.0,"close_timestamp":1516723500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.417475728155342,"open_date":"2018-01-23 14:55:00+00:00","close_date":"2018-01-23 15:35:00+00:00","open_rate":5.15e-05,"close_rate":5.175814536340851e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5814536340851513e-07,"sell_reason":"roi","initial_stop_loss_abs":4.635e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.635e-05,"stop_loss_ratio":0.1,"min_rate":5.15e-05,"max_rate":5.175814536340851e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516719300000.0,"close_timestamp":1516721700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011023294646713328,"open_date":"2018-01-23 16:35:00+00:00","close_date":"2018-01-24 00:05:00+00:00","open_rate":0.09071698,"close_rate":0.09117170170426064,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":450,"profit_ratio":0.0,"profit_abs":0.00045472170426064107,"sell_reason":"roi","initial_stop_loss_abs":0.081645282,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.081645282,"stop_loss_ratio":0.1,"min_rate":0.09071698,"max_rate":0.09117170170426064,"is_open":false,"buy_tag":null,"open_timestamp":1516725300000.0,"close_timestamp":1516752300000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.969309462915604,"open_date":"2018-01-23 17:25:00+00:00","close_date":"2018-01-23 18:45:00+00:00","open_rate":3.128e-05,"close_rate":3.1436791979949865e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":1.5679197994986587e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8152e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8152e-05,"stop_loss_ratio":0.1,"min_rate":3.128e-05,"max_rate":3.1436791979949865e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516728300000.0,"close_timestamp":1516733100000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.465724751439037,"open_date":"2018-01-23 20:15:00+00:00","close_date":"2018-01-23 22:00:00+00:00","open_rate":9.555e-05,"close_rate":9.602894736842104e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":4.789473684210343e-07,"sell_reason":"roi","initial_stop_loss_abs":8.5995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.5995e-05,"stop_loss_ratio":0.1,"min_rate":9.555e-05,"max_rate":9.602894736842104e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516738500000.0,"close_timestamp":1516744800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02450979791426522,"open_date":"2018-01-23 22:30:00+00:00","close_date":"2018-01-23 23:10:00+00:00","open_rate":0.04080001,"close_rate":0.0410045213283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00020451132832080554,"sell_reason":"roi","initial_stop_loss_abs":0.036720009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036720009,"stop_loss_ratio":0.1,"min_rate":0.04080001,"max_rate":0.0410045213283208,"is_open":false,"buy_tag":null,"open_timestamp":1516746600000.0,"close_timestamp":1516749000000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.368584156498162,"open_date":"2018-01-23 23:50:00+00:00","close_date":"2018-01-24 03:35:00+00:00","open_rate":5.163e-05,"close_rate":5.18887969924812e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":225,"profit_ratio":-0.0,"profit_abs":2.587969924812037e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6467e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6467e-05,"stop_loss_ratio":0.1,"min_rate":5.163e-05,"max_rate":5.18887969924812e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516751400000.0,"close_timestamp":1516764900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024747691102289384,"open_date":"2018-01-24 00:20:00+00:00","close_date":"2018-01-24 01:50:00+00:00","open_rate":0.04040781,"close_rate":0.04061035541353383,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":0.0,"profit_abs":0.0002025454135338306,"sell_reason":"roi","initial_stop_loss_abs":0.036367029,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036367029,"stop_loss_ratio":0.1,"min_rate":0.04040781,"max_rate":0.04061035541353383,"is_open":false,"buy_tag":null,"open_timestamp":1516753200000.0,"close_timestamp":1516758600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.485580670303975,"open_date":"2018-01-24 06:45:00+00:00","close_date":"2018-01-24 07:25:00+00:00","open_rate":5.132e-05,"close_rate":5.157724310776942e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5724310776941724e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6188000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6188000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.132e-05,"max_rate":5.157724310776942e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516776300000.0,"close_timestamp":1516778700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.23816852635629,"open_date":"2018-01-24 14:15:00+00:00","close_date":"2018-01-24 14:25:00+00:00","open_rate":5.198e-05,"close_rate":5.432496240601503e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":2.344962406015033e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6782e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6782e-05,"stop_loss_ratio":0.1,"min_rate":5.198e-05,"max_rate":5.432496240601503e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516803300000.0,"close_timestamp":1516803900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.74394237066143,"open_date":"2018-01-24 14:50:00+00:00","close_date":"2018-01-24 16:35:00+00:00","open_rate":3.054e-05,"close_rate":3.069308270676692e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":1.5308270676691466e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7486000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7486000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.054e-05,"max_rate":3.069308270676692e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516805400000.0,"close_timestamp":1516811700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.795638562020944,"open_date":"2018-01-24 15:10:00+00:00","close_date":"2018-01-24 16:15:00+00:00","open_rate":9.263e-05,"close_rate":9.309431077694236e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":0.0,"profit_abs":4.6431077694236234e-07,"sell_reason":"roi","initial_stop_loss_abs":8.3367e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.3367e-05,"stop_loss_ratio":0.1,"min_rate":9.263e-05,"max_rate":9.309431077694236e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516806600000.0,"close_timestamp":1516810500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.13565469713457,"open_date":"2018-01-24 22:40:00+00:00","close_date":"2018-01-24 23:25:00+00:00","open_rate":5.514e-05,"close_rate":5.54163909774436e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.7639097744360576e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9625999999999995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9625999999999995e-05,"stop_loss_ratio":0.1,"min_rate":5.514e-05,"max_rate":5.54163909774436e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516833600000.0,"close_timestamp":1516836300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":20.3210729526519,"open_date":"2018-01-25 00:50:00+00:00","close_date":"2018-01-25 01:30:00+00:00","open_rate":4.921e-05,"close_rate":4.9456666666666664e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.4666666666666543e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4289e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4289e-05,"stop_loss_ratio":0.1,"min_rate":4.921e-05,"max_rate":4.9456666666666664e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516841400000.0,"close_timestamp":1516843800000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38461538461538464,"open_date":"2018-01-25 08:15:00+00:00","close_date":"2018-01-25 12:15:00+00:00","open_rate":0.0026,"close_rate":0.002613032581453634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":240,"profit_ratio":0.0,"profit_abs":1.3032581453634e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00234,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00234,"stop_loss_ratio":0.1,"min_rate":0.0026,"max_rate":0.002613032581453634,"is_open":false,"buy_tag":null,"open_timestamp":1516868100000.0,"close_timestamp":1516882500000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03571593119825878,"open_date":"2018-01-25 10:25:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":0.02799871,"close_rate":0.028139054411027563,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":350,"profit_ratio":-0.0,"profit_abs":0.00014034441102756326,"sell_reason":"roi","initial_stop_loss_abs":0.025198839,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025198839,"stop_loss_ratio":0.1,"min_rate":0.02799871,"max_rate":0.028139054411027563,"is_open":false,"buy_tag":null,"open_timestamp":1516875900000.0,"close_timestamp":1516896900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024516401717913302,"open_date":"2018-01-25 11:00:00+00:00","close_date":"2018-01-25 11:45:00+00:00","open_rate":0.04078902,"close_rate":0.0409934762406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020445624060149575,"sell_reason":"roi","initial_stop_loss_abs":0.036710118,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036710118,"stop_loss_ratio":0.1,"min_rate":0.04078902,"max_rate":0.0409934762406015,"is_open":false,"buy_tag":null,"open_timestamp":1516878000000.0,"close_timestamp":1516880700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.602076124567475,"open_date":"2018-01-25 13:05:00+00:00","close_date":"2018-01-25 13:45:00+00:00","open_rate":2.89e-05,"close_rate":2.904486215538847e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4486215538846723e-07,"sell_reason":"roi","initial_stop_loss_abs":2.601e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.601e-05,"stop_loss_ratio":0.1,"min_rate":2.89e-05,"max_rate":2.904486215538847e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516885500000.0,"close_timestamp":1516887900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02432912439481303,"open_date":"2018-01-25 13:20:00+00:00","close_date":"2018-01-25 14:05:00+00:00","open_rate":0.041103,"close_rate":0.04130903007518797,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020603007518796984,"sell_reason":"roi","initial_stop_loss_abs":0.0369927,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0369927,"stop_loss_ratio":0.1,"min_rate":0.041103,"max_rate":0.04130903007518797,"is_open":false,"buy_tag":null,"open_timestamp":1516886400000.0,"close_timestamp":1516889100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.422991893883566,"open_date":"2018-01-25 15:45:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":5.428e-05,"close_rate":5.509624060150376e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.162406015037611e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8852000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8852000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.428e-05,"max_rate":5.509624060150376e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516895100000.0,"close_timestamp":1516896900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.47063169560399,"open_date":"2018-01-25 17:45:00+00:00","close_date":"2018-01-25 23:15:00+00:00","open_rate":5.414e-05,"close_rate":5.441137844611528e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":330,"profit_ratio":-0.0,"profit_abs":2.713784461152774e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8726e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8726e-05,"stop_loss_ratio":0.1,"min_rate":5.414e-05,"max_rate":5.441137844611528e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516902300000.0,"close_timestamp":1516922100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024150056861308878,"open_date":"2018-01-25 21:15:00+00:00","close_date":"2018-01-25 21:55:00+00:00","open_rate":0.04140777,"close_rate":0.0416153277443609,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002075577443608964,"sell_reason":"roi","initial_stop_loss_abs":0.037266993000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.037266993000000005,"stop_loss_ratio":0.1,"min_rate":0.04140777,"max_rate":0.0416153277443609,"is_open":false,"buy_tag":null,"open_timestamp":1516914900000.0,"close_timestamp":1516917300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3932224183965176,"open_date":"2018-01-26 02:05:00+00:00","close_date":"2018-01-26 02:45:00+00:00","open_rate":0.00254309,"close_rate":0.002555837318295739,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.2747318295739177e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002288781,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002288781,"stop_loss_ratio":0.1,"min_rate":0.00254309,"max_rate":0.002555837318295739,"is_open":false,"buy_tag":null,"open_timestamp":1516932300000.0,"close_timestamp":1516934700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.834849295523455,"open_date":"2018-01-26 02:55:00+00:00","close_date":"2018-01-26 15:10:00+00:00","open_rate":5.607e-05,"close_rate":5.6351052631578935e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":735,"profit_ratio":-0.0,"profit_abs":2.810526315789381e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0463e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0463e-05,"stop_loss_ratio":0.1,"min_rate":5.607e-05,"max_rate":5.6351052631578935e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516935300000.0,"close_timestamp":1516979400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.39400171784748983,"open_date":"2018-01-26 06:10:00+00:00","close_date":"2018-01-26 09:25:00+00:00","open_rate":0.00253806,"close_rate":0.0025507821052631577,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":195,"profit_ratio":0.0,"profit_abs":1.2722105263157733e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002284254,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002284254,"stop_loss_ratio":0.1,"min_rate":0.00253806,"max_rate":0.0025507821052631577,"is_open":false,"buy_tag":null,"open_timestamp":1516947000000.0,"close_timestamp":1516958700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024096385542168672,"open_date":"2018-01-26 07:25:00+00:00","close_date":"2018-01-26 09:55:00+00:00","open_rate":0.0415,"close_rate":0.04170802005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":150,"profit_ratio":-0.0,"profit_abs":0.00020802005012530989,"sell_reason":"roi","initial_stop_loss_abs":0.03735,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03735,"stop_loss_ratio":0.1,"min_rate":0.0415,"max_rate":0.04170802005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516951500000.0,"close_timestamp":1516960500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.793459875963165,"open_date":"2018-01-26 09:55:00+00:00","close_date":"2018-01-26 10:25:00+00:00","open_rate":5.321e-05,"close_rate":5.401015037593984e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.00150375939842e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7889e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7889e-05,"stop_loss_ratio":0.1,"min_rate":5.321e-05,"max_rate":5.401015037593984e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516960500000.0,"close_timestamp":1516962300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.036074437437185386,"open_date":"2018-01-26 16:05:00+00:00","close_date":"2018-01-26 16:45:00+00:00","open_rate":0.02772046,"close_rate":0.02785940967418546,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013894967418546025,"sell_reason":"roi","initial_stop_loss_abs":0.024948414,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024948414,"stop_loss_ratio":0.1,"min_rate":0.02772046,"max_rate":0.02785940967418546,"is_open":false,"buy_tag":null,"open_timestamp":1516982700000.0,"close_timestamp":1516985100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010569326272036914,"open_date":"2018-01-26 23:35:00+00:00","close_date":"2018-01-27 00:15:00+00:00","open_rate":0.09461341,"close_rate":0.09508766268170424,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00047425268170424306,"sell_reason":"roi","initial_stop_loss_abs":0.085152069,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085152069,"stop_loss_ratio":0.1,"min_rate":0.09461341,"max_rate":0.09508766268170424,"is_open":false,"buy_tag":null,"open_timestamp":1517009700000.0,"close_timestamp":1517012100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":17.809439002671414,"open_date":"2018-01-27 00:35:00+00:00","close_date":"2018-01-27 01:30:00+00:00","open_rate":5.615e-05,"close_rate":5.643145363408521e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.814536340852038e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0535e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0535e-05,"stop_loss_ratio":0.1,"min_rate":5.615e-05,"max_rate":5.643145363408521e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013300000.0,"close_timestamp":1517016600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.998560115190784,"open_date":"2018-01-27 00:45:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":5.556e-05,"close_rate":5.144e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4560,"profit_ratio":-0.07877175,"profit_abs":-4.120000000000001e-06,"sell_reason":"force_sell","initial_stop_loss_abs":5.0004000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0004000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.144e-05,"max_rate":5.556e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013900000.0,"close_timestamp":1517287500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492751522789634,"open_date":"2018-01-27 02:30:00+00:00","close_date":"2018-01-27 11:25:00+00:00","open_rate":0.06900001,"close_rate":0.06934587471177944,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":535,"profit_ratio":-0.0,"profit_abs":0.0003458647117794422,"sell_reason":"roi","initial_stop_loss_abs":0.062100009000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.062100009000000005,"stop_loss_ratio":0.1,"min_rate":0.06900001,"max_rate":0.06934587471177944,"is_open":false,"buy_tag":null,"open_timestamp":1517020200000.0,"close_timestamp":1517052300000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010582027378879437,"open_date":"2018-01-27 06:25:00+00:00","close_date":"2018-01-27 07:05:00+00:00","open_rate":0.09449985,"close_rate":0.0949735334586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004736834586466093,"sell_reason":"roi","initial_stop_loss_abs":0.085049865,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085049865,"stop_loss_ratio":0.1,"min_rate":0.09449985,"max_rate":0.0949735334586466,"is_open":false,"buy_tag":null,"open_timestamp":1517034300000.0,"close_timestamp":1517036700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02434885085598385,"open_date":"2018-01-27 09:40:00+00:00","close_date":"2018-01-30 04:40:00+00:00","open_rate":0.0410697,"close_rate":0.03928809,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4020,"profit_ratio":-0.04815133,"profit_abs":-0.001781610000000003,"sell_reason":"force_sell","initial_stop_loss_abs":0.03696273,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03696273,"stop_loss_ratio":0.1,"min_rate":0.03928809,"max_rate":0.0410697,"is_open":false,"buy_tag":null,"open_timestamp":1517046000000.0,"close_timestamp":1517287200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03508771929824561,"open_date":"2018-01-27 11:45:00+00:00","close_date":"2018-01-27 12:30:00+00:00","open_rate":0.0285,"close_rate":0.02864285714285714,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00014285714285713902,"sell_reason":"roi","initial_stop_loss_abs":0.025650000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025650000000000003,"stop_loss_ratio":0.1,"min_rate":0.0285,"max_rate":0.02864285714285714,"is_open":false,"buy_tag":null,"open_timestamp":1517053500000.0,"close_timestamp":1517056200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.034887307020861215,"open_date":"2018-01-27 12:35:00+00:00","close_date":"2018-01-27 15:25:00+00:00","open_rate":0.02866372,"close_rate":0.02880739779448621,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":170,"profit_ratio":-0.0,"profit_abs":0.00014367779448621124,"sell_reason":"roi","initial_stop_loss_abs":0.025797348,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025797348,"stop_loss_ratio":0.1,"min_rate":0.02866372,"max_rate":0.02880739779448621,"is_open":false,"buy_tag":null,"open_timestamp":1517056500000.0,"close_timestamp":1517066700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010484268355332824,"open_date":"2018-01-27 15:50:00+00:00","close_date":"2018-01-27 16:50:00+00:00","open_rate":0.095381,"close_rate":0.09585910025062656,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.00047810025062657024,"sell_reason":"roi","initial_stop_loss_abs":0.0858429,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0858429,"stop_loss_ratio":0.1,"min_rate":0.095381,"max_rate":0.09585910025062656,"is_open":false,"buy_tag":null,"open_timestamp":1517068200000.0,"close_timestamp":1517071800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014794886650455417,"open_date":"2018-01-27 17:05:00+00:00","close_date":"2018-01-27 17:45:00+00:00","open_rate":0.06759092,"close_rate":0.06792972160401002,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00033880160401002224,"sell_reason":"roi","initial_stop_loss_abs":0.060831828,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060831828,"stop_loss_ratio":0.1,"min_rate":0.06759092,"max_rate":0.06792972160401002,"is_open":false,"buy_tag":null,"open_timestamp":1517072700000.0,"close_timestamp":1517075100000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38684569885609726,"open_date":"2018-01-27 23:40:00+00:00","close_date":"2018-01-28 01:05:00+00:00","open_rate":0.00258501,"close_rate":0.002597967443609022,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":-0.0,"profit_abs":1.2957443609021985e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326509,"stop_loss_ratio":0.1,"min_rate":0.00258501,"max_rate":0.002597967443609022,"is_open":false,"buy_tag":null,"open_timestamp":1517096400000.0,"close_timestamp":1517101500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014928710926711672,"open_date":"2018-01-28 02:25:00+00:00","close_date":"2018-01-28 08:10:00+00:00","open_rate":0.06698502,"close_rate":0.0673207845112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":345,"profit_ratio":-0.0,"profit_abs":0.00033576451127818874,"sell_reason":"roi","initial_stop_loss_abs":0.060286518000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060286518000000004,"stop_loss_ratio":0.1,"min_rate":0.06698502,"max_rate":0.0673207845112782,"is_open":false,"buy_tag":null,"open_timestamp":1517106300000.0,"close_timestamp":1517127000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014767187899175548,"open_date":"2018-01-28 10:25:00+00:00","close_date":"2018-01-28 16:30:00+00:00","open_rate":0.0677177,"close_rate":0.06805713709273183,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":365,"profit_ratio":-0.0,"profit_abs":0.0003394370927318202,"sell_reason":"roi","initial_stop_loss_abs":0.06094593000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06094593000000001,"stop_loss_ratio":0.1,"min_rate":0.0677177,"max_rate":0.06805713709273183,"is_open":false,"buy_tag":null,"open_timestamp":1517135100000.0,"close_timestamp":1517157000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":19.175455417066157,"open_date":"2018-01-28 20:35:00+00:00","close_date":"2018-01-28 21:35:00+00:00","open_rate":5.215e-05,"close_rate":5.2411403508771925e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":0.0,"profit_abs":2.6140350877192417e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6935000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6935000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.215e-05,"max_rate":5.2411403508771925e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517171700000.0,"close_timestamp":1517175300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.36521808998243305,"open_date":"2018-01-28 22:00:00+00:00","close_date":"2018-01-28 22:30:00+00:00","open_rate":0.00273809,"close_rate":0.002779264285714285,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.117428571428529e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002464281,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002464281,"stop_loss_ratio":0.1,"min_rate":0.00273809,"max_rate":0.002779264285714285,"is_open":false,"buy_tag":null,"open_timestamp":1517176800000.0,"close_timestamp":1517178600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3641236272539253,"open_date":"2018-01-29 00:00:00+00:00","close_date":"2018-01-29 00:30:00+00:00","open_rate":0.00274632,"close_rate":0.002787618045112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.129804511278194e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002471688,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002471688,"stop_loss_ratio":0.1,"min_rate":0.00274632,"max_rate":0.002787618045112782,"is_open":false,"buy_tag":null,"open_timestamp":1517184000000.0,"close_timestamp":1517185800000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.061634117689115045,"open_date":"2018-01-29 02:15:00+00:00","close_date":"2018-01-29 03:00:00+00:00","open_rate":0.01622478,"close_rate":0.016306107218045113,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":8.132721804511231e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014602302000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014602302000000001,"stop_loss_ratio":0.1,"min_rate":0.01622478,"max_rate":0.016306107218045113,"is_open":false,"buy_tag":null,"open_timestamp":1517192100000.0,"close_timestamp":1517194800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492753623188404,"open_date":"2018-01-29 03:05:00+00:00","close_date":"2018-01-29 03:45:00+00:00","open_rate":0.069,"close_rate":0.06934586466165413,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00034586466165412166,"sell_reason":"roi","initial_stop_loss_abs":0.06210000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06210000000000001,"stop_loss_ratio":0.1,"min_rate":0.069,"max_rate":0.06934586466165413,"is_open":false,"buy_tag":null,"open_timestamp":1517195100000.0,"close_timestamp":1517197500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.42204454597373,"open_date":"2018-01-29 05:20:00+00:00","close_date":"2018-01-29 06:55:00+00:00","open_rate":8.755e-05,"close_rate":8.798884711779448e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":95,"profit_ratio":-0.0,"profit_abs":4.3884711779447504e-07,"sell_reason":"roi","initial_stop_loss_abs":7.879500000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.879500000000001e-05,"stop_loss_ratio":0.1,"min_rate":8.755e-05,"max_rate":8.798884711779448e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517203200000.0,"close_timestamp":1517208900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014650376815016871,"open_date":"2018-01-29 07:00:00+00:00","close_date":"2018-01-29 19:25:00+00:00","open_rate":0.06825763,"close_rate":0.06859977350877192,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":745,"profit_ratio":-0.0,"profit_abs":0.00034214350877191657,"sell_reason":"roi","initial_stop_loss_abs":0.061431867,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061431867,"stop_loss_ratio":0.1,"min_rate":0.06825763,"max_rate":0.06859977350877192,"is_open":false,"buy_tag":null,"open_timestamp":1517209200000.0,"close_timestamp":1517253900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014894490408841846,"open_date":"2018-01-29 19:45:00+00:00","close_date":"2018-01-29 20:25:00+00:00","open_rate":0.06713892,"close_rate":0.06747545593984962,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0003365359398496137,"sell_reason":"roi","initial_stop_loss_abs":0.060425028000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060425028000000006,"stop_loss_ratio":0.1,"min_rate":0.06713892,"max_rate":0.06747545593984962,"is_open":false,"buy_tag":null,"open_timestamp":1517255100000.0,"close_timestamp":1517257500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.193194537721066,"open_date":"2018-01-29 23:30:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":8.934e-05,"close_rate":8.8e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":315,"profit_ratio":-0.0199116,"profit_abs":-1.3399999999999973e-06,"sell_reason":"force_sell","initial_stop_loss_abs":8.0406e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.0406e-05,"stop_loss_ratio":0.1,"min_rate":8.8e-05,"max_rate":8.934e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517268600000.0,"close_timestamp":1517287500000.0}],"locks":[],"best_pair":{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},"worst_pair":{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},"results_per_pair":[{"key":"ETH/BTC","trades":21,"profit_mean":0.0009500057142857142,"profit_mean_pct":0.09500057142857142,"profit_sum":0.01995012,"profit_sum_pct":2.0,"profit_total_abs":0.011505731278195264,"profit_total":1.1505731278195264,"profit_total_pct":115.06,"duration_avg":"2:17:00","wins":21,"draws":0,"losses":0},{"key":"DASH/BTC","trades":16,"profit_mean":0.0018703237499999997,"profit_mean_pct":0.18703237499999997,"profit_sum":0.029925179999999996,"profit_sum_pct":2.99,"profit_total_abs":0.007475052681704161,"profit_total":0.7475052681704161,"profit_total_pct":74.75,"duration_avg":"3:03:00","wins":16,"draws":0,"losses":0},{"key":"ZEC/BTC","trades":21,"profit_mean":-0.00039290904761904774,"profit_mean_pct":-0.03929090476190478,"profit_sum":-0.008251090000000003,"profit_sum_pct":-0.83,"profit_total_abs":0.004452605639097655,"profit_total":0.4452605639097655,"profit_total_pct":44.53,"duration_avg":"4:17:00","wins":20,"draws":0,"losses":1},{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},{"key":"ETC/BTC","trades":20,"profit_mean":0.0022568569999999997,"profit_mean_pct":0.22568569999999996,"profit_sum":0.04513713999999999,"profit_sum_pct":4.51,"profit_total_abs":0.00036538235338345404,"profit_total":0.0365382353383454,"profit_total_pct":3.65,"duration_avg":"1:45:00","wins":19,"draws":0,"losses":1},{"key":"TRX/BTC","trades":15,"profit_mean":0.0023467073333333323,"profit_mean_pct":0.23467073333333321,"profit_sum":0.035200609999999986,"profit_sum_pct":3.52,"profit_total_abs":1.1329523809523682e-05,"profit_total":0.0011329523809523682,"profit_total_pct":0.11,"duration_avg":"2:28:00","wins":13,"draws":0,"losses":2},{"key":"XLM/BTC","trades":21,"profit_mean":0.0026243899999999994,"profit_mean_pct":0.2624389999999999,"profit_sum":0.05511218999999999,"profit_sum_pct":5.51,"profit_total_abs":7.340779448621465e-06,"profit_total":0.0007340779448621465,"profit_total_pct":0.07,"duration_avg":"3:21:00","wins":20,"draws":0,"losses":1},{"key":"ADA/BTC","trades":29,"profit_mean":-0.0011598141379310352,"profit_mean_pct":-0.11598141379310352,"profit_sum":-0.03363461000000002,"profit_sum_pct":-3.36,"profit_total_abs":4.916634085212862e-06,"profit_total":0.0004916634085212862,"profit_total_pct":0.05,"duration_avg":"5:35:00","wins":27,"draws":0,"losses":2},{"key":"NXT/BTC","trades":12,"profit_mean":-0.0012261025000000006,"profit_mean_pct":-0.12261025000000006,"profit_sum":-0.014713230000000008,"profit_sum_pct":-1.47,"profit_total_abs":1.4774411027568458e-06,"profit_total":0.00014774411027568458,"profit_total_pct":0.01,"duration_avg":"0:57:00","wins":11,"draws":0,"losses":1},{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"results_per_buy_tag":[{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"sell_reason_summary":[{"sell_reason":"roi","trades":170,"wins":170,"draws":0,"losses":0,"profit_mean":0.005398268352941177,"profit_mean_pct":0.54,"profit_sum":0.91770562,"profit_sum_pct":91.77,"profit_total_abs":0.031232837493733862,"profit_total":0.30590187333333335,"profit_total_pct":30.59},{"sell_reason":"stop_loss","trades":6,"wins":0,"draws":0,"losses":6,"profit_mean":-0.10448878000000002,"profit_mean_pct":-10.45,"profit_sum":-0.6269326800000001,"profit_sum_pct":-62.69,"profit_total_abs":-0.0033602680000000026,"profit_total":-0.20897756000000003,"profit_total_pct":-20.9},{"sell_reason":"force_sell","trades":3,"wins":0,"draws":0,"losses":3,"profit_mean":-0.04894489333333333,"profit_mean_pct":-4.89,"profit_sum":-0.14683468,"profit_sum_pct":-14.68,"profit_total_abs":-0.001787070000000003,"profit_total":-0.04894489333333333,"profit_total_pct":-4.89}],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":179,"total_volume":0.17900000000000005,"avg_stake_amount":0.0010000000000000002,"profit_mean":0.0008041243575418989,"profit_median":0.0,"profit_total":2.6085499493733857,"profit_total_abs":0.026085499493733857,"backtest_start":"2018-01-10 07:15:00","backtest_start_ts":1515568500000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":"2020-10-01 18:00:00+00:00","backtest_run_end_ts":"2020-10-01 18:01:00+00:00","trades_per_day":9.42,"market_change":1.22,"pairlist":[],"stake_amount":0.001,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":0.01,"dry_run_wallet":0.01,"final_balance":0.03608549949373386,"rejected_signals":0,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timeframe_detail":"","timerange":"","enable_protections":false,"strategy_name":"StrategyTestV2","stoploss":0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":false,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.17955111999999998,"backtest_worst_day":-0.14683468,"backtest_best_day_abs":0.0071570099,"backtest_worst_day_abs":-0.0023093218,"winning_days":19,"draw_days":0,"losing_days":2,"daily_profit":[["2018-01-10",0.0025815306],["2018-01-11",0.0049356655],["2018-01-12",0.0006395218],["2018-01-13",0.0002574589],["2018-01-14",0.0010443828],["2018-01-15",0.0024030209],["2018-01-16",0.0071570099],["2018-01-17",0.001137038],["2018-01-18",0.0013712174],["2018-01-19",0.000584673],["2018-01-20",0.0006143386],["2018-01-21",0.0004749361],["2018-01-22",9.91669e-05],["2018-01-23",0.0015726664],["2018-01-24",0.0006610219],["2018-01-25",-0.0023093218],["2018-01-26",0.0003735204],["2018-01-27",0.0023975191],["2018-01-28",0.0007295947],["2018-01-29",0.0011476082],["2018-01-30",-0.00178707]],"wins":48,"losses":9,"draws":122,"holding_avg":"3:40:00","holding_avg_s":13200.0,"winner_holding_avg":"0:24:00","winner_holding_avg_s":1440.0,"loser_holding_avg":"1 day, 5:57:00","loser_holding_avg_s":107820.0,"max_drawdown":0.21142322000000008,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":0.0030822220000000025,"drawdown_start":"2018-01-25 01:30:00","drawdown_start_ts":1516843800000.0,"drawdown_end":"2018-01-25 03:50:00","drawdown_end_ts":1516852200000.0,"max_drawdown_low":0.02245167355388436,"max_drawdown_high":0.025533895553884363,"csum_min":0.01000434887218045,"csum_max":0.03608683949373386}},"strategy_comparison":[{"key":"StrategyTestV2","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":"0.00308222"}]}
\ No newline at end of file
+{"strategy":{"StrategyTestV3":{"trades":[{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.37344398340249,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:20:00+00:00","open_rate":9.64e-05,"close_rate":0.00010074887218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":4.348872180451118e-06,"sell_reason":"roi","initial_stop_loss_abs":8.676e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.676e-05,"stop_loss_ratio":0.1,"min_rate":9.64e-05,"max_rate":0.00010074887218045112,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515568800000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":21.026072329688816,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:30:00+00:00","open_rate":4.756e-05,"close_rate":4.9705563909774425e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":2.1455639097744267e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2804e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2804e-05,"stop_loss_ratio":0.1,"min_rate":4.756e-05,"max_rate":4.9705563909774425e-05,"is_open":false,"buy_tag":"buy_tag","open_timestamp":1515568500000.0,"close_timestamp":1515569400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.94908655286014,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:35:00+00:00","open_rate":3.339e-05,"close_rate":3.489631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":1.506315789473681e-06,"sell_reason":"roi","initial_stop_loss_abs":3.0050999999999997e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0050999999999997e-05,"stop_loss_ratio":0.1,"min_rate":3.339e-05,"max_rate":3.489631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515569700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.313531353135314,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:40:00+00:00","open_rate":9.696e-05,"close_rate":0.00010133413533834584,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":4.3741353383458455e-06,"sell_reason":"roi","initial_stop_loss_abs":8.7264e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.7264e-05,"stop_loss_ratio":0.1,"min_rate":9.696e-05,"max_rate":0.00010133413533834584,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515570000000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010604453870625663,"open_date":"2018-01-10 07:35:00+00:00","close_date":"2018-01-10 08:35:00+00:00","open_rate":0.0943,"close_rate":0.09477268170426063,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.0004726817042606385,"sell_reason":"roi","initial_stop_loss_abs":0.08487,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08487,"stop_loss_ratio":0.1,"min_rate":0.0943,"max_rate":0.09477268170426063,"is_open":false,"buy_tag":null,"open_timestamp":1515569700000.0,"close_timestamp":1515573300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03677001860930642,"open_date":"2018-01-10 07:40:00+00:00","close_date":"2018-01-10 08:10:00+00:00","open_rate":0.02719607,"close_rate":0.02760503345864661,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00040896345864661204,"sell_reason":"roi","initial_stop_loss_abs":0.024476463,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024476463,"stop_loss_ratio":0.1,"min_rate":0.02719607,"max_rate":0.02760503345864661,"is_open":false,"buy_tag":null,"open_timestamp":1515570000000.0,"close_timestamp":1515571800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021575196463739,"open_date":"2018-01-10 08:15:00+00:00","close_date":"2018-01-10 09:55:00+00:00","open_rate":0.04634952,"close_rate":0.046581848421052625,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":100,"profit_ratio":0.0,"profit_abs":0.0002323284210526272,"sell_reason":"roi","initial_stop_loss_abs":0.041714568,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041714568,"stop_loss_ratio":0.1,"min_rate":0.04634952,"max_rate":0.046581848421052625,"is_open":false,"buy_tag":null,"open_timestamp":1515572100000.0,"close_timestamp":1515578100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.615786040443574,"open_date":"2018-01-10 14:45:00+00:00","close_date":"2018-01-10 15:50:00+00:00","open_rate":3.066e-05,"close_rate":3.081368421052631e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":1.5368421052630647e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7594e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7594e-05,"stop_loss_ratio":0.1,"min_rate":3.066e-05,"max_rate":3.081368421052631e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515595500000.0,"close_timestamp":1515599400000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.05917194776300452,"open_date":"2018-01-10 16:35:00+00:00","close_date":"2018-01-10 17:15:00+00:00","open_rate":0.0168999,"close_rate":0.016984611278195488,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.471127819548868e-05,"sell_reason":"roi","initial_stop_loss_abs":0.01520991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01520991,"stop_loss_ratio":0.1,"min_rate":0.0168999,"max_rate":0.016984611278195488,"is_open":false,"buy_tag":null,"open_timestamp":1515602100000.0,"close_timestamp":1515604500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010949822656672253,"open_date":"2018-01-10 16:40:00+00:00","close_date":"2018-01-10 17:20:00+00:00","open_rate":0.09132568,"close_rate":0.0917834528320802,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004577728320801916,"sell_reason":"roi","initial_stop_loss_abs":0.08219311200000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08219311200000001,"stop_loss_ratio":0.1,"min_rate":0.09132568,"max_rate":0.0917834528320802,"is_open":false,"buy_tag":null,"open_timestamp":1515602400000.0,"close_timestamp":1515604800000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011238476768326556,"open_date":"2018-01-10 18:50:00+00:00","close_date":"2018-01-10 19:45:00+00:00","open_rate":0.08898003,"close_rate":0.08942604518796991,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00044601518796991146,"sell_reason":"roi","initial_stop_loss_abs":0.080082027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080082027,"stop_loss_ratio":0.1,"min_rate":0.08898003,"max_rate":0.08942604518796991,"is_open":false,"buy_tag":null,"open_timestamp":1515610200000.0,"close_timestamp":1515613500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011682232072680307,"open_date":"2018-01-10 22:15:00+00:00","close_date":"2018-01-10 23:00:00+00:00","open_rate":0.08560008,"close_rate":0.08602915308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":0.00042907308270676014,"sell_reason":"roi","initial_stop_loss_abs":0.077040072,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077040072,"stop_loss_ratio":0.1,"min_rate":0.08560008,"max_rate":0.08602915308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515622500000.0,"close_timestamp":1515625200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4014726015023105,"open_date":"2018-01-10 22:50:00+00:00","close_date":"2018-01-10 23:20:00+00:00","open_rate":0.00249083,"close_rate":0.0025282860902255634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.745609022556351e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002241747,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002241747,"stop_loss_ratio":0.1,"min_rate":0.00249083,"max_rate":0.0025282860902255634,"is_open":false,"buy_tag":null,"open_timestamp":1515624600000.0,"close_timestamp":1515626400000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.090668431502316,"open_date":"2018-01-10 23:15:00+00:00","close_date":"2018-01-11 00:15:00+00:00","open_rate":3.022e-05,"close_rate":3.037147869674185e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":1.5147869674185174e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7198e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7198e-05,"stop_loss_ratio":0.1,"min_rate":3.022e-05,"max_rate":3.037147869674185e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515626100000.0,"close_timestamp":1515629700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.41034058268362744,"open_date":"2018-01-10 23:40:00+00:00","close_date":"2018-01-11 00:05:00+00:00","open_rate":0.002437,"close_rate":0.0024980776942355883,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":6.107769423558838e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0021933,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0021933,"stop_loss_ratio":0.1,"min_rate":0.002437,"max_rate":0.0024980776942355883,"is_open":false,"buy_tag":null,"open_timestamp":1515627600000.0,"close_timestamp":1515629100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02095643931654345,"open_date":"2018-01-11 00:00:00+00:00","close_date":"2018-01-11 00:35:00+00:00","open_rate":0.04771803,"close_rate":0.04843559436090225,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0007175643609022495,"sell_reason":"roi","initial_stop_loss_abs":0.042946227000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.042946227000000003,"stop_loss_ratio":0.1,"min_rate":0.04771803,"max_rate":0.04843559436090225,"is_open":false,"buy_tag":null,"open_timestamp":1515628800000.0,"close_timestamp":1515630900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.389756231169542,"open_date":"2018-01-11 03:40:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":3.651e-05,"close_rate":3.2859000000000005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.10448878,"profit_abs":-3.650999999999996e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":3.2859000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2859000000000005e-05,"stop_loss_ratio":0.1,"min_rate":3.2859000000000005e-05,"max_rate":3.651e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515642000000.0,"close_timestamp":1515644700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011332594070446804,"open_date":"2018-01-11 03:55:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":0.08824105,"close_rate":0.08956798308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013269330827067605,"sell_reason":"roi","initial_stop_loss_abs":0.079416945,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079416945,"stop_loss_ratio":0.1,"min_rate":0.08824105,"max_rate":0.08956798308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515642900000.0,"close_timestamp":1515644700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.411522633744856,"open_date":"2018-01-11 04:00:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":0.00243,"close_rate":0.002442180451127819,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":1.2180451127819219e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002187,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002187,"stop_loss_ratio":0.1,"min_rate":0.00243,"max_rate":0.002442180451127819,"is_open":false,"buy_tag":null,"open_timestamp":1515643200000.0,"close_timestamp":1515646200000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022001890402423376,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:55:00+00:00","open_rate":0.04545064,"close_rate":0.046589753784461146,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":0.001139113784461146,"sell_reason":"roi","initial_stop_loss_abs":0.040905576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040905576,"stop_loss_ratio":0.1,"min_rate":0.04545064,"max_rate":0.046589753784461146,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.655990510083036,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":3.372e-05,"close_rate":3.456511278195488e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":8.4511278195488e-07,"sell_reason":"roi","initial_stop_loss_abs":3.0348e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0348e-05,"stop_loss_ratio":0.1,"min_rate":3.372e-05,"max_rate":3.456511278195488e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037821482602118005,"open_date":"2018-01-11 04:55:00+00:00","close_date":"2018-01-11 05:15:00+00:00","open_rate":0.02644,"close_rate":0.02710265664160401,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0006626566416040071,"sell_reason":"roi","initial_stop_loss_abs":0.023796,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023796,"stop_loss_ratio":0.1,"min_rate":0.02644,"max_rate":0.02710265664160401,"is_open":false,"buy_tag":null,"open_timestamp":1515646500000.0,"close_timestamp":1515647700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011348161597821153,"open_date":"2018-01-11 11:20:00+00:00","close_date":"2018-01-11 12:00:00+00:00","open_rate":0.08812,"close_rate":0.08856170426065162,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004417042606516125,"sell_reason":"roi","initial_stop_loss_abs":0.079308,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079308,"stop_loss_ratio":0.1,"min_rate":0.08812,"max_rate":0.08856170426065162,"is_open":false,"buy_tag":null,"open_timestamp":1515669600000.0,"close_timestamp":1515672000000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037263696923919086,"open_date":"2018-01-11 11:35:00+00:00","close_date":"2018-01-11 12:15:00+00:00","open_rate":0.02683577,"close_rate":0.026970285137844607,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013451513784460897,"sell_reason":"roi","initial_stop_loss_abs":0.024152193,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024152193,"stop_loss_ratio":0.1,"min_rate":0.02683577,"max_rate":0.026970285137844607,"is_open":false,"buy_tag":null,"open_timestamp":1515670500000.0,"close_timestamp":1515672900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.329335230737954,"open_date":"2018-01-11 14:00:00+00:00","close_date":"2018-01-11 14:25:00+00:00","open_rate":4.919e-05,"close_rate":5.04228320802005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.232832080200495e-06,"sell_reason":"roi","initial_stop_loss_abs":4.4271000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4271000000000004e-05,"stop_loss_ratio":0.1,"min_rate":4.919e-05,"max_rate":5.04228320802005e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515679200000.0,"close_timestamp":1515680700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.01138317402960718,"open_date":"2018-01-11 19:25:00+00:00","close_date":"2018-01-11 20:35:00+00:00","open_rate":0.08784896,"close_rate":0.08828930566416039,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":70,"profit_ratio":-0.0,"profit_abs":0.0004403456641603881,"sell_reason":"roi","initial_stop_loss_abs":0.079064064,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079064064,"stop_loss_ratio":0.1,"min_rate":0.08784896,"max_rate":0.08828930566416039,"is_open":false,"buy_tag":null,"open_timestamp":1515698700000.0,"close_timestamp":1515702900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.58863858961802,"open_date":"2018-01-11 22:35:00+00:00","close_date":"2018-01-11 23:30:00+00:00","open_rate":5.105e-05,"close_rate":5.130588972431077e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.558897243107704e-07,"sell_reason":"roi","initial_stop_loss_abs":4.5945e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.5945e-05,"stop_loss_ratio":0.1,"min_rate":5.105e-05,"max_rate":5.130588972431077e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515710100000.0,"close_timestamp":1515713400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.252525252525253,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:25:00+00:00","open_rate":3.96e-05,"close_rate":4.019548872180451e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":5.954887218045116e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5640000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5640000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.96e-05,"max_rate":4.019548872180451e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.66204506065858,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:35:00+00:00","open_rate":2.885e-05,"close_rate":2.899461152882205e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4461152882205115e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5965e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5965e-05,"stop_loss_ratio":0.1,"min_rate":2.885e-05,"max_rate":2.899461152882205e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03780718336483932,"open_date":"2018-01-11 23:30:00+00:00","close_date":"2018-01-12 00:05:00+00:00","open_rate":0.02645,"close_rate":0.026847744360902256,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0003977443609022545,"sell_reason":"roi","initial_stop_loss_abs":0.023805000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023805000000000003,"stop_loss_ratio":0.1,"min_rate":0.02645,"max_rate":0.026847744360902256,"is_open":false,"buy_tag":null,"open_timestamp":1515713400000.0,"close_timestamp":1515715500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020833333333333332,"open_date":"2018-01-11 23:55:00+00:00","close_date":"2018-01-12 01:15:00+00:00","open_rate":0.048,"close_rate":0.04824060150375939,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00024060150375938838,"sell_reason":"roi","initial_stop_loss_abs":0.0432,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0432,"stop_loss_ratio":0.1,"min_rate":0.048,"max_rate":0.04824060150375939,"is_open":false,"buy_tag":null,"open_timestamp":1515714900000.0,"close_timestamp":1515719700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.31287297527707,"open_date":"2018-01-12 21:15:00+00:00","close_date":"2018-01-12 21:40:00+00:00","open_rate":4.692e-05,"close_rate":4.809593984962405e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1759398496240516e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2227999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2227999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.692e-05,"max_rate":4.809593984962405e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515791700000.0,"close_timestamp":1515793200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38915654211062944,"open_date":"2018-01-13 00:55:00+00:00","close_date":"2018-01-13 06:20:00+00:00","open_rate":0.00256966,"close_rate":0.0025825405012531327,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":325,"profit_ratio":-0.0,"profit_abs":1.2880501253132587e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002312694,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002312694,"stop_loss_ratio":0.1,"min_rate":0.00256966,"max_rate":0.0025825405012531327,"is_open":false,"buy_tag":null,"open_timestamp":1515804900000.0,"close_timestamp":1515824400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":15.96933886937081,"open_date":"2018-01-13 10:55:00+00:00","close_date":"2018-01-13 11:35:00+00:00","open_rate":6.262e-05,"close_rate":6.293388471177944e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":3.138847117794446e-07,"sell_reason":"roi","initial_stop_loss_abs":5.6358e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.6358e-05,"stop_loss_ratio":0.1,"min_rate":6.262e-05,"max_rate":6.293388471177944e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515840900000.0,"close_timestamp":1515843300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.141649048625794,"open_date":"2018-01-13 13:05:00+00:00","close_date":"2018-01-15 14:10:00+00:00","open_rate":4.73e-05,"close_rate":4.753709273182957e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":2945,"profit_ratio":0.0,"profit_abs":2.3709273182957117e-07,"sell_reason":"roi","initial_stop_loss_abs":4.257e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.257e-05,"stop_loss_ratio":0.1,"min_rate":4.73e-05,"max_rate":4.753709273182957e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515848700000.0,"close_timestamp":1516025400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.49348507339601,"open_date":"2018-01-13 13:30:00+00:00","close_date":"2018-01-13 14:45:00+00:00","open_rate":6.063e-05,"close_rate":6.0933909774436085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":3.039097744360846e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4567e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4567e-05,"stop_loss_ratio":0.1,"min_rate":6.063e-05,"max_rate":6.0933909774436085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515850200000.0,"close_timestamp":1515854700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.023641941887746,"open_date":"2018-01-13 13:40:00+00:00","close_date":"2018-01-13 23:30:00+00:00","open_rate":0.00011082,"close_rate":0.00011137548872180448,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":590,"profit_ratio":-0.0,"profit_abs":5.554887218044781e-07,"sell_reason":"roi","initial_stop_loss_abs":9.9738e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.9738e-05,"stop_loss_ratio":0.1,"min_rate":0.00011082,"max_rate":0.00011137548872180448,"is_open":false,"buy_tag":null,"open_timestamp":1515850800000.0,"close_timestamp":1515886200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.863406408094438,"open_date":"2018-01-13 15:15:00+00:00","close_date":"2018-01-13 15:55:00+00:00","open_rate":5.93e-05,"close_rate":5.9597243107769415e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.9724310776941686e-07,"sell_reason":"roi","initial_stop_loss_abs":5.337e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.337e-05,"stop_loss_ratio":0.1,"min_rate":5.93e-05,"max_rate":5.9597243107769415e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515856500000.0,"close_timestamp":1515858900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020618543947292404,"open_date":"2018-01-13 16:30:00+00:00","close_date":"2018-01-13 17:10:00+00:00","open_rate":0.04850003,"close_rate":0.04874313791979949,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00024310791979949287,"sell_reason":"roi","initial_stop_loss_abs":0.043650027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.043650027,"stop_loss_ratio":0.1,"min_rate":0.04850003,"max_rate":0.04874313791979949,"is_open":false,"buy_tag":null,"open_timestamp":1515861000000.0,"close_timestamp":1515863400000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010178097365511457,"open_date":"2018-01-13 22:05:00+00:00","close_date":"2018-01-14 06:25:00+00:00","open_rate":0.09825019,"close_rate":0.09874267215538848,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":500,"profit_ratio":-0.0,"profit_abs":0.0004924821553884823,"sell_reason":"roi","initial_stop_loss_abs":0.088425171,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.088425171,"stop_loss_ratio":0.1,"min_rate":0.09825019,"max_rate":0.09874267215538848,"is_open":false,"buy_tag":null,"open_timestamp":1515881100000.0,"close_timestamp":1515911100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.616816218012627,"open_date":"2018-01-14 00:20:00+00:00","close_date":"2018-01-14 22:55:00+00:00","open_rate":6.018e-05,"close_rate":6.048165413533834e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":1355,"profit_ratio":0.0,"profit_abs":3.0165413533833987e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4162e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4162e-05,"stop_loss_ratio":0.1,"min_rate":6.018e-05,"max_rate":6.048165413533834e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515889200000.0,"close_timestamp":1515970500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010246952581919518,"open_date":"2018-01-14 12:45:00+00:00","close_date":"2018-01-14 13:25:00+00:00","open_rate":0.09758999,"close_rate":0.0980791628822055,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004891728822054991,"sell_reason":"roi","initial_stop_loss_abs":0.087830991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.087830991,"stop_loss_ratio":0.1,"min_rate":0.09758999,"max_rate":0.0980791628822055,"is_open":false,"buy_tag":null,"open_timestamp":1515933900000.0,"close_timestamp":1515936300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3215434083601286,"open_date":"2018-01-14 15:30:00+00:00","close_date":"2018-01-14 16:00:00+00:00","open_rate":0.00311,"close_rate":0.0031567669172932328,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.676691729323286e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002799,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002799,"stop_loss_ratio":0.1,"min_rate":0.00311,"max_rate":0.0031567669172932328,"is_open":false,"buy_tag":null,"open_timestamp":1515943800000.0,"close_timestamp":1515945600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.32010140812609433,"open_date":"2018-01-14 20:45:00+00:00","close_date":"2018-01-14 22:15:00+00:00","open_rate":0.00312401,"close_rate":0.003139669197994987,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":-0.0,"profit_abs":1.5659197994987058e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002811609,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002811609,"stop_loss_ratio":0.1,"min_rate":0.00312401,"max_rate":0.003139669197994987,"is_open":false,"buy_tag":null,"open_timestamp":1515962700000.0,"close_timestamp":1515968100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.057247866085791646,"open_date":"2018-01-14 23:35:00+00:00","close_date":"2018-01-15 00:30:00+00:00","open_rate":0.0174679,"close_rate":0.017555458395989976,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":8.755839598997492e-05,"sell_reason":"roi","initial_stop_loss_abs":0.015721110000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.015721110000000003,"stop_loss_ratio":0.1,"min_rate":0.0174679,"max_rate":0.017555458395989976,"is_open":false,"buy_tag":null,"open_timestamp":1515972900000.0,"close_timestamp":1515976200000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.013611282991367997,"open_date":"2018-01-14 23:45:00+00:00","close_date":"2018-01-15 00:25:00+00:00","open_rate":0.07346846,"close_rate":0.07383672295739348,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00036826295739347814,"sell_reason":"roi","initial_stop_loss_abs":0.066121614,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.066121614,"stop_loss_ratio":0.1,"min_rate":0.07346846,"max_rate":0.07383672295739348,"is_open":false,"buy_tag":null,"open_timestamp":1515973500000.0,"close_timestamp":1515975900000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010204706410596568,"open_date":"2018-01-15 02:25:00+00:00","close_date":"2018-01-15 03:05:00+00:00","open_rate":0.097994,"close_rate":0.09848519799498744,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004911979949874384,"sell_reason":"roi","initial_stop_loss_abs":0.0881946,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0881946,"stop_loss_ratio":0.1,"min_rate":0.097994,"max_rate":0.09848519799498744,"is_open":false,"buy_tag":null,"open_timestamp":1515983100000.0,"close_timestamp":1515985500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010353038616834042,"open_date":"2018-01-15 07:20:00+00:00","close_date":"2018-01-15 08:00:00+00:00","open_rate":0.09659,"close_rate":0.09707416040100247,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004841604010024786,"sell_reason":"roi","initial_stop_loss_abs":0.086931,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.086931,"stop_loss_ratio":0.1,"min_rate":0.09659,"max_rate":0.09707416040100247,"is_open":false,"buy_tag":null,"open_timestamp":1516000800000.0,"close_timestamp":1516003200000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.013016921998599,"open_date":"2018-01-15 08:20:00+00:00","close_date":"2018-01-15 08:55:00+00:00","open_rate":9.987e-05,"close_rate":0.00010137180451127818,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":1.501804511278178e-06,"sell_reason":"roi","initial_stop_loss_abs":8.9883e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.9883e-05,"stop_loss_ratio":0.1,"min_rate":9.987e-05,"max_rate":0.00010137180451127818,"is_open":false,"buy_tag":null,"open_timestamp":1516004400000.0,"close_timestamp":1516006500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010537752023511832,"open_date":"2018-01-15 12:10:00+00:00","close_date":"2018-01-16 02:50:00+00:00","open_rate":0.0948969,"close_rate":0.09537257368421052,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":880,"profit_ratio":0.0,"profit_abs":0.0004756736842105175,"sell_reason":"roi","initial_stop_loss_abs":0.08540721000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08540721000000001,"stop_loss_ratio":0.1,"min_rate":0.0948969,"max_rate":0.09537257368421052,"is_open":false,"buy_tag":null,"open_timestamp":1516018200000.0,"close_timestamp":1516071000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014084507042253523,"open_date":"2018-01-15 14:10:00+00:00","close_date":"2018-01-15 17:40:00+00:00","open_rate":0.071,"close_rate":0.07135588972431077,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":210,"profit_ratio":-0.0,"profit_abs":0.00035588972431077615,"sell_reason":"roi","initial_stop_loss_abs":0.0639,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0639,"stop_loss_ratio":0.1,"min_rate":0.071,"max_rate":0.07135588972431077,"is_open":false,"buy_tag":null,"open_timestamp":1516025400000.0,"close_timestamp":1516038000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021736763017766978,"open_date":"2018-01-15 14:30:00+00:00","close_date":"2018-01-15 15:10:00+00:00","open_rate":0.04600501,"close_rate":0.046235611553884705,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00023060155388470588,"sell_reason":"roi","initial_stop_loss_abs":0.041404509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041404509,"stop_loss_ratio":0.1,"min_rate":0.04600501,"max_rate":0.046235611553884705,"is_open":false,"buy_tag":null,"open_timestamp":1516026600000.0,"close_timestamp":1516029000000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.595465140919686,"open_date":"2018-01-15 18:10:00+00:00","close_date":"2018-01-15 19:25:00+00:00","open_rate":9.438e-05,"close_rate":9.485308270676693e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":4.7308270676692514e-07,"sell_reason":"roi","initial_stop_loss_abs":8.4942e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.4942e-05,"stop_loss_ratio":0.1,"min_rate":9.438e-05,"max_rate":9.485308270676693e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516039800000.0,"close_timestamp":1516044300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.032894726021471705,"open_date":"2018-01-15 18:35:00+00:00","close_date":"2018-01-15 19:15:00+00:00","open_rate":0.03040001,"close_rate":0.030552391002506264,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0001523810025062626,"sell_reason":"roi","initial_stop_loss_abs":0.027360009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027360009,"stop_loss_ratio":0.1,"min_rate":0.03040001,"max_rate":0.030552391002506264,"is_open":false,"buy_tag":null,"open_timestamp":1516041300000.0,"close_timestamp":1516043700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.13208840157615,"open_date":"2018-01-15 20:25:00+00:00","close_date":"2018-01-16 08:25:00+00:00","open_rate":5.837e-05,"close_rate":5.2533e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":720,"profit_ratio":-0.10448878,"profit_abs":-5.8369999999999985e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":5.2533e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2533e-05,"stop_loss_ratio":0.1,"min_rate":5.2533e-05,"max_rate":5.837e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516047900000.0,"close_timestamp":1516091100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021722130506560085,"open_date":"2018-01-15 20:40:00+00:00","close_date":"2018-01-15 22:00:00+00:00","open_rate":0.046036,"close_rate":0.04626675689223057,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00023075689223057277,"sell_reason":"roi","initial_stop_loss_abs":0.0414324,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0414324,"stop_loss_ratio":0.1,"min_rate":0.046036,"max_rate":0.04626675689223057,"is_open":false,"buy_tag":null,"open_timestamp":1516048800000.0,"close_timestamp":1516053600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.34861425832316545,"open_date":"2018-01-16 00:30:00+00:00","close_date":"2018-01-16 01:10:00+00:00","open_rate":0.0028685,"close_rate":0.0028828784461152877,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4378446115287727e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00258165,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00258165,"stop_loss_ratio":0.1,"min_rate":0.0028685,"max_rate":0.0028828784461152877,"is_open":false,"buy_tag":null,"open_timestamp":1516062600000.0,"close_timestamp":1516065000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014854967241083492,"open_date":"2018-01-16 01:15:00+00:00","close_date":"2018-01-16 02:35:00+00:00","open_rate":0.06731755,"close_rate":0.0676549813283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":0.0,"profit_abs":0.00033743132832080025,"sell_reason":"roi","initial_stop_loss_abs":0.060585795000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060585795000000005,"stop_loss_ratio":0.1,"min_rate":0.06731755,"max_rate":0.0676549813283208,"is_open":false,"buy_tag":null,"open_timestamp":1516065300000.0,"close_timestamp":1516070100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010848794492804754,"open_date":"2018-01-16 07:45:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":0.09217614,"close_rate":0.09263817578947368,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":0.0004620357894736804,"sell_reason":"roi","initial_stop_loss_abs":0.082958526,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082958526,"stop_loss_ratio":0.1,"min_rate":0.09217614,"max_rate":0.09263817578947368,"is_open":false,"buy_tag":null,"open_timestamp":1516088700000.0,"close_timestamp":1516092000000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06060606060606061,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:55:00+00:00","open_rate":0.0165,"close_rate":0.016913533834586467,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.00041353383458646656,"sell_reason":"roi","initial_stop_loss_abs":0.01485,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01485,"stop_loss_ratio":0.1,"min_rate":0.0165,"max_rate":0.016913533834586467,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":12.57387149503332,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":7.953e-05,"close_rate":8.311781954887218e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":3.587819548872171e-06,"sell_reason":"roi","initial_stop_loss_abs":7.157700000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.157700000000001e-05,"stop_loss_ratio":0.1,"min_rate":7.953e-05,"max_rate":8.311781954887218e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022122914915269236,"open_date":"2018-01-16 08:45:00+00:00","close_date":"2018-01-16 09:50:00+00:00","open_rate":0.045202,"close_rate":0.04542857644110275,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":0.00022657644110275071,"sell_reason":"roi","initial_stop_loss_abs":0.0406818,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0406818,"stop_loss_ratio":0.1,"min_rate":0.045202,"max_rate":0.04542857644110275,"is_open":false,"buy_tag":null,"open_timestamp":1516092300000.0,"close_timestamp":1516096200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.054878048780488,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:45:00+00:00","open_rate":5.248e-05,"close_rate":5.326917293233082e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.891729323308177e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7232e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7232e-05,"stop_loss_ratio":0.1,"min_rate":5.248e-05,"max_rate":5.326917293233082e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516095900000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03457434486802627,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:55:00+00:00","open_rate":0.02892318,"close_rate":0.02906815834586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0001449783458646603,"sell_reason":"roi","initial_stop_loss_abs":0.026030862000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.026030862000000002,"stop_loss_ratio":0.1,"min_rate":0.02892318,"max_rate":0.02906815834586466,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516096500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.38735944164405,"open_date":"2018-01-16 09:50:00+00:00","close_date":"2018-01-16 10:10:00+00:00","open_rate":5.158e-05,"close_rate":5.287273182957392e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.2927318295739246e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6422e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6422e-05,"stop_loss_ratio":0.1,"min_rate":5.158e-05,"max_rate":5.287273182957392e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516096200000.0,"close_timestamp":1516097400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035357778286929785,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:35:00+00:00","open_rate":0.02828232,"close_rate":0.02870761804511278,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042529804511277913,"sell_reason":"roi","initial_stop_loss_abs":0.025454088,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025454088,"stop_loss_ratio":0.1,"min_rate":0.02828232,"max_rate":0.02870761804511278,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516098900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022948496230938982,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:40:00+00:00","open_rate":0.04357584,"close_rate":0.044231115789473675,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0006552757894736777,"sell_reason":"roi","initial_stop_loss_abs":0.039218256,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039218256,"stop_loss_ratio":0.1,"min_rate":0.04357584,"max_rate":0.044231115789473675,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516099200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.64975755315181,"open_date":"2018-01-16 13:45:00+00:00","close_date":"2018-01-16 14:20:00+00:00","open_rate":5.362e-05,"close_rate":5.442631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":8.063157894736843e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8258e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8258e-05,"stop_loss_ratio":0.1,"min_rate":5.362e-05,"max_rate":5.442631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516110300000.0,"close_timestamp":1516112400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.86080724254998,"open_date":"2018-01-16 17:30:00+00:00","close_date":"2018-01-16 18:25:00+00:00","open_rate":5.302e-05,"close_rate":5.328576441102756e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.6576441102756397e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7718e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7718e-05,"stop_loss_ratio":0.1,"min_rate":5.302e-05,"max_rate":5.328576441102756e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516123800000.0,"close_timestamp":1516127100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010952903718828448,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:45:00+00:00","open_rate":0.09129999,"close_rate":0.09267292218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013729321804511196,"sell_reason":"roi","initial_stop_loss_abs":0.082169991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082169991,"stop_loss_ratio":0.1,"min_rate":0.09129999,"max_rate":0.09267292218045112,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516128300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":26.26050420168067,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:35:00+00:00","open_rate":3.808e-05,"close_rate":3.903438596491228e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":9.543859649122774e-07,"sell_reason":"roi","initial_stop_loss_abs":3.4272e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.4272e-05,"stop_loss_ratio":0.1,"min_rate":3.808e-05,"max_rate":3.903438596491228e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516127700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035574376772493324,"open_date":"2018-01-16 19:00:00+00:00","close_date":"2018-01-16 19:30:00+00:00","open_rate":0.02811012,"close_rate":0.028532828571428567,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042270857142856846,"sell_reason":"roi","initial_stop_loss_abs":0.025299108,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025299108,"stop_loss_ratio":0.1,"min_rate":0.02811012,"max_rate":0.028532828571428567,"is_open":false,"buy_tag":null,"open_timestamp":1516129200000.0,"close_timestamp":1516131000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.387028357567759,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":0.00258379,"close_rate":0.002325411,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.10448878,"profit_abs":-0.000258379,"sell_reason":"stop_loss","initial_stop_loss_abs":0.002325411,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002325411,"stop_loss_ratio":0.1,"min_rate":0.002325411,"max_rate":0.00258379,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516141500000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":39.07776475185619,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":2.559e-05,"close_rate":2.3031e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.10448878,"profit_abs":-2.5590000000000004e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":2.3031e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.3031e-05,"stop_loss_ratio":0.1,"min_rate":2.3031e-05,"max_rate":2.559e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516142700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":13.123359580052494,"open_date":"2018-01-16 21:35:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":7.62e-05,"close_rate":6.858e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.10448878,"profit_abs":-7.619999999999998e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":6.858e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.858e-05,"stop_loss_ratio":0.1,"min_rate":6.858e-05,"max_rate":7.62e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516138500000.0,"close_timestamp":1516141500000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4350777048780912,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:35:00+00:00","open_rate":0.00229844,"close_rate":0.002402129022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010368902255639091,"sell_reason":"roi","initial_stop_loss_abs":0.0020685960000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0020685960000000002,"stop_loss_ratio":0.1,"min_rate":0.00229844,"max_rate":0.002402129022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06622516556291391,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:40:00+00:00","open_rate":0.0151,"close_rate":0.015781203007518795,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":0.0006812030075187946,"sell_reason":"roi","initial_stop_loss_abs":0.013590000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.013590000000000001,"stop_loss_ratio":0.1,"min_rate":0.0151,"max_rate":0.015781203007518795,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.42431134269081283,"open_date":"2018-01-16 22:40:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":0.00235676,"close_rate":0.00246308,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010632000000000003,"sell_reason":"roi","initial_stop_loss_abs":0.002121084,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002121084,"stop_loss_ratio":0.1,"min_rate":0.00235676,"max_rate":0.00246308,"is_open":false,"buy_tag":null,"open_timestamp":1516142400000.0,"close_timestamp":1516142700000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01585559988076589,"open_date":"2018-01-16 22:45:00+00:00","close_date":"2018-01-16 23:05:00+00:00","open_rate":0.0630692,"close_rate":0.06464988170426066,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0015806817042606502,"sell_reason":"roi","initial_stop_loss_abs":0.056762280000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.056762280000000005,"stop_loss_ratio":0.1,"min_rate":0.0630692,"max_rate":0.06464988170426066,"is_open":false,"buy_tag":null,"open_timestamp":1516142700000.0,"close_timestamp":1516143900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":45.45454545454545,"open_date":"2018-01-16 22:50:00+00:00","close_date":"2018-01-16 22:55:00+00:00","open_rate":2.2e-05,"close_rate":2.299248120300751e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":9.924812030075114e-07,"sell_reason":"roi","initial_stop_loss_abs":1.98e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":1.98e-05,"stop_loss_ratio":0.1,"min_rate":2.2e-05,"max_rate":2.299248120300751e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516143000000.0,"close_timestamp":1516143300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.10454362685967,"open_date":"2018-01-17 03:30:00+00:00","close_date":"2018-01-17 04:00:00+00:00","open_rate":4.974e-05,"close_rate":5.048796992481203e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.479699248120277e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4766000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4766000000000005e-05,"stop_loss_ratio":0.1,"min_rate":4.974e-05,"max_rate":5.048796992481203e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516159800000.0,"close_timestamp":1516161600000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":14.068655036578503,"open_date":"2018-01-17 03:55:00+00:00","close_date":"2018-01-17 04:15:00+00:00","open_rate":7.108e-05,"close_rate":7.28614536340852e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.7814536340851996e-06,"sell_reason":"roi","initial_stop_loss_abs":6.3972e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.3972e-05,"stop_loss_ratio":0.1,"min_rate":7.108e-05,"max_rate":7.28614536340852e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516161300000.0,"close_timestamp":1516162500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.0231107002542177,"open_date":"2018-01-17 09:35:00+00:00","close_date":"2018-01-17 10:15:00+00:00","open_rate":0.04327,"close_rate":0.04348689223057644,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002168922305764362,"sell_reason":"roi","initial_stop_loss_abs":0.038943000000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.038943000000000005,"stop_loss_ratio":0.1,"min_rate":0.04327,"max_rate":0.04348689223057644,"is_open":false,"buy_tag":null,"open_timestamp":1516181700000.0,"close_timestamp":1516184100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.012007204322593,"open_date":"2018-01-17 10:20:00+00:00","close_date":"2018-01-17 17:00:00+00:00","open_rate":4.997e-05,"close_rate":5.022047619047618e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":400,"profit_ratio":-0.0,"profit_abs":2.504761904761831e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4973e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4973e-05,"stop_loss_ratio":0.1,"min_rate":4.997e-05,"max_rate":5.022047619047618e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516184400000.0,"close_timestamp":1516208400000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014626687444363738,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:25:00+00:00","open_rate":0.06836818,"close_rate":0.06871087764411027,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00034269764411026804,"sell_reason":"roi","initial_stop_loss_abs":0.061531362,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061531362,"stop_loss_ratio":0.1,"min_rate":0.06836818,"max_rate":0.06871087764411027,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516188300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.548209366391184,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:10:00+00:00","open_rate":3.63e-05,"close_rate":3.648195488721804e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.8195488721804031e-07,"sell_reason":"roi","initial_stop_loss_abs":3.2670000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2670000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.63e-05,"max_rate":3.648195488721804e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516187400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03558718861209965,"open_date":"2018-01-17 12:30:00+00:00","close_date":"2018-01-17 22:05:00+00:00","open_rate":0.0281,"close_rate":0.02824085213032581,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":575,"profit_ratio":-0.0,"profit_abs":0.0001408521303258095,"sell_reason":"roi","initial_stop_loss_abs":0.02529,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.02529,"stop_loss_ratio":0.1,"min_rate":0.0281,"max_rate":0.02824085213032581,"is_open":false,"buy_tag":null,"open_timestamp":1516192200000.0,"close_timestamp":1516226700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011559355963546878,"open_date":"2018-01-17 12:35:00+00:00","close_date":"2018-01-17 16:55:00+00:00","open_rate":0.08651001,"close_rate":0.08694364413533832,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":-0.0,"profit_abs":0.00043363413533832607,"sell_reason":"roi","initial_stop_loss_abs":0.077859009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077859009,"stop_loss_ratio":0.1,"min_rate":0.08651001,"max_rate":0.08694364413533832,"is_open":false,"buy_tag":null,"open_timestamp":1516192500000.0,"close_timestamp":1516208100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.752529735487308,"open_date":"2018-01-18 05:00:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":5.633e-05,"close_rate":5.6612355889724306e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.8235588972430847e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0697e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0697e-05,"stop_loss_ratio":0.1,"min_rate":5.633e-05,"max_rate":5.6612355889724306e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516251600000.0,"close_timestamp":1516254900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01430923457900944,"open_date":"2018-01-18 05:20:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":0.06988494,"close_rate":0.07093584135338346,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0010509013533834544,"sell_reason":"roi","initial_stop_loss_abs":0.06289644600000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06289644600000001,"stop_loss_ratio":0.1,"min_rate":0.06988494,"max_rate":0.07093584135338346,"is_open":false,"buy_tag":null,"open_timestamp":1516252800000.0,"close_timestamp":1516254900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.034265103697024,"open_date":"2018-01-18 07:35:00+00:00","close_date":"2018-01-18 08:15:00+00:00","open_rate":5.545e-05,"close_rate":5.572794486215538e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.779448621553787e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9905e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9905e-05,"stop_loss_ratio":0.1,"min_rate":5.545e-05,"max_rate":5.572794486215538e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516260900000.0,"close_timestamp":1516263300000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06121723118136401,"open_date":"2018-01-18 09:00:00+00:00","close_date":"2018-01-18 09:40:00+00:00","open_rate":0.01633527,"close_rate":0.016417151052631574,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":8.188105263157511e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014701743,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014701743,"stop_loss_ratio":0.1,"min_rate":0.01633527,"max_rate":0.016417151052631574,"is_open":false,"buy_tag":null,"open_timestamp":1516266000000.0,"close_timestamp":1516268400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3707356136045141,"open_date":"2018-01-18 16:40:00+00:00","close_date":"2018-01-18 17:20:00+00:00","open_rate":0.00269734,"close_rate":0.002710860501253133,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.3520501253133123e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002427606,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002427606,"stop_loss_ratio":0.1,"min_rate":0.00269734,"max_rate":0.002710860501253133,"is_open":false,"buy_tag":null,"open_timestamp":1516293600000.0,"close_timestamp":1516296000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.346368715083802,"open_date":"2018-01-18 18:05:00+00:00","close_date":"2018-01-18 18:30:00+00:00","open_rate":4.475e-05,"close_rate":4.587155388471177e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1215538847117757e-06,"sell_reason":"roi","initial_stop_loss_abs":4.0274999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.0274999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.475e-05,"max_rate":4.587155388471177e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516298700000.0,"close_timestamp":1516300200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":35.842293906810035,"open_date":"2018-01-18 18:25:00+00:00","close_date":"2018-01-18 18:55:00+00:00","open_rate":2.79e-05,"close_rate":2.8319548872180444e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.1954887218044365e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5110000000000002e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5110000000000002e-05,"stop_loss_ratio":0.1,"min_rate":2.79e-05,"max_rate":2.8319548872180444e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516299900000.0,"close_timestamp":1516301700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022525942001105574,"open_date":"2018-01-18 20:10:00+00:00","close_date":"2018-01-18 20:50:00+00:00","open_rate":0.04439326,"close_rate":0.04461578260651629,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00022252260651629135,"sell_reason":"roi","initial_stop_loss_abs":0.039953933999999997,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039953933999999997,"stop_loss_ratio":0.1,"min_rate":0.04439326,"max_rate":0.04461578260651629,"is_open":false,"buy_tag":null,"open_timestamp":1516306200000.0,"close_timestamp":1516308600000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.271714922048996,"open_date":"2018-01-18 21:30:00+00:00","close_date":"2018-01-19 00:35:00+00:00","open_rate":4.49e-05,"close_rate":4.51250626566416e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":185,"profit_ratio":0.0,"profit_abs":2.2506265664159932e-07,"sell_reason":"roi","initial_stop_loss_abs":4.041e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.041e-05,"stop_loss_ratio":0.1,"min_rate":4.49e-05,"max_rate":4.51250626566416e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516311000000.0,"close_timestamp":1516322100000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03502626970227671,"open_date":"2018-01-18 21:55:00+00:00","close_date":"2018-01-19 05:05:00+00:00","open_rate":0.02855,"close_rate":0.028693107769423555,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":430,"profit_ratio":-0.0,"profit_abs":0.00014310776942355607,"sell_reason":"roi","initial_stop_loss_abs":0.025695,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025695,"stop_loss_ratio":0.1,"min_rate":0.02855,"max_rate":0.028693107769423555,"is_open":false,"buy_tag":null,"open_timestamp":1516312500000.0,"close_timestamp":1516338300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.25327812284334,"open_date":"2018-01-18 22:10:00+00:00","close_date":"2018-01-18 22:50:00+00:00","open_rate":5.796e-05,"close_rate":5.8250526315789473e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.905263157894727e-07,"sell_reason":"roi","initial_stop_loss_abs":5.2164000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2164000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.796e-05,"max_rate":5.8250526315789473e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516313400000.0,"close_timestamp":1516315800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02303975994413319,"open_date":"2018-01-18 23:50:00+00:00","close_date":"2018-01-19 00:30:00+00:00","open_rate":0.04340323,"close_rate":0.04362079005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0002175600501253122,"sell_reason":"roi","initial_stop_loss_abs":0.039062907,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039062907,"stop_loss_ratio":0.1,"min_rate":0.04340323,"max_rate":0.04362079005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516319400000.0,"close_timestamp":1516321800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02244943545282195,"open_date":"2018-01-19 16:45:00+00:00","close_date":"2018-01-19 17:35:00+00:00","open_rate":0.04454455,"close_rate":0.04476783095238095,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0002232809523809512,"sell_reason":"roi","initial_stop_loss_abs":0.040090095000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040090095000000006,"stop_loss_ratio":0.1,"min_rate":0.04454455,"max_rate":0.04476783095238095,"is_open":false,"buy_tag":null,"open_timestamp":1516380300000.0,"close_timestamp":1516383300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.793594306049823,"open_date":"2018-01-19 17:15:00+00:00","close_date":"2018-01-19 19:55:00+00:00","open_rate":5.62e-05,"close_rate":5.648170426065162e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":160,"profit_ratio":-0.0,"profit_abs":2.817042606516199e-07,"sell_reason":"roi","initial_stop_loss_abs":5.058e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.058e-05,"stop_loss_ratio":0.1,"min_rate":5.62e-05,"max_rate":5.648170426065162e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382100000.0,"close_timestamp":1516391700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.046784973496194,"open_date":"2018-01-19 17:20:00+00:00","close_date":"2018-01-19 20:15:00+00:00","open_rate":4.339e-05,"close_rate":4.360749373433584e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.174937343358337e-07,"sell_reason":"roi","initial_stop_loss_abs":3.9051e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.9051e-05,"stop_loss_ratio":0.1,"min_rate":4.339e-05,"max_rate":4.360749373433584e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382400000.0,"close_timestamp":1516392900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.910802775024777,"open_date":"2018-01-20 04:45:00+00:00","close_date":"2018-01-20 17:35:00+00:00","open_rate":0.0001009,"close_rate":0.00010140576441102755,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":770,"profit_ratio":0.0,"profit_abs":5.057644110275549e-07,"sell_reason":"roi","initial_stop_loss_abs":9.081e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.081e-05,"stop_loss_ratio":0.1,"min_rate":0.0001009,"max_rate":0.00010140576441102755,"is_open":false,"buy_tag":null,"open_timestamp":1516423500000.0,"close_timestamp":1516469700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3696789338459548,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 15:15:00+00:00","open_rate":0.00270505,"close_rate":0.002718609147869674,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":625,"profit_ratio":-0.0,"profit_abs":1.3559147869673764e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002434545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002434545,"stop_loss_ratio":0.1,"min_rate":0.00270505,"max_rate":0.002718609147869674,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516461300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.033333311111125925,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 07:00:00+00:00","open_rate":0.03000002,"close_rate":0.030150396040100245,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":0.00015037604010024672,"sell_reason":"roi","initial_stop_loss_abs":0.027000018,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027000018,"stop_loss_ratio":0.1,"min_rate":0.03000002,"max_rate":0.030150396040100245,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516431600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.315018315018317,"open_date":"2018-01-20 09:00:00+00:00","close_date":"2018-01-20 09:40:00+00:00","open_rate":5.46e-05,"close_rate":5.4873684210526304e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.736842105263053e-07,"sell_reason":"roi","initial_stop_loss_abs":4.914e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.914e-05,"stop_loss_ratio":0.1,"min_rate":5.46e-05,"max_rate":5.4873684210526304e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516438800000.0,"close_timestamp":1516441200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03244412634781012,"open_date":"2018-01-20 18:25:00+00:00","close_date":"2018-01-25 03:50:00+00:00","open_rate":0.03082222,"close_rate":0.027739998,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":6325,"profit_ratio":-0.10448878,"profit_abs":-0.0030822220000000025,"sell_reason":"stop_loss","initial_stop_loss_abs":0.027739998000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027739998000000002,"stop_loss_ratio":0.1,"min_rate":0.027739998,"max_rate":0.03082222,"is_open":false,"buy_tag":null,"open_timestamp":1516472700000.0,"close_timestamp":1516852200000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011148273260677065,"open_date":"2018-01-20 22:25:00+00:00","close_date":"2018-01-20 23:15:00+00:00","open_rate":0.08969999,"close_rate":0.09014961401002504,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":0.00044962401002504593,"sell_reason":"roi","initial_stop_loss_abs":0.080729991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080729991,"stop_loss_ratio":0.1,"min_rate":0.08969999,"max_rate":0.09014961401002504,"is_open":false,"buy_tag":null,"open_timestamp":1516487100000.0,"close_timestamp":1516490100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06125570520324337,"open_date":"2018-01-21 02:50:00+00:00","close_date":"2018-01-21 14:30:00+00:00","open_rate":0.01632501,"close_rate":0.01640683962406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":700,"profit_ratio":0.0,"profit_abs":8.182962406014932e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014692509000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014692509000000001,"stop_loss_ratio":0.1,"min_rate":0.01632501,"max_rate":0.01640683962406015,"is_open":false,"buy_tag":null,"open_timestamp":1516503000000.0,"close_timestamp":1516545000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01417675579120474,"open_date":"2018-01-21 10:20:00+00:00","close_date":"2018-01-21 11:00:00+00:00","open_rate":0.070538,"close_rate":0.07089157393483708,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00035357393483707866,"sell_reason":"roi","initial_stop_loss_abs":0.0634842,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0634842,"stop_loss_ratio":0.1,"min_rate":0.070538,"max_rate":0.07089157393483708,"is_open":false,"buy_tag":null,"open_timestamp":1516530000000.0,"close_timestamp":1516532400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.864365214110546,"open_date":"2018-01-21 15:50:00+00:00","close_date":"2018-01-21 18:45:00+00:00","open_rate":5.301e-05,"close_rate":5.327571428571427e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.657142857142672e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7709e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7709e-05,"stop_loss_ratio":0.1,"min_rate":5.301e-05,"max_rate":5.327571428571427e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516549800000.0,"close_timestamp":1516560300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.284450063211125,"open_date":"2018-01-21 16:20:00+00:00","close_date":"2018-01-21 17:00:00+00:00","open_rate":3.955e-05,"close_rate":3.9748245614035085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.9824561403508552e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5595e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5595e-05,"stop_loss_ratio":0.1,"min_rate":3.955e-05,"max_rate":3.9748245614035085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516551600000.0,"close_timestamp":1516554000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38683971296493297,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:45:00+00:00","open_rate":0.00258505,"close_rate":0.002623922932330827,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.8872932330826816e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326545,"stop_loss_ratio":0.1,"min_rate":0.00258505,"max_rate":0.002623922932330827,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.621316935690498,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:55:00+00:00","open_rate":3.903e-05,"close_rate":3.922563909774435e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.9563909774435151e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5127e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5127e-05,"stop_loss_ratio":0.1,"min_rate":3.903e-05,"max_rate":3.922563909774435e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.098548510313215,"open_date":"2018-01-22 00:35:00+00:00","close_date":"2018-01-22 10:35:00+00:00","open_rate":5.236e-05,"close_rate":5.262245614035087e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":600,"profit_ratio":-0.0,"profit_abs":2.624561403508717e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7124e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7124e-05,"stop_loss_ratio":0.1,"min_rate":5.236e-05,"max_rate":5.262245614035087e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516581300000.0,"close_timestamp":1516617300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.076650420912717,"open_date":"2018-01-22 01:30:00+00:00","close_date":"2018-01-22 02:10:00+00:00","open_rate":9.028e-05,"close_rate":9.07325313283208e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":4.5253132832080657e-07,"sell_reason":"roi","initial_stop_loss_abs":8.1252e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.1252e-05,"stop_loss_ratio":0.1,"min_rate":9.028e-05,"max_rate":9.07325313283208e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516584600000.0,"close_timestamp":1516587000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3721622627465575,"open_date":"2018-01-22 12:25:00+00:00","close_date":"2018-01-22 14:35:00+00:00","open_rate":0.002687,"close_rate":0.002700468671679198,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":1.3468671679197925e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0024183000000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0024183000000000004,"stop_loss_ratio":0.1,"min_rate":0.002687,"max_rate":0.002700468671679198,"is_open":false,"buy_tag":null,"open_timestamp":1516623900000.0,"close_timestamp":1516631700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.99232245681382,"open_date":"2018-01-22 13:15:00+00:00","close_date":"2018-01-22 13:55:00+00:00","open_rate":4.168e-05,"close_rate":4.188892230576441e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.0892230576441054e-07,"sell_reason":"roi","initial_stop_loss_abs":3.7512e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.7512e-05,"stop_loss_ratio":0.1,"min_rate":4.168e-05,"max_rate":4.188892230576441e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516626900000.0,"close_timestamp":1516629300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.336583153837434,"open_date":"2018-01-22 14:00:00+00:00","close_date":"2018-01-22 14:30:00+00:00","open_rate":8.821e-05,"close_rate":8.953646616541353e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":1.326466165413529e-06,"sell_reason":"roi","initial_stop_loss_abs":7.9389e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.9389e-05,"stop_loss_ratio":0.1,"min_rate":8.821e-05,"max_rate":8.953646616541353e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516629600000.0,"close_timestamp":1516631400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.334880123743233,"open_date":"2018-01-22 15:55:00+00:00","close_date":"2018-01-22 16:40:00+00:00","open_rate":5.172e-05,"close_rate":5.1979248120300745e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.592481203007459e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6548e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6548e-05,"stop_loss_ratio":0.1,"min_rate":5.172e-05,"max_rate":5.1979248120300745e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516636500000.0,"close_timestamp":1516639200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.04692663582287,"open_date":"2018-01-22 16:05:00+00:00","close_date":"2018-01-22 16:25:00+00:00","open_rate":3.026e-05,"close_rate":3.101839598997494e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":7.5839598997494e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7234e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7234e-05,"stop_loss_ratio":0.1,"min_rate":3.026e-05,"max_rate":3.101839598997494e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516637100000.0,"close_timestamp":1516638300000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014156285390713478,"open_date":"2018-01-22 19:50:00+00:00","close_date":"2018-01-23 00:10:00+00:00","open_rate":0.07064,"close_rate":0.07099408521303258,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":0.0,"profit_abs":0.00035408521303258167,"sell_reason":"roi","initial_stop_loss_abs":0.063576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.063576,"stop_loss_ratio":0.1,"min_rate":0.07064,"max_rate":0.07099408521303258,"is_open":false,"buy_tag":null,"open_timestamp":1516650600000.0,"close_timestamp":1516666200000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06080938507725528,"open_date":"2018-01-22 21:25:00+00:00","close_date":"2018-01-22 22:05:00+00:00","open_rate":0.01644483,"close_rate":0.01652726022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.243022556390922e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014800347,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014800347,"stop_loss_ratio":0.1,"min_rate":0.01644483,"max_rate":0.01652726022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516656300000.0,"close_timestamp":1516658700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.08935580697299,"open_date":"2018-01-23 00:05:00+00:00","close_date":"2018-01-23 00:35:00+00:00","open_rate":4.331e-05,"close_rate":4.3961278195488714e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":6.512781954887175e-07,"sell_reason":"roi","initial_stop_loss_abs":3.8979e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.8979e-05,"stop_loss_ratio":0.1,"min_rate":4.331e-05,"max_rate":4.3961278195488714e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516665900000.0,"close_timestamp":1516667700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.250000000000004,"open_date":"2018-01-23 01:50:00+00:00","close_date":"2018-01-23 02:15:00+00:00","open_rate":3.2e-05,"close_rate":3.2802005012531326e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":8.020050125313278e-07,"sell_reason":"roi","initial_stop_loss_abs":2.88e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.88e-05,"stop_loss_ratio":0.1,"min_rate":3.2e-05,"max_rate":3.2802005012531326e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516672200000.0,"close_timestamp":1516673700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010907854156754155,"open_date":"2018-01-23 04:25:00+00:00","close_date":"2018-01-23 05:15:00+00:00","open_rate":0.09167706,"close_rate":0.09213659413533835,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0004595341353383492,"sell_reason":"roi","initial_stop_loss_abs":0.08250935400000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08250935400000001,"stop_loss_ratio":0.1,"min_rate":0.09167706,"max_rate":0.09213659413533835,"is_open":false,"buy_tag":null,"open_timestamp":1516681500000.0,"close_timestamp":1516684500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014440474918339115,"open_date":"2018-01-23 07:35:00+00:00","close_date":"2018-01-23 09:00:00+00:00","open_rate":0.0692498,"close_rate":0.06959691679197995,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":0.0,"profit_abs":0.0003471167919799484,"sell_reason":"roi","initial_stop_loss_abs":0.06232482,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06232482,"stop_loss_ratio":0.1,"min_rate":0.0692498,"max_rate":0.06959691679197995,"is_open":false,"buy_tag":null,"open_timestamp":1516692900000.0,"close_timestamp":1516698000000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.426775612822127,"open_date":"2018-01-23 10:50:00+00:00","close_date":"2018-01-23 13:05:00+00:00","open_rate":3.182e-05,"close_rate":3.197949874686716e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":135,"profit_ratio":0.0,"profit_abs":1.594987468671663e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8638e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8638e-05,"stop_loss_ratio":0.1,"min_rate":3.182e-05,"max_rate":3.197949874686716e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516704600000.0,"close_timestamp":1516712700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024461839530332683,"open_date":"2018-01-23 11:05:00+00:00","close_date":"2018-01-23 16:05:00+00:00","open_rate":0.04088,"close_rate":0.04108491228070175,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":300,"profit_ratio":-0.0,"profit_abs":0.0002049122807017481,"sell_reason":"roi","initial_stop_loss_abs":0.036792,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036792,"stop_loss_ratio":0.1,"min_rate":0.04088,"max_rate":0.04108491228070175,"is_open":false,"buy_tag":null,"open_timestamp":1516705500000.0,"close_timestamp":1516723500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.417475728155342,"open_date":"2018-01-23 14:55:00+00:00","close_date":"2018-01-23 15:35:00+00:00","open_rate":5.15e-05,"close_rate":5.175814536340851e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5814536340851513e-07,"sell_reason":"roi","initial_stop_loss_abs":4.635e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.635e-05,"stop_loss_ratio":0.1,"min_rate":5.15e-05,"max_rate":5.175814536340851e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516719300000.0,"close_timestamp":1516721700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011023294646713328,"open_date":"2018-01-23 16:35:00+00:00","close_date":"2018-01-24 00:05:00+00:00","open_rate":0.09071698,"close_rate":0.09117170170426064,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":450,"profit_ratio":0.0,"profit_abs":0.00045472170426064107,"sell_reason":"roi","initial_stop_loss_abs":0.081645282,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.081645282,"stop_loss_ratio":0.1,"min_rate":0.09071698,"max_rate":0.09117170170426064,"is_open":false,"buy_tag":null,"open_timestamp":1516725300000.0,"close_timestamp":1516752300000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.969309462915604,"open_date":"2018-01-23 17:25:00+00:00","close_date":"2018-01-23 18:45:00+00:00","open_rate":3.128e-05,"close_rate":3.1436791979949865e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":1.5679197994986587e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8152e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8152e-05,"stop_loss_ratio":0.1,"min_rate":3.128e-05,"max_rate":3.1436791979949865e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516728300000.0,"close_timestamp":1516733100000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.465724751439037,"open_date":"2018-01-23 20:15:00+00:00","close_date":"2018-01-23 22:00:00+00:00","open_rate":9.555e-05,"close_rate":9.602894736842104e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":4.789473684210343e-07,"sell_reason":"roi","initial_stop_loss_abs":8.5995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.5995e-05,"stop_loss_ratio":0.1,"min_rate":9.555e-05,"max_rate":9.602894736842104e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516738500000.0,"close_timestamp":1516744800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02450979791426522,"open_date":"2018-01-23 22:30:00+00:00","close_date":"2018-01-23 23:10:00+00:00","open_rate":0.04080001,"close_rate":0.0410045213283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00020451132832080554,"sell_reason":"roi","initial_stop_loss_abs":0.036720009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036720009,"stop_loss_ratio":0.1,"min_rate":0.04080001,"max_rate":0.0410045213283208,"is_open":false,"buy_tag":null,"open_timestamp":1516746600000.0,"close_timestamp":1516749000000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.368584156498162,"open_date":"2018-01-23 23:50:00+00:00","close_date":"2018-01-24 03:35:00+00:00","open_rate":5.163e-05,"close_rate":5.18887969924812e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":225,"profit_ratio":-0.0,"profit_abs":2.587969924812037e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6467e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6467e-05,"stop_loss_ratio":0.1,"min_rate":5.163e-05,"max_rate":5.18887969924812e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516751400000.0,"close_timestamp":1516764900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024747691102289384,"open_date":"2018-01-24 00:20:00+00:00","close_date":"2018-01-24 01:50:00+00:00","open_rate":0.04040781,"close_rate":0.04061035541353383,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":0.0,"profit_abs":0.0002025454135338306,"sell_reason":"roi","initial_stop_loss_abs":0.036367029,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036367029,"stop_loss_ratio":0.1,"min_rate":0.04040781,"max_rate":0.04061035541353383,"is_open":false,"buy_tag":null,"open_timestamp":1516753200000.0,"close_timestamp":1516758600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.485580670303975,"open_date":"2018-01-24 06:45:00+00:00","close_date":"2018-01-24 07:25:00+00:00","open_rate":5.132e-05,"close_rate":5.157724310776942e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5724310776941724e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6188000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6188000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.132e-05,"max_rate":5.157724310776942e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516776300000.0,"close_timestamp":1516778700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.23816852635629,"open_date":"2018-01-24 14:15:00+00:00","close_date":"2018-01-24 14:25:00+00:00","open_rate":5.198e-05,"close_rate":5.432496240601503e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":2.344962406015033e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6782e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6782e-05,"stop_loss_ratio":0.1,"min_rate":5.198e-05,"max_rate":5.432496240601503e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516803300000.0,"close_timestamp":1516803900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.74394237066143,"open_date":"2018-01-24 14:50:00+00:00","close_date":"2018-01-24 16:35:00+00:00","open_rate":3.054e-05,"close_rate":3.069308270676692e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":1.5308270676691466e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7486000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7486000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.054e-05,"max_rate":3.069308270676692e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516805400000.0,"close_timestamp":1516811700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.795638562020944,"open_date":"2018-01-24 15:10:00+00:00","close_date":"2018-01-24 16:15:00+00:00","open_rate":9.263e-05,"close_rate":9.309431077694236e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":0.0,"profit_abs":4.6431077694236234e-07,"sell_reason":"roi","initial_stop_loss_abs":8.3367e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.3367e-05,"stop_loss_ratio":0.1,"min_rate":9.263e-05,"max_rate":9.309431077694236e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516806600000.0,"close_timestamp":1516810500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.13565469713457,"open_date":"2018-01-24 22:40:00+00:00","close_date":"2018-01-24 23:25:00+00:00","open_rate":5.514e-05,"close_rate":5.54163909774436e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.7639097744360576e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9625999999999995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9625999999999995e-05,"stop_loss_ratio":0.1,"min_rate":5.514e-05,"max_rate":5.54163909774436e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516833600000.0,"close_timestamp":1516836300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":20.3210729526519,"open_date":"2018-01-25 00:50:00+00:00","close_date":"2018-01-25 01:30:00+00:00","open_rate":4.921e-05,"close_rate":4.9456666666666664e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.4666666666666543e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4289e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4289e-05,"stop_loss_ratio":0.1,"min_rate":4.921e-05,"max_rate":4.9456666666666664e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516841400000.0,"close_timestamp":1516843800000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38461538461538464,"open_date":"2018-01-25 08:15:00+00:00","close_date":"2018-01-25 12:15:00+00:00","open_rate":0.0026,"close_rate":0.002613032581453634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":240,"profit_ratio":0.0,"profit_abs":1.3032581453634e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00234,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00234,"stop_loss_ratio":0.1,"min_rate":0.0026,"max_rate":0.002613032581453634,"is_open":false,"buy_tag":null,"open_timestamp":1516868100000.0,"close_timestamp":1516882500000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03571593119825878,"open_date":"2018-01-25 10:25:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":0.02799871,"close_rate":0.028139054411027563,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":350,"profit_ratio":-0.0,"profit_abs":0.00014034441102756326,"sell_reason":"roi","initial_stop_loss_abs":0.025198839,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025198839,"stop_loss_ratio":0.1,"min_rate":0.02799871,"max_rate":0.028139054411027563,"is_open":false,"buy_tag":null,"open_timestamp":1516875900000.0,"close_timestamp":1516896900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024516401717913302,"open_date":"2018-01-25 11:00:00+00:00","close_date":"2018-01-25 11:45:00+00:00","open_rate":0.04078902,"close_rate":0.0409934762406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020445624060149575,"sell_reason":"roi","initial_stop_loss_abs":0.036710118,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036710118,"stop_loss_ratio":0.1,"min_rate":0.04078902,"max_rate":0.0409934762406015,"is_open":false,"buy_tag":null,"open_timestamp":1516878000000.0,"close_timestamp":1516880700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.602076124567475,"open_date":"2018-01-25 13:05:00+00:00","close_date":"2018-01-25 13:45:00+00:00","open_rate":2.89e-05,"close_rate":2.904486215538847e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4486215538846723e-07,"sell_reason":"roi","initial_stop_loss_abs":2.601e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.601e-05,"stop_loss_ratio":0.1,"min_rate":2.89e-05,"max_rate":2.904486215538847e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516885500000.0,"close_timestamp":1516887900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02432912439481303,"open_date":"2018-01-25 13:20:00+00:00","close_date":"2018-01-25 14:05:00+00:00","open_rate":0.041103,"close_rate":0.04130903007518797,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020603007518796984,"sell_reason":"roi","initial_stop_loss_abs":0.0369927,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0369927,"stop_loss_ratio":0.1,"min_rate":0.041103,"max_rate":0.04130903007518797,"is_open":false,"buy_tag":null,"open_timestamp":1516886400000.0,"close_timestamp":1516889100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.422991893883566,"open_date":"2018-01-25 15:45:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":5.428e-05,"close_rate":5.509624060150376e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.162406015037611e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8852000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8852000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.428e-05,"max_rate":5.509624060150376e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516895100000.0,"close_timestamp":1516896900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.47063169560399,"open_date":"2018-01-25 17:45:00+00:00","close_date":"2018-01-25 23:15:00+00:00","open_rate":5.414e-05,"close_rate":5.441137844611528e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":330,"profit_ratio":-0.0,"profit_abs":2.713784461152774e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8726e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8726e-05,"stop_loss_ratio":0.1,"min_rate":5.414e-05,"max_rate":5.441137844611528e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516902300000.0,"close_timestamp":1516922100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024150056861308878,"open_date":"2018-01-25 21:15:00+00:00","close_date":"2018-01-25 21:55:00+00:00","open_rate":0.04140777,"close_rate":0.0416153277443609,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002075577443608964,"sell_reason":"roi","initial_stop_loss_abs":0.037266993000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.037266993000000005,"stop_loss_ratio":0.1,"min_rate":0.04140777,"max_rate":0.0416153277443609,"is_open":false,"buy_tag":null,"open_timestamp":1516914900000.0,"close_timestamp":1516917300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3932224183965176,"open_date":"2018-01-26 02:05:00+00:00","close_date":"2018-01-26 02:45:00+00:00","open_rate":0.00254309,"close_rate":0.002555837318295739,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.2747318295739177e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002288781,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002288781,"stop_loss_ratio":0.1,"min_rate":0.00254309,"max_rate":0.002555837318295739,"is_open":false,"buy_tag":null,"open_timestamp":1516932300000.0,"close_timestamp":1516934700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.834849295523455,"open_date":"2018-01-26 02:55:00+00:00","close_date":"2018-01-26 15:10:00+00:00","open_rate":5.607e-05,"close_rate":5.6351052631578935e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":735,"profit_ratio":-0.0,"profit_abs":2.810526315789381e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0463e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0463e-05,"stop_loss_ratio":0.1,"min_rate":5.607e-05,"max_rate":5.6351052631578935e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516935300000.0,"close_timestamp":1516979400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.39400171784748983,"open_date":"2018-01-26 06:10:00+00:00","close_date":"2018-01-26 09:25:00+00:00","open_rate":0.00253806,"close_rate":0.0025507821052631577,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":195,"profit_ratio":0.0,"profit_abs":1.2722105263157733e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002284254,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002284254,"stop_loss_ratio":0.1,"min_rate":0.00253806,"max_rate":0.0025507821052631577,"is_open":false,"buy_tag":null,"open_timestamp":1516947000000.0,"close_timestamp":1516958700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024096385542168672,"open_date":"2018-01-26 07:25:00+00:00","close_date":"2018-01-26 09:55:00+00:00","open_rate":0.0415,"close_rate":0.04170802005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":150,"profit_ratio":-0.0,"profit_abs":0.00020802005012530989,"sell_reason":"roi","initial_stop_loss_abs":0.03735,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03735,"stop_loss_ratio":0.1,"min_rate":0.0415,"max_rate":0.04170802005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516951500000.0,"close_timestamp":1516960500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.793459875963165,"open_date":"2018-01-26 09:55:00+00:00","close_date":"2018-01-26 10:25:00+00:00","open_rate":5.321e-05,"close_rate":5.401015037593984e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.00150375939842e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7889e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7889e-05,"stop_loss_ratio":0.1,"min_rate":5.321e-05,"max_rate":5.401015037593984e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516960500000.0,"close_timestamp":1516962300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.036074437437185386,"open_date":"2018-01-26 16:05:00+00:00","close_date":"2018-01-26 16:45:00+00:00","open_rate":0.02772046,"close_rate":0.02785940967418546,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013894967418546025,"sell_reason":"roi","initial_stop_loss_abs":0.024948414,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024948414,"stop_loss_ratio":0.1,"min_rate":0.02772046,"max_rate":0.02785940967418546,"is_open":false,"buy_tag":null,"open_timestamp":1516982700000.0,"close_timestamp":1516985100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010569326272036914,"open_date":"2018-01-26 23:35:00+00:00","close_date":"2018-01-27 00:15:00+00:00","open_rate":0.09461341,"close_rate":0.09508766268170424,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00047425268170424306,"sell_reason":"roi","initial_stop_loss_abs":0.085152069,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085152069,"stop_loss_ratio":0.1,"min_rate":0.09461341,"max_rate":0.09508766268170424,"is_open":false,"buy_tag":null,"open_timestamp":1517009700000.0,"close_timestamp":1517012100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":17.809439002671414,"open_date":"2018-01-27 00:35:00+00:00","close_date":"2018-01-27 01:30:00+00:00","open_rate":5.615e-05,"close_rate":5.643145363408521e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.814536340852038e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0535e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0535e-05,"stop_loss_ratio":0.1,"min_rate":5.615e-05,"max_rate":5.643145363408521e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013300000.0,"close_timestamp":1517016600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.998560115190784,"open_date":"2018-01-27 00:45:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":5.556e-05,"close_rate":5.144e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4560,"profit_ratio":-0.07877175,"profit_abs":-4.120000000000001e-06,"sell_reason":"force_sell","initial_stop_loss_abs":5.0004000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0004000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.144e-05,"max_rate":5.556e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013900000.0,"close_timestamp":1517287500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492751522789634,"open_date":"2018-01-27 02:30:00+00:00","close_date":"2018-01-27 11:25:00+00:00","open_rate":0.06900001,"close_rate":0.06934587471177944,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":535,"profit_ratio":-0.0,"profit_abs":0.0003458647117794422,"sell_reason":"roi","initial_stop_loss_abs":0.062100009000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.062100009000000005,"stop_loss_ratio":0.1,"min_rate":0.06900001,"max_rate":0.06934587471177944,"is_open":false,"buy_tag":null,"open_timestamp":1517020200000.0,"close_timestamp":1517052300000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010582027378879437,"open_date":"2018-01-27 06:25:00+00:00","close_date":"2018-01-27 07:05:00+00:00","open_rate":0.09449985,"close_rate":0.0949735334586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004736834586466093,"sell_reason":"roi","initial_stop_loss_abs":0.085049865,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085049865,"stop_loss_ratio":0.1,"min_rate":0.09449985,"max_rate":0.0949735334586466,"is_open":false,"buy_tag":null,"open_timestamp":1517034300000.0,"close_timestamp":1517036700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02434885085598385,"open_date":"2018-01-27 09:40:00+00:00","close_date":"2018-01-30 04:40:00+00:00","open_rate":0.0410697,"close_rate":0.03928809,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4020,"profit_ratio":-0.04815133,"profit_abs":-0.001781610000000003,"sell_reason":"force_sell","initial_stop_loss_abs":0.03696273,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03696273,"stop_loss_ratio":0.1,"min_rate":0.03928809,"max_rate":0.0410697,"is_open":false,"buy_tag":null,"open_timestamp":1517046000000.0,"close_timestamp":1517287200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03508771929824561,"open_date":"2018-01-27 11:45:00+00:00","close_date":"2018-01-27 12:30:00+00:00","open_rate":0.0285,"close_rate":0.02864285714285714,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00014285714285713902,"sell_reason":"roi","initial_stop_loss_abs":0.025650000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025650000000000003,"stop_loss_ratio":0.1,"min_rate":0.0285,"max_rate":0.02864285714285714,"is_open":false,"buy_tag":null,"open_timestamp":1517053500000.0,"close_timestamp":1517056200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.034887307020861215,"open_date":"2018-01-27 12:35:00+00:00","close_date":"2018-01-27 15:25:00+00:00","open_rate":0.02866372,"close_rate":0.02880739779448621,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":170,"profit_ratio":-0.0,"profit_abs":0.00014367779448621124,"sell_reason":"roi","initial_stop_loss_abs":0.025797348,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025797348,"stop_loss_ratio":0.1,"min_rate":0.02866372,"max_rate":0.02880739779448621,"is_open":false,"buy_tag":null,"open_timestamp":1517056500000.0,"close_timestamp":1517066700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010484268355332824,"open_date":"2018-01-27 15:50:00+00:00","close_date":"2018-01-27 16:50:00+00:00","open_rate":0.095381,"close_rate":0.09585910025062656,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.00047810025062657024,"sell_reason":"roi","initial_stop_loss_abs":0.0858429,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0858429,"stop_loss_ratio":0.1,"min_rate":0.095381,"max_rate":0.09585910025062656,"is_open":false,"buy_tag":null,"open_timestamp":1517068200000.0,"close_timestamp":1517071800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014794886650455417,"open_date":"2018-01-27 17:05:00+00:00","close_date":"2018-01-27 17:45:00+00:00","open_rate":0.06759092,"close_rate":0.06792972160401002,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00033880160401002224,"sell_reason":"roi","initial_stop_loss_abs":0.060831828,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060831828,"stop_loss_ratio":0.1,"min_rate":0.06759092,"max_rate":0.06792972160401002,"is_open":false,"buy_tag":null,"open_timestamp":1517072700000.0,"close_timestamp":1517075100000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38684569885609726,"open_date":"2018-01-27 23:40:00+00:00","close_date":"2018-01-28 01:05:00+00:00","open_rate":0.00258501,"close_rate":0.002597967443609022,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":-0.0,"profit_abs":1.2957443609021985e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326509,"stop_loss_ratio":0.1,"min_rate":0.00258501,"max_rate":0.002597967443609022,"is_open":false,"buy_tag":null,"open_timestamp":1517096400000.0,"close_timestamp":1517101500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014928710926711672,"open_date":"2018-01-28 02:25:00+00:00","close_date":"2018-01-28 08:10:00+00:00","open_rate":0.06698502,"close_rate":0.0673207845112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":345,"profit_ratio":-0.0,"profit_abs":0.00033576451127818874,"sell_reason":"roi","initial_stop_loss_abs":0.060286518000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060286518000000004,"stop_loss_ratio":0.1,"min_rate":0.06698502,"max_rate":0.0673207845112782,"is_open":false,"buy_tag":null,"open_timestamp":1517106300000.0,"close_timestamp":1517127000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014767187899175548,"open_date":"2018-01-28 10:25:00+00:00","close_date":"2018-01-28 16:30:00+00:00","open_rate":0.0677177,"close_rate":0.06805713709273183,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":365,"profit_ratio":-0.0,"profit_abs":0.0003394370927318202,"sell_reason":"roi","initial_stop_loss_abs":0.06094593000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06094593000000001,"stop_loss_ratio":0.1,"min_rate":0.0677177,"max_rate":0.06805713709273183,"is_open":false,"buy_tag":null,"open_timestamp":1517135100000.0,"close_timestamp":1517157000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":19.175455417066157,"open_date":"2018-01-28 20:35:00+00:00","close_date":"2018-01-28 21:35:00+00:00","open_rate":5.215e-05,"close_rate":5.2411403508771925e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":0.0,"profit_abs":2.6140350877192417e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6935000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6935000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.215e-05,"max_rate":5.2411403508771925e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517171700000.0,"close_timestamp":1517175300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.36521808998243305,"open_date":"2018-01-28 22:00:00+00:00","close_date":"2018-01-28 22:30:00+00:00","open_rate":0.00273809,"close_rate":0.002779264285714285,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.117428571428529e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002464281,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002464281,"stop_loss_ratio":0.1,"min_rate":0.00273809,"max_rate":0.002779264285714285,"is_open":false,"buy_tag":null,"open_timestamp":1517176800000.0,"close_timestamp":1517178600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3641236272539253,"open_date":"2018-01-29 00:00:00+00:00","close_date":"2018-01-29 00:30:00+00:00","open_rate":0.00274632,"close_rate":0.002787618045112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.129804511278194e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002471688,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002471688,"stop_loss_ratio":0.1,"min_rate":0.00274632,"max_rate":0.002787618045112782,"is_open":false,"buy_tag":null,"open_timestamp":1517184000000.0,"close_timestamp":1517185800000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.061634117689115045,"open_date":"2018-01-29 02:15:00+00:00","close_date":"2018-01-29 03:00:00+00:00","open_rate":0.01622478,"close_rate":0.016306107218045113,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":8.132721804511231e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014602302000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014602302000000001,"stop_loss_ratio":0.1,"min_rate":0.01622478,"max_rate":0.016306107218045113,"is_open":false,"buy_tag":null,"open_timestamp":1517192100000.0,"close_timestamp":1517194800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492753623188404,"open_date":"2018-01-29 03:05:00+00:00","close_date":"2018-01-29 03:45:00+00:00","open_rate":0.069,"close_rate":0.06934586466165413,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00034586466165412166,"sell_reason":"roi","initial_stop_loss_abs":0.06210000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06210000000000001,"stop_loss_ratio":0.1,"min_rate":0.069,"max_rate":0.06934586466165413,"is_open":false,"buy_tag":null,"open_timestamp":1517195100000.0,"close_timestamp":1517197500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.42204454597373,"open_date":"2018-01-29 05:20:00+00:00","close_date":"2018-01-29 06:55:00+00:00","open_rate":8.755e-05,"close_rate":8.798884711779448e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":95,"profit_ratio":-0.0,"profit_abs":4.3884711779447504e-07,"sell_reason":"roi","initial_stop_loss_abs":7.879500000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.879500000000001e-05,"stop_loss_ratio":0.1,"min_rate":8.755e-05,"max_rate":8.798884711779448e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517203200000.0,"close_timestamp":1517208900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014650376815016871,"open_date":"2018-01-29 07:00:00+00:00","close_date":"2018-01-29 19:25:00+00:00","open_rate":0.06825763,"close_rate":0.06859977350877192,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":745,"profit_ratio":-0.0,"profit_abs":0.00034214350877191657,"sell_reason":"roi","initial_stop_loss_abs":0.061431867,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061431867,"stop_loss_ratio":0.1,"min_rate":0.06825763,"max_rate":0.06859977350877192,"is_open":false,"buy_tag":null,"open_timestamp":1517209200000.0,"close_timestamp":1517253900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014894490408841846,"open_date":"2018-01-29 19:45:00+00:00","close_date":"2018-01-29 20:25:00+00:00","open_rate":0.06713892,"close_rate":0.06747545593984962,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0003365359398496137,"sell_reason":"roi","initial_stop_loss_abs":0.060425028000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060425028000000006,"stop_loss_ratio":0.1,"min_rate":0.06713892,"max_rate":0.06747545593984962,"is_open":false,"buy_tag":null,"open_timestamp":1517255100000.0,"close_timestamp":1517257500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.193194537721066,"open_date":"2018-01-29 23:30:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":8.934e-05,"close_rate":8.8e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":315,"profit_ratio":-0.0199116,"profit_abs":-1.3399999999999973e-06,"sell_reason":"force_sell","initial_stop_loss_abs":8.0406e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.0406e-05,"stop_loss_ratio":0.1,"min_rate":8.8e-05,"max_rate":8.934e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517268600000.0,"close_timestamp":1517287500000.0}],"locks":[],"best_pair":{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},"worst_pair":{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},"results_per_pair":[{"key":"ETH/BTC","trades":21,"profit_mean":0.0009500057142857142,"profit_mean_pct":0.09500057142857142,"profit_sum":0.01995012,"profit_sum_pct":2.0,"profit_total_abs":0.011505731278195264,"profit_total":1.1505731278195264,"profit_total_pct":115.06,"duration_avg":"2:17:00","wins":21,"draws":0,"losses":0},{"key":"DASH/BTC","trades":16,"profit_mean":0.0018703237499999997,"profit_mean_pct":0.18703237499999997,"profit_sum":0.029925179999999996,"profit_sum_pct":2.99,"profit_total_abs":0.007475052681704161,"profit_total":0.7475052681704161,"profit_total_pct":74.75,"duration_avg":"3:03:00","wins":16,"draws":0,"losses":0},{"key":"ZEC/BTC","trades":21,"profit_mean":-0.00039290904761904774,"profit_mean_pct":-0.03929090476190478,"profit_sum":-0.008251090000000003,"profit_sum_pct":-0.83,"profit_total_abs":0.004452605639097655,"profit_total":0.4452605639097655,"profit_total_pct":44.53,"duration_avg":"4:17:00","wins":20,"draws":0,"losses":1},{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},{"key":"ETC/BTC","trades":20,"profit_mean":0.0022568569999999997,"profit_mean_pct":0.22568569999999996,"profit_sum":0.04513713999999999,"profit_sum_pct":4.51,"profit_total_abs":0.00036538235338345404,"profit_total":0.0365382353383454,"profit_total_pct":3.65,"duration_avg":"1:45:00","wins":19,"draws":0,"losses":1},{"key":"TRX/BTC","trades":15,"profit_mean":0.0023467073333333323,"profit_mean_pct":0.23467073333333321,"profit_sum":0.035200609999999986,"profit_sum_pct":3.52,"profit_total_abs":1.1329523809523682e-05,"profit_total":0.0011329523809523682,"profit_total_pct":0.11,"duration_avg":"2:28:00","wins":13,"draws":0,"losses":2},{"key":"XLM/BTC","trades":21,"profit_mean":0.0026243899999999994,"profit_mean_pct":0.2624389999999999,"profit_sum":0.05511218999999999,"profit_sum_pct":5.51,"profit_total_abs":7.340779448621465e-06,"profit_total":0.0007340779448621465,"profit_total_pct":0.07,"duration_avg":"3:21:00","wins":20,"draws":0,"losses":1},{"key":"ADA/BTC","trades":29,"profit_mean":-0.0011598141379310352,"profit_mean_pct":-0.11598141379310352,"profit_sum":-0.03363461000000002,"profit_sum_pct":-3.36,"profit_total_abs":4.916634085212862e-06,"profit_total":0.0004916634085212862,"profit_total_pct":0.05,"duration_avg":"5:35:00","wins":27,"draws":0,"losses":2},{"key":"NXT/BTC","trades":12,"profit_mean":-0.0012261025000000006,"profit_mean_pct":-0.12261025000000006,"profit_sum":-0.014713230000000008,"profit_sum_pct":-1.47,"profit_total_abs":1.4774411027568458e-06,"profit_total":0.00014774411027568458,"profit_total_pct":0.01,"duration_avg":"0:57:00","wins":11,"draws":0,"losses":1},{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"results_per_enter_tag":[{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"sell_reason_summary":[{"sell_reason":"roi","trades":170,"wins":170,"draws":0,"losses":0,"profit_mean":0.005398268352941177,"profit_mean_pct":0.54,"profit_sum":0.91770562,"profit_sum_pct":91.77,"profit_total_abs":0.031232837493733862,"profit_total":0.30590187333333335,"profit_total_pct":30.59},{"sell_reason":"stop_loss","trades":6,"wins":0,"draws":0,"losses":6,"profit_mean":-0.10448878000000002,"profit_mean_pct":-10.45,"profit_sum":-0.6269326800000001,"profit_sum_pct":-62.69,"profit_total_abs":-0.0033602680000000026,"profit_total":-0.20897756000000003,"profit_total_pct":-20.9},{"sell_reason":"force_sell","trades":3,"wins":0,"draws":0,"losses":3,"profit_mean":-0.04894489333333333,"profit_mean_pct":-4.89,"profit_sum":-0.14683468,"profit_sum_pct":-14.68,"profit_total_abs":-0.001787070000000003,"profit_total":-0.04894489333333333,"profit_total_pct":-4.89}],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":179,"total_volume":0.17900000000000005,"avg_stake_amount":0.0010000000000000002,"profit_mean":0.0008041243575418989,"profit_median":0.0,"profit_total":2.6085499493733857,"profit_total_abs":0.026085499493733857,"backtest_start":"2018-01-10 07:15:00","backtest_start_ts":1515568500000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":"2020-10-01 18:00:00+00:00","backtest_run_end_ts":"2020-10-01 18:01:00+00:00","trades_per_day":9.42,"market_change":1.22,"pairlist":[],"stake_amount":0.001,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":0.01,"dry_run_wallet":0.01,"final_balance":0.03608549949373386,"rejected_signals":0,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timeframe_detail":"","timerange":"","enable_protections":false,"strategy_name":"StrategyTestV2","stoploss":0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":false,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.17955111999999998,"backtest_worst_day":-0.14683468,"backtest_best_day_abs":0.0071570099,"backtest_worst_day_abs":-0.0023093218,"winning_days":19,"draw_days":0,"losing_days":2,"daily_profit":[["2018-01-10",0.0025815306],["2018-01-11",0.0049356655],["2018-01-12",0.0006395218],["2018-01-13",0.0002574589],["2018-01-14",0.0010443828],["2018-01-15",0.0024030209],["2018-01-16",0.0071570099],["2018-01-17",0.001137038],["2018-01-18",0.0013712174],["2018-01-19",0.000584673],["2018-01-20",0.0006143386],["2018-01-21",0.0004749361],["2018-01-22",9.91669e-05],["2018-01-23",0.0015726664],["2018-01-24",0.0006610219],["2018-01-25",-0.0023093218],["2018-01-26",0.0003735204],["2018-01-27",0.0023975191],["2018-01-28",0.0007295947],["2018-01-29",0.0011476082],["2018-01-30",-0.00178707]],"wins":48,"losses":9,"draws":122,"holding_avg":"3:40:00","holding_avg_s":13200.0,"winner_holding_avg":"0:24:00","winner_holding_avg_s":1440.0,"loser_holding_avg":"1 day, 5:57:00","loser_holding_avg_s":107820.0,"max_drawdown":0.21142322000000008,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":0.0030822220000000025,"drawdown_start":"2018-01-25 01:30:00","drawdown_start_ts":1516843800000.0,"drawdown_end":"2018-01-25 03:50:00","drawdown_end_ts":1516852200000.0,"max_drawdown_low":0.02245167355388436,"max_drawdown_high":0.025533895553884363,"csum_min":0.01000434887218045,"csum_max":0.03608683949373386}},"strategy_comparison":[{"key":"StrategyTestV2","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":"0.00308222"}]}
diff --git a/tests/testdata/futures/UNITTEST_USDT-1h-mark.json b/tests/testdata/futures/UNITTEST_USDT-1h-mark.json
new file mode 100644
index 000000000..312f616fb
--- /dev/null
+++ b/tests/testdata/futures/UNITTEST_USDT-1h-mark.json
@@ -0,0 +1,102 @@
+[
+  [1636959600000, 1.21431, 1.2198, 1.20895, 1.20895, null],
+  [1636963200000, 1.20902, 1.21106, 1.19972, 1.20968, null],
+  [1636966800000, 1.20968, 1.21876, 1.20791, 1.20998, null],
+  [1636970400000, 1.20999, 1.21043, 1.20442, 1.20859, null],
+  [1636974000000, 1.20858, 1.20933, 1.20154, 1.20581, null],
+  [1636977600000, 1.20584, 1.20775, 1.20065, 1.20337, null],
+  [1636981200000, 1.20342, 1.2097, 1.19327, 1.19792, null],
+  [1636984800000, 1.19796, 1.1982, 1.18611, 1.19024, null],
+  [1636988400000, 1.19025, 1.19177, 1.18373, 1.18771, null],
+  [1636992000000, 1.18768, 1.19109, 1.18095, 1.1887, null],
+  [1636995600000, 1.18869, 1.18968, 1.18355, 1.18387, null],
+  [1636999200000, 1.18388, 1.18729, 1.17753, 1.18138, null],
+  [1637002800000, 1.18145, 1.18684, 1.17799, 1.18463, null],
+  [1637006400000, 1.18464, 1.18474, 1.17368, 1.17652, null],
+  [1637010000000, 1.17653, 1.18185, 1.16557, 1.17979, null],
+  [1637013600000, 1.17979, 1.18113, 1.16934, 1.18014, null],
+  [1637017200000, 1.18014, 1.18015, 1.16999, 1.17214, null],
+  [1637020800000, 1.17214, 1.17217, 1.12958, 1.14209, null],
+  [1637024400000, 1.14255, 1.14666, 1.10933, 1.14198, null],
+  [1637028000000, 1.14197, 1.14419, 1.12766, 1.12999, null],
+  [1637031600000, 1.12999, 1.13522, 1.11142, 1.12177, null],
+  [1637035200000, 1.12176, 1.13211, 1.10579, 1.1288, null],
+  [1637038800000, 1.12871, 1.13243, 1.12142, 1.12316, null],
+  [1637042400000, 1.12323, 1.1262, 1.11489, 1.12429, null],
+  [1637046000000, 1.12406, 1.12727, 1.11835, 1.1249, null],
+  [1637049600000, 1.12485, 1.13047, 1.1211, 1.12931, null],
+  [1637053200000, 1.12931, 1.13346, 1.10256, 1.10267, null],
+  [1637056800000, 1.10266, 1.10412, 1.04149, 1.0928, null],
+  [1637060400000, 1.09277, 1.09856, 1.08371, 1.09093, null],
+  [1637064000000, 1.09094, 1.09512, 1.079, 1.08003, null],
+  [1637067600000, 1.0802, 1.09914, 1.08016, 1.09515, null],
+  [1637071200000, 1.09518, 1.11627, 1.0937, 1.10985, null],
+  [1637074800000, 1.10985, 1.11353, 1.09618, 1.10071, null],
+  [1637078400000, 1.09989, 1.10852, 1.09763, 1.10461, null],
+  [1637082000000, 1.10459, 1.10837, 1.09662, 1.09847, null],
+  [1637085600000, 1.09858, 1.10506, 1.08687, 1.08716, null],
+  [1637089200000, 1.08677, 1.10096, 1.08151, 1.09271, null],
+  [1637092800000, 1.09245, 1.09269, 1.06592, 1.08025, null],
+  [1637096400000, 1.08026, 1.09732, 1.07953, 1.09527, null],
+  [1637100000000, 1.09527, 1.10506, 1.09524, 1.09933, null],
+  [1637103600000, 1.09933, 1.10205, 1.08761, 1.08785, null],
+  [1637107200000, 1.08763, 1.09518, 1.07646, 1.07999, null],
+  [1637110800000, 1.07997, 1.0978, 1.07651, 1.07936, null],
+  [1637114400000, 1.07932, 1.08758, 1.07352, 1.07603, null],
+  [1637118000000, 1.07604, 1.08542, 1.05931, 1.06764, null],
+  [1637121600000, 1.06788, 1.07848, 1.06045, 1.07608, null],
+  [1637125200000, 1.07613, 1.08797, 1.07293, 1.08377, null],
+  [1637128800000, 1.08379, 1.08567, 1.07428, 1.07942, null],
+  [1637132400000, 1.07958, 1.09472, 1.07356, 1.08713, null],
+  [1637136000000, 1.08714, 1.09149, 1.08018, 1.08021, null],
+  [1637139600000, 1.08021, 1.08021, 1.0668, 1.07032, null],
+  [1637143200000, 1.07042, 1.10563, 1.07034, 1.10255, null],
+  [1637146800000, 1.10284, 1.10954, 1.09767, 1.10685, null],
+  [1637150400000, 1.10669, 1.10848, 1.10157, 1.10537, null],
+  [1637154000000, 1.10537, 1.11263, 1.09554, 1.09585, null],
+  [1637157600000, 1.09569, 1.10051, 1.08402, 1.08431, null],
+  [1637161200000, 1.08444, 1.08942, 1.07569, 1.08489, null],
+  [1637164800000, 1.08498, 1.09581, 1.07939, 1.09485, null],
+  [1637168400000, 1.09443, 1.09793, 1.08778, 1.0944, null],
+  [1637172000000, 1.09445, 1.10227, 1.09376, 1.0992, null],
+  [1637175600000, 1.0992, 1.10189, 1.09216, 1.09474, null],
+  [1637179200000, 1.09476, 1.10198, 1.09045, 1.0993, null],
+  [1637182800000, 1.09934, 1.09959, 1.08755, 1.0948, null],
+  [1637186400000, 1.09483, 1.09519, 1.08532, 1.0923, null],
+  [1637190000000, 1.0923, 1.09876, 1.0874, 1.095, null],
+  [1637193600000, 1.09503, 1.10673, 1.09047, 1.10441, null],
+  [1637197200000, 1.10437, 1.16166, 1.09815, 1.12902, null],
+  [1637200800000, 1.12875, 1.15094, 1.1242, 1.13764, null],
+  [1637204400000, 1.13795, 1.14262, 1.12341, 1.12423, null],
+  [1637208000000, 1.12424, 1.14806, 1.11333, 1.1142, null],
+  [1637211600000, 1.11435, 1.12608, 1.11085, 1.11436, null],
+  [1637215200000, 1.11398, 1.11718, 1.10538, 1.11388, null],
+  [1637218800000, 1.1139, 1.11452, 1.09674, 1.1072, null],
+  [1637222400000, 1.10725, 1.10999, 1.10209, 1.10706, null],
+  [1637226000000, 1.10712, 1.10712, 1.07747, 1.08658, null],
+  [1637229600000, 1.08692, 1.09865, 1.0807, 1.09767, null],
+  [1637233200000, 1.09768, 1.10211, 1.08348, 1.08409, null],
+  [1637236800000, 1.08423, 1.09498, 1.08002, 1.08259, null],
+  [1637240400000, 1.0827, 1.08773, 1.06597, 1.07719, null],
+  [1637244000000, 1.07718, 1.08075, 1.06678, 1.07077, null],
+  [1637247600000, 1.07029, 1.07824, 1.04568, 1.05497, null],
+  [1637251200000, 1.05591, 1.06325, 1.03957, 1.04032, null],
+  [1637254800000, 1.04051, 1.05342, 1.01557, 1.04158, null],
+  [1637258400000, 1.04153, 1.05436, 1.04122, 1.05208, null],
+  [1637262000000, 1.05207, 1.05948, 1.04961, 1.05515, null],
+  [1637265600000, 1.05516, 1.05927, 1.04767, 1.04808, null],
+  [1637269200000, 1.04789, 1.05622, 1.04191, 1.04587, null],
+  [1637272800000, 1.04575, 1.05336, 1.03405, 1.03941, null],
+  [1637276400000, 1.03931, 1.04614, 1.02868, 1.0411, null],
+  [1637280000000, 1.04093, 1.05672, 1.0295, 1.05495, null],
+  [1637283600000, 1.05495, 1.0553, 1.03548, 1.03595, null],
+  [1637287200000, 1.0359, 1.04585, 1.02026, 1.02312, null],
+  [1637290800000, 1.0242, 1.02908, 1.01788, 1.02871, null],
+  [1637294400000, 1.02871, 1.04474, 1.02584, 1.04247, null],
+  [1637298000000, 1.04251, 1.04654, 1.03685, 1.0449, null],
+  [1637301600000, 1.0449, 1.04971, 1.04109, 1.04452, null],
+  [1637305200000, 1.04456, 1.04875, 1.03802, 1.04268, null],
+  [1637308800000, 1.04239, 1.06573, 1.04164, 1.05717, null],
+  [1637312400000, 1.05721, 1.06464, 1.05619, 1.05896, null],
+  [1637316000000, 1.05893, 1.05918, 1.04976, 1.05188, null]
+]
diff --git a/tests/testdata/futures/UNITTEST_USDT_USDT-1h-mark.h5 b/tests/testdata/futures/UNITTEST_USDT_USDT-1h-mark.h5
new file mode 100644
index 000000000..e6b128dc1
Binary files /dev/null and b/tests/testdata/futures/UNITTEST_USDT_USDT-1h-mark.h5 differ
diff --git a/tests/testdata/futures/XRP_USDT-1h-futures.json b/tests/testdata/futures/XRP_USDT-1h-futures.json
new file mode 100644
index 000000000..58944e717
--- /dev/null
+++ b/tests/testdata/futures/XRP_USDT-1h-futures.json
@@ -0,0 +1,102 @@
+[
+  [ 1637110800000, 1.0801, 1.09758, 1.07654, 1.07925, 3153694.607359 ],
+  [ 1637114400000, 1.07896, 1.0875, 1.07351, 1.07616, 2697616.070908 ],
+  [ 1637118000000, 1.07607, 1.08521, 1.05896, 1.06804, 4014666.826073 ],
+  [ 1637121600000, 1.06848, 1.07846, 1.06067, 1.07629, 3764015.567745 ],
+  [ 1637125200000, 1.07647, 1.08791, 1.07309, 1.0839, 1669038.113726 ],
+  [ 1637128800000, 1.08414, 1.0856, 1.07431, 1.0794, 1921068.874499 ],
+  [ 1637132400000, 1.0798, 1.09499, 1.07363, 1.08721, 2491096.863582 ],
+  [ 1637136000000, 1.08688, 1.09133, 1.08004, 1.08011, 1983486.794272 ],
+  [ 1637139600000, 1.08017, 1.08027, 1.06667, 1.07039, 3429247.985309 ],
+  [ 1637143200000, 1.07054, 1.10699, 1.07038, 1.10284, 4554151.954177 ],
+  [ 1637146800000, 1.10315, 1.10989, 1.09781, 1.1071, 2012983.10465 ],
+  [ 1637150400000, 1.10627, 1.10849, 1.10155, 1.10539, 1117804.08918 ],
+  [ 1637154000000, 1.10545, 1.11299, 1.09574, 1.09604, 2252781.33926 ],
+  [ 1637157600000, 1.09583, 1.10037, 1.08402, 1.08404, 1882359.279342 ],
+  [ 1637161200000, 1.08433, 1.08924, 1.07583, 1.08543, 1826745.82579 ],
+  [ 1637164800000, 1.08571, 1.09622, 1.07946, 1.09496, 1651730.678891 ],
+  [ 1637168400000, 1.09509, 1.0979, 1.0878, 1.0945, 1081210.614598 ],
+  [ 1637172000000, 1.09483, 1.10223, 1.09362, 1.09922, 1065998.492028 ],
+  [ 1637175600000, 1.09916, 1.10201, 1.09226, 1.09459, 924935.492048 ],
+  [ 1637179200000, 1.09458, 1.10196, 1.09051, 1.09916, 1253539.625345 ],
+  [ 1637182800000, 1.09939, 1.09948, 1.08751, 1.09485, 1066269.190094 ],
+  [ 1637186400000, 1.0949, 1.095, 1.08537, 1.09229, 924726.680514 ],
+  [ 1637190000000, 1.0923, 1.09877, 1.08753, 1.09522, 1150213.905599 ],
+  [ 1637193600000, 1.09538, 1.10675, 1.09058, 1.10453, 1489867.578178 ],
+  [ 1637197200000, 1.10446, 1.16313, 1.0978, 1.12907, 10016166.026355 ],
+  [ 1637200800000, 1.1287, 1.15367, 1.12403, 1.1381, 7167920.053752 ],
+  [ 1637204400000, 1.13818, 1.14242, 1.12358, 1.1244, 2665326.190545 ],
+  [ 1637208000000, 1.12432, 1.14864, 1.11061, 1.11447, 9340547.947608 ],
+  [ 1637211600000, 1.114, 1.12618, 1.10911, 1.11412, 11759138.472952 ],
+  [ 1637215200000, 1.11381, 1.11701, 1.10507, 1.1136, 3104670.727264 ],
+  [ 1637218800000, 1.11433, 1.1145, 1.09682, 1.10715, 2522287.830673 ],
+  [ 1637222400000, 1.1073, 1.11, 1.10224, 1.10697, 2021691.204473 ],
+  [ 1637226000000, 1.10622, 1.10707, 1.07727, 1.08674, 3679010.223352 ],
+  [ 1637229600000, 1.08651, 1.09861, 1.08065, 1.09771, 2041421.476307 ],
+  [ 1637233200000, 1.09784, 1.102, 1.08339, 1.08399, 1920597.122813 ],
+  [ 1637236800000, 1.08458, 1.09523, 1.07961, 1.08263, 2403158.337373 ],
+  [ 1637240400000, 1.08309, 1.08959, 1.06094, 1.07703, 4425686.808376 ],
+  [ 1637244000000, 1.07702, 1.08064, 1.063, 1.07049, 3361334.048801 ],
+  [ 1637247600000, 1.07126, 1.07851, 1.04538, 1.0562, 5865602.611111 ],
+  [ 1637251200000, 1.05616, 1.06326, 1.0395, 1.04074, 4206860.947352 ],
+  [ 1637254800000, 1.04023, 1.0533, 1.01478, 1.0417, 5641193.647291 ],
+  [ 1637258400000, 1.04177, 1.05444, 1.04132, 1.05204, 1819341.083656 ],
+  [ 1637262000000, 1.05201, 1.05962, 1.04964, 1.05518, 1567923.362515 ],
+  [ 1637265600000, 1.05579, 1.05924, 1.04772, 1.04773, 1794108.065606 ],
+  [ 1637269200000, 1.0484, 1.05622, 1.04183, 1.04544, 1936537.403899 ],
+  [ 1637272800000, 1.04543, 1.05331, 1.03396, 1.03892, 2839486.418143 ],
+  [ 1637276400000, 1.03969, 1.04592, 1.02886, 1.04086, 3116275.899177 ],
+  [ 1637280000000, 1.0409, 1.05681, 1.02922, 1.05481, 4671209.916896 ],
+  [ 1637283600000, 1.05489, 1.05538, 1.03539, 1.03599, 2566357.247547 ],
+  [ 1637287200000, 1.03596, 1.04606, 1.02038, 1.02428, 3441834.238546 ],
+  [ 1637290800000, 1.02483, 1.0291, 1.01785, 1.0285, 2678602.729339 ],
+  [ 1637294400000, 1.0287, 1.0446, 1.0259, 1.04264, 2303621.340808 ],
+  [ 1637298000000, 1.04313, 1.04676, 1.03662, 1.04499, 2426475.439485 ],
+  [ 1637301600000, 1.0451, 1.04971, 1.041, 1.04448, 2088365.810515 ],
+  [ 1637305200000, 1.04473, 1.04845, 1.03801, 1.04227, 2222396.213472 ],
+  [ 1637308800000, 1.04211, 1.06965, 1.04168, 1.05711, 3267643.936025 ],
+  [ 1637312400000, 1.0569, 1.06578, 1.05626, 1.05844, 1512848.016057 ],
+  [ 1637316000000, 1.05814, 1.05916, 1.04923, 1.05464, 1710694.805693 ],
+  [ 1637319600000, 1.05484, 1.05731, 1.0458, 1.05359, 1587100.45253 ],
+  [ 1637323200000, 1.05382, 1.06063, 1.05156, 1.05227, 1409095.236152 ],
+  [ 1637326800000, 1.05256, 1.06489, 1.04996, 1.06471, 1879315.174541 ],
+  [ 1637330400000, 1.06491, 1.1036, 1.06489, 1.09439, 6212842.71216 ],
+  [ 1637334000000, 1.09441, 1.10252, 1.082, 1.08879, 4833417.181969 ],
+  [ 1637337600000, 1.08866, 1.09485, 1.07538, 1.09045, 2554438.746366 ],
+  [ 1637341200000, 1.09058, 1.09906, 1.08881, 1.09039, 1961024.28963 ],
+  [ 1637344800000, 1.09063, 1.09447, 1.08555, 1.09041, 1427538.639232 ],
+  [ 1637348400000, 1.09066, 1.09521, 1.088, 1.09332, 847724.821691 ],
+  [ 1637352000000, 1.09335, 1.09489, 1.08402, 1.08501, 1035043.133874 ],
+  [ 1637355600000, 1.08474, 1.08694, 1.08, 1.08606, 969952.892274 ],
+  [ 1637359200000, 1.08601, 1.09, 1.08201, 1.08476, 1105782.581808 ],
+  [ 1637362800000, 1.08463, 1.09245, 1.08201, 1.08971, 1334467.438673 ],
+  [ 1637366400000, 1.0897, 1.09925, 1.08634, 1.09049, 2460070.020396 ],
+  [ 1637370000000, 1.0908, 1.10002, 1.09002, 1.09845, 1210028.489394 ],
+  [ 1637373600000, 1.09785, 1.09791, 1.08944, 1.08962, 1261987.295847 ],
+  [ 1637377200000, 1.08951, 1.0919, 1.08429, 1.08548, 1124938.783404 ],
+  [ 1637380800000, 1.08536, 1.09, 1.08424, 1.08783, 1330935.680168 ],
+  [ 1637384400000, 1.0877, 1.08969, 1.08266, 1.08617, 874900.746037 ],
+  [ 1637388000000, 1.08622, 1.09224, 1.0843, 1.0889, 1240184.759178 ],
+  [ 1637391600000, 1.08917, 1.0909, 1.08408, 1.08535, 706148.380072 ],
+  [ 1637395200000, 1.08521, 1.08857, 1.07829, 1.08349, 1713832.050838 ],
+  [ 1637398800000, 1.08343, 1.08841, 1.08272, 1.0855, 696597.06327 ],
+  [ 1637402400000, 1.08553, 1.0898, 1.08353, 1.08695, 1104159.802108 ],
+  [ 1637406000000, 1.08703, 1.09838, 1.08635, 1.09695, 1404001.384389 ],
+  [ 1637409600000, 1.09695, 1.10175, 1.09024, 1.09278, 1219090.620484 ],
+  [ 1637413200000, 1.093, 1.09577, 1.08615, 1.08792, 994797.546591 ],
+  [ 1637416800000, 1.08793, 1.09239, 1.08572, 1.08725, 1251685.429497 ],
+  [ 1637420400000, 1.08721, 1.08767, 1.06029, 1.06556, 3955719.53631 ],
+  [ 1637424000000, 1.06553, 1.07385, 1.06169, 1.07257, 1868359.179534 ],
+  [ 1637427600000, 1.07266, 1.0745, 1.06759, 1.07261, 1015134.469304 ],
+  [ 1637431200000, 1.07255, 1.0974, 1.06819, 1.09369, 4377675.964829 ],
+  [ 1637434800000, 1.09368, 1.09562, 1.08899, 1.09036, 914791.699929 ],
+  [ 1637438400000, 1.09085, 1.09262, 1.08855, 1.09214, 661436.936672 ],
+  [ 1637442000000, 1.0924, 1.09475, 1.08874, 1.09282, 593143.283519 ],
+  [ 1637445600000, 1.09301, 1.09638, 1.09154, 1.09611, 603952.916221 ],
+  [ 1637449200000, 1.09569, 1.09828, 1.09301, 1.09747, 676053.591571 ],
+  [ 1637452800000, 1.09742, 1.09822, 1.09011, 1.0902, 1375704.506469 ],
+  [ 1637456400000, 1.0901, 1.09311, 1.08619, 1.08856, 928706.03929 ],
+  [ 1637460000000, 1.08855, 1.08941, 1.07401, 1.08035, 2669150.388642 ],
+  [ 1637463600000, 1.08016, 1.08341, 1.07448, 1.07672, 1604049.131307 ],
+  [ 1637467200000, 1.07685, 1.08229, 1.07552, 1.0765, 1153357.274076 ]
+]
diff --git a/tests/testdata/futures/XRP_USDT-1h-futures.json.gz b/tests/testdata/futures/XRP_USDT-1h-futures.json.gz
new file mode 100644
index 000000000..f2a223b03
Binary files /dev/null and b/tests/testdata/futures/XRP_USDT-1h-futures.json.gz differ
diff --git a/tests/testdata/futures/XRP_USDT-1h-mark.json b/tests/testdata/futures/XRP_USDT-1h-mark.json
new file mode 100644
index 000000000..26703b945
--- /dev/null
+++ b/tests/testdata/futures/XRP_USDT-1h-mark.json
@@ -0,0 +1,102 @@
+[
+  [1636956000000, 1.20932, 1.21787, 1.20763, 1.21431, null],
+  [1636959600000, 1.21431, 1.2198, 1.20895, 1.20895, null],
+  [1636963200000, 1.20902, 1.21106, 1.19972, 1.20968, null],
+  [1636966800000, 1.20968, 1.21876, 1.20791, 1.20998, null],
+  [1636970400000, 1.20999, 1.21043, 1.20442, 1.20859, null],
+  [1636974000000, 1.20858, 1.20933, 1.20154, 1.20581, null],
+  [1636977600000, 1.20584, 1.20775, 1.20065, 1.20337, null],
+  [1636981200000, 1.20342, 1.2097, 1.19327, 1.19792, null],
+  [1636984800000, 1.19796, 1.1982, 1.18611, 1.19024, null],
+  [1636988400000, 1.19025, 1.19177, 1.18373, 1.18771, null],
+  [1636992000000, 1.18768, 1.19109, 1.18095, 1.1887, null],
+  [1636995600000, 1.18869, 1.18968, 1.18355, 1.18387, null],
+  [1636999200000, 1.18388, 1.18729, 1.17753, 1.18138, null],
+  [1637002800000, 1.18145, 1.18684, 1.17799, 1.18463, null],
+  [1637006400000, 1.18464, 1.18474, 1.17368, 1.17652, null],
+  [1637010000000, 1.17653, 1.18185, 1.16557, 1.17979, null],
+  [1637013600000, 1.17979, 1.18113, 1.16934, 1.18014, null],
+  [1637017200000, 1.18014, 1.18015, 1.16999, 1.17214, null],
+  [1637020800000, 1.17214, 1.17217, 1.12958, 1.14209, null],
+  [1637024400000, 1.14255, 1.14666, 1.10933, 1.14198, null],
+  [1637028000000, 1.14197, 1.14419, 1.12766, 1.12999, null],
+  [1637031600000, 1.12999, 1.13522, 1.11142, 1.12177, null],
+  [1637035200000, 1.12176, 1.13211, 1.10579, 1.1288, null],
+  [1637038800000, 1.12871, 1.13243, 1.12142, 1.12316, null],
+  [1637042400000, 1.12323, 1.1262, 1.11489, 1.12429, null],
+  [1637046000000, 1.12406, 1.12727, 1.11835, 1.1249, null],
+  [1637049600000, 1.12485, 1.13047, 1.1211, 1.12931, null],
+  [1637053200000, 1.12931, 1.13346, 1.10256, 1.10267, null],
+  [1637056800000, 1.10266, 1.10412, 1.04149, 1.0928, null],
+  [1637060400000, 1.09277, 1.09856, 1.08371, 1.09093, null],
+  [1637064000000, 1.09094, 1.09512, 1.079, 1.08003, null],
+  [1637067600000, 1.0802, 1.09914, 1.08016, 1.09515, null],
+  [1637071200000, 1.09518, 1.11627, 1.0937, 1.10985, null],
+  [1637074800000, 1.10985, 1.11353, 1.09618, 1.10071, null],
+  [1637078400000, 1.09989, 1.10852, 1.09763, 1.10461, null],
+  [1637082000000, 1.10459, 1.10837, 1.09662, 1.09847, null],
+  [1637085600000, 1.09858, 1.10506, 1.08687, 1.08716, null],
+  [1637089200000, 1.08677, 1.10096, 1.08151, 1.09271, null],
+  [1637092800000, 1.09245, 1.09269, 1.06592, 1.08025, null],
+  [1637096400000, 1.08026, 1.09732, 1.07953, 1.09527, null],
+  [1637100000000, 1.09527, 1.10506, 1.09524, 1.09933, null],
+  [1637103600000, 1.09933, 1.10205, 1.08761, 1.08785, null],
+  [1637107200000, 1.08763, 1.09518, 1.07646, 1.07999, null],
+  [1637110800000, 1.07997, 1.0978, 1.07651, 1.07936, null],
+  [1637114400000, 1.07932, 1.08758, 1.07352, 1.07603, null],
+  [1637118000000, 1.07604, 1.08542, 1.05931, 1.06764, null],
+  [1637121600000, 1.06788, 1.07848, 1.06045, 1.07608, null],
+  [1637125200000, 1.07613, 1.08797, 1.07293, 1.08377, null],
+  [1637128800000, 1.08379, 1.08567, 1.07428, 1.07942, null],
+  [1637132400000, 1.07958, 1.09472, 1.07356, 1.08713, null],
+  [1637136000000, 1.08714, 1.09149, 1.08018, 1.08021, null],
+  [1637139600000, 1.08021, 1.08021, 1.0668, 1.07032, null],
+  [1637143200000, 1.07042, 1.10563, 1.07034, 1.10255, null],
+  [1637146800000, 1.10284, 1.10954, 1.09767, 1.10685, null],
+  [1637150400000, 1.10669, 1.10848, 1.10157, 1.10537, null],
+  [1637154000000, 1.10537, 1.11263, 1.09554, 1.09585, null],
+  [1637157600000, 1.09569, 1.10051, 1.08402, 1.08431, null],
+  [1637161200000, 1.08444, 1.08942, 1.07569, 1.08489, null],
+  [1637164800000, 1.08498, 1.09581, 1.07939, 1.09485, null],
+  [1637168400000, 1.09443, 1.09793, 1.08778, 1.0944, null],
+  [1637172000000, 1.09445, 1.10227, 1.09376, 1.0992, null],
+  [1637175600000, 1.0992, 1.10189, 1.09216, 1.09474, null],
+  [1637179200000, 1.09476, 1.10198, 1.09045, 1.0993, null],
+  [1637182800000, 1.09934, 1.09959, 1.08755, 1.0948, null],
+  [1637186400000, 1.09483, 1.09519, 1.08532, 1.0923, null],
+  [1637190000000, 1.0923, 1.09876, 1.0874, 1.095, null],
+  [1637193600000, 1.09503, 1.10673, 1.09047, 1.10441, null],
+  [1637197200000, 1.10437, 1.16166, 1.09815, 1.12902, null],
+  [1637200800000, 1.12875, 1.15094, 1.1242, 1.13764, null],
+  [1637204400000, 1.13795, 1.14262, 1.12341, 1.12423, null],
+  [1637208000000, 1.12424, 1.14806, 1.11333, 1.1142, null],
+  [1637211600000, 1.11435, 1.12608, 1.11085, 1.11436, null],
+  [1637215200000, 1.11398, 1.11718, 1.10538, 1.11388, null],
+  [1637218800000, 1.1139, 1.11452, 1.09674, 1.1072, null],
+  [1637222400000, 1.10725, 1.10999, 1.10209, 1.10706, null],
+  [1637226000000, 1.10712, 1.10712, 1.07747, 1.08658, null],
+  [1637229600000, 1.08692, 1.09865, 1.0807, 1.09767, null],
+  [1637233200000, 1.09768, 1.10211, 1.08348, 1.08409, null],
+  [1637236800000, 1.08423, 1.09498, 1.08002, 1.08259, null],
+  [1637240400000, 1.0827, 1.08773, 1.06597, 1.07719, null],
+  [1637244000000, 1.07718, 1.08075, 1.06678, 1.07077, null],
+  [1637247600000, 1.07029, 1.07824, 1.04568, 1.05497, null],
+  [1637251200000, 1.05591, 1.06325, 1.03957, 1.04032, null],
+  [1637254800000, 1.04051, 1.05342, 1.01557, 1.04158, null],
+  [1637258400000, 1.04153, 1.05436, 1.04122, 1.05208, null],
+  [1637262000000, 1.05207, 1.05948, 1.04961, 1.05515, null],
+  [1637265600000, 1.05516, 1.05927, 1.04767, 1.04808, null],
+  [1637269200000, 1.04789, 1.05622, 1.04191, 1.04587, null],
+  [1637272800000, 1.04575, 1.05336, 1.03405, 1.03941, null],
+  [1637276400000, 1.03931, 1.04614, 1.02868, 1.0411, null],
+  [1637280000000, 1.04093, 1.05672, 1.0295, 1.05495, null],
+  [1637283600000, 1.05495, 1.0553, 1.03548, 1.03595, null],
+  [1637287200000, 1.0359, 1.04585, 1.02026, 1.02312, null],
+  [1637290800000, 1.0242, 1.02908, 1.01788, 1.02871, null],
+  [1637294400000, 1.02871, 1.04474, 1.02584, 1.04247, null],
+  [1637298000000, 1.04251, 1.04654, 1.03685, 1.0449, null],
+  [1637301600000, 1.0449, 1.04971, 1.04109, 1.04452, null],
+  [1637305200000, 1.04456, 1.04875, 1.03802, 1.04268, null],
+  [1637308800000, 1.04239, 1.06573, 1.04164, 1.05717, null],
+  [1637312400000, 1.05721, 1.06464, 1.05619, 1.06051, null]
+]
diff --git a/tests/testdata/futures/XRP_USDT-8h-funding_rate.json b/tests/testdata/futures/XRP_USDT-8h-funding_rate.json
new file mode 100644
index 000000000..494da4efc
--- /dev/null
+++ b/tests/testdata/futures/XRP_USDT-8h-funding_rate.json
@@ -0,0 +1 @@
+[[1637193600017,0.0001,0.0,0.0,0.0,0.0],[1637222400007,0.0001,0.0,0.0,0.0,0.0],[1637251200011,0.0001,0.0,0.0,0.0,0.0],[1637280000000,0.0001,0.0,0.0,0.0,0.0],[1637308800000,0.0001,0.0,0.0,0.0,0.0],[1637337600005,0.0001,0.0,0.0,0.0,0.0],[1637366400012,0.00013046,0.0,0.0,0.0,0.0],[1637395200000,0.0001,0.0,0.0,0.0,0.0],[1637424000007,0.0001,0.0,0.0,0.0,0.0],[1637452800000,0.00013862,0.0,0.0,0.0,0.0],[1637481600006,0.0001,0.0,0.0,0.0,0.0],[1637510400000,0.00019881,0.0,0.0,0.0,0.0],[1637539200004,0.00013991,0.0,0.0,0.0,0.0],[1637568000000,0.0001,0.0,0.0,0.0,0.0],[1637596800000,0.0001,0.0,0.0,0.0,0.0],[1637625600004,0.0001,0.0,0.0,0.0,0.0],[1637654400010,0.0001,0.0,0.0,0.0,0.0],[1637683200005,0.00017402,0.0,0.0,0.0,0.0],[1637712000001,0.00016775,0.0,0.0,0.0,0.0],[1637740800003,0.00033523,0.0,0.0,0.0,0.0],[1637769600010,0.0001,0.0,0.0,0.0,0.0],[1637798400000,0.00020066,0.0,0.0,0.0,0.0],[1637827200010,0.00034381,0.0,0.0,0.0,0.0],[1637856000000,0.00032096,0.0,0.0,0.0,0.0],[1637884800000,0.00058316,0.0,0.0,0.0,0.0],[1637913600000,0.0001646,0.0,0.0,0.0,0.0],[1637942400016,0.0001,0.0,0.0,0.0,0.0],[1637971200005,0.0001,0.0,0.0,0.0,0.0],[1638000000008,0.0001,0.0,0.0,0.0,0.0],[1638028800007,0.0001,0.0,0.0,0.0,0.0],[1638057600018,0.0001,0.0,0.0,0.0,0.0],[1638086400000,0.0001,0.0,0.0,0.0,0.0],[1638115200004,0.0001,0.0,0.0,0.0,0.0],[1638144000002,0.0001,0.0,0.0,0.0,0.0],[1638172800004,0.0001,0.0,0.0,0.0,0.0],[1638201600000,0.0001,0.0,0.0,0.0,0.0],[1638230400000,0.0001,0.0,0.0,0.0,0.0],[1638259200006,0.0001,0.0,0.0,0.0,0.0],[1638288000000,0.0001,0.0,0.0,0.0,0.0],[1638316800000,0.0001,0.0,0.0,0.0,0.0],[1638345600000,0.0001,0.0,0.0,0.0,0.0],[1638374400001,0.0001,0.0,0.0,0.0,0.0],[1638403200000,0.0001,0.0,0.0,0.0,0.0],[1638432000007,0.0001,0.0,0.0,0.0,0.0],[1638460800008,0.0001,0.0,0.0,0.0,0.0],[1638489600004,0.0001,0.0,0.0,0.0,0.0],[1638518400002,0.0001,0.0,0.0,0.0,0.0],[1638547200006,0.0001,0.0,0.0,0.0,0.0],[1638576000006,0.0001,0.0,0.0,0.0,0.0],[1638604800004,-0.00219334,0.0,0.0,0.0,0.0],[1638633600000,0.0001,0.0,0.0,0.0,0.0],[1638662400003,0.00006147,0.0,0.0,0.0,0.0],[1638691200008,0.0001,0.0,0.0,0.0,0.0],[1638720000007,0.0001,0.0,0.0,0.0,0.0],[1638748800009,0.0001,0.0,0.0,0.0,0.0],[1638777600001,0.0001,0.0,0.0,0.0,0.0],[1638806400000,0.0001,0.0,0.0,0.0,0.0],[1638835200018,0.0001,0.0,0.0,0.0,0.0],[1638864000000,0.0001,0.0,0.0,0.0,0.0],[1638892800000,0.0001,0.0,0.0,0.0,0.0],[1638921600000,0.0001,0.0,0.0,0.0,0.0],[1638950400018,0.0001,0.0,0.0,0.0,0.0],[1638979200010,0.0001,0.0,0.0,0.0,0.0],[1639008000010,0.0001,0.0,0.0,0.0,0.0],[1639036800000,0.0001,0.0,0.0,0.0,0.0],[1639065600000,0.0001,0.0,0.0,0.0,0.0],[1639094400000,0.0001,0.0,0.0,0.0,0.0],[1639123200008,0.0001,0.0,0.0,0.0,0.0],[1639152000012,0.00008995,0.0,0.0,0.0,0.0],[1639180800009,0.0001,0.0,0.0,0.0,0.0],[1639209600008,-0.00002574,0.0,0.0,0.0,0.0],[1639238400000,-0.00002024,0.0,0.0,0.0,0.0],[1639267200001,-0.00008282,0.0,0.0,0.0,0.0],[1639296000015,0.0001,0.0,0.0,0.0,0.0],[1639324800011,0.00008752,0.0,0.0,0.0,0.0],[1639353600006,0.0001,0.0,0.0,0.0,0.0],[1639382400019,0.0001,0.0,0.0,0.0,0.0],[1639411200000,0.0001,0.0,0.0,0.0,0.0],[1639440000004,0.00007825,0.0,0.0,0.0,0.0],[1639468800000,0.00007108,0.0,0.0,0.0,0.0],[1639497600015,0.0001,0.0,0.0,0.0,0.0],[1639526400000,0.0001,0.0,0.0,0.0,0.0],[1639555200008,0.0001,0.0,0.0,0.0,0.0],[1639584000005,0.0001,0.0,0.0,0.0,0.0],[1639612800006,0.0001,0.0,0.0,0.0,0.0],[1639641600009,0.0001,0.0,0.0,0.0,0.0],[1639670400000,0.0001,0.0,0.0,0.0,0.0],[1639699200000,0.0001,0.0,0.0,0.0,0.0],[1639728000005,0.0001,0.0,0.0,0.0,0.0],[1639756800006,0.0001,0.0,0.0,0.0,0.0],[1639785600014,0.0001,0.0,0.0,0.0,0.0]]
\ No newline at end of file
diff --git a/tests/testdata/futures/XRP_USDT-8h-mark.json b/tests/testdata/futures/XRP_USDT-8h-mark.json
new file mode 100644
index 000000000..63dad259b
--- /dev/null
+++ b/tests/testdata/futures/XRP_USDT-8h-mark.json
@@ -0,0 +1 @@
+[[1637193600000,1.0959,1.162,1.0907,1.1074,523374743.8000000119],[1637222400000,1.1075,1.1104,1.045,1.0563,429699821.3999999762],[1637251200000,1.0564,1.0635,1.0145,1.041,417701240.6000000238],[1637280000000,1.0411,1.0572,1.0179,1.0421,262751968.599999994],[1637308800000,1.042,1.1034,1.0418,1.0891,322658150.1999999881],[1637337600000,1.0891,1.099,1.0748,1.0903,176970752.400000006],[1637366400000,1.0903,1.1005,1.0821,1.0856,125726657.400000006],[1637395200000,1.0857,1.1024,1.06,1.0657,193947922.5],[1637424000000,1.0656,1.0987,1.0619,1.0976,165812883.599999994],[1637452800000,1.0975,1.0988,1.0732,1.0803,103157439.799999997],[1637481600000,1.0804,1.0818,1.0638,1.0788,139946704.400000006],[1637510400000,1.0787,1.0867,1.055,1.0581,155236087.3000000119],[1637539200000,1.0582,1.0604,1.026,1.0433,245459370.400000006],[1637568000000,1.0434,1.072,1.0373,1.0577,214156908.400000006],[1637596800000,1.0577,1.0598,1.0284,1.0366,171637007.0],[1637625600000,1.0365,1.0569,1.0311,1.0368,133990133.0],[1637654400000,1.0367,1.0623,1.02,1.0474,300886007.5],[1637683200000,1.0472,1.0725,1.043,1.067,164993866.900000006],[1637712000000,1.0671,1.0741,1.0328,1.0398,162787182.099999994],[1637740800000,1.0397,1.0496,1.005,1.0287,263357085.900000006],[1637769600000,1.0287,1.0343,1.0142,1.0329,142076018.1999999881],[1637798400000,1.0329,1.0525,1.0266,1.0332,151346926.0],[1637827200000,1.0333,1.0597,1.023,1.0529,210738649.0],[1637856000000,1.053,1.0663,1.041,1.0447,169577266.5],[1637884800000,1.0448,1.0479,1.0,1.0145,243945720.900000006],[1637913600000,1.0144,1.0146,0.8836,0.9465,1033033518.6000000238],[1637942400000,0.9467,0.9608,0.9333,0.9392,185904492.1999999881],[1637971200000,0.9392,0.9614,0.9354,0.947,133557450.400000006],[1638000000000,0.947,0.9659,0.9466,0.9563,130188025.599999994],[1638028800000,0.9562,0.9615,0.9338,0.9455,143028245.599999994],[1638057600000,0.9455,0.947,0.8779,0.93,306498284.8999999762],[1638086400000,0.93,0.9415,0.9177,0.9257,126269337.799999997],[1638115200000,0.9256,0.9693,0.8855,0.9686,298275834.3000000119],[1638144000000,0.9686,0.9954,0.9661,0.99,178517855.1999999881],[1638172800000,0.99,0.9926,0.9632,0.9772,199170626.8000000119],[1638201600000,0.9772,1.0024,0.973,0.9901,226187446.0],[1638230400000,0.9901,1.0154,0.9718,0.9833,239524176.3000000119],[1638259200000,0.9834,1.0301,0.97,1.0065,296499649.0],[1638288000000,1.0064,1.0138,0.9845,0.9989,232078115.8000000119],[1638316800000,0.9989,1.0182,0.9934,1.0143,118435865.599999994],[1638345600000,1.0143,1.017,0.9966,1.0119,169147098.3000000119],[1638374400000,1.0118,1.0182,0.98,0.9906,181653125.0],[1638403200000,0.9906,0.9909,0.9545,0.9746,168864095.6999999881],[1638432000000,0.9745,0.9844,0.9629,0.9748,153223080.3000000119],[1638460800000,0.9748,0.9843,0.9645,0.9722,110476722.5],[1638489600000,0.9722,0.981,0.9583,0.9779,132304244.599999994],[1638518400000,0.978,0.984,0.9569,0.9615,147970618.1999999881],[1638547200000,0.9614,0.9614,0.8854,0.9213,403564589.8000000119],[1638576000000,0.9212,0.9246,0.5764,0.7497,1544746782.7000000477],[1638604800000,0.7497,0.8066,0.7405,0.792,741292824.2999999523],[1638633600000,0.792,0.8574,0.7855,0.8449,360411800.1999999881],[1638662400000,0.8449,0.8623,0.8131,0.8382,270770494.1000000238],[1638691200000,0.8381,0.8437,0.7674,0.7897,459358701.5],[1638720000000,0.7897,0.8099,0.7816,0.8041,355021022.1999999881],[1638748800000,0.8041,0.8068,0.7619,0.7747,268906281.8000000119],[1638777600000,0.7748,0.7939,0.7475,0.7934,511486538.1000000238],[1638806400000,0.7934,0.8299,0.786,0.8251,246683461.0],[1638835200000,0.8252,0.8459,0.8209,0.8303,176572777.6999999881],[1638864000000,0.8303,0.844,0.8133,0.8368,248783345.1999999881],[1638892800000,0.8368,0.8386,0.8037,0.8154,188883508.6999999881],[1638921600000,0.8155,0.8437,0.8037,0.8404,168332179.400000006],[1638950400000,0.8404,0.8796,0.8022,0.8669,452844121.1999999881],[1638979200000,0.8668,0.8842,0.85,0.862,291431732.3000000119],[1639008000000,0.8619,0.8718,0.842,0.8602,203577851.900000006],[1639036800000,0.8603,0.934,0.8315,0.8911,1062305914.5],[1639065600000,0.8913,0.9045,0.8433,0.8582,451002103.3999999762],[1639094400000,0.8582,0.8829,0.8252,0.8333,426994850.3999999762],[1639123200000,0.8333,0.8677,0.8155,0.8234,472180388.3000000119],[1639152000000,0.8234,0.8365,0.791,0.7985,293320072.3999999762],[1639180800000,0.7984,0.8369,0.7837,0.826,287918666.5],[1639209600000,0.8261,0.8433,0.8228,0.8376,194875683.0],[1639238400000,0.8376,0.8442,0.8201,0.8388,171763908.1999999881],[1639267200000,0.8388,0.842,0.8151,0.8244,165764217.599999994],[1639296000000,0.8243,0.83,0.8081,0.822,154383486.900000006],[1639324800000,0.8221,0.857,0.8201,0.8393,188392105.5],[1639353600000,0.8393,0.8444,0.8165,0.8272,174739478.8000000119],[1639382400000,0.8271,0.8381,0.7923,0.7991,303947463.0],[1639411200000,0.7992,0.807,0.76,0.7815,426182302.3000000119],[1639440000000,0.7815,0.7966,0.7716,0.7891,237419158.400000006],[1639468800000,0.7891,0.8178,0.7787,0.7927,307108359.8999999762],[1639497600000,0.7926,0.8212,0.7883,0.8101,235803972.599999994],[1639526400000,0.8101,0.8151,0.8009,0.8102,155175275.900000006],[1639555200000,0.8102,0.8176,0.7767,0.7781,222914476.5],[1639584000000,0.778,0.8396,0.7768,0.8259,439618329.1000000238],[1639612800000,0.826,0.8361,0.8181,0.8238,133573251.099999994],[1639641600000,0.8238,0.8335,0.8191,0.8263,140555140.3000000119],[1639670400000,0.8263,0.8274,0.8025,0.8045,155672857.0],[1639699200000,0.8047,0.8323,0.8013,0.8124,173377367.599999994],[1639728000000,0.8124,0.8151,0.7749,0.7953,243494249.400000006],[1639756800000,0.7953,0.8109,0.7871,0.7963,186657940.1999999881],[1639785600000,0.7963,0.8159,0.7904,0.8124,144712394.5]]
\ No newline at end of file