Continue implementing live risk checks
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from datetime import datetime
|
||||
from decimal import Decimal as D
|
||||
|
||||
from core.exchanges import GenericAPIError
|
||||
from core.exchanges import GenericAPIError, common
|
||||
from core.lib.notify import sendmsg
|
||||
from core.models import Account, Strategy, Trade
|
||||
from core.trading.crossfilter import crossfilter
|
||||
@@ -10,21 +10,50 @@ from core.util import logs
|
||||
log = logs.get_logger(__name__)
|
||||
|
||||
|
||||
def side_to_direction(side):
|
||||
def side_to_direction(side, flip_direction=False):
|
||||
"""
|
||||
Convert a side to a direction.
|
||||
:param side: Side, e.g. long, short
|
||||
:param flip_direction: Flip the direction
|
||||
:return: Direction, e.g. buy, sell
|
||||
"""
|
||||
if side == "long":
|
||||
if flip_direction:
|
||||
return "sell"
|
||||
return "buy"
|
||||
elif side == "short":
|
||||
if flip_direction:
|
||||
return "buy"
|
||||
return "sell"
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def direction_to_side(direction, flip_side=False):
|
||||
"""
|
||||
Convert a direction to a side.
|
||||
:param direction: Direction, e.g. buy, sell
|
||||
:param flip_side: Flip the side
|
||||
:return: Side, e.g. long, short
|
||||
"""
|
||||
if direction == "buy":
|
||||
if flip_side:
|
||||
return "short"
|
||||
return "long"
|
||||
elif direction == "sell":
|
||||
if flip_side:
|
||||
return "long"
|
||||
return "short"
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def convert_trades_to_usd(account, trades):
|
||||
"""
|
||||
Convert a list of trades to USD.
|
||||
Convert a list of trades to USD. Input will also be mutated.
|
||||
:param account: Account object
|
||||
:param trades: List of trades
|
||||
:return: List of trades, with amount_usd added
|
||||
"""
|
||||
for trade in trades:
|
||||
amount = trade["amount"]
|
||||
@@ -35,34 +64,19 @@ def convert_trades_to_usd(account, trades):
|
||||
print("BASE", base)
|
||||
print("QUOTE", quote)
|
||||
print("AMOUNT", amount)
|
||||
amount_usd = to_currency(direction, account, amount, quote, "USD")
|
||||
print("AMOUNT USD", amount_usd)
|
||||
amount_usd = common.to_currency(direction, account, amount, base, "USD")
|
||||
print("TRADE AMOUNT USD", amount_usd)
|
||||
trade["trade_amount_usd"] = amount_usd
|
||||
if "stop_loss_percent" in trade:
|
||||
trade["stop_loss_usd"] = (trade["stop_loss_percent"] / 100) * amount_usd
|
||||
if "take_profit_percent" in trade:
|
||||
trade["take_profit_usd"] = (trade["take_profit_percent"] / 100) * amount_usd
|
||||
if "trailing_stop_loss_percent" in trade:
|
||||
trade["trailing_stop_loss_usd"] = (
|
||||
trade["trailing_stop_loss_percent"] / 100
|
||||
) * amount_usd
|
||||
|
||||
|
||||
def get_pair(account, base, quote, invert=False):
|
||||
"""
|
||||
Get the pair for the given account and currencies.
|
||||
:param account: Account object
|
||||
:param base: Base currency
|
||||
:param quote: Quote currency
|
||||
:param invert: Invert the pair
|
||||
:return: currency symbol, e.g. BTC_USD, BTC/USD, etc.
|
||||
"""
|
||||
# Currently we only have two exchanges with different pair separators
|
||||
if account.exchange == "alpaca":
|
||||
separator = "/"
|
||||
elif account.exchange == "oanda":
|
||||
separator = "_"
|
||||
|
||||
# Flip the pair if needed
|
||||
if invert:
|
||||
symbol = f"{quote.upper()}{separator}{base.upper()}"
|
||||
else:
|
||||
symbol = f"{base.upper()}{separator}{quote.upper()}"
|
||||
# Check it exists
|
||||
if symbol not in account.supported_symbols:
|
||||
return False
|
||||
return symbol
|
||||
return trades
|
||||
|
||||
|
||||
def get_base_quote(exchange, symbol):
|
||||
@@ -80,49 +94,6 @@ def get_base_quote(exchange, symbol):
|
||||
return (base, quote)
|
||||
|
||||
|
||||
def to_currency(direction, account, amount, from_currency, to_currency):
|
||||
"""
|
||||
Convert an amount from one currency to another.
|
||||
:param direction: Direction of the trade
|
||||
:param account: Account object
|
||||
:param amount: Amount to convert
|
||||
:param from_currency: Currency to convert from
|
||||
:param to_currency: Currency to convert to
|
||||
:return: Converted amount
|
||||
"""
|
||||
inverted = False
|
||||
|
||||
# This is needed because OANDA has different values for bid and ask
|
||||
if direction == "buy":
|
||||
price_index = "bids"
|
||||
elif direction == "sell":
|
||||
price_index = "asks"
|
||||
symbol = get_pair(account, from_currency, to_currency)
|
||||
if not symbol:
|
||||
symbol = get_pair(account, from_currency, to_currency, invert=True)
|
||||
inverted = True
|
||||
|
||||
# Bit of a hack but it works
|
||||
if not symbol:
|
||||
log.error(f"Could not find symbol for {from_currency} -> {to_currency}")
|
||||
raise Exception("Could not find symbol")
|
||||
try:
|
||||
prices = account.client.get_currencies([symbol])
|
||||
except GenericAPIError as e:
|
||||
log.error(f"Error getting currencies and inverted currencies: {e}")
|
||||
return None
|
||||
price = D(prices["prices"][0][price_index][0]["price"])
|
||||
|
||||
# If we had to flip base and quote, we need to use the reciprocal of the price
|
||||
if inverted:
|
||||
price = D(1.0) / price
|
||||
|
||||
# Convert the amount to the destination currency
|
||||
converted = D(amount) * price
|
||||
|
||||
return converted
|
||||
|
||||
|
||||
def get_price(account, direction, symbol):
|
||||
"""
|
||||
Get the price for a given symbol.
|
||||
@@ -168,7 +139,7 @@ def get_trade_size_in_base(direction, account, strategy, cash_balance, base):
|
||||
if account.currency.lower() == base.lower():
|
||||
trade_size_in_base = amount_fiat
|
||||
else:
|
||||
trade_size_in_base = to_currency(
|
||||
trade_size_in_base = common.to_currency(
|
||||
direction, account, amount_fiat, account.currency, base
|
||||
)
|
||||
log.debug(f"Trade size in base: {trade_size_in_base}")
|
||||
@@ -381,7 +352,7 @@ def execute_strategy(callback, strategy, func):
|
||||
return
|
||||
|
||||
# Get the pair we are trading
|
||||
symbol = get_pair(account, base, quote)
|
||||
symbol = common.get_pair(account, base, quote)
|
||||
if not symbol:
|
||||
sendmsg(user, f"Symbol not supported by account: {symbol}", title="Error")
|
||||
log.error(f"Symbol not supported by account: {symbol}")
|
||||
|
||||
Reference in New Issue
Block a user