2022-10-30 10:57:53 +00:00
|
|
|
from oandapyV20 import API
|
2022-11-29 07:20:39 +00:00
|
|
|
from oandapyV20.endpoints import accounts, orders, positions, pricing, trades
|
2022-10-30 11:21:48 +00:00
|
|
|
|
2022-12-13 07:20:49 +00:00
|
|
|
from core.exchanges import BaseExchange, common
|
2023-02-20 07:20:03 +00:00
|
|
|
from core.util import logs
|
|
|
|
|
|
|
|
log = logs.get_logger("oanda")
|
2022-10-30 11:21:48 +00:00
|
|
|
|
2022-10-30 10:57:53 +00:00
|
|
|
|
|
|
|
class OANDAExchange(BaseExchange):
|
2022-11-04 07:20:55 +00:00
|
|
|
def call_method(self, request):
|
2022-10-31 08:58:08 +00:00
|
|
|
self.client.request(request)
|
|
|
|
response = request.response
|
|
|
|
if isinstance(response, list):
|
|
|
|
response = {"itemlist": response}
|
2022-11-04 07:20:55 +00:00
|
|
|
return response
|
2022-10-30 19:11:07 +00:00
|
|
|
|
2022-10-30 10:57:53 +00:00
|
|
|
def connect(self):
|
|
|
|
self.client = API(access_token=self.account.api_secret)
|
|
|
|
self.account_id = self.account.api_key
|
|
|
|
|
2022-10-30 11:21:48 +00:00
|
|
|
def get_account(self):
|
|
|
|
r = accounts.AccountDetails(self.account_id)
|
2022-11-04 07:20:12 +00:00
|
|
|
return self.call(r)
|
2022-10-30 11:21:48 +00:00
|
|
|
|
2022-11-10 19:27:46 +00:00
|
|
|
def get_instruments(self):
|
2022-11-04 07:20:14 +00:00
|
|
|
r = accounts.AccountInstruments(accountID=self.account_id)
|
|
|
|
response = self.call(r)
|
2022-11-10 19:27:46 +00:00
|
|
|
return response
|
|
|
|
|
|
|
|
def get_currencies(self, currencies):
|
|
|
|
params = {"instruments": ",".join(currencies)}
|
|
|
|
r = pricing.PricingInfo(accountID=self.account_id, params=params)
|
|
|
|
response = self.call(r)
|
|
|
|
return response
|
|
|
|
|
|
|
|
def get_supported_assets(self, response=None):
|
|
|
|
if not response:
|
|
|
|
response = self.get_instruments()
|
2022-11-04 07:20:14 +00:00
|
|
|
return [x["name"] for x in response["itemlist"]]
|
2022-10-30 10:57:53 +00:00
|
|
|
|
2023-01-11 19:46:47 +00:00
|
|
|
def get_balance(self, return_usd=False):
|
2022-11-10 07:20:14 +00:00
|
|
|
r = accounts.AccountSummary(self.account_id)
|
|
|
|
response = self.call(r)
|
2022-12-13 07:20:49 +00:00
|
|
|
balance = float(response["balance"])
|
2023-01-11 19:46:47 +00:00
|
|
|
currency = response["currency"]
|
|
|
|
balance_usd = common.to_currency("sell", self.account, balance, currency, "USD")
|
2022-12-13 07:20:49 +00:00
|
|
|
|
|
|
|
common.get_balance_hook(
|
|
|
|
self.account.user.id,
|
|
|
|
self.account.user.username,
|
|
|
|
self.account.id,
|
|
|
|
self.account.name,
|
2023-01-11 19:46:47 +00:00
|
|
|
balance_usd,
|
2022-12-13 07:20:49 +00:00
|
|
|
)
|
2023-01-11 19:46:47 +00:00
|
|
|
if return_usd:
|
|
|
|
return balance_usd
|
2022-12-13 07:20:49 +00:00
|
|
|
return balance
|
2022-10-30 10:57:53 +00:00
|
|
|
|
|
|
|
def get_market_value(self, symbol):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
def post_trade(self, trade):
|
2022-11-10 07:20:14 +00:00
|
|
|
if trade.direction == "sell":
|
|
|
|
amount = -trade.amount
|
|
|
|
else:
|
|
|
|
amount = trade.amount
|
|
|
|
data = {
|
|
|
|
"order": {
|
|
|
|
# "price": "1.5000", - added later
|
2022-11-15 07:20:17 +00:00
|
|
|
"timeInForce": trade.time_in_force.upper(),
|
2022-11-10 07:20:14 +00:00
|
|
|
"instrument": trade.symbol,
|
|
|
|
"units": str(amount),
|
|
|
|
"type": trade.type.upper(),
|
|
|
|
"positionFill": "DEFAULT",
|
|
|
|
}
|
|
|
|
}
|
2022-11-29 07:20:39 +00:00
|
|
|
if trade.stop_loss is not None:
|
|
|
|
data["order"]["stopLossOnFill"] = {
|
|
|
|
"timeInForce": "GTC",
|
|
|
|
"price": str(trade.stop_loss),
|
|
|
|
}
|
|
|
|
if trade.take_profit is not None:
|
|
|
|
data["order"]["takeProfitOnFill"] = {"price": str(trade.take_profit)}
|
2022-11-10 19:52:52 +00:00
|
|
|
if trade.price is not None:
|
|
|
|
if trade.type == "limit":
|
|
|
|
data["order"]["price"] = str(trade.price)
|
|
|
|
elif trade.type == "market":
|
|
|
|
data["order"]["priceBound"] = str(trade.price)
|
2022-11-15 07:20:17 +00:00
|
|
|
if trade.trailing_stop_loss is not None:
|
|
|
|
data["order"]["trailingStopLossOnFill"] = {
|
|
|
|
"distance": str(trade.trailing_stop_loss),
|
|
|
|
"timeInForce": "GTC",
|
|
|
|
}
|
2022-11-10 07:20:14 +00:00
|
|
|
r = orders.OrderCreate(self.account_id, data=data)
|
|
|
|
response = self.call(r)
|
2022-11-10 19:27:46 +00:00
|
|
|
trade.response = response
|
|
|
|
trade.status = "posted"
|
2022-11-29 07:20:39 +00:00
|
|
|
trade.order_id = str(int(response["id"]) + 1)
|
2022-11-10 19:27:46 +00:00
|
|
|
trade.client_order_id = response["requestID"]
|
|
|
|
trade.save()
|
2022-11-10 07:20:14 +00:00
|
|
|
return response
|
2022-10-30 10:57:53 +00:00
|
|
|
|
2023-02-20 07:20:03 +00:00
|
|
|
def get_trade_precision(self, symbol):
|
|
|
|
instruments = self.account.instruments
|
|
|
|
if not instruments:
|
|
|
|
log.error("No instruments found")
|
|
|
|
return None
|
|
|
|
# Extract the information for the symbol
|
|
|
|
instrument = self.extract_instrument(instruments, symbol)
|
|
|
|
if not instrument:
|
|
|
|
log.error(f"Symbol not found: {symbol}")
|
|
|
|
return None
|
|
|
|
# Get the required precision
|
|
|
|
try:
|
|
|
|
trade_precision = instrument["tradeUnitsPrecision"]
|
|
|
|
return trade_precision
|
|
|
|
except KeyError:
|
|
|
|
log.error(f"Precision not found for {symbol} from {instrument}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def close_trade(self, trade_id, units=None, symbol=None):
|
2023-01-02 18:42:03 +00:00
|
|
|
"""
|
|
|
|
Close a trade.
|
|
|
|
"""
|
2023-02-20 07:20:03 +00:00
|
|
|
if not units:
|
|
|
|
r = trades.TradeClose(accountID=self.account_id, tradeID=trade_id)
|
|
|
|
return self.call(r)
|
|
|
|
else:
|
|
|
|
trade_precision = self.get_trade_precision(symbol)
|
|
|
|
if trade_precision is None:
|
|
|
|
log.error(f"Unable to get trade precision for {symbol}")
|
|
|
|
return None
|
|
|
|
units = round(units, trade_precision)
|
|
|
|
data = {
|
|
|
|
"units": str(units),
|
|
|
|
}
|
|
|
|
r = trades.TradeClose(
|
|
|
|
accountID=self.account_id, tradeID=trade_id, data=data
|
|
|
|
)
|
|
|
|
return self.call(r)
|
2023-01-02 18:42:03 +00:00
|
|
|
|
2022-10-30 10:57:53 +00:00
|
|
|
def get_trade(self, trade_id):
|
2022-11-29 07:20:39 +00:00
|
|
|
# OANDA is off by one...
|
|
|
|
r = trades.TradeDetails(accountID=self.account_id, tradeID=trade_id)
|
2022-11-04 07:20:12 +00:00
|
|
|
return self.call(r)
|
2022-10-30 10:57:53 +00:00
|
|
|
|
2023-02-22 07:20:37 +00:00
|
|
|
def update_trade(self, trade_id, take_profit_price, stop_loss_price):
|
|
|
|
data = {}
|
|
|
|
if take_profit_price:
|
|
|
|
data["takeProfit"] = {"price": str(take_profit_price)}
|
|
|
|
if stop_loss_price:
|
|
|
|
data["stopLoss"] = {"price": str(stop_loss_price)}
|
|
|
|
r = trades.TradeCRCDO(accountID=self.account_id, tradeID=trade_id, data=data)
|
|
|
|
return self.call(r)
|
2022-10-30 10:57:53 +00:00
|
|
|
|
|
|
|
def cancel_trade(self, trade_id):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2022-11-02 18:25:34 +00:00
|
|
|
def get_position_info(self, symbol):
|
|
|
|
r = positions.PositionDetails(self.account_id, symbol)
|
2022-12-08 07:20:46 +00:00
|
|
|
response = self.call(r)
|
|
|
|
response["account"] = self.account.name
|
|
|
|
response["account_id"] = self.account.id
|
|
|
|
return response
|
2022-10-30 10:57:53 +00:00
|
|
|
|
|
|
|
def get_all_positions(self):
|
2022-11-02 18:25:34 +00:00
|
|
|
items = []
|
2022-10-30 11:21:48 +00:00
|
|
|
r = positions.OpenPositions(accountID=self.account_id)
|
2022-11-04 07:20:55 +00:00
|
|
|
response = self.call(r)
|
2022-10-31 08:58:08 +00:00
|
|
|
|
2022-11-02 18:25:34 +00:00
|
|
|
for item in response["itemlist"]:
|
2022-11-02 18:28:31 +00:00
|
|
|
item["account"] = self.account.name
|
2022-11-02 19:04:05 +00:00
|
|
|
item["account_id"] = self.account.id
|
2022-11-02 18:25:34 +00:00
|
|
|
item["unrealized_pl"] = float(item["unrealized_pl"])
|
|
|
|
items.append(item)
|
2022-11-04 07:20:55 +00:00
|
|
|
return items
|
2022-11-14 18:29:07 +00:00
|
|
|
|
2022-12-22 07:20:49 +00:00
|
|
|
def get_all_open_trades(self):
|
|
|
|
r = trades.OpenTrades(accountID=self.account_id)
|
2023-01-11 19:46:47 +00:00
|
|
|
return self.call(r)["itemlist"]
|
2022-12-22 07:20:49 +00:00
|
|
|
|
2022-12-01 20:36:58 +00:00
|
|
|
def close_position(self, side, symbol):
|
|
|
|
data = {
|
|
|
|
f"{side}Units": "ALL",
|
|
|
|
}
|
|
|
|
r = positions.PositionClose(
|
|
|
|
accountID=self.account_id, instrument=symbol, data=data
|
|
|
|
)
|
|
|
|
response = self.call(r)
|
|
|
|
return response
|
|
|
|
|
2022-11-14 18:29:07 +00:00
|
|
|
def close_all_positions(self):
|
2023-01-05 17:25:06 +00:00
|
|
|
all_positions = self.get_all_positions()
|
|
|
|
responses = []
|
|
|
|
for position in all_positions:
|
|
|
|
side = position["side"]
|
|
|
|
symbol = position["symbol"]
|
|
|
|
data = {
|
|
|
|
f"{side}Units": "ALL",
|
|
|
|
}
|
|
|
|
r = positions.PositionClose(
|
|
|
|
accountID=self.account_id, instrument=symbol, data=data
|
|
|
|
)
|
|
|
|
response = self.call(r)
|
|
|
|
responses.append(response)
|
|
|
|
return responses
|