+
Freqtrade UI not installed.
+
Please run `freqtrade install-ui` in your terminal to install the UI files and restart your bot.
+
You can then refresh this page and you should see the UI.
+
+
+
diff --git a/freqtrade/rpc/api_server/ui/favicon.ico b/freqtrade/rpc/api_server/ui/favicon.ico
new file mode 100644
index 000000000..78c7e43b1
Binary files /dev/null and b/freqtrade/rpc/api_server/ui/favicon.ico differ
diff --git a/freqtrade/rpc/api_server/ui/installed/.gitkeep b/freqtrade/rpc/api_server/ui/installed/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/freqtrade/rpc/api_server/web_ui.py b/freqtrade/rpc/api_server/web_ui.py
new file mode 100644
index 000000000..6d7e77953
--- /dev/null
+++ b/freqtrade/rpc/api_server/web_ui.py
@@ -0,0 +1,31 @@
+from pathlib import Path
+
+from fastapi import APIRouter
+from fastapi.exceptions import HTTPException
+from starlette.responses import FileResponse
+
+
+router_ui = APIRouter()
+
+
+@router_ui.get('/favicon.ico', include_in_schema=False)
+async def favicon():
+ return FileResponse(Path(__file__).parent / 'ui/favicon.ico')
+
+
+@router_ui.get('/{rest_of_path:path}', include_in_schema=False)
+async def index_html(rest_of_path: str):
+ """
+ Emulate path fallback to index.html.
+ """
+ if rest_of_path.startswith('api') or rest_of_path.startswith('.'):
+ raise HTTPException(status_code=404, detail="Not Found")
+ uibase = Path(__file__).parent / 'ui/installed/'
+ if (uibase / rest_of_path).is_file():
+ return FileResponse(str(uibase / rest_of_path))
+
+ index_file = uibase / 'index.html'
+ if not index_file.is_file():
+ return FileResponse(str(uibase.parent / 'fallback_file.html'))
+ # Fall back to index.html, as indicated by vue router docs
+ return FileResponse(str(index_file))
diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py
index 9c0779274..f3eaa1ebc 100644
--- a/freqtrade/rpc/api_server/webserver.py
+++ b/freqtrade/rpc/api_server/webserver.py
@@ -57,12 +57,16 @@ class ApiServer(RPCHandler):
from freqtrade.rpc.api_server.api_auth import http_basic_or_jwt_token, router_login
from freqtrade.rpc.api_server.api_v1 import router as api_v1
from freqtrade.rpc.api_server.api_v1 import router_public as api_v1_public
+ from freqtrade.rpc.api_server.web_ui import router_ui
+
app.include_router(api_v1_public, prefix="/api/v1")
app.include_router(api_v1, prefix="/api/v1",
dependencies=[Depends(http_basic_or_jwt_token)],
)
app.include_router(router_login, prefix="/api/v1", tags=["auth"])
+ # UI Router MUST be last!
+ app.include_router(router_ui, prefix='')
app.add_middleware(
CORSMiddleware,
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index 491d1cde6..7549c38be 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -9,7 +9,7 @@ from math import isnan
from typing import Any, Dict, List, Optional, Tuple, Union
import arrow
-from numpy import NAN, int64, mean
+from numpy import NAN, inf, int64, mean
from pandas import DataFrame
from freqtrade.configuration.timerange import TimeRange
@@ -451,7 +451,7 @@ class RPC:
pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency)
rate = tickers.get(pair, {}).get('bid', None)
if rate:
- if pair.startswith(stake_currency):
+ if pair.startswith(stake_currency) and not pair.endswith(stake_currency):
rate = 1.0 / rate
est_stake = rate * balance.total
except (ExchangeError):
@@ -590,7 +590,8 @@ class RPC:
raise RPCException(f'position for {pair} already open - id: {trade.id}')
# gen stake amount
- stakeamount = self._freqtrade.get_trade_stake_amount(pair)
+ stakeamount = self._freqtrade.wallets.get_trade_stake_amount(
+ pair, self._freqtrade.get_free_open_trades())
# execute buy
if self._freqtrade.execute_buy(pair, stakeamount, price):
@@ -746,6 +747,7 @@ class RPC:
sell_mask = (dataframe['sell'] == 1)
sell_signals = int(sell_mask.sum())
dataframe.loc[sell_mask, '_sell_signal_open'] = dataframe.loc[sell_mask, 'open']
+ dataframe = dataframe.replace([inf, -inf], NAN)
dataframe = dataframe.replace({NAN: None})
res = {
@@ -774,7 +776,8 @@ class RPC:
})
return res
- def _rpc_analysed_dataframe(self, pair: str, timeframe: str, limit: int) -> Dict[str, Any]:
+ def _rpc_analysed_dataframe(self, pair: str, timeframe: str,
+ limit: Optional[int]) -> Dict[str, Any]:
_data, last_analyzed = self._freqtrade.dataprovider.get_analyzed_dataframe(
pair, timeframe)
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 80d7be60f..87cd7b43d 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -18,6 +18,7 @@ from telegram.utils.helpers import escape_markdown
from freqtrade.__init__ import __version__
from freqtrade.exceptions import OperationalException
+from freqtrade.misc import round_coin_value
from freqtrade.rpc import RPC, RPCException, RPCHandler, RPCMessageType
@@ -205,14 +206,14 @@ class Telegram(RPCHandler):
else:
msg['stake_amount_fiat'] = 0
- message = ("\N{LARGE BLUE CIRCLE} *{exchange}:* Buying {pair}\n"
- "*Amount:* `{amount:.8f}`\n"
- "*Open Rate:* `{limit:.8f}`\n"
- "*Current Rate:* `{current_rate:.8f}`\n"
- "*Total:* `({stake_amount:.6f} {stake_currency}").format(**msg)
+ message = (f"\N{LARGE BLUE CIRCLE} *{msg['exchange']}:* Buying {msg['pair']}\n"
+ f"*Amount:* `{msg['amount']:.8f}`\n"
+ f"*Open Rate:* `{msg['limit']:.8f}`\n"
+ f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
+ f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}")
if msg.get('fiat_currency', None):
- message += ", {stake_amount_fiat:.3f} {fiat_currency}".format(**msg)
+ message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
message += ")`"
elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION:
@@ -352,6 +353,7 @@ class Telegram(RPCHandler):
try:
statlist, head = self._rpc._rpc_status_table(
self._config['stake_currency'], self._config.get('fiat_display_currency', ''))
+
message = tabulate(statlist, headers=head, tablefmt='simple')
if(update.callback_query):
query = update.callback_query
@@ -384,7 +386,7 @@ class Telegram(RPCHandler):
)
stats_tab = tabulate(
[[day['date'],
- f"{day['abs_profit']:.8f} {stats['stake_currency']}",
+ f"{round_coin_value(day['abs_profit'], stats['stake_currency'])}",
f"{day['fiat_value']:.3f} {stats['fiat_display_currency']}",
f"{day['trade_count']} trades"] for day in stats['data']],
headers=[
@@ -438,18 +440,18 @@ class Telegram(RPCHandler):
# Message to display
if stats['closed_trade_count'] > 0:
markdown_msg = ("*ROI:* Closed trades\n"
- f"∙ `{profit_closed_coin:.8f} {stake_cur} "
+ f"∙ `{round_coin_value(profit_closed_coin, stake_cur)} "
f"({profit_closed_percent_mean:.2f}%) "
f"({profit_closed_percent_sum} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
- f"∙ `{profit_closed_fiat:.3f} {fiat_disp_cur}`\n")
+ f"∙ `{round_coin_value(profit_closed_fiat, fiat_disp_cur)}`\n")
else:
markdown_msg = "`No closed trade` \n"
markdown_msg += (f"*ROI:* All trades\n"
- f"∙ `{profit_all_coin:.8f} {stake_cur} "
+ f"∙ `{round_coin_value(profit_all_coin, stake_cur)} "
f"({profit_all_percent_mean:.2f}%) "
f"({profit_all_percent_sum} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
- f"∙ `{profit_all_fiat:.3f} {fiat_disp_cur}`\n"
+ f"∙ `{round_coin_value(profit_all_fiat, fiat_disp_cur)}`\n"
f"*Total Trade Count:* `{trade_count}`\n"
f"*First Trade opened:* `{first_trade_date}`\n"
f"*Latest Trade opened:* `{latest_trade_date}\n`"
@@ -521,15 +523,17 @@ class Telegram(RPCHandler):
"Starting capital: "
f"`{self._config['dry_run_wallet']}` {self._config['stake_currency']}.\n"
)
- for currency in result['currencies']:
- if currency['est_stake'] > 0.0001:
- curr_output = ("*{currency}:*\n"
- "\t`Available: {free: .8f}`\n"
- "\t`Balance: {balance: .8f}`\n"
- "\t`Pending: {used: .8f}`\n"
- "\t`Est. {stake}: {est_stake: .8f}`\n").format(**currency)
+ for curr in result['currencies']:
+ if curr['est_stake'] > 0.0001:
+ 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")
else:
- curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency)
+ curr_output = f"*{curr['currency']}:* not showing <1$ amount \n"
# Handle overflowing messsage length
if len(output + curr_output) >= MAX_TELEGRAM_MESSAGE_LENGTH:
@@ -539,8 +543,9 @@ class Telegram(RPCHandler):
output += curr_output
output += ("\n*Estimated Value*:\n"
- "\t`{stake}: {total: .8f}`\n"
- "\t`{symbol}: {value: .2f}`\n").format(**result)
+ f"\t`{result['stake']}: {result['total']: .8f}`\n"
+ f"\t`{result['symbol']}: "
+ f"{round_coin_value(result['value'], result['symbol'], False)}`\n")
if(update.callback_query):
query = update.callback_query
self._update_msg(chat_id=query.message.chat_id, message_id=query.message.message_id, msg=output, callback_path="update_balance", reload_able=True)
diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2
index 4a1b43e36..dd6b773e1 100644
--- a/freqtrade/templates/base_strategy.py.j2
+++ b/freqtrade/templates/base_strategy.py.j2
@@ -5,7 +5,7 @@ import numpy as np # noqa
import pandas as pd # noqa
from pandas import DataFrame
-from freqtrade.strategy.interface import IStrategy
+from freqtrade.strategy import IStrategy
# --------------------------------
# Add your lib to import here
diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py
index b3f9fef07..db1ba48b8 100644
--- a/freqtrade/templates/sample_strategy.py
+++ b/freqtrade/templates/sample_strategy.py
@@ -17,7 +17,7 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
class SampleStrategy(IStrategy):
"""
This is a sample strategy to inspire you.
- More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md
+ More information in https://www.freqtrade.io/en/latest/strategy-customization/
You can:
:return: a Dataframe with all mandatory indicators for the strategies
diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py
index 3680dd416..d7dcfd487 100644
--- a/freqtrade/wallets.py
+++ b/freqtrade/wallets.py
@@ -7,6 +7,8 @@ from typing import Any, Dict, NamedTuple
import arrow
+from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
+from freqtrade.exceptions import DependencyException
from freqtrade.exchange import Exchange
from freqtrade.persistence import Trade
@@ -118,3 +120,79 @@ class Wallets:
def get_all_balances(self) -> Dict[str, Any]:
return self._wallets
+
+ def _get_available_stake_amount(self) -> float:
+ """
+ Return the total currently available balance in stake currency,
+ respecting tradable_balance_ratio.
+ Calculated as
+ (