Modified files for partial trades operation
Signed-off-by: Es Fem <esfem.es@gmail.com>
This commit is contained in:
parent
520a597f83
commit
5d7fced3a4
@ -4,7 +4,7 @@ Freqtrade is the main module of this bot. It contains the class Freqtrade()
|
|||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from math import isclose
|
from math import isclose
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
@ -22,7 +22,7 @@ from freqtrade.exceptions import (DependencyException, ExchangeError, Insufficie
|
|||||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date
|
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date
|
||||||
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
|
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
|
||||||
from freqtrade.pairlist.pairlistmanager import PairListManager
|
from freqtrade.pairlist.pairlistmanager import PairListManager
|
||||||
from freqtrade.persistence import Order, Trade
|
from freqtrade.persistence import Order, PairLocks, Trade, cleanup_db, init_db
|
||||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||||
from freqtrade.rpc import RPCManager, RPCMessageType
|
from freqtrade.rpc import RPCManager, RPCMessageType
|
||||||
from freqtrade.state import State
|
from freqtrade.state import State
|
||||||
@ -57,8 +57,8 @@ class FreqtradeBot:
|
|||||||
# Cache values for 1800 to avoid frequent polling of the exchange for prices
|
# 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
|
# Caching only applies to RPC methods, so prices for open trades are still
|
||||||
# refreshed once every iteration.
|
# refreshed once every iteration.
|
||||||
self._sell_rate_cache = TTLCache(maxsize=100, ttl=1800)
|
self._sell_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
||||||
self._buy_rate_cache = TTLCache(maxsize=100, ttl=1800)
|
self._buy_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
||||||
|
|
||||||
self.strategy: IStrategy = StrategyResolver.load_strategy(self.config)
|
self.strategy: IStrategy = StrategyResolver.load_strategy(self.config)
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ class FreqtradeBot:
|
|||||||
|
|
||||||
self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config)
|
self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config)
|
||||||
|
|
||||||
persistence.init(self.config.get('db_url', None), clean_open_orders=self.config['dry_run'])
|
init_db(self.config.get('db_url', None), clean_open_orders=self.config['dry_run'])
|
||||||
|
|
||||||
self.wallets = Wallets(self.config, self.exchange)
|
self.wallets = Wallets(self.config, self.exchange)
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ class FreqtradeBot:
|
|||||||
self.check_for_open_trades()
|
self.check_for_open_trades()
|
||||||
|
|
||||||
self.rpc.cleanup()
|
self.rpc.cleanup()
|
||||||
persistence.cleanup()
|
cleanup_db()
|
||||||
|
|
||||||
def startup(self) -> None:
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -734,7 +734,7 @@ class FreqtradeBot:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
amount = stake_amount / buy_limit_requested
|
amount = stake_amount / buy_limit_requested
|
||||||
order_type = self.strategy.order_types['buy']
|
order_type = self.strategy.order_types['partial_buy']
|
||||||
|
|
||||||
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
|
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
|
||||||
pair=pair, order_type=order_type, amount=amount, rate=buy_limit_requested,
|
pair=pair, order_type=order_type, amount=amount, rate=buy_limit_requested,
|
||||||
@ -801,10 +801,10 @@ class FreqtradeBot:
|
|||||||
open_rate=buy_limit_filled_price,
|
open_rate=buy_limit_filled_price,
|
||||||
open_rate_requested=buy_limit_requested,
|
open_rate_requested=buy_limit_requested,
|
||||||
open_date=datetime.utcnow(),
|
open_date=datetime.utcnow(),
|
||||||
exchange=_bot.exchange.id,
|
exchange=self.exchange.id,
|
||||||
open_order_id=order_id,
|
open_order_id=order_id,
|
||||||
strategy=_bot.strategy.get_strategy_name(),
|
strategy=self.strategy.get_strategy_name(),
|
||||||
timeframe=timeframe_to_minutes(_bot.config['timeframe'])
|
timeframe=timeframe_to_minutes(self.config['timeframe'])
|
||||||
)
|
)
|
||||||
trade.orders.append(order_obj)
|
trade.orders.append(order_obj)
|
||||||
# Update fees if order is closed
|
# Update fees if order is closed
|
||||||
@ -1062,8 +1062,8 @@ class FreqtradeBot:
|
|||||||
self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
|
self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
|
||||||
stoploss_order=True)
|
stoploss_order=True)
|
||||||
# Lock pair for one candle to prevent immediate rebuys
|
# Lock pair for one candle to prevent immediate rebuys
|
||||||
self.strategy.lock_pair(trade.pair,
|
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
|
||||||
timeframe_to_next_date(self.config['timeframe']))
|
reason='Auto lock')
|
||||||
self._notify_sell(trade, "stoploss")
|
self._notify_sell(trade, "stoploss")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ _DECL_BASE: Any = declarative_base()
|
|||||||
_SQL_DOCS_URL = 'http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls'
|
_SQL_DOCS_URL = 'http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls'
|
||||||
|
|
||||||
|
|
||||||
def init(db_url: str, clean_open_orders: bool = False) -> None:
|
def init_db(db_url: str, clean_open_orders: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
Initializes this module with the given config,
|
Initializes this module with the given config,
|
||||||
registers all known command handlers
|
registers all known command handlers
|
||||||
@ -70,7 +70,7 @@ def init(db_url: str, clean_open_orders: bool = False) -> None:
|
|||||||
clean_dry_run_db()
|
clean_dry_run_db()
|
||||||
|
|
||||||
|
|
||||||
def cleanup() -> None:
|
def cleanup_db() -> None:
|
||||||
"""
|
"""
|
||||||
Flushes all pending operations to disk.
|
Flushes all pending operations to disk.
|
||||||
:return: None
|
:return: None
|
||||||
@ -404,7 +404,7 @@ class Trade(_DECL_BASE):
|
|||||||
self.close(order['average'])
|
self.close(order['average'])
|
||||||
else:
|
else:
|
||||||
raise ValueError(f'Unknown order type: {order_type}')
|
raise ValueError(f'Unknown order type: {order_type}')
|
||||||
cleanup()
|
cleanup_db()
|
||||||
|
|
||||||
def partial_update(self, order: Dict) -> None:
|
def partial_update(self, order: Dict) -> None:
|
||||||
"""
|
"""
|
||||||
@ -442,7 +442,7 @@ class Trade(_DECL_BASE):
|
|||||||
self.close(order['average'])
|
self.close(order['average'])
|
||||||
else:
|
else:
|
||||||
raise ValueError(f'Unknown order type: {order_type}')
|
raise ValueError(f'Unknown order type: {order_type}')
|
||||||
cleanup()
|
cleanup_db()
|
||||||
|
|
||||||
def close(self, rate: float) -> None:
|
def close(self, rate: float) -> None:
|
||||||
"""
|
"""
|
||||||
@ -738,3 +738,56 @@ class Trade(_DECL_BASE):
|
|||||||
trade.stop_loss = None
|
trade.stop_loss = None
|
||||||
trade.adjust_stop_loss(trade.open_rate, desired_stoploss)
|
trade.adjust_stop_loss(trade.open_rate, desired_stoploss)
|
||||||
logger.info(f"New stoploss: {trade.stop_loss}.")
|
logger.info(f"New stoploss: {trade.stop_loss}.")
|
||||||
|
|
||||||
|
|
||||||
|
class PairLock(_DECL_BASE):
|
||||||
|
"""
|
||||||
|
Pair Locks database model.
|
||||||
|
"""
|
||||||
|
__tablename__ = 'pairlocks'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
|
||||||
|
pair = Column(String, nullable=False, index=True)
|
||||||
|
reason = Column(String, nullable=True)
|
||||||
|
# Time the pair was locked (start time)
|
||||||
|
lock_time = Column(DateTime, nullable=False)
|
||||||
|
# Time until the pair is locked (end time)
|
||||||
|
lock_end_time = Column(DateTime, nullable=False, index=True)
|
||||||
|
|
||||||
|
active = Column(Boolean, nullable=False, default=True, index=True)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
lock_time = self.lock_time.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
lock_end_time = self.lock_end_time.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
return (f'PairLock(id={self.id}, pair={self.pair}, lock_time={lock_time}, '
|
||||||
|
f'lock_end_time={lock_end_time})')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def query_pair_locks(pair: Optional[str], now: datetime) -> Query:
|
||||||
|
"""
|
||||||
|
Get all locks for this pair
|
||||||
|
:param pair: Pair to check for. Returns all current locks if pair is empty
|
||||||
|
:param now: Datetime object (generated via datetime.now(timezone.utc)).
|
||||||
|
"""
|
||||||
|
|
||||||
|
filters = [PairLock.lock_end_time > now,
|
||||||
|
# Only active locks
|
||||||
|
PairLock.active.is_(True), ]
|
||||||
|
if pair:
|
||||||
|
filters.append(PairLock.pair == pair)
|
||||||
|
return PairLock.query.filter(
|
||||||
|
*filters
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_json(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
'pair': self.pair,
|
||||||
|
'lock_time': self.lock_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
'lock_timestamp': int(self.lock_time.replace(tzinfo=timezone.utc).timestamp() * 1000),
|
||||||
|
'lock_end_time': self.lock_end_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
'lock_end_timestamp': int(self.lock_end_time.replace(tzinfo=timezone.utc
|
||||||
|
).timestamp() * 1000),
|
||||||
|
'reason': self.reason,
|
||||||
|
'active': self.active,
|
||||||
|
}
|
||||||
|
@ -17,7 +17,7 @@ from freqtrade.data.dataprovider import DataProvider
|
|||||||
from freqtrade.exceptions import OperationalException, StrategyError
|
from freqtrade.exceptions import OperationalException, StrategyError
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
from freqtrade.exchange.exchange import timeframe_to_next_date
|
from freqtrade.exchange.exchange import timeframe_to_next_date
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import PairLocks, Trade
|
||||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||||
from freqtrade.wallets import Wallets
|
from freqtrade.wallets import Wallets
|
||||||
|
|
||||||
@ -287,7 +287,7 @@ class IStrategy(ABC):
|
|||||||
"""
|
"""
|
||||||
return self.__class__.__name__
|
return self.__class__.__name__
|
||||||
|
|
||||||
def lock_pair(self, pair: str, until: datetime) -> None:
|
def lock_pair(self, pair: str, until: datetime, reason: str = None) -> None:
|
||||||
"""
|
"""
|
||||||
Locks pair until a given timestamp happens.
|
Locks pair until a given timestamp happens.
|
||||||
Locked pairs are not analyzed, and are prevented from opening new trades.
|
Locked pairs are not analyzed, and are prevented from opening new trades.
|
||||||
@ -297,8 +297,8 @@ class IStrategy(ABC):
|
|||||||
:param until: datetime in UTC until the pair should be blocked from opening new trades.
|
:param until: datetime in UTC until the pair should be blocked from opening new trades.
|
||||||
Needs to be timezone aware `datetime.now(timezone.utc)`
|
Needs to be timezone aware `datetime.now(timezone.utc)`
|
||||||
"""
|
"""
|
||||||
if pair not in self._pair_locked_until or self._pair_locked_until[pair] < until:
|
PairLocks.lock_pair(pair, until, reason)
|
||||||
self._pair_locked_until[pair] = until
|
|
||||||
|
|
||||||
def unlock_pair(self, pair: str) -> None:
|
def unlock_pair(self, pair: str) -> None:
|
||||||
"""
|
"""
|
||||||
@ -307,8 +307,7 @@ class IStrategy(ABC):
|
|||||||
manually from within the strategy, to allow an easy way to unlock pairs.
|
manually from within the strategy, to allow an easy way to unlock pairs.
|
||||||
:param pair: Unlock pair to allow trading again
|
:param pair: Unlock pair to allow trading again
|
||||||
"""
|
"""
|
||||||
if pair in self._pair_locked_until:
|
PairLocks.unlock_pair(pair, datetime.now(timezone.utc))
|
||||||
del self._pair_locked_until[pair]
|
|
||||||
|
|
||||||
def is_pair_locked(self, pair: str, candle_date: datetime = None) -> bool:
|
def is_pair_locked(self, pair: str, candle_date: datetime = None) -> bool:
|
||||||
"""
|
"""
|
||||||
@ -368,6 +367,8 @@ class IStrategy(ABC):
|
|||||||
logger.debug("Skipping TA Analysis for already analyzed candle")
|
logger.debug("Skipping TA Analysis for already analyzed candle")
|
||||||
dataframe['buy'] = 0
|
dataframe['buy'] = 0
|
||||||
dataframe['sell'] = 0
|
dataframe['sell'] = 0
|
||||||
|
dataframe['partial_buy'] = PartialTradeTuple(0, 0)
|
||||||
|
dataframe['partial_sell'] = PartialTradeTuple(0, 0)
|
||||||
|
|
||||||
# Other Defs in strategy that want to be called every loop here
|
# Other Defs in strategy that want to be called every loop here
|
||||||
# twitter_sell = self.watch_twitter_feed(dataframe, metadata)
|
# twitter_sell = self.watch_twitter_feed(dataframe, metadata)
|
||||||
@ -464,9 +465,16 @@ class IStrategy(ABC):
|
|||||||
)
|
)
|
||||||
return False, False, PartialTradeTuple(False,0), PartialTradeTuple(False,0)
|
return False, False, PartialTradeTuple(False,0), PartialTradeTuple(False,0)
|
||||||
|
|
||||||
|
# Check if partials are set in strategy and set to 0 if not
|
||||||
|
if latest.partial_buy and latest.partial_sell:
|
||||||
|
if type(latest.partial_buy) is not PartialTradeTuple:
|
||||||
|
latest.partial_buy = PartialTradeTuple(0, 0)
|
||||||
|
if type(latest.partial_sell) is not PartialTradeTuple:
|
||||||
|
latest.partial_sell = PartialTradeTuple(0, 0)
|
||||||
|
|
||||||
(buy, sell, partial_buy, partial_sell) = \
|
(buy, sell, partial_buy, partial_sell) = \
|
||||||
latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1,\
|
latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1,\
|
||||||
latest[SignalType.PARTIAL_BUY] == 1, latest[SignalType.PARTIAL_SELL == 1]
|
latest[SignalType.PARTIAL_BUY.value], latest[SignalType.PARTIAL_SELL.value]
|
||||||
logger.debug('trigger: %s (pair=%s) buy=%s sell=%s partial_buy = %s partial_sell = %s',
|
logger.debug('trigger: %s (pair=%s) buy=%s sell=%s partial_buy = %s partial_sell = %s',
|
||||||
latest['date'], pair, str(buy), str(sell), str(partial_buy), str(partial_sell))
|
latest['date'], pair, str(buy), str(sell), str(partial_buy), str(partial_sell))
|
||||||
return buy, sell, partial_buy, partial_sell
|
return buy, sell, partial_buy, partial_sell
|
||||||
|
@ -47,3 +47,4 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame,
|
|||||||
dataframe = dataframe.ffill()
|
dataframe = dataframe.ffill()
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user