Merge branch 'develop' into feat/freqai
This commit is contained in:
@@ -314,6 +314,10 @@ CONF_SCHEMA = {
|
||||
'type': 'string',
|
||||
'enum': TELEGRAM_SETTING_OPTIONS,
|
||||
},
|
||||
'show_candle': {
|
||||
'type': 'string',
|
||||
'enum': ['off', 'ohlc'],
|
||||
},
|
||||
}
|
||||
},
|
||||
'reload': {'type': 'boolean'},
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -46,6 +46,7 @@ MAP_EXCHANGE_CHILDCLASS = {
|
||||
'binanceje': 'binance',
|
||||
'binanceusdm': 'binance',
|
||||
'okex': 'okx',
|
||||
'gate': 'gateio',
|
||||
}
|
||||
|
||||
SUPPORTED_EXCHANGES = [
|
||||
@@ -63,17 +64,16 @@ EXCHANGE_HAS_REQUIRED = [
|
||||
'fetchOrder',
|
||||
'cancelOrder',
|
||||
'createOrder',
|
||||
# 'createLimitOrder', 'createMarketOrder',
|
||||
'fetchBalance',
|
||||
|
||||
# Public endpoints
|
||||
'loadMarkets',
|
||||
'fetchOHLCV',
|
||||
]
|
||||
|
||||
EXCHANGE_HAS_OPTIONAL = [
|
||||
# Private
|
||||
'fetchMyTrades', # Trades for order - fee detection
|
||||
'createLimitOrder', 'createMarketOrder', # Either OR for orders
|
||||
# 'setLeverage', # Margin/Futures trading
|
||||
# 'setMarginMode', # Margin/Futures trading
|
||||
# 'fetchFundingHistory', # Futures trading
|
||||
|
@@ -77,7 +77,9 @@ class Exchange:
|
||||
"mark_ohlcv_price": "mark",
|
||||
"mark_ohlcv_timeframe": "8h",
|
||||
"ccxt_futures_name": "swap",
|
||||
"fee_cost_in_contracts": False, # Fee cost needs contract conversion
|
||||
"needs_trading_fees": False, # use fetch_trading_fees to cache fees
|
||||
"order_props_in_contracts": ['amount', 'cost', 'filled', 'remaining'],
|
||||
}
|
||||
_ft_has: Dict = {}
|
||||
_ft_has_futures: Dict = {}
|
||||
@@ -174,23 +176,11 @@ class Exchange:
|
||||
logger.info(f'Using Exchange "{self.name}"')
|
||||
|
||||
if validate:
|
||||
# Check if timeframe is available
|
||||
self.validate_timeframes(config.get('timeframe'))
|
||||
|
||||
# Initial markets load
|
||||
self._load_markets()
|
||||
|
||||
# Check if all pairs are available
|
||||
self.validate_stakecurrency(config['stake_currency'])
|
||||
if not exchange_config.get('skip_pair_validation'):
|
||||
self.validate_pairs(config['exchange']['pair_whitelist'])
|
||||
self.validate_ordertypes(config.get('order_types', {}))
|
||||
self.validate_order_time_in_force(config.get('order_time_in_force', {}))
|
||||
self.validate_config(config)
|
||||
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(
|
||||
@@ -213,6 +203,20 @@ class Exchange:
|
||||
logger.info("Closing async ccxt session.")
|
||||
self.loop.run_until_complete(self._api_async.close())
|
||||
|
||||
def validate_config(self, config):
|
||||
# Check if timeframe is available
|
||||
self.validate_timeframes(config.get('timeframe'))
|
||||
|
||||
# Check if all pairs are available
|
||||
self.validate_stakecurrency(config['stake_currency'])
|
||||
if not config['exchange'].get('skip_pair_validation'):
|
||||
self.validate_pairs(config['exchange']['pair_whitelist'])
|
||||
self.validate_ordertypes(config.get('order_types', {}))
|
||||
self.validate_order_time_in_force(config.get('order_time_in_force', {}))
|
||||
self.validate_trading_mode_and_margin_mode(self.trading_mode, self.margin_mode)
|
||||
self.validate_pricing(config['exit_pricing'])
|
||||
self.validate_pricing(config['entry_pricing'])
|
||||
|
||||
def _init_ccxt(self, exchange_config: Dict[str, Any], ccxt_module: CcxtModuleType = ccxt,
|
||||
ccxt_kwargs: Dict = {}) -> ccxt.Exchange:
|
||||
"""
|
||||
@@ -422,7 +426,7 @@ class Exchange:
|
||||
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']:
|
||||
for prop in self._ft_has.get('order_props_in_contracts', []):
|
||||
if prop in order and order[prop] is not None:
|
||||
order[prop] = order[prop] * contract_size
|
||||
return order
|
||||
@@ -820,7 +824,7 @@ class Exchange:
|
||||
'price': rate,
|
||||
'average': rate,
|
||||
'amount': _amount,
|
||||
'cost': _amount * rate / leverage,
|
||||
'cost': _amount * rate,
|
||||
'type': ordertype,
|
||||
'side': side,
|
||||
'filled': 0,
|
||||
@@ -1631,27 +1635,35 @@ class Exchange:
|
||||
and order['fee']['cost'] is not None
|
||||
)
|
||||
|
||||
def calculate_fee_rate(self, order: Dict) -> Optional[float]:
|
||||
def calculate_fee_rate(
|
||||
self, fee: Dict, symbol: str, cost: float, amount: float) -> Optional[float]:
|
||||
"""
|
||||
Calculate fee rate if it's not given by the exchange.
|
||||
:param order: Order or trade (one trade) dict
|
||||
:param fee: ccxt Fee dict - must contain cost / currency / rate
|
||||
:param symbol: Symbol of the order
|
||||
:param cost: Total cost of the order
|
||||
:param amount: Amount of the order
|
||||
"""
|
||||
if order['fee'].get('rate') is not None:
|
||||
return order['fee'].get('rate')
|
||||
fee_curr = order['fee']['currency']
|
||||
if fee.get('rate') is not None:
|
||||
return fee.get('rate')
|
||||
fee_curr = fee.get('currency')
|
||||
if fee_curr is None:
|
||||
return None
|
||||
fee_cost = float(fee['cost'])
|
||||
if self._ft_has['fee_cost_in_contracts']:
|
||||
# Convert cost via "contracts" conversion
|
||||
fee_cost = self._contracts_to_amount(symbol, fee['cost'])
|
||||
|
||||
# Calculate fee based on order details
|
||||
if fee_curr in self.get_pair_base_currency(order['symbol']):
|
||||
if fee_curr == self.get_pair_base_currency(symbol):
|
||||
# Base currency - divide by amount
|
||||
return round(
|
||||
order['fee']['cost'] / safe_value_fallback2(order, order, 'filled', 'amount'), 8)
|
||||
elif fee_curr in self.get_pair_quote_currency(order['symbol']):
|
||||
return round(fee_cost / amount, 8)
|
||||
elif fee_curr == self.get_pair_quote_currency(symbol):
|
||||
# Quote currency - divide by cost
|
||||
return round(self._contracts_to_amount(
|
||||
order['symbol'], order['fee']['cost']) / order['cost'],
|
||||
8) if order['cost'] else None
|
||||
return round(fee_cost / cost, 8) if cost else None
|
||||
else:
|
||||
# If Fee currency is a different currency
|
||||
if not order['cost']:
|
||||
if not cost:
|
||||
# If cost is None or 0.0 -> falsy, return None
|
||||
return None
|
||||
try:
|
||||
@@ -1663,19 +1675,28 @@ class Exchange:
|
||||
fee_to_quote_rate = self._config['exchange'].get('unknown_fee_rate', None)
|
||||
if not fee_to_quote_rate:
|
||||
return None
|
||||
return round((self._contracts_to_amount(
|
||||
order['symbol'], order['fee']['cost']) * fee_to_quote_rate) / order['cost'], 8)
|
||||
return round((fee_cost * fee_to_quote_rate) / cost, 8)
|
||||
|
||||
def extract_cost_curr_rate(self, order: Dict) -> Tuple[float, str, Optional[float]]:
|
||||
def extract_cost_curr_rate(self, fee: Dict, symbol: str, cost: float,
|
||||
amount: float) -> Tuple[float, str, Optional[float]]:
|
||||
"""
|
||||
Extract tuple of cost, currency, rate.
|
||||
Requires order_has_fee to run first!
|
||||
:param order: Order or trade (one trade) dict
|
||||
:param fee: ccxt Fee dict - must contain cost / currency / rate
|
||||
:param symbol: Symbol of the order
|
||||
:param cost: Total cost of the order
|
||||
:param amount: Amount of the order
|
||||
:return: Tuple with cost, currency, rate of the given fee dict
|
||||
"""
|
||||
return (order['fee']['cost'],
|
||||
order['fee']['currency'],
|
||||
self.calculate_fee_rate(order))
|
||||
return (float(fee['cost']),
|
||||
fee['currency'],
|
||||
self.calculate_fee_rate(
|
||||
fee,
|
||||
symbol,
|
||||
cost,
|
||||
amount
|
||||
)
|
||||
)
|
||||
|
||||
# Historic data
|
||||
|
||||
|
@@ -1,12 +1,13 @@
|
||||
""" Gate.io exchange subclass """
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from freqtrade.constants import BuySell
|
||||
from freqtrade.enums import MarginMode, TradingMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.misc import safe_value_fallback2
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -32,7 +33,9 @@ class Gateio(Exchange):
|
||||
}
|
||||
|
||||
_ft_has_futures: Dict = {
|
||||
"needs_trading_fees": True
|
||||
"needs_trading_fees": True,
|
||||
"fee_cost_in_contracts": False, # Set explicitly to false for clarity
|
||||
"order_props_in_contracts": ['amount', 'filled', 'remaining'],
|
||||
}
|
||||
|
||||
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
|
||||
@@ -95,12 +98,29 @@ class Gateio(Exchange):
|
||||
}
|
||||
return trades
|
||||
|
||||
def get_order_id_conditional(self, order: Dict[str, Any]) -> str:
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
return safe_value_fallback2(order, order, 'id_stop', 'id')
|
||||
return order['id']
|
||||
|
||||
def fetch_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
|
||||
return self.fetch_order(
|
||||
order = self.fetch_order(
|
||||
order_id=order_id,
|
||||
pair=pair,
|
||||
params={'stop': True}
|
||||
)
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
if order['status'] == 'closed':
|
||||
# Places a real order - which we need to fetch explicitly.
|
||||
new_orderid = order.get('info', {}).get('trade_id')
|
||||
if new_orderid:
|
||||
order1 = self.fetch_order(order_id=new_orderid, pair=pair, params=params)
|
||||
order1['id_stop'] = order1['id']
|
||||
order1['id'] = order_id
|
||||
order1['stopPrice'] = order.get('stopPrice')
|
||||
|
||||
return order1
|
||||
return order
|
||||
|
||||
def cancel_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
|
||||
return self.cancel_order(
|
||||
|
@@ -28,6 +28,7 @@ class Okx(Exchange):
|
||||
}
|
||||
_ft_has_futures: Dict = {
|
||||
"tickers_have_quoteVolume": False,
|
||||
"fee_cost_in_contracts": True,
|
||||
}
|
||||
|
||||
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
|
||||
|
@@ -332,6 +332,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
if not trade.is_open and not trade.fee_updated(trade.exit_side):
|
||||
# Get sell fee
|
||||
order = trade.select_order(trade.exit_side, False)
|
||||
if not order:
|
||||
order = trade.select_order('stoploss', False)
|
||||
if order:
|
||||
logger.info(
|
||||
f"Updating {trade.exit_side}-fee on trade {trade}"
|
||||
@@ -634,7 +636,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
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=enter_tag, side=trade_side):
|
||||
logger.info(f"User requested abortion of buying {pair}")
|
||||
logger.info(f"User denied entry for {pair}.")
|
||||
return False
|
||||
order = self.exchange.create_order(
|
||||
pair=pair,
|
||||
@@ -814,7 +816,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
pair=pair, current_time=datetime.now(timezone.utc),
|
||||
current_rate=enter_limit_requested, proposed_stake=stake_amount,
|
||||
min_stake=min_stake_amount, max_stake=min(max_stake_amount, stake_available),
|
||||
entry_tag=entry_tag, side=trade_side
|
||||
leverage=leverage, entry_tag=entry_tag, side=trade_side
|
||||
)
|
||||
|
||||
stake_amount = self.wallets.validate_stake_amount(
|
||||
@@ -1465,7 +1467,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
time_in_force=time_in_force, exit_reason=exit_reason,
|
||||
sell_reason=exit_reason, # sellreason -> compatibility
|
||||
current_time=datetime.now(timezone.utc)):
|
||||
logger.info(f"User requested abortion of {trade.pair} exit.")
|
||||
logger.info(f"User denied exit for {trade.pair}.")
|
||||
return False
|
||||
|
||||
try:
|
||||
@@ -1742,7 +1744,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
trade_base_currency = self.exchange.get_pair_base_currency(trade.pair)
|
||||
# use fee from order-dict if possible
|
||||
if self.exchange.order_has_fee(order):
|
||||
fee_cost, fee_currency, fee_rate = self.exchange.extract_cost_curr_rate(order)
|
||||
fee_cost, fee_currency, fee_rate = self.exchange.extract_cost_curr_rate(
|
||||
order['fee'], order['symbol'], order['cost'], order_obj.safe_filled)
|
||||
logger.info(f"Fee for Trade {trade} [{order_obj.ft_order_side}]: "
|
||||
f"{fee_cost:.8g} {fee_currency} - rate: {fee_rate}")
|
||||
if fee_rate is None or fee_rate < 0.02:
|
||||
@@ -1780,7 +1783,15 @@ class FreqtradeBot(LoggingMixin):
|
||||
for exectrade in trades:
|
||||
amount += exectrade['amount']
|
||||
if self.exchange.order_has_fee(exectrade):
|
||||
fee_cost_, fee_currency, fee_rate_ = self.exchange.extract_cost_curr_rate(exectrade)
|
||||
# Prefer singular fee
|
||||
fees = [exectrade['fee']]
|
||||
else:
|
||||
fees = exectrade.get('fees', [])
|
||||
for fee in fees:
|
||||
|
||||
fee_cost_, fee_currency, fee_rate_ = self.exchange.extract_cost_curr_rate(
|
||||
fee, exectrade['symbol'], exectrade['cost'], exectrade['amount']
|
||||
)
|
||||
fee_cost += fee_cost_
|
||||
if fee_rate_ is not None:
|
||||
fee_rate_array.append(fee_rate_)
|
||||
|
@@ -727,7 +727,7 @@ class Backtesting:
|
||||
pair=pair, current_time=current_time, current_rate=propose_rate,
|
||||
proposed_stake=stake_amount, min_stake=min_stake_amount,
|
||||
max_stake=min(stake_available, max_stake_amount),
|
||||
entry_tag=entry_tag, side=direction)
|
||||
leverage=leverage, entry_tag=entry_tag, side=direction)
|
||||
|
||||
stake_amount_val = self.wallets.validate_stake_amount(
|
||||
pair=pair,
|
||||
|
@@ -6,6 +6,7 @@ This module contains the hyperopt logic
|
||||
|
||||
import logging
|
||||
import random
|
||||
import sys
|
||||
import warnings
|
||||
from datetime import datetime, timezone
|
||||
from math import ceil
|
||||
@@ -17,6 +18,7 @@ import rapidjson
|
||||
from colorama import Fore, Style
|
||||
from colorama import init as colorama_init
|
||||
from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects
|
||||
from joblib.externals import cloudpickle
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, FTHYPT_FILEVERSION, LAST_BT_RESULT_FN
|
||||
@@ -87,6 +89,7 @@ class Hyperopt:
|
||||
self.backtesting._set_strategy(self.backtesting.strategylist[0])
|
||||
self.custom_hyperopt.strategy = self.backtesting.strategy
|
||||
|
||||
self.hyperopt_pickle_magic(self.backtesting.strategy.__class__.__bases__)
|
||||
self.custom_hyperoptloss: IHyperOptLoss = HyperOptLossResolver.load_hyperoptloss(
|
||||
self.config)
|
||||
self.calculate_loss = self.custom_hyperoptloss.hyperopt_loss_function
|
||||
@@ -137,6 +140,17 @@ class Hyperopt:
|
||||
logger.info(f"Removing `{p}`.")
|
||||
p.unlink()
|
||||
|
||||
def hyperopt_pickle_magic(self, bases) -> None:
|
||||
"""
|
||||
Hyperopt magic to allow strategy inheritance across files.
|
||||
For this to properly work, we need to register the module of the imported class
|
||||
to pickle as value.
|
||||
"""
|
||||
for modules in bases:
|
||||
if modules.__name__ != 'IStrategy':
|
||||
cloudpickle.register_pickle_by_value(sys.modules[modules.__module__])
|
||||
self.hyperopt_pickle_magic(modules.__bases__)
|
||||
|
||||
def _get_params_dict(self, dimensions: List[Dimension], raw_params: List[Any]) -> Dict:
|
||||
|
||||
# Ensure the number of dimensions match
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from sqlalchemy import inspect, text
|
||||
from sqlalchemy import inspect, select, text, tuple_, update
|
||||
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.persistence.trade_model import Order, Trade
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -251,31 +252,31 @@ def set_sqlite_to_wal(engine):
|
||||
|
||||
def fix_old_dry_orders(engine):
|
||||
with engine.begin() as connection:
|
||||
connection.execute(
|
||||
text(
|
||||
"""
|
||||
update orders
|
||||
set ft_is_open = 0
|
||||
where ft_is_open = 1 and (ft_trade_id, order_id) not in (
|
||||
select id, stoploss_order_id from trades where stoploss_order_id is not null
|
||||
) and ft_order_side = 'stoploss'
|
||||
and order_id like 'dry_%'
|
||||
"""
|
||||
)
|
||||
)
|
||||
connection.execute(
|
||||
text(
|
||||
"""
|
||||
update orders
|
||||
set ft_is_open = 0
|
||||
where ft_is_open = 1
|
||||
and (ft_trade_id, order_id) not in (
|
||||
select id, open_order_id from trades where open_order_id is not null
|
||||
) and ft_order_side != 'stoploss'
|
||||
and order_id like 'dry_%'
|
||||
"""
|
||||
)
|
||||
)
|
||||
stmt = update(Order).where(
|
||||
Order.ft_is_open.is_(True),
|
||||
tuple_(Order.ft_trade_id, Order.order_id).not_in(
|
||||
select(
|
||||
Trade.id, Trade.stoploss_order_id
|
||||
).where(Trade.stoploss_order_id.is_not(None))
|
||||
),
|
||||
Order.ft_order_side == 'stoploss',
|
||||
Order.order_id.like('dry%'),
|
||||
|
||||
).values(ft_is_open=False)
|
||||
connection.execute(stmt)
|
||||
|
||||
stmt = update(Order).where(
|
||||
Order.ft_is_open.is_(True),
|
||||
tuple_(Order.ft_trade_id, Order.order_id).not_in(
|
||||
select(
|
||||
Trade.id, Trade.open_order_id
|
||||
).where(Trade.open_order_id.is_not(None))
|
||||
),
|
||||
Order.ft_order_side != 'stoploss',
|
||||
Order.order_id.like('dry%')
|
||||
|
||||
).values(ft_is_open=False)
|
||||
connection.execute(stmt)
|
||||
|
||||
|
||||
def check_migrate(engine, decl_base, previous_tables) -> None:
|
||||
|
@@ -821,7 +821,7 @@ class LocalTrade():
|
||||
self.open_rate = total_stake / total_amount
|
||||
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.fee_open_cost = self.fee_open * total_stake
|
||||
self.recalc_open_trade_value()
|
||||
if self.stop_loss_pct is not None and self.open_rate is not None:
|
||||
self.adjust_stop_loss(self.open_rate, self.stop_loss_pct)
|
||||
|
@@ -243,6 +243,22 @@ class Telegram(RPCHandler):
|
||||
"""
|
||||
return f"{msg['exchange']}{' (dry)' if self._config['dry_run'] else ''}"
|
||||
|
||||
def _add_analyzed_candle(self, pair: str) -> str:
|
||||
candle_val = self._config['telegram'].get(
|
||||
'notification_settings', {}).get('show_candle', 'off')
|
||||
if candle_val != 'off':
|
||||
if candle_val == 'ohlc':
|
||||
analyzed_df, _ = self._rpc._freqtrade.dataprovider.get_analyzed_dataframe(
|
||||
pair, self._config['timeframe'])
|
||||
candle = analyzed_df.iloc[-1].squeeze() if len(analyzed_df) > 0 else None
|
||||
if candle is not None:
|
||||
return (
|
||||
f"*Candle OHLC*: `{candle['open']}, {candle['high']}, "
|
||||
f"{candle['low']}, {candle['close']}`\n"
|
||||
)
|
||||
|
||||
return ''
|
||||
|
||||
def _format_entry_msg(self, msg: Dict[str, Any]) -> str:
|
||||
if self._rpc._fiat_converter:
|
||||
msg['stake_amount_fiat'] = self._rpc._fiat_converter.convert_amount(
|
||||
@@ -259,6 +275,7 @@ class Telegram(RPCHandler):
|
||||
f" {entry_side['entered'] if is_fill else entry_side['enter']} {msg['pair']}"
|
||||
f" (#{msg['trade_id']})\n"
|
||||
)
|
||||
message += self._add_analyzed_candle(msg['pair'])
|
||||
message += f"*Enter Tag:* `{msg['enter_tag']}`\n" if msg.get('enter_tag') else ""
|
||||
message += f"*Amount:* `{msg['amount']:.8f}`\n"
|
||||
if msg.get('leverage') and msg.get('leverage', 1.0) != 1.0:
|
||||
@@ -306,6 +323,7 @@ class Telegram(RPCHandler):
|
||||
message = (
|
||||
f"{msg['emoji']} *{self._exchange_from_msg(msg)}:* "
|
||||
f"{'Exited' if is_fill else 'Exiting'} {msg['pair']} (#{msg['trade_id']})\n"
|
||||
f"{self._add_analyzed_candle(msg['pair'])}"
|
||||
f"*{'Profit' if is_fill else 'Unrealized Profit'}:* "
|
||||
f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n"
|
||||
f"*Enter Tag:* `{msg['enter_tag']}`\n"
|
||||
|
@@ -191,6 +191,7 @@ def detect_parameters(
|
||||
and attr.category is not None and attr.category != category):
|
||||
raise OperationalException(
|
||||
f'Inconclusive parameter name {attr_name}, category: {attr.category}.')
|
||||
|
||||
if (category == attr.category or
|
||||
(attr_name.startswith(category + '_') and attr.category is None)):
|
||||
yield attr_name, attr
|
||||
|
@@ -442,7 +442,8 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
|
||||
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
||||
proposed_stake: float, min_stake: Optional[float], max_stake: float,
|
||||
entry_tag: Optional[str], side: str, **kwargs) -> float:
|
||||
leverage: float, entry_tag: Optional[str], side: str,
|
||||
**kwargs) -> float:
|
||||
"""
|
||||
Customize stake size for each new trade.
|
||||
|
||||
@@ -452,6 +453,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
: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 leverage: Leverage selected for this trade.
|
||||
: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.
|
||||
|
@@ -51,11 +51,13 @@
|
||||
"source": [
|
||||
"# Load data using values set above\n",
|
||||
"from freqtrade.data.history import load_pair_history\n",
|
||||
"from freqtrade.enums import CandleType\n",
|
||||
"\n",
|
||||
"candles = load_pair_history(datadir=data_location,\n",
|
||||
" timeframe=config[\"timeframe\"],\n",
|
||||
" pair=pair,\n",
|
||||
" data_format = \"hdf5\",\n",
|
||||
" candle_type=CandleType.SPOT,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"# Confirm success\n",
|
||||
|
@@ -79,9 +79,10 @@ def custom_exit_price(self, pair: str, trade: 'Trade',
|
||||
"""
|
||||
return proposed_rate
|
||||
|
||||
def custom_stake_amount(self, pair: str, current_time: 'datetime', current_rate: float,
|
||||
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
||||
proposed_stake: float, min_stake: Optional[float], max_stake: float,
|
||||
entry_tag: 'Optional[str]', side: str, **kwargs) -> float:
|
||||
leverage: float, entry_tag: Optional[str], side: str,
|
||||
**kwargs) -> float:
|
||||
"""
|
||||
Customize stake size for each new trade.
|
||||
|
||||
@@ -91,6 +92,7 @@ def custom_stake_amount(self, pair: str, current_time: 'datetime', current_rate:
|
||||
: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 leverage: Leverage selected for this trade.
|
||||
: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.
|
||||
|
Reference in New Issue
Block a user