Modified files for partial trades operation
Signed-off-by: Es Fem <esfem.es@gmail.com>
This commit is contained in:
parent
4e40c4c95d
commit
4b7d8c4419
@ -12,17 +12,17 @@ from typing import Any, Dict, List, Optional
|
|||||||
import arrow
|
import arrow
|
||||||
from cachetools import TTLCache
|
from cachetools import TTLCache
|
||||||
|
|
||||||
from freqtrade import __version__, constants, persistence
|
from freqtrade import __version__, constants
|
||||||
from freqtrade.configuration import validate_config_consistency
|
from freqtrade.configuration import validate_config_consistency
|
||||||
from freqtrade.data.converter import order_book_to_dataframe
|
from freqtrade.data.converter import order_book_to_dataframe
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.edge import Edge
|
from freqtrade.edge import Edge
|
||||||
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
|
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
|
||||||
InvalidOrderException, PricingError)
|
InvalidOrderException, PricingError)
|
||||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
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, cleanup_db, init_db
|
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
|
||||||
@ -71,6 +71,8 @@ class FreqtradeBot:
|
|||||||
|
|
||||||
self.wallets = Wallets(self.config, self.exchange)
|
self.wallets = Wallets(self.config, self.exchange)
|
||||||
|
|
||||||
|
PairLocks.timeframe = self.config['timeframe']
|
||||||
|
|
||||||
self.pairlists = PairListManager(self.exchange, self.config)
|
self.pairlists = PairListManager(self.exchange, self.config)
|
||||||
|
|
||||||
self.dataprovider = DataProvider(self.config, self.exchange, self.pairlists)
|
self.dataprovider = DataProvider(self.config, self.exchange, self.pairlists)
|
||||||
@ -344,6 +346,7 @@ class FreqtradeBot:
|
|||||||
whitelist = copy.deepcopy(self.active_pair_whitelist)
|
whitelist = copy.deepcopy(self.active_pair_whitelist)
|
||||||
if not whitelist:
|
if not whitelist:
|
||||||
logger.info("Active pair whitelist is empty.")
|
logger.info("Active pair whitelist is empty.")
|
||||||
|
return trades_created
|
||||||
else:
|
else:
|
||||||
'''# Remove pairs for currently opened trades from the whitelist
|
'''# Remove pairs for currently opened trades from the whitelist
|
||||||
for trade in Trade.get_open_trades():
|
for trade in Trade.get_open_trades():
|
||||||
@ -354,6 +357,7 @@ class FreqtradeBot:
|
|||||||
if not whitelist:
|
if not whitelist:
|
||||||
logger.info("No currency pair in active pair whitelist, "
|
logger.info("No currency pair in active pair whitelist, "
|
||||||
"but checking to sell open trades.")
|
"but checking to sell open trades.")
|
||||||
|
return trades_created
|
||||||
else:
|
else:
|
||||||
# Create entity and execute trade for each pair from whitelist
|
# Create entity and execute trade for each pair from whitelist
|
||||||
for pair in whitelist:
|
for pair in whitelist:
|
||||||
@ -1062,8 +1066,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
|
||||||
|
|
||||||
@ -1395,7 +1399,8 @@ class FreqtradeBot:
|
|||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
|
||||||
# Lock pair for one candle to prevent immediate rebuys
|
# 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, order_type)
|
self._notify_sell(trade, order_type)
|
||||||
|
|
||||||
@ -1467,7 +1472,8 @@ class FreqtradeBot:
|
|||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
|
||||||
#Lock pair for one candle to prevent immediate rebuys
|
#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, order_type)
|
self._notify_sell(trade, order_type)
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ from decimal import Decimal
|
|||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Integer,
|
from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Integer, String,
|
||||||
String, create_engine, desc, func, inspect)
|
create_engine, desc, func, inspect)
|
||||||
from sqlalchemy.exc import NoSuchModuleError
|
from sqlalchemy.exc import NoSuchModuleError
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.orm import Query, relationship
|
from sqlalchemy.orm import Query, relationship
|
||||||
@ -17,6 +17,7 @@ from sqlalchemy.orm.session import sessionmaker
|
|||||||
from sqlalchemy.pool import StaticPool
|
from sqlalchemy.pool import StaticPool
|
||||||
from sqlalchemy.sql.schema import UniqueConstraint
|
from sqlalchemy.sql.schema import UniqueConstraint
|
||||||
|
|
||||||
|
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
||||||
from freqtrade.exceptions import DependencyException, OperationalException
|
from freqtrade.exceptions import DependencyException, OperationalException
|
||||||
from freqtrade.misc import safe_value_fallback
|
from freqtrade.misc import safe_value_fallback
|
||||||
from freqtrade.persistence.migrations import check_migrate
|
from freqtrade.persistence.migrations import check_migrate
|
||||||
@ -61,6 +62,9 @@ def init_db(db_url: str, clean_open_orders: bool = False) -> None:
|
|||||||
# Copy session attributes to order object too
|
# Copy session attributes to order object too
|
||||||
Order.session = Trade.session
|
Order.session = Trade.session
|
||||||
Order.query = Order.session.query_property()
|
Order.query = Order.session.query_property()
|
||||||
|
PairLock.session = Trade.session
|
||||||
|
PairLock.query = PairLock.session.query_property()
|
||||||
|
|
||||||
previous_tables = inspect(engine).get_table_names()
|
previous_tables = inspect(engine).get_table_names()
|
||||||
_DECL_BASE.metadata.create_all(engine)
|
_DECL_BASE.metadata.create_all(engine)
|
||||||
check_migrate(engine, decl_base=_DECL_BASE, previous_tables=previous_tables)
|
check_migrate(engine, decl_base=_DECL_BASE, previous_tables=previous_tables)
|
||||||
@ -124,8 +128,7 @@ class Order(_DECL_BASE):
|
|||||||
filled = Column(Float, nullable=True)
|
filled = Column(Float, nullable=True)
|
||||||
remaining = Column(Float, nullable=True)
|
remaining = Column(Float, nullable=True)
|
||||||
cost = Column(Float, nullable=True)
|
cost = Column(Float, nullable=True)
|
||||||
fee = Column(Float, nullable=True) # OSM
|
fee = Column(Float, nullable=True)
|
||||||
fee_cost = Column(Float, nullable=True) # OSM
|
|
||||||
order_date = Column(DateTime, nullable=True, default=datetime.utcnow)
|
order_date = Column(DateTime, nullable=True, default=datetime.utcnow)
|
||||||
order_filled_date = Column(DateTime, nullable=True)
|
order_filled_date = Column(DateTime, nullable=True)
|
||||||
order_update_date = Column(DateTime, nullable=True)
|
order_update_date = Column(DateTime, nullable=True)
|
||||||
@ -168,12 +171,12 @@ class Order(_DECL_BASE):
|
|||||||
"""
|
"""
|
||||||
Get all non-closed orders - useful when trying to batch-update orders
|
Get all non-closed orders - useful when trying to batch-update orders
|
||||||
"""
|
"""
|
||||||
filtered_orders = [o for o in orders if o.order_id == order['id']]
|
filtered_orders = [o for o in orders if o.order_id == order.get('id')]
|
||||||
if filtered_orders:
|
if filtered_orders:
|
||||||
oobj = filtered_orders[0]
|
oobj = filtered_orders[0]
|
||||||
oobj.update_from_ccxt_object(order)
|
oobj.update_from_ccxt_object(order)
|
||||||
else:
|
else:
|
||||||
logger.warning(f"Did not find order for {order['id']}.")
|
logger.warning(f"Did not find order for {order}.")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_from_ccxt_object(order: Dict[str, Any], pair: str, side: str) -> 'Order':
|
def parse_from_ccxt_object(order: Dict[str, Any], pair: str, side: str) -> 'Order':
|
||||||
@ -252,7 +255,7 @@ class Trade(_DECL_BASE):
|
|||||||
self.recalc_open_trade_price()
|
self.recalc_open_trade_price()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
open_since = self.open_date.strftime('%Y-%m-%d %H:%M:%S') if self.is_open else 'closed'
|
open_since = self.open_date.strftime(DATETIME_PRINT_FORMAT) if self.is_open else 'closed'
|
||||||
|
|
||||||
return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, '
|
return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, '
|
||||||
f'open_rate={self.open_rate:.8f}, open_since={open_since})')
|
f'open_rate={self.open_rate:.8f}, open_since={open_since})')
|
||||||
@ -278,7 +281,7 @@ class Trade(_DECL_BASE):
|
|||||||
'fee_close_currency': self.fee_close_currency,
|
'fee_close_currency': self.fee_close_currency,
|
||||||
|
|
||||||
'open_date_hum': arrow.get(self.open_date).humanize(),
|
'open_date_hum': arrow.get(self.open_date).humanize(),
|
||||||
'open_date': self.open_date.strftime("%Y-%m-%d %H:%M:%S"),
|
'open_date': self.open_date.strftime(DATETIME_PRINT_FORMAT),
|
||||||
'open_timestamp': int(self.open_date.replace(tzinfo=timezone.utc).timestamp() * 1000),
|
'open_timestamp': int(self.open_date.replace(tzinfo=timezone.utc).timestamp() * 1000),
|
||||||
'open_rate': self.open_rate,
|
'open_rate': self.open_rate,
|
||||||
'open_rate_requested': self.open_rate_requested,
|
'open_rate_requested': self.open_rate_requested,
|
||||||
@ -286,7 +289,7 @@ class Trade(_DECL_BASE):
|
|||||||
|
|
||||||
'close_date_hum': (arrow.get(self.close_date).humanize()
|
'close_date_hum': (arrow.get(self.close_date).humanize()
|
||||||
if self.close_date else None),
|
if self.close_date else None),
|
||||||
'close_date': (self.close_date.strftime("%Y-%m-%d %H:%M:%S")
|
'close_date': (self.close_date.strftime(DATETIME_PRINT_FORMAT)
|
||||||
if self.close_date else None),
|
if self.close_date else None),
|
||||||
'close_timestamp': int(self.close_date.replace(
|
'close_timestamp': int(self.close_date.replace(
|
||||||
tzinfo=timezone.utc).timestamp() * 1000) if self.close_date else None,
|
tzinfo=timezone.utc).timestamp() * 1000) if self.close_date else None,
|
||||||
@ -302,7 +305,7 @@ class Trade(_DECL_BASE):
|
|||||||
'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None,
|
'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None,
|
||||||
'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None,
|
'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None,
|
||||||
'stoploss_order_id': self.stoploss_order_id,
|
'stoploss_order_id': self.stoploss_order_id,
|
||||||
'stoploss_last_update': (self.stoploss_last_update.strftime("%Y-%m-%d %H:%M:%S")
|
'stoploss_last_update': (self.stoploss_last_update.strftime(DATETIME_PRINT_FORMAT)
|
||||||
if self.stoploss_last_update else None),
|
if self.stoploss_last_update else None),
|
||||||
'stoploss_last_update_timestamp': int(self.stoploss_last_update.replace(
|
'stoploss_last_update_timestamp': int(self.stoploss_last_update.replace(
|
||||||
tzinfo=timezone.utc).timestamp() * 1000) if self.stoploss_last_update else None,
|
tzinfo=timezone.utc).timestamp() * 1000) if self.stoploss_last_update else None,
|
||||||
@ -444,6 +447,44 @@ class Trade(_DECL_BASE):
|
|||||||
raise ValueError(f'Unknown order type: {order_type}')
|
raise ValueError(f'Unknown order type: {order_type}')
|
||||||
cleanup_db()
|
cleanup_db()
|
||||||
|
|
||||||
|
def partial_update(self, order: Dict) -> None:
|
||||||
|
"""
|
||||||
|
Updates this entity with amount and actual open/close rates,
|
||||||
|
modified to support multiple orders keeping the trade opened
|
||||||
|
:param order: order retrieved by exchange.fetch_order()
|
||||||
|
:return: None
|
||||||
|
|
||||||
|
"""
|
||||||
|
order_type = order['type']
|
||||||
|
|
||||||
|
if order_type in ('market', 'limit') and order['side'] == 'buy':
|
||||||
|
# Update open rate and actual amount
|
||||||
|
self.open_rate = self.average_open_rate(order['filled'],
|
||||||
|
safe_value_fallback(order, 'average', 'price'),
|
||||||
|
self.amount, self.open_rate)
|
||||||
|
self.amount = Decimal(self.amount or 0) + Decimal(order['filled'])
|
||||||
|
self.decrease_wallet(self, Decimal(order['filled']), self.open_rate)
|
||||||
|
if self.is_open and order['filled'] != 0:
|
||||||
|
logger.info(f'{order_type.upper()}_Partial BUY has been fulfilled for {self}.')
|
||||||
|
self.open_order_id = None
|
||||||
|
|
||||||
|
elif order_type in ('market', 'limit') and order['side'] == 'sell':
|
||||||
|
self.amount = (Decimal(self.amount or 0) - Decimal(order['filled']))
|
||||||
|
if self.is_open and order['filled'] != 0:
|
||||||
|
logger.info(f'{order_type.upper()}_Partial SELL has been fulfilled for {self}.')
|
||||||
|
self.partial_close(self, safe_value_fallback(order, 'average', 'price'))
|
||||||
|
self.increase_wallet(self, Decimal(order['filled']), order['price'])
|
||||||
|
|
||||||
|
elif order_type in ('stop_loss_limit', 'stop-loss', 'stop'):
|
||||||
|
self.stoploss_order_id = None
|
||||||
|
self.close_rate_requested = self.stop_loss
|
||||||
|
if self.is_open:
|
||||||
|
logger.info(f'{order_type.upper()} is hit for {self}.')
|
||||||
|
self.close(order['average'])
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Unknown order type: {order_type}')
|
||||||
|
cleanup_db()
|
||||||
|
|
||||||
def close(self, rate: float) -> None:
|
def close(self, rate: float) -> None:
|
||||||
"""
|
"""
|
||||||
Sets close_rate to the given rate, calculates total profit
|
Sets close_rate to the given rate, calculates total profit
|
||||||
@ -468,7 +509,7 @@ class Trade(_DECL_BASE):
|
|||||||
self.sell_order_status = 'closed'
|
self.sell_order_status = 'closed'
|
||||||
self.open_order_id = None
|
self.open_order_id = None
|
||||||
logger.info(
|
logger.info(
|
||||||
'Updated position %s,',
|
'Updated position %s,',
|
||||||
self
|
self
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -606,7 +647,6 @@ class Trade(_DECL_BASE):
|
|||||||
:trade_open_rate: Actual open price of position.
|
:trade_open_rate: Actual open price of position.
|
||||||
:return: New open rate modified with the order data.
|
:return: New open rate modified with the order data.
|
||||||
"""
|
"""
|
||||||
#((order['amount'] * order['price']) + (self.amount * trade.open_rate)) / (order['amount'] + trade.amount)
|
|
||||||
return ((order_amount * order_price) + (trade_amount * trade_open_rate)) / (order_amount + trade_amount)
|
return ((order_amount * order_price) + (trade_amount * trade_open_rate)) / (order_amount + trade_amount)
|
||||||
|
|
||||||
def increase_wallet(self, amount: float, rate: float) -> None:
|
def increase_wallet(self, amount: float, rate: float) -> None:
|
||||||
@ -758,8 +798,8 @@ class PairLock(_DECL_BASE):
|
|||||||
active = Column(Boolean, nullable=False, default=True, index=True)
|
active = Column(Boolean, nullable=False, default=True, index=True)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
lock_time = self.lock_time.strftime('%Y-%m-%d %H:%M:%S')
|
lock_time = self.lock_time.strftime(DATETIME_PRINT_FORMAT)
|
||||||
lock_end_time = self.lock_end_time.strftime('%Y-%m-%d %H:%M:%S')
|
lock_end_time = self.lock_end_time.strftime(DATETIME_PRINT_FORMAT)
|
||||||
return (f'PairLock(id={self.id}, pair={self.pair}, lock_time={lock_time}, '
|
return (f'PairLock(id={self.id}, pair={self.pair}, lock_time={lock_time}, '
|
||||||
f'lock_end_time={lock_end_time})')
|
f'lock_end_time={lock_end_time})')
|
||||||
|
|
||||||
@ -783,9 +823,9 @@ class PairLock(_DECL_BASE):
|
|||||||
def to_json(self) -> Dict[str, Any]:
|
def to_json(self) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
'pair': self.pair,
|
'pair': self.pair,
|
||||||
'lock_time': self.lock_time.strftime('%Y-%m-%d %H:%M:%S'),
|
'lock_time': self.lock_time.strftime(DATETIME_PRINT_FORMAT),
|
||||||
'lock_timestamp': int(self.lock_time.replace(tzinfo=timezone.utc).timestamp() * 1000),
|
'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_time': self.lock_end_time.strftime(DATETIME_PRINT_FORMAT),
|
||||||
'lock_end_timestamp': int(self.lock_end_time.replace(tzinfo=timezone.utc
|
'lock_end_timestamp': int(self.lock_end_time.replace(tzinfo=timezone.utc
|
||||||
).timestamp() * 1000),
|
).timestamp() * 1000),
|
||||||
'reason': self.reason,
|
'reason': self.reason,
|
||||||
|
@ -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
|
||||||
|
|
||||||
@ -134,6 +134,8 @@ class IStrategy(ABC):
|
|||||||
# and wallets - access to the current balance.
|
# and wallets - access to the current balance.
|
||||||
dp: Optional[DataProvider] = None
|
dp: Optional[DataProvider] = None
|
||||||
wallets: Optional[Wallets] = None
|
wallets: Optional[Wallets] = None
|
||||||
|
# container variable for strategy source code
|
||||||
|
__source__: str = ''
|
||||||
|
|
||||||
# Definition of plot_config. See plotting documentation for more details.
|
# Definition of plot_config. See plotting documentation for more details.
|
||||||
plot_config: Dict = {}
|
plot_config: Dict = {}
|
||||||
@ -142,7 +144,6 @@ class IStrategy(ABC):
|
|||||||
self.config = config
|
self.config = config
|
||||||
# Dict to determine if analysis is necessary
|
# Dict to determine if analysis is necessary
|
||||||
self._last_candle_seen_per_pair: Dict[str, datetime] = {}
|
self._last_candle_seen_per_pair: Dict[str, datetime] = {}
|
||||||
self._pair_locked_until: Dict[str, datetime] = {}
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
@ -287,7 +288,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,9 +298,7 @@ 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:
|
||||||
"""
|
"""
|
||||||
@ -308,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:
|
||||||
"""
|
"""
|
||||||
@ -321,15 +319,13 @@ class IStrategy(ABC):
|
|||||||
:param candle_date: Date of the last candle. Optional, defaults to current date
|
:param candle_date: Date of the last candle. Optional, defaults to current date
|
||||||
:returns: locking state of the pair in question.
|
:returns: locking state of the pair in question.
|
||||||
"""
|
"""
|
||||||
if pair not in self._pair_locked_until:
|
|
||||||
return False
|
|
||||||
if not candle_date:
|
if not candle_date:
|
||||||
return self._pair_locked_until[pair] >= datetime.now(timezone.utc)
|
# Simple call ...
|
||||||
|
return PairLocks.is_pair_locked(pair, candle_date)
|
||||||
else:
|
else:
|
||||||
# Locking should happen until a new candle arrives
|
|
||||||
lock_time = timeframe_to_next_date(self.timeframe, candle_date)
|
lock_time = timeframe_to_next_date(self.timeframe, candle_date)
|
||||||
# lock_time = candle_date + timedelta(minutes=timeframe_to_minutes(self.timeframe))
|
return PairLocks.is_pair_locked(pair, lock_time)
|
||||||
return self._pair_locked_until[pair] > lock_time
|
|
||||||
|
|
||||||
def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user