Merge pull request #1365 from mishaker/edge_position
Fix edge position sizing.
This commit is contained in:
commit
aa579bafa4
@ -57,7 +57,7 @@
|
|||||||
"enabled": false,
|
"enabled": false,
|
||||||
"process_throttle_secs": 3600,
|
"process_throttle_secs": 3600,
|
||||||
"calculate_since_number_of_days": 7,
|
"calculate_since_number_of_days": 7,
|
||||||
"total_capital_in_stake_currency": 0.5,
|
"capital_available_percentage": 0.5,
|
||||||
"allowed_risk": 0.01,
|
"allowed_risk": 0.01,
|
||||||
"stoploss_range_min": -0.01,
|
"stoploss_range_min": -0.01,
|
||||||
"stoploss_range_max": -0.1,
|
"stoploss_range_max": -0.1,
|
||||||
|
@ -59,7 +59,7 @@
|
|||||||
"enabled": false,
|
"enabled": false,
|
||||||
"process_throttle_secs": 3600,
|
"process_throttle_secs": 3600,
|
||||||
"calculate_since_number_of_days": 7,
|
"calculate_since_number_of_days": 7,
|
||||||
"total_capital_in_stake_currency": 0.5,
|
"capital_available_percentage": 0.5,
|
||||||
"allowed_risk": 0.01,
|
"allowed_risk": 0.01,
|
||||||
"stoploss_range_min": -0.01,
|
"stoploss_range_min": -0.01,
|
||||||
"stoploss_range_max": -0.1,
|
"stoploss_range_max": -0.1,
|
||||||
|
@ -68,7 +68,8 @@
|
|||||||
"edge": {
|
"edge": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"process_throttle_secs": 3600,
|
"process_throttle_secs": 3600,
|
||||||
"calculate_since_number_of_days": 2,
|
"calculate_since_number_of_days": 7,
|
||||||
|
"capital_available_percentage": 0.5,
|
||||||
"allowed_risk": 0.01,
|
"allowed_risk": 0.01,
|
||||||
"stoploss_range_min": -0.01,
|
"stoploss_range_min": -0.01,
|
||||||
"stoploss_range_max": -0.1,
|
"stoploss_range_max": -0.1,
|
||||||
|
29
docs/edge.md
29
docs/edge.md
@ -82,9 +82,7 @@ Edge dictates the stake amount for each trade to the bot according to the follow
|
|||||||
|
|
||||||
Allowed capital at risk is calculated as follows:
|
Allowed capital at risk is calculated as follows:
|
||||||
|
|
||||||
**allowed capital at risk** = **total capital** X **allowed risk per trade**
|
**allowed capital at risk** = **capital_available_percentage** X **allowed risk per trade**
|
||||||
|
|
||||||
**total capital** is your stake amount.
|
|
||||||
|
|
||||||
**Stoploss** is calculated as described above against historical data.
|
**Stoploss** is calculated as described above against historical data.
|
||||||
|
|
||||||
@ -92,14 +90,20 @@ Your position size then will be:
|
|||||||
|
|
||||||
**position size** = **allowed capital at risk** / **stoploss**
|
**position size** = **allowed capital at risk** / **stoploss**
|
||||||
|
|
||||||
Example:
|
Example:<br/>
|
||||||
Let's say your stake amount is 3 ETH, you would allow 1% of risk for each trade. thus your allowed capital at risk would be **3 x 0.01 = 0.03 ETH**. Let's assume Edge has calculated that for **XLM/ETH** market your stoploss should be at 2%. So your position size will be **0.03 / 0.02= 1.5ETH**.<br/>
|
Let's say the stake currency is ETH and you have 10 ETH on the exchange, your **capital_available_percentage** is 50% and you would allow 1% of risk for each trade. thus your available capital for trading is **10 x 0.5 = 5 ETH** and allowed capital at risk would be **5 x 0.01 = 0.05 ETH**. <br/>
|
||||||
|
Let's assume Edge has calculated that for **XLM/ETH** market your stoploss should be at 2%. So your position size will be **0.05 / 0.02 = 2.5ETH**.<br/>
|
||||||
|
Bot takes a position of 2.5ETH on XLM/ETH (call it trade 1). Up next, you receive another buy signal while trade 1 is still open. This time on BTC/ETH market. Edge calculated stoploss for this market at 4%. So your position size would be 0.05 / 0.04 = 1.25ETH (call it trade 2).<br/>
|
||||||
|
Note that available capital for trading didn’t change for trade 2 even if you had already trade 1. The available capital doesn’t mean the free amount on your wallet.<br/>
|
||||||
|
Now you have two trades open. The Bot receives yet another buy signal for another market: **ADA/ETH**. This time the stoploss is calculated at 1%. So your position size is **0.05 / 0.01 = 5ETH**. But there are already 4ETH blocked in two previous trades. So the position size for this third trade would be 1ETH.<br/>
|
||||||
|
Available capital doesn’t change before a position is sold. Let’s assume that trade 1 receives a sell signal and it is sold with a profit of 1ETH. Your total capital on exchange would be 11 ETH and the available capital for trading becomes 5.5ETH. <br/>
|
||||||
|
So the Bot receives another buy signal for trade 4 with a stoploss at 2% then your position size would be **0.055 / 0.02 = 2.75**.
|
||||||
|
|
||||||
## Configurations
|
## Configurations
|
||||||
Edge has following configurations:
|
Edge has following configurations:
|
||||||
|
|
||||||
#### enabled
|
#### enabled
|
||||||
If true, then Edge will run periodically<br/>
|
If true, then Edge will run periodically.<br/>
|
||||||
(default to false)
|
(default to false)
|
||||||
|
|
||||||
#### process_throttle_secs
|
#### process_throttle_secs
|
||||||
@ -108,19 +112,24 @@ How often should Edge run in seconds? <br/>
|
|||||||
|
|
||||||
#### calculate_since_number_of_days
|
#### calculate_since_number_of_days
|
||||||
Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy
|
Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy
|
||||||
Note that it downloads historical data so increasing this number would lead to slowing down the bot<br/>
|
Note that it downloads historical data so increasing this number would lead to slowing down the bot.<br/>
|
||||||
(default to 7)
|
(default to 7)
|
||||||
|
|
||||||
|
#### capital_available_percentage
|
||||||
|
This is the percentage of the total capital on exchange in stake currency. <br/>
|
||||||
|
As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital.<br/>
|
||||||
|
(default to 0.5)
|
||||||
|
|
||||||
#### allowed_risk
|
#### allowed_risk
|
||||||
Percentage of allowed risk per trade<br/>
|
Percentage of allowed risk per trade.<br/>
|
||||||
(default to 0.01 [1%])
|
(default to 0.01 [1%])
|
||||||
|
|
||||||
#### stoploss_range_min
|
#### stoploss_range_min
|
||||||
Minimum stoploss <br/>
|
Minimum stoploss.<br/>
|
||||||
(default to -0.01)
|
(default to -0.01)
|
||||||
|
|
||||||
#### stoploss_range_max
|
#### stoploss_range_max
|
||||||
Maximum stoploss <br/>
|
Maximum stoploss.<br/>
|
||||||
(default to -0.10)
|
(default to -0.10)
|
||||||
|
|
||||||
#### stoploss_range_step
|
#### stoploss_range_step
|
||||||
|
@ -192,6 +192,7 @@ CONF_SCHEMA = {
|
|||||||
"process_throttle_secs": {'type': 'integer', 'minimum': 600},
|
"process_throttle_secs": {'type': 'integer', 'minimum': 600},
|
||||||
"calculate_since_number_of_days": {'type': 'integer'},
|
"calculate_since_number_of_days": {'type': 'integer'},
|
||||||
"allowed_risk": {'type': 'number'},
|
"allowed_risk": {'type': 'number'},
|
||||||
|
"capital_available_percentage": {'type': 'number'},
|
||||||
"stoploss_range_min": {'type': 'number'},
|
"stoploss_range_min": {'type': 'number'},
|
||||||
"stoploss_range_max": {'type': 'number'},
|
"stoploss_range_max": {'type': 'number'},
|
||||||
"stoploss_range_step": {'type': 'number'},
|
"stoploss_range_step": {'type': 'number'},
|
||||||
@ -200,7 +201,8 @@ CONF_SCHEMA = {
|
|||||||
"min_trade_number": {'type': 'number'},
|
"min_trade_number": {'type': 'number'},
|
||||||
"max_trade_duration_minute": {'type': 'integer'},
|
"max_trade_duration_minute": {'type': 'integer'},
|
||||||
"remove_pumps": {'type': 'boolean'}
|
"remove_pumps": {'type': 'boolean'}
|
||||||
}
|
},
|
||||||
|
'required': ['process_throttle_secs', 'allowed_risk', 'capital_available_percentage']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'anyOf': [
|
'anyOf': [
|
||||||
|
@ -9,6 +9,7 @@ import utils_find_1st as utf1st
|
|||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
import freqtrade.optimize as optimize
|
import freqtrade.optimize as optimize
|
||||||
|
from freqtrade import constants, OperationalException
|
||||||
from freqtrade.arguments import Arguments
|
from freqtrade.arguments import Arguments
|
||||||
from freqtrade.arguments import TimeRange
|
from freqtrade.arguments import TimeRange
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
@ -52,8 +53,17 @@ class Edge():
|
|||||||
|
|
||||||
self.edge_config = self.config.get('edge', {})
|
self.edge_config = self.config.get('edge', {})
|
||||||
self._cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs
|
self._cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs
|
||||||
|
self._final_pairs: list = []
|
||||||
|
|
||||||
self._total_capital: float = self.config['stake_amount']
|
# checking max_open_trades. it should be -1 as with Edge
|
||||||
|
# the number of trades is determined by position size
|
||||||
|
if self.config['max_open_trades'] != -1:
|
||||||
|
logger.critical('max_open_trades should be -1 in config !')
|
||||||
|
|
||||||
|
if self.config['stake_amount'] != constants.UNLIMITED_STAKE_AMOUNT:
|
||||||
|
raise OperationalException('Edge works only with unlimited stake amount')
|
||||||
|
|
||||||
|
self._capital_percentage: float = self.edge_config.get('capital_available_percentage')
|
||||||
self._allowed_risk: float = self.edge_config.get('allowed_risk')
|
self._allowed_risk: float = self.edge_config.get('allowed_risk')
|
||||||
self._since_number_of_days: int = self.edge_config.get('calculate_since_number_of_days', 14)
|
self._since_number_of_days: int = self.edge_config.get('calculate_since_number_of_days', 14)
|
||||||
self._last_updated: int = 0 # Timestamp of pairs last updated time
|
self._last_updated: int = 0 # Timestamp of pairs last updated time
|
||||||
@ -150,11 +160,25 @@ class Edge():
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def stake_amount(self, pair: str) -> float:
|
def stake_amount(self, pair: str, free_capital: float,
|
||||||
stoploss = self._cached_pairs[pair].stoploss
|
total_capital: float, capital_in_trade: float) -> float:
|
||||||
allowed_capital_at_risk = round(self._total_capital * self._allowed_risk, 5)
|
stoploss = self.stoploss(pair)
|
||||||
position_size = abs(round((allowed_capital_at_risk / stoploss), 5))
|
available_capital = (total_capital + capital_in_trade) * self._capital_percentage
|
||||||
return position_size
|
allowed_capital_at_risk = available_capital * self._allowed_risk
|
||||||
|
max_position_size = abs(allowed_capital_at_risk / stoploss)
|
||||||
|
position_size = min(max_position_size, free_capital)
|
||||||
|
if pair in self._cached_pairs:
|
||||||
|
logger.info(
|
||||||
|
'winrate: %s, expectancy: %s, position size: %s, pair: %s,'
|
||||||
|
' capital in trade: %s, free capital: %s, total capital: %s,'
|
||||||
|
' stoploss: %s, available capital: %s.',
|
||||||
|
self._cached_pairs[pair].winrate,
|
||||||
|
self._cached_pairs[pair].expectancy,
|
||||||
|
position_size, pair,
|
||||||
|
capital_in_trade, free_capital, total_capital,
|
||||||
|
stoploss, available_capital
|
||||||
|
)
|
||||||
|
return round(position_size, 15)
|
||||||
|
|
||||||
def stoploss(self, pair: str) -> float:
|
def stoploss(self, pair: str) -> float:
|
||||||
if pair in self._cached_pairs:
|
if pair in self._cached_pairs:
|
||||||
@ -168,7 +192,6 @@ class Edge():
|
|||||||
"""
|
"""
|
||||||
Filters out and sorts "pairs" according to Edge calculated pairs
|
Filters out and sorts "pairs" according to Edge calculated pairs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
final = []
|
final = []
|
||||||
for pair, info in self._cached_pairs.items():
|
for pair, info in self._cached_pairs.items():
|
||||||
if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \
|
if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \
|
||||||
@ -176,12 +199,14 @@ class Edge():
|
|||||||
pair in pairs:
|
pair in pairs:
|
||||||
final.append(pair)
|
final.append(pair)
|
||||||
|
|
||||||
if final:
|
if self._final_pairs != final:
|
||||||
logger.info('Edge validated only %s', final)
|
self._final_pairs = final
|
||||||
else:
|
if self._final_pairs:
|
||||||
logger.info('Edge removed all pairs as no pair with minimum expectancy was found !')
|
logger.info('Edge validated only %s', self._final_pairs)
|
||||||
|
else:
|
||||||
|
logger.info('Edge removed all pairs as no pair with minimum expectancy was found !')
|
||||||
|
|
||||||
return final
|
return self._final_pairs
|
||||||
|
|
||||||
def _fill_calculable_fields(self, result: DataFrame) -> DataFrame:
|
def _fill_calculable_fields(self, result: DataFrame) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
@ -202,9 +227,11 @@ class Edge():
|
|||||||
# 0.05% is 0.0005
|
# 0.05% is 0.0005
|
||||||
# fee = 0.001
|
# fee = 0.001
|
||||||
|
|
||||||
stake = self.config.get('stake_amount')
|
# we set stake amount to an arbitrary amount.
|
||||||
|
# as it doesn't change the calculation.
|
||||||
|
# all returned values are relative. they are percentages.
|
||||||
|
stake = 0.015
|
||||||
fee = self.fee
|
fee = self.fee
|
||||||
|
|
||||||
open_fee = fee / 2
|
open_fee = fee / 2
|
||||||
close_fee = fee / 2
|
close_fee = fee / 2
|
||||||
|
|
||||||
|
@ -302,7 +302,12 @@ class FreqtradeBot(object):
|
|||||||
:return: float: Stake Amount
|
:return: float: Stake Amount
|
||||||
"""
|
"""
|
||||||
if self.edge:
|
if self.edge:
|
||||||
stake_amount = self.edge.stake_amount(pair)
|
return self.edge.stake_amount(
|
||||||
|
pair,
|
||||||
|
self.wallets.get_free(self.config['stake_currency']),
|
||||||
|
self.wallets.get_total(self.config['stake_currency']),
|
||||||
|
Trade.total_open_trades_stakes()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
stake_amount = self.config['stake_amount']
|
stake_amount = self.config['stake_amount']
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ from sqlalchemy.exc import NoSuchModuleError
|
|||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.orm.scoping import scoped_session
|
from sqlalchemy.orm.scoping import scoped_session
|
||||||
from sqlalchemy.orm.session import sessionmaker
|
from sqlalchemy.orm.session import sessionmaker
|
||||||
|
from sqlalchemy import func
|
||||||
from sqlalchemy.pool import StaticPool
|
from sqlalchemy.pool import StaticPool
|
||||||
|
|
||||||
from freqtrade import OperationalException
|
from freqtrade import OperationalException
|
||||||
@ -349,3 +350,14 @@ class Trade(_DECL_BASE):
|
|||||||
)
|
)
|
||||||
profit_percent = (close_trade_price / open_trade_price) - 1
|
profit_percent = (close_trade_price / open_trade_price) - 1
|
||||||
return float(f"{profit_percent:.8f}")
|
return float(f"{profit_percent:.8f}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def total_open_trades_stakes() -> float:
|
||||||
|
"""
|
||||||
|
Calculates total invested amount in open trades
|
||||||
|
in stake currency
|
||||||
|
"""
|
||||||
|
total_open_stake_amount = Trade.session.query(func.sum(Trade.stake_amount))\
|
||||||
|
.filter(Trade.is_open.is_(True))\
|
||||||
|
.scalar()
|
||||||
|
return total_open_stake_amount or 0
|
||||||
|
@ -10,6 +10,7 @@ import arrow
|
|||||||
import pytest
|
import pytest
|
||||||
from telegram import Chat, Message, Update
|
from telegram import Chat, Message, Update
|
||||||
|
|
||||||
|
from freqtrade import constants
|
||||||
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
|
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
from freqtrade.edge import Edge, PairInfo
|
from freqtrade.edge import Edge, PairInfo
|
||||||
@ -63,7 +64,6 @@ def patch_edge(mocker) -> None:
|
|||||||
'LTC/BTC': PairInfo(-0.21, 0.66, 3.71, 0.50, 1.71, 11, 20),
|
'LTC/BTC': PairInfo(-0.21, 0.66, 3.71, 0.50, 1.71, 11, 20),
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
mocker.patch('freqtrade.edge.Edge.stoploss', MagicMock(return_value=-0.20))
|
|
||||||
mocker.patch('freqtrade.edge.Edge.calculate', MagicMock(return_value=True))
|
mocker.patch('freqtrade.edge.Edge.calculate', MagicMock(return_value=True))
|
||||||
|
|
||||||
|
|
||||||
@ -788,10 +788,13 @@ def buy_order_fee():
|
|||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def edge_conf(default_conf):
|
def edge_conf(default_conf):
|
||||||
|
default_conf['max_open_trades'] = -1
|
||||||
|
default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
||||||
default_conf['edge'] = {
|
default_conf['edge'] = {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"process_throttle_secs": 1800,
|
"process_throttle_secs": 1800,
|
||||||
"calculate_since_number_of_days": 14,
|
"calculate_since_number_of_days": 14,
|
||||||
|
"capital_available_percentage": 0.5,
|
||||||
"allowed_risk": 0.01,
|
"allowed_risk": 0.01,
|
||||||
"stoploss_range_min": -0.01,
|
"stoploss_range_min": -0.01,
|
||||||
"stoploss_range_max": -0.1,
|
"stoploss_range_max": -0.1,
|
||||||
|
@ -123,9 +123,9 @@ def test_edge_results(edge_conf, mocker, caplog, data) -> None:
|
|||||||
assert res.close_time == _get_frame_time_from_offset(trade.close_tick)
|
assert res.close_time == _get_frame_time_from_offset(trade.close_tick)
|
||||||
|
|
||||||
|
|
||||||
def test_adjust(mocker, default_conf):
|
def test_adjust(mocker, edge_conf):
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||||
return_value={
|
return_value={
|
||||||
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
||||||
@ -138,9 +138,9 @@ def test_adjust(mocker, default_conf):
|
|||||||
assert(edge.adjust(pairs) == ['E/F', 'C/D'])
|
assert(edge.adjust(pairs) == ['E/F', 'C/D'])
|
||||||
|
|
||||||
|
|
||||||
def test_stoploss(mocker, default_conf):
|
def test_stoploss(mocker, edge_conf):
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||||
return_value={
|
return_value={
|
||||||
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
||||||
@ -152,9 +152,9 @@ def test_stoploss(mocker, default_conf):
|
|||||||
assert edge.stoploss('E/F') == -0.01
|
assert edge.stoploss('E/F') == -0.01
|
||||||
|
|
||||||
|
|
||||||
def test_nonexisting_stoploss(mocker, default_conf):
|
def test_nonexisting_stoploss(mocker, edge_conf):
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||||
return_value={
|
return_value={
|
||||||
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
||||||
@ -164,6 +164,42 @@ def test_nonexisting_stoploss(mocker, default_conf):
|
|||||||
assert edge.stoploss('N/O') == -0.1
|
assert edge.stoploss('N/O') == -0.1
|
||||||
|
|
||||||
|
|
||||||
|
def test_stake_amount(mocker, edge_conf):
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||||
|
return_value={
|
||||||
|
'E/F': PairInfo(-0.02, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
||||||
|
}
|
||||||
|
))
|
||||||
|
free = 100
|
||||||
|
total = 100
|
||||||
|
in_trade = 25
|
||||||
|
assert edge.stake_amount('E/F', free, total, in_trade) == 31.25
|
||||||
|
|
||||||
|
free = 20
|
||||||
|
total = 100
|
||||||
|
in_trade = 25
|
||||||
|
assert edge.stake_amount('E/F', free, total, in_trade) == 20
|
||||||
|
|
||||||
|
free = 0
|
||||||
|
total = 100
|
||||||
|
in_trade = 25
|
||||||
|
assert edge.stake_amount('E/F', free, total, in_trade) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_nonexisting_stake_amount(mocker, edge_conf):
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||||
|
return_value={
|
||||||
|
'E/F': PairInfo(-0.11, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
||||||
|
}
|
||||||
|
))
|
||||||
|
# should use strategy stoploss
|
||||||
|
assert edge.stake_amount('N/O', 1, 2, 1) == 0.15
|
||||||
|
|
||||||
|
|
||||||
def _validate_ohlc(buy_ohlc_sell_matrice):
|
def _validate_ohlc(buy_ohlc_sell_matrice):
|
||||||
for index, ohlc in enumerate(buy_ohlc_sell_matrice):
|
for index, ohlc in enumerate(buy_ohlc_sell_matrice):
|
||||||
# if not high < open < low or not high < close < low
|
# if not high < open < low or not high < close < low
|
||||||
@ -246,12 +282,12 @@ def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=Fals
|
|||||||
return pairdata
|
return pairdata
|
||||||
|
|
||||||
|
|
||||||
def test_edge_process_downloaded_data(mocker, default_conf):
|
def test_edge_process_downloaded_data(mocker, edge_conf):
|
||||||
default_conf['datadir'] = None
|
edge_conf['datadir'] = None
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
|
||||||
mocker.patch('freqtrade.optimize.load_data', mocked_load_data)
|
mocker.patch('freqtrade.optimize.load_data', mocked_load_data)
|
||||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
|
|
||||||
assert edge.calculate()
|
assert edge.calculate()
|
||||||
assert len(edge._cached_pairs) == 2
|
assert len(edge._cached_pairs) == 2
|
||||||
|
@ -260,8 +260,8 @@ def test_edge_overrides_stake_amount(mocker, edge_conf) -> None:
|
|||||||
patch_edge(mocker)
|
patch_edge(mocker)
|
||||||
freqtrade = FreqtradeBot(edge_conf)
|
freqtrade = FreqtradeBot(edge_conf)
|
||||||
|
|
||||||
assert freqtrade._get_trade_stake_amount('NEO/BTC') == (0.001 * 0.01) / 0.20
|
assert freqtrade._get_trade_stake_amount('NEO/BTC') == (999.9 * 0.5 * 0.01) / 0.20
|
||||||
assert freqtrade._get_trade_stake_amount('LTC/BTC') == (0.001 * 0.01) / 0.20
|
assert freqtrade._get_trade_stake_amount('LTC/BTC') == (999.9 * 0.5 * 0.01) / 0.21
|
||||||
|
|
||||||
|
|
||||||
def test_edge_overrides_stoploss(limit_buy_order, fee, markets, caplog, mocker, edge_conf) -> None:
|
def test_edge_overrides_stoploss(limit_buy_order, fee, markets, caplog, mocker, edge_conf) -> None:
|
||||||
@ -342,6 +342,39 @@ def test_edge_should_ignore_strategy_stoploss(limit_buy_order, fee, markets,
|
|||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_total_open_trades_stakes(mocker, default_conf, ticker,
|
||||||
|
limit_buy_order, fee, markets) -> None:
|
||||||
|
patch_RPCManager(mocker)
|
||||||
|
patch_exchange(mocker)
|
||||||
|
default_conf['stake_amount'] = 0.0000098751
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
get_ticker=ticker,
|
||||||
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
|
)
|
||||||
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
|
patch_get_signal(freqtrade)
|
||||||
|
freqtrade.create_trade()
|
||||||
|
trade = Trade.query.first()
|
||||||
|
|
||||||
|
assert trade is not None
|
||||||
|
assert trade.stake_amount == 0.0000098751
|
||||||
|
assert trade.is_open
|
||||||
|
assert trade.open_date is not None
|
||||||
|
|
||||||
|
freqtrade.create_trade()
|
||||||
|
trade = Trade.query.order_by(Trade.id.desc()).first()
|
||||||
|
|
||||||
|
assert trade is not None
|
||||||
|
assert trade.stake_amount == 0.0000098751
|
||||||
|
assert trade.is_open
|
||||||
|
assert trade.open_date is not None
|
||||||
|
|
||||||
|
assert Trade.total_open_trades_stakes() == 1.97502e-05
|
||||||
|
|
||||||
|
|
||||||
def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
||||||
patch_RPCManager(mocker)
|
patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
@ -58,6 +58,8 @@ def test_sync_wallet_at_boot(mocker, default_conf):
|
|||||||
assert freqtrade.wallets.wallets['GAS'].used == 0.1
|
assert freqtrade.wallets.wallets['GAS'].used == 0.1
|
||||||
assert freqtrade.wallets.wallets['GAS'].total == 0.260439
|
assert freqtrade.wallets.wallets['GAS'].total == 0.260439
|
||||||
assert freqtrade.wallets.get_free('GAS') == 0.270739
|
assert freqtrade.wallets.get_free('GAS') == 0.270739
|
||||||
|
assert freqtrade.wallets.get_used('GAS') == 0.1
|
||||||
|
assert freqtrade.wallets.get_total('GAS') == 0.260439
|
||||||
|
|
||||||
|
|
||||||
def test_sync_wallet_missing_data(mocker, default_conf):
|
def test_sync_wallet_missing_data(mocker, default_conf):
|
||||||
|
@ -40,6 +40,28 @@ class Wallets(object):
|
|||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def get_used(self, currency) -> float:
|
||||||
|
|
||||||
|
if self.exchange._conf['dry_run']:
|
||||||
|
return 999.9
|
||||||
|
|
||||||
|
balance = self.wallets.get(currency)
|
||||||
|
if balance and balance.used:
|
||||||
|
return balance.used
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_total(self, currency) -> float:
|
||||||
|
|
||||||
|
if self.exchange._conf['dry_run']:
|
||||||
|
return 999.9
|
||||||
|
|
||||||
|
balance = self.wallets.get(currency)
|
||||||
|
if balance and balance.total:
|
||||||
|
return balance.total
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
def update(self) -> None:
|
def update(self) -> None:
|
||||||
balances = self.exchange.get_balances()
|
balances = self.exchange.get_balances()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user