Modified files for partial trades operation

Signed-off-by: Es Fem <esfem.es@gmail.com>
This commit is contained in:
esfem 2020-10-29 19:47:50 -04:00 committed by Es Fem
parent 520a597f83
commit 5d7fced3a4
10 changed files with 85 additions and 23 deletions

View File

@ -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

View File

@ -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,
}

View File

@ -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

View File

@ -47,3 +47,4 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame,
dataframe = dataframe.ffill() dataframe = dataframe.ffill()
return dataframe return dataframe