diff --git a/app/urls.py b/app/urls.py index ff882e0..a6b07d4 100644 --- a/app/urls.py +++ b/app/urls.py @@ -105,6 +105,11 @@ urlpatterns = [ trades.TradeUpdate.as_view(), name="trade_update", ), + path( + "trades//view//", + trades.TradeAction.as_view(), + name="trade_action", + ), path( "trades//delete//", trades.TradeDelete.as_view(), diff --git a/core/exchanges/__init__.py b/core/exchanges/__init__.py index 16bb021..f7182e4 100644 --- a/core/exchanges/__init__.py +++ b/core/exchanges/__init__.py @@ -131,7 +131,6 @@ class BaseExchange(ABC): def validate_response(self, response, method): schema = self.get_schema(method) # Return a dict of the validated response - print("RESP", response) response_valid = schema(**response).dict() return response_valid diff --git a/core/exchanges/oanda.py b/core/exchanges/oanda.py index 9a53b02..005205b 100644 --- a/core/exchanges/oanda.py +++ b/core/exchanges/oanda.py @@ -1,5 +1,5 @@ from oandapyV20 import API -from oandapyV20.endpoints import accounts, orders, positions, pricing +from oandapyV20.endpoints import accounts, orders, positions, pricing, trades from core.exchanges import BaseExchange @@ -52,8 +52,6 @@ class OANDAExchange(BaseExchange): data = { "order": { # "price": "1.5000", - added later - "stopLossOnFill": {"timeInForce": "GTC", "price": str(trade.stop_loss)}, - "takeProfitOnFill": {"price": str(trade.take_profit)}, "timeInForce": trade.time_in_force.upper(), "instrument": trade.symbol, "units": str(amount), @@ -61,6 +59,13 @@ class OANDAExchange(BaseExchange): "positionFill": "DEFAULT", } } + 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)} if trade.price is not None: if trade.type == "limit": data["order"]["price"] = str(trade.price) @@ -75,13 +80,14 @@ class OANDAExchange(BaseExchange): response = self.call(r) trade.response = response trade.status = "posted" - trade.order_id = response["id"] + trade.order_id = str(int(response["id"]) + 1) trade.client_order_id = response["requestID"] trade.save() return response def get_trade(self, trade_id): - r = accounts.TradeDetails(accountID=self.account_id, tradeID=trade_id) + # OANDA is off by one... + r = trades.TradeDetails(accountID=self.account_id, tradeID=trade_id) return self.call(r) def update_trade(self, trade): diff --git a/core/lib/schemas/oanda_s.py b/core/lib/schemas/oanda_s.py index d3ac826..2e915e6 100644 --- a/core/lib/schemas/oanda_s.py +++ b/core/lib/schemas/oanda_s.py @@ -49,7 +49,7 @@ class OpenPositions(BaseModel): def parse_prices(x): if float(x["long"]["units"]) > 0: return x["long"]["averagePrice"] - elif float(x["short"]["units"]) > 0: + elif float(x["short"]["units"]) < 0: return x["short"]["averagePrice"] else: return 0 @@ -58,7 +58,7 @@ def parse_prices(x): def parse_units(x): if float(x["long"]["units"]) > 0: return x["long"]["units"] - elif float(x["short"]["units"]) > 0: + elif float(x["short"]["units"]) < 0: return x["short"]["units"] else: return 0 @@ -67,7 +67,7 @@ def parse_units(x): def parse_value(x): if float(x["long"]["units"]) > 0: return D(x["long"]["units"]) * D(x["long"]["averagePrice"]) - elif float(x["short"]["units"]) > 0: + elif float(x["short"]["units"]) < 0: return D(x["short"]["units"]) * D(x["short"]["averagePrice"]) else: return 0 @@ -76,12 +76,21 @@ def parse_value(x): def parse_side(x): if float(x["long"]["units"]) > 0: return "long" - elif float(x["short"]["units"]) > 0: + elif float(x["short"]["units"]) < 0: return "short" else: return "unknown" +def parse_trade_ids(x, sum=-1): + if float(x["long"]["units"]) > 0: + return [str(int(y) + sum) for y in x["long"]["tradeIDs"]] + elif float(x["short"]["units"]) < 0: + return [str(int(y) + sum) for y in x["short"]["tradeIDs"]] + else: + return "unknown" + + OpenPositionsSchema = { "itemlist": ( "positions", @@ -89,6 +98,7 @@ OpenPositionsSchema = { { "symbol": "instrument", "unrealized_pl": "unrealizedPL", + "trade_ids": parse_trade_ids, # actual value is lower by 1 "price": parse_prices, "units": parse_units, "side": parse_side, @@ -284,6 +294,9 @@ PositionDetailsSchema = { "units": lambda x: parse_units(x["position"]), "side": lambda x: parse_side(x["position"]), "value": lambda x: parse_value(x["position"]), + "trade_ids": lambda x: parse_trade_ids( + x["position"], sum=0 + ), # this value is correct } diff --git a/core/models.py b/core/models.py index 37c18cd..b5f5758 100644 --- a/core/models.py +++ b/core/models.py @@ -207,6 +207,20 @@ class Trade(models.Model): # close the trade super().delete(*args, **kwargs) + @classmethod + def get_by_id(cls, trade_id, user): + return cls.objects.get(id=trade_id, user=user) + + @classmethod + def get_by_id_or_order(cls, trade_id, user): + try: + return cls.objects.get(id=trade_id, user=user) + except cls.DoesNotExist: + try: + return cls.objects.get(order_id=trade_id, user=user) + except cls.DoesNotExist: + return None + class Callback(models.Model): hook = models.ForeignKey(Hook, on_delete=models.CASCADE) diff --git a/core/templates/partials/error.html b/core/templates/partials/error.html new file mode 100644 index 0000000..f7c740d --- /dev/null +++ b/core/templates/partials/error.html @@ -0,0 +1,5 @@ +{% extends 'base.html' %} + +{% block content %} + {% include 'partials/notify.html' %} +{% endblock %} \ No newline at end of file diff --git a/core/templates/partials/position-list.html b/core/templates/partials/position-list.html index 7912d85..c4d91fa 100644 --- a/core/templates/partials/position-list.html +++ b/core/templates/partials/position-list.html @@ -4,10 +4,11 @@ account asset price - value in base - value in quote + units + quote P/L side + stored actions {% for item in items %} @@ -21,7 +22,18 @@ {{ item.units }} {{ item.value }} {{ item.unrealized_pl }} - {{ item.side }} + + {% if item.side == 'long' %} + + + + {% elif item.side == 'short' %} + + + + {% endif %} + + {{ item.trades|length }}
{% if type == 'page' %} - + {% else %}