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 logging
import traceback
from datetime import datetime
from datetime import datetime, timezone
from math import isclose
from threading import Lock
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.misc import safe_value_fallback, safe_value_fallback2
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.rpc import RPCManager, RPCMessageType
from freqtrade.state import State
@ -57,8 +57,8 @@ class FreqtradeBot:
# 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
# refreshed once every iteration.
self._sell_rate_cache = TTLCache(maxsize=100, ttl=1800)
self._buy_rate_cache = TTLCache(maxsize=100, ttl=1800)
self._sell_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
self._buy_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
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)
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)
@ -122,7 +122,7 @@ class FreqtradeBot:
self.check_for_open_trades()
self.rpc.cleanup()
persistence.cleanup()
cleanup_db()
def startup(self) -> None:
"""
@ -734,7 +734,7 @@ class FreqtradeBot:
return False
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)(
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_requested=buy_limit_requested,
open_date=datetime.utcnow(),
exchange=_bot.exchange.id,
exchange=self.exchange.id,
open_order_id=order_id,
strategy=_bot.strategy.get_strategy_name(),
timeframe=timeframe_to_minutes(_bot.config['timeframe'])
strategy=self.strategy.get_strategy_name(),
timeframe=timeframe_to_minutes(self.config['timeframe'])
)
trade.orders.append(order_obj)
# Update fees if order is closed
@ -1062,8 +1062,8 @@ class FreqtradeBot:
self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
stoploss_order=True)
# Lock pair for one candle to prevent immediate rebuys
self.strategy.lock_pair(trade.pair,
timeframe_to_next_date(self.config['timeframe']))
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
reason='Auto lock')
self._notify_sell(trade, "stoploss")
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'
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,
registers all known command handlers
@ -70,7 +70,7 @@ def init(db_url: str, clean_open_orders: bool = False) -> None:
clean_dry_run_db()
def cleanup() -> None:
def cleanup_db() -> None:
"""
Flushes all pending operations to disk.
:return: None
@ -404,7 +404,7 @@ class Trade(_DECL_BASE):
self.close(order['average'])
else:
raise ValueError(f'Unknown order type: {order_type}')
cleanup()
cleanup_db()
def partial_update(self, order: Dict) -> None:
"""
@ -442,7 +442,7 @@ class Trade(_DECL_BASE):
self.close(order['average'])
else:
raise ValueError(f'Unknown order type: {order_type}')
cleanup()
cleanup_db()
def close(self, rate: float) -> None:
"""
@ -738,3 +738,56 @@ class Trade(_DECL_BASE):
trade.stop_loss = None
trade.adjust_stop_loss(trade.open_rate, desired_stoploss)
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.exchange import timeframe_to_minutes
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.wallets import Wallets
@ -287,7 +287,7 @@ class IStrategy(ABC):
"""
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.
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.
Needs to be timezone aware `datetime.now(timezone.utc)`
"""
if pair not in self._pair_locked_until or self._pair_locked_until[pair] < until:
self._pair_locked_until[pair] = until
PairLocks.lock_pair(pair, until, reason)
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.
:param pair: Unlock pair to allow trading again
"""
if pair in self._pair_locked_until:
del self._pair_locked_until[pair]
PairLocks.unlock_pair(pair, datetime.now(timezone.utc))
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")
dataframe['buy'] = 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
# twitter_sell = self.watch_twitter_feed(dataframe, metadata)
@ -464,9 +465,16 @@ class IStrategy(ABC):
)
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) = \
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',
latest['date'], pair, str(buy), str(sell), str(partial_buy), str(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()
return dataframe