Merge branch 'develop' into feat/short
This commit is contained in:
@@ -52,6 +52,8 @@ USERPATH_STRATEGIES = 'strategies'
|
||||
USERPATH_NOTEBOOKS = 'notebooks'
|
||||
|
||||
TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
|
||||
WEBHOOK_FORMAT_OPTIONS = ['form', 'json', 'raw']
|
||||
|
||||
ENV_VAR_PREFIX = 'FREQTRADE__'
|
||||
|
||||
NON_OPEN_EXCHANGE_STATES = ('cancelled', 'canceled', 'closed', 'expired')
|
||||
@@ -316,10 +318,16 @@ CONF_SCHEMA = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'enabled': {'type': 'boolean'},
|
||||
'url': {'type': 'string'},
|
||||
'format': {'type': 'string', 'enum': WEBHOOK_FORMAT_OPTIONS, 'default': 'form'},
|
||||
'retries': {'type': 'integer', 'minimum': 0},
|
||||
'retry_delay': {'type': 'number', 'minimum': 0},
|
||||
'webhookbuy': {'type': 'object'},
|
||||
'webhookbuycancel': {'type': 'object'},
|
||||
'webhookbuyfill': {'type': 'object'},
|
||||
'webhooksell': {'type': 'object'},
|
||||
'webhooksellcancel': {'type': 'object'},
|
||||
'webhooksellfill': {'type': 'object'},
|
||||
'webhookstatus': {'type': 'object'},
|
||||
},
|
||||
},
|
||||
|
@@ -1365,7 +1365,7 @@ class Exchange:
|
||||
results = await asyncio.gather(*input_coro, return_exceptions=True)
|
||||
for res in results:
|
||||
if isinstance(res, Exception):
|
||||
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
|
||||
logger.warning(f"Async code raised an exception: {repr(res)}")
|
||||
if raise_:
|
||||
raise
|
||||
continue
|
||||
@@ -1396,7 +1396,7 @@ class Exchange:
|
||||
cached_pairs = []
|
||||
# Gather coroutines to run
|
||||
for pair, timeframe in set(pair_list):
|
||||
if ((pair, timeframe) not in self._klines
|
||||
if ((pair, timeframe) not in self._klines or not cache
|
||||
or self._now_is_time_to_refresh(pair, timeframe)):
|
||||
if not since_ms and self.required_candle_call_count > 1:
|
||||
# Multiple calls for one pair - to get more history
|
||||
@@ -1419,27 +1419,30 @@ class Exchange:
|
||||
)
|
||||
cached_pairs.append((pair, timeframe))
|
||||
|
||||
results = asyncio.get_event_loop().run_until_complete(
|
||||
asyncio.gather(*input_coroutines, return_exceptions=True))
|
||||
|
||||
results_df = {}
|
||||
# handle caching
|
||||
for res in results:
|
||||
if isinstance(res, Exception):
|
||||
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
|
||||
continue
|
||||
# Deconstruct tuple (has 3 elements)
|
||||
pair, timeframe, 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
|
||||
# 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
|
||||
if cache:
|
||||
self._klines[(pair, timeframe)] = ohlcv_df
|
||||
# Chunk requests into batches of 100 to avoid overwelming ccxt Throttling
|
||||
for input_coro in chunks(input_coroutines, 100):
|
||||
results = asyncio.get_event_loop().run_until_complete(
|
||||
asyncio.gather(*input_coro, return_exceptions=True))
|
||||
|
||||
# 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
|
||||
# keeping last candle time as last refreshed time of the pair
|
||||
if ticks:
|
||||
self._pairs_last_refresh_time[(pair, timeframe)] = 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
|
||||
if cache:
|
||||
self._klines[(pair, timeframe)] = ohlcv_df
|
||||
|
||||
# Return cached klines
|
||||
for pair, timeframe in cached_pairs:
|
||||
results_df[(pair, timeframe)] = self.klines((pair, timeframe), copy=False)
|
||||
|
@@ -575,8 +575,9 @@ class FreqtradeBot(LoggingMixin):
|
||||
pair: str,
|
||||
stake_amount: float,
|
||||
price: Optional[float] = None,
|
||||
forcebuy: bool = False,
|
||||
*,
|
||||
is_short: bool = False,
|
||||
ordertype: Optional[str] = None,
|
||||
enter_tag: Optional[str] = None
|
||||
) -> bool:
|
||||
"""
|
||||
@@ -649,12 +650,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
)
|
||||
|
||||
amount = (stake_amount / enter_limit_requested) * leverage
|
||||
order_type = self.strategy.order_types['buy']
|
||||
if forcebuy:
|
||||
# Forcebuy can define a different ordertype
|
||||
# TODO-lev: get a forceshort? What is this
|
||||
order_type = self.strategy.order_types.get('forcebuy', order_type)
|
||||
# TODO-lev: Will this work for shorting?
|
||||
order_type = ordertype or self.strategy.order_types['buy']
|
||||
|
||||
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
|
||||
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
|
||||
@@ -1053,7 +1049,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
if should_exit.sell_flag:
|
||||
logger.info(f'Exit for {trade.pair} detected. Reason: {should_exit.sell_type}'
|
||||
f'Tag: {exit_tag if exit_tag is not None else "None"}')
|
||||
self.execute_trade_exit(trade, exit_rate, should_exit, exit_tag)
|
||||
self.execute_trade_exit(trade, exit_rate, should_exit, exit_tag=exit_tag)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -1271,12 +1267,14 @@ class FreqtradeBot(LoggingMixin):
|
||||
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, # TODO-lev update to exit_reason
|
||||
exit_tag: Optional[str] = None
|
||||
) -> bool:
|
||||
self,
|
||||
trade: Trade,
|
||||
limit: float,
|
||||
sell_reason: SellCheckTuple,
|
||||
*,
|
||||
exit_tag: Optional[str] = None,
|
||||
ordertype: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Executes a trade exit for the given trade and limit
|
||||
:param trade: Trade instance
|
||||
@@ -1319,14 +1317,10 @@ class FreqtradeBot(LoggingMixin):
|
||||
except InvalidOrderException:
|
||||
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
|
||||
|
||||
order_type = self.strategy.order_types[exit_type]
|
||||
order_type = ordertype or self.strategy.order_types[exit_type]
|
||||
if sell_reason.sell_type == SellType.EMERGENCY_SELL:
|
||||
# Emergency sells (default to market!)
|
||||
order_type = self.strategy.order_types.get("emergencysell", "market")
|
||||
if sell_reason.sell_type == SellType.FORCE_SELL:
|
||||
# Force sells (default to the sell_type defined in the strategy,
|
||||
# but we allow this value to be changed)
|
||||
order_type = self.strategy.order_types.get("forcesell", order_type)
|
||||
|
||||
amount = self._safe_exit_amount(trade.pair, trade.amount)
|
||||
time_in_force = self.strategy.order_time_in_force['sell'] # TODO-lev update to exit
|
||||
|
@@ -92,7 +92,8 @@ class Backtesting:
|
||||
self.init_backtest_detail()
|
||||
self.pairlists = PairListManager(self.exchange, self.config)
|
||||
if 'VolumePairList' in self.pairlists.name_list:
|
||||
raise OperationalException("VolumePairList not allowed for backtesting.")
|
||||
raise OperationalException("VolumePairList not allowed for backtesting. "
|
||||
"Please use StaticPairlist instead.")
|
||||
if 'PerformanceFilter' in self.pairlists.name_list:
|
||||
raise OperationalException("PerformanceFilter not allowed for backtesting.")
|
||||
|
||||
|
@@ -46,20 +46,11 @@ def _get_line_floatfmt(stake_currency: str) -> List[str]:
|
||||
'.2f', 'd', 's', 's']
|
||||
|
||||
|
||||
def _get_line_header(first_column: str, stake_currency: str) -> List[str]:
|
||||
def _get_line_header(first_column: str, stake_currency: str, direction: str = 'Buys') -> List[str]:
|
||||
"""
|
||||
Generate header lines (goes in line with _generate_result_line())
|
||||
"""
|
||||
return [first_column, 'Buys', 'Avg Profit %', 'Cum Profit %',
|
||||
f'Tot Profit {stake_currency}', 'Tot Profit %', 'Avg Duration',
|
||||
'Win Draw Loss Win%']
|
||||
|
||||
|
||||
def _get_line_header_sell(first_column: str, stake_currency: str) -> List[str]:
|
||||
"""
|
||||
Generate header lines (goes in line with _generate_result_line())
|
||||
"""
|
||||
return [first_column, 'Sells', 'Avg Profit %', 'Cum Profit %',
|
||||
return [first_column, direction, 'Avg Profit %', 'Cum Profit %',
|
||||
f'Tot Profit {stake_currency}', 'Tot Profit %', 'Avg Duration',
|
||||
'Win Draw Loss Win%']
|
||||
|
||||
@@ -156,7 +147,7 @@ def generate_tag_metrics(tag_type: str,
|
||||
if skip_nan and result['profit_abs'].isnull().all():
|
||||
continue
|
||||
|
||||
tabular_data.append(_generate_tag_result_line(result, starting_balance, tag))
|
||||
tabular_data.append(_generate_result_line(result, starting_balance, tag))
|
||||
|
||||
# Sort by total profit %:
|
||||
tabular_data = sorted(tabular_data, key=lambda k: k['profit_total_abs'], reverse=True)
|
||||
@@ -168,39 +159,6 @@ def generate_tag_metrics(tag_type: str,
|
||||
return []
|
||||
|
||||
|
||||
def _generate_tag_result_line(result: DataFrame, starting_balance: int, first_column: str) -> Dict:
|
||||
"""
|
||||
Generate one result dict, with "first_column" as key.
|
||||
"""
|
||||
profit_sum = result['profit_ratio'].sum()
|
||||
# (end-capital - starting capital) / starting capital
|
||||
profit_total = result['profit_abs'].sum() / starting_balance
|
||||
|
||||
return {
|
||||
'key': first_column,
|
||||
'trades': len(result),
|
||||
'profit_mean': result['profit_ratio'].mean() if len(result) > 0 else 0.0,
|
||||
'profit_mean_pct': result['profit_ratio'].mean() * 100.0 if len(result) > 0 else 0.0,
|
||||
'profit_sum': profit_sum,
|
||||
'profit_sum_pct': round(profit_sum * 100.0, 2),
|
||||
'profit_total_abs': result['profit_abs'].sum(),
|
||||
'profit_total': profit_total,
|
||||
'profit_total_pct': round(profit_total * 100.0, 2),
|
||||
'duration_avg': str(timedelta(
|
||||
minutes=round(result['trade_duration'].mean()))
|
||||
) if not result.empty else '0:00',
|
||||
# 'duration_max': str(timedelta(
|
||||
# minutes=round(result['trade_duration'].max()))
|
||||
# ) if not result.empty else '0:00',
|
||||
# 'duration_min': str(timedelta(
|
||||
# minutes=round(result['trade_duration'].min()))
|
||||
# ) if not result.empty else '0:00',
|
||||
'wins': len(result[result['profit_abs'] > 0]),
|
||||
'draws': len(result[result['profit_abs'] == 0]),
|
||||
'losses': len(result[result['profit_abs'] < 0]),
|
||||
}
|
||||
|
||||
|
||||
def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List[Dict]:
|
||||
"""
|
||||
Generate small table outlining Backtest results
|
||||
@@ -637,7 +595,7 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr
|
||||
if(tag_type == "enter_tag"):
|
||||
headers = _get_line_header("TAG", stake_currency)
|
||||
else:
|
||||
headers = _get_line_header_sell("TAG", stake_currency)
|
||||
headers = _get_line_header("TAG", stake_currency, 'Sells')
|
||||
floatfmt = _get_line_floatfmt(stake_currency)
|
||||
output = [
|
||||
[
|
||||
|
@@ -5,6 +5,7 @@ import logging
|
||||
import random
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from freqtrade.enums.runmode import RunMode
|
||||
from freqtrade.plugins.pairlist.IPairList import IPairList
|
||||
|
||||
|
||||
@@ -18,7 +19,15 @@ class ShuffleFilter(IPairList):
|
||||
pairlist_pos: int) -> None:
|
||||
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
|
||||
|
||||
self._seed = pairlistconfig.get('seed')
|
||||
# Apply seed in backtesting mode to get comparable results,
|
||||
# but not in live modes to get a non-repeating order of pairs during live modes.
|
||||
if config.get('runmode') in (RunMode.LIVE, RunMode.DRY_RUN):
|
||||
self._seed = None
|
||||
logger.info("Live mode detected, not applying seed.")
|
||||
else:
|
||||
self._seed = pairlistconfig.get('seed')
|
||||
logger.info(f"Backtesting mode detected, applying seed value: {self._seed}")
|
||||
|
||||
self._random = random.Random(self._seed)
|
||||
|
||||
@property
|
||||
|
@@ -1,4 +1,5 @@
|
||||
from datetime import date, datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
@@ -125,19 +126,24 @@ class Daily(BaseModel):
|
||||
|
||||
|
||||
class UnfilledTimeout(BaseModel):
|
||||
buy: int
|
||||
sell: int
|
||||
unit: str
|
||||
buy: Optional[int]
|
||||
sell: Optional[int]
|
||||
unit: Optional[str]
|
||||
exit_timeout_count: Optional[int]
|
||||
|
||||
|
||||
class OrderTypeValues(str, Enum):
|
||||
limit = 'limit'
|
||||
market = 'market'
|
||||
|
||||
|
||||
class OrderTypes(BaseModel):
|
||||
buy: str
|
||||
sell: str
|
||||
emergencysell: Optional[str]
|
||||
forcesell: Optional[str]
|
||||
forcebuy: Optional[str]
|
||||
stoploss: str
|
||||
buy: OrderTypeValues
|
||||
sell: OrderTypeValues
|
||||
emergencysell: Optional[OrderTypeValues]
|
||||
forcesell: Optional[OrderTypeValues]
|
||||
forcebuy: Optional[OrderTypeValues]
|
||||
stoploss: OrderTypeValues
|
||||
stoploss_on_exchange: bool
|
||||
stoploss_on_exchange_interval: Optional[int]
|
||||
|
||||
@@ -278,10 +284,12 @@ class Logs(BaseModel):
|
||||
class ForceBuyPayload(BaseModel):
|
||||
pair: str
|
||||
price: Optional[float]
|
||||
ordertype: Optional[OrderTypeValues]
|
||||
|
||||
|
||||
class ForceSellPayload(BaseModel):
|
||||
tradeid: str
|
||||
ordertype: Optional[OrderTypeValues]
|
||||
|
||||
|
||||
class BlacklistPayload(BaseModel):
|
||||
|
@@ -29,7 +29,8 @@ logger = logging.getLogger(__name__)
|
||||
# API version
|
||||
# Pre-1.1, no version was provided
|
||||
# Version increments should happen in "small" steps (1.1, 1.12, ...) unless big changes happen.
|
||||
API_VERSION = 1.1
|
||||
# 1.11: forcebuy and forcesell accept ordertype
|
||||
API_VERSION = 1.11
|
||||
|
||||
# Public API, requires no auth.
|
||||
router_public = APIRouter()
|
||||
@@ -129,7 +130,8 @@ def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(g
|
||||
|
||||
@router.post('/forcebuy', response_model=ForceBuyResponse, tags=['trading'])
|
||||
def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)):
|
||||
trade = rpc._rpc_forcebuy(payload.pair, payload.price)
|
||||
ordertype = payload.ordertype.value if payload.ordertype else None
|
||||
trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype)
|
||||
|
||||
if trade:
|
||||
return ForceBuyResponse.parse_obj(trade.to_json())
|
||||
@@ -139,7 +141,8 @@ def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)):
|
||||
|
||||
@router.post('/forcesell', response_model=ResultMsg, tags=['trading'])
|
||||
def forcesell(payload: ForceSellPayload, rpc: RPC = Depends(get_rpc)):
|
||||
return rpc._rpc_forcesell(payload.tradeid)
|
||||
ordertype = payload.ordertype.value if payload.ordertype else None
|
||||
return rpc._rpc_forcesell(payload.tradeid, ordertype)
|
||||
|
||||
|
||||
@router.get('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist'])
|
||||
|
@@ -646,7 +646,7 @@ class RPC:
|
||||
|
||||
return {'status': 'No more buy will occur from now. Run /reload_config to reset.'}
|
||||
|
||||
def _rpc_forcesell(self, trade_id: str) -> Dict[str, str]:
|
||||
def _rpc_forcesell(self, trade_id: str, ordertype: Optional[str] = None) -> Dict[str, str]:
|
||||
"""
|
||||
Handler for forcesell <id>.
|
||||
Sells the given trade at current price
|
||||
@@ -671,7 +671,11 @@ class RPC:
|
||||
current_rate = self._freqtrade.exchange.get_rate(
|
||||
trade.pair, refresh=False, side=closing_side)
|
||||
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
|
||||
self._freqtrade.execute_trade_exit(trade, current_rate, sell_reason)
|
||||
order_type = ordertype or self._freqtrade.strategy.order_types.get(
|
||||
"forcesell", self._freqtrade.strategy.order_types["sell"])
|
||||
|
||||
self._freqtrade.execute_trade_exit(
|
||||
trade, current_rate, sell_reason, ordertype=order_type)
|
||||
# ---- EOF def _exec_forcesell ----
|
||||
|
||||
if self._freqtrade.state != State.RUNNING:
|
||||
@@ -699,7 +703,8 @@ 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]) -> Optional[Trade]:
|
||||
def _rpc_forcebuy(self, pair: str, price: Optional[float],
|
||||
order_type: Optional[str] = None) -> Optional[Trade]:
|
||||
"""
|
||||
Handler for forcebuy <asset> <price>
|
||||
Buys a pair trade at the given or current price
|
||||
@@ -727,7 +732,10 @@ class RPC:
|
||||
stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair)
|
||||
|
||||
# execute buy
|
||||
if self._freqtrade.execute_entry(pair, stakeamount, price, forcebuy=True):
|
||||
if not order_type:
|
||||
order_type = self._freqtrade.strategy.order_types.get(
|
||||
'forcebuy', self._freqtrade.strategy.order_types['buy'])
|
||||
if self._freqtrade.execute_entry(pair, stakeamount, price, ordertype=order_type):
|
||||
Trade.commit()
|
||||
trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first()
|
||||
return trade
|
||||
|
@@ -112,6 +112,7 @@ class Telegram(RPCHandler):
|
||||
r'/stats$', r'/count$', r'/locks$', r'/balance$',
|
||||
r'/stopbuy$', r'/reload_config$', r'/show_config$',
|
||||
r'/logs$', r'/whitelist$', r'/blacklist$', r'/edge$',
|
||||
r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$',
|
||||
r'/forcebuy$', r'/help$', r'/version$']
|
||||
# Create keys for generation
|
||||
valid_keys_print = [k.replace('$', '') for k in valid_keys]
|
||||
|
@@ -2,6 +2,7 @@
|
||||
This module manages webhook communication
|
||||
"""
|
||||
import logging
|
||||
import time
|
||||
from typing import Any, Dict
|
||||
|
||||
from requests import RequestException, post
|
||||
@@ -28,12 +29,9 @@ class Webhook(RPCHandler):
|
||||
super().__init__(rpc, config)
|
||||
|
||||
self._url = self._config['webhook']['url']
|
||||
|
||||
self._format = self._config['webhook'].get('format', 'form')
|
||||
|
||||
if self._format != 'form' and self._format != 'json':
|
||||
raise NotImplementedError('Unknown webhook format `{}`, possible values are '
|
||||
'`form` (default) and `json`'.format(self._format))
|
||||
self._retries = self._config['webhook'].get('retries', 0)
|
||||
self._retry_delay = self._config['webhook'].get('retry_delay', 0.1)
|
||||
|
||||
def cleanup(self) -> None:
|
||||
"""
|
||||
@@ -77,13 +75,30 @@ class Webhook(RPCHandler):
|
||||
def _send_msg(self, payload: dict) -> None:
|
||||
"""do the actual call to the webhook"""
|
||||
|
||||
try:
|
||||
if self._format == 'form':
|
||||
post(self._url, data=payload)
|
||||
elif self._format == 'json':
|
||||
post(self._url, json=payload)
|
||||
else:
|
||||
raise NotImplementedError('Unknown format: {}'.format(self._format))
|
||||
success = False
|
||||
attempts = 0
|
||||
while not success and attempts <= self._retries:
|
||||
if attempts:
|
||||
if self._retry_delay:
|
||||
time.sleep(self._retry_delay)
|
||||
logger.info("Retrying webhook...")
|
||||
|
||||
except RequestException as exc:
|
||||
logger.warning("Could not call webhook url. Exception: %s", exc)
|
||||
attempts += 1
|
||||
|
||||
try:
|
||||
if self._format == 'form':
|
||||
response = post(self._url, data=payload)
|
||||
elif self._format == 'json':
|
||||
response = post(self._url, json=payload)
|
||||
elif self._format == 'raw':
|
||||
response = post(self._url, data=payload['data'],
|
||||
headers={'Content-Type': 'text/plain'})
|
||||
else:
|
||||
raise NotImplementedError('Unknown format: {}'.format(self._format))
|
||||
|
||||
# Throw a RequestException if the post was not successful
|
||||
response.raise_for_status()
|
||||
success = True
|
||||
|
||||
except RequestException as exc:
|
||||
logger.warning("Could not call webhook url. Exception: %s", exc)
|
||||
|
@@ -87,6 +87,7 @@ class {{ strategy }}(IStrategy):
|
||||
'sell': 'gtc'
|
||||
}
|
||||
{{ plot_config | indent(4) }}
|
||||
|
||||
def informative_pairs(self):
|
||||
"""
|
||||
Define additional, informative pair/interval combinations to be cached from the exchange.
|
||||
|
@@ -79,7 +79,9 @@
|
||||
"source": [
|
||||
"# Load strategy using values set above\n",
|
||||
"from freqtrade.resolvers import StrategyResolver\n",
|
||||
"from freqtrade.data.dataprovider import DataProvider\n",
|
||||
"strategy = StrategyResolver.load_strategy(config)\n",
|
||||
"strategy.dp = DataProvider(config, None, None)\n",
|
||||
"\n",
|
||||
"# Generate buy/sell signals using strategy\n",
|
||||
"df = strategy.analyze_ticker(candles, {'pair': pair})\n",
|
||||
|
@@ -1,18 +1,20 @@
|
||||
|
||||
plot_config = {
|
||||
# Main plot indicators (Moving averages, ...)
|
||||
'main_plot': {
|
||||
'tema': {},
|
||||
'sar': {'color': 'white'},
|
||||
},
|
||||
'subplots': {
|
||||
# Subplots - each dict defines one additional plot
|
||||
"MACD": {
|
||||
'macd': {'color': 'blue'},
|
||||
'macdsignal': {'color': 'orange'},
|
||||
@property
|
||||
def plot_config(self):
|
||||
return {
|
||||
# Main plot indicators (Moving averages, ...)
|
||||
'main_plot': {
|
||||
'tema': {},
|
||||
'sar': {'color': 'white'},
|
||||
},
|
||||
"RSI": {
|
||||
'rsi': {'color': 'red'},
|
||||
'subplots': {
|
||||
# Subplots - each dict defines one additional plot
|
||||
"MACD": {
|
||||
'macd': {'color': 'blue'},
|
||||
'macdsignal': {'color': 'orange'},
|
||||
},
|
||||
"RSI": {
|
||||
'rsi': {'color': 'red'},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user