Improve navigating trades and positions by cross-linking
This commit is contained in:
parent
4e1b574921
commit
f240c4b381
|
@ -105,6 +105,11 @@ urlpatterns = [
|
||||||
trades.TradeUpdate.as_view(),
|
trades.TradeUpdate.as_view(),
|
||||||
name="trade_update",
|
name="trade_update",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"trades/<str:type>/view/<str:trade_id>/",
|
||||||
|
trades.TradeAction.as_view(),
|
||||||
|
name="trade_action",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"trades/<str:type>/delete/<str:pk>/",
|
"trades/<str:type>/delete/<str:pk>/",
|
||||||
trades.TradeDelete.as_view(),
|
trades.TradeDelete.as_view(),
|
||||||
|
|
|
@ -131,7 +131,6 @@ class BaseExchange(ABC):
|
||||||
def validate_response(self, response, method):
|
def validate_response(self, response, method):
|
||||||
schema = self.get_schema(method)
|
schema = self.get_schema(method)
|
||||||
# Return a dict of the validated response
|
# Return a dict of the validated response
|
||||||
print("RESP", response)
|
|
||||||
response_valid = schema(**response).dict()
|
response_valid = schema(**response).dict()
|
||||||
return response_valid
|
return response_valid
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from oandapyV20 import API
|
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
|
from core.exchanges import BaseExchange
|
||||||
|
|
||||||
|
@ -52,8 +52,6 @@ class OANDAExchange(BaseExchange):
|
||||||
data = {
|
data = {
|
||||||
"order": {
|
"order": {
|
||||||
# "price": "1.5000", - added later
|
# "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(),
|
"timeInForce": trade.time_in_force.upper(),
|
||||||
"instrument": trade.symbol,
|
"instrument": trade.symbol,
|
||||||
"units": str(amount),
|
"units": str(amount),
|
||||||
|
@ -61,6 +59,13 @@ class OANDAExchange(BaseExchange):
|
||||||
"positionFill": "DEFAULT",
|
"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.price is not None:
|
||||||
if trade.type == "limit":
|
if trade.type == "limit":
|
||||||
data["order"]["price"] = str(trade.price)
|
data["order"]["price"] = str(trade.price)
|
||||||
|
@ -75,13 +80,14 @@ class OANDAExchange(BaseExchange):
|
||||||
response = self.call(r)
|
response = self.call(r)
|
||||||
trade.response = response
|
trade.response = response
|
||||||
trade.status = "posted"
|
trade.status = "posted"
|
||||||
trade.order_id = response["id"]
|
trade.order_id = str(int(response["id"]) + 1)
|
||||||
trade.client_order_id = response["requestID"]
|
trade.client_order_id = response["requestID"]
|
||||||
trade.save()
|
trade.save()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get_trade(self, trade_id):
|
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)
|
return self.call(r)
|
||||||
|
|
||||||
def update_trade(self, trade):
|
def update_trade(self, trade):
|
||||||
|
|
|
@ -49,7 +49,7 @@ class OpenPositions(BaseModel):
|
||||||
def parse_prices(x):
|
def parse_prices(x):
|
||||||
if float(x["long"]["units"]) > 0:
|
if float(x["long"]["units"]) > 0:
|
||||||
return x["long"]["averagePrice"]
|
return x["long"]["averagePrice"]
|
||||||
elif float(x["short"]["units"]) > 0:
|
elif float(x["short"]["units"]) < 0:
|
||||||
return x["short"]["averagePrice"]
|
return x["short"]["averagePrice"]
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
@ -58,7 +58,7 @@ def parse_prices(x):
|
||||||
def parse_units(x):
|
def parse_units(x):
|
||||||
if float(x["long"]["units"]) > 0:
|
if float(x["long"]["units"]) > 0:
|
||||||
return x["long"]["units"]
|
return x["long"]["units"]
|
||||||
elif float(x["short"]["units"]) > 0:
|
elif float(x["short"]["units"]) < 0:
|
||||||
return x["short"]["units"]
|
return x["short"]["units"]
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
@ -67,7 +67,7 @@ def parse_units(x):
|
||||||
def parse_value(x):
|
def parse_value(x):
|
||||||
if float(x["long"]["units"]) > 0:
|
if float(x["long"]["units"]) > 0:
|
||||||
return D(x["long"]["units"]) * D(x["long"]["averagePrice"])
|
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"])
|
return D(x["short"]["units"]) * D(x["short"]["averagePrice"])
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
@ -76,12 +76,21 @@ def parse_value(x):
|
||||||
def parse_side(x):
|
def parse_side(x):
|
||||||
if float(x["long"]["units"]) > 0:
|
if float(x["long"]["units"]) > 0:
|
||||||
return "long"
|
return "long"
|
||||||
elif float(x["short"]["units"]) > 0:
|
elif float(x["short"]["units"]) < 0:
|
||||||
return "short"
|
return "short"
|
||||||
else:
|
else:
|
||||||
return "unknown"
|
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 = {
|
OpenPositionsSchema = {
|
||||||
"itemlist": (
|
"itemlist": (
|
||||||
"positions",
|
"positions",
|
||||||
|
@ -89,6 +98,7 @@ OpenPositionsSchema = {
|
||||||
{
|
{
|
||||||
"symbol": "instrument",
|
"symbol": "instrument",
|
||||||
"unrealized_pl": "unrealizedPL",
|
"unrealized_pl": "unrealizedPL",
|
||||||
|
"trade_ids": parse_trade_ids, # actual value is lower by 1
|
||||||
"price": parse_prices,
|
"price": parse_prices,
|
||||||
"units": parse_units,
|
"units": parse_units,
|
||||||
"side": parse_side,
|
"side": parse_side,
|
||||||
|
@ -284,6 +294,9 @@ PositionDetailsSchema = {
|
||||||
"units": lambda x: parse_units(x["position"]),
|
"units": lambda x: parse_units(x["position"]),
|
||||||
"side": lambda x: parse_side(x["position"]),
|
"side": lambda x: parse_side(x["position"]),
|
||||||
"value": lambda x: parse_value(x["position"]),
|
"value": lambda x: parse_value(x["position"]),
|
||||||
|
"trade_ids": lambda x: parse_trade_ids(
|
||||||
|
x["position"], sum=0
|
||||||
|
), # this value is correct
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -207,6 +207,20 @@ class Trade(models.Model):
|
||||||
# close the trade
|
# close the trade
|
||||||
super().delete(*args, **kwargs)
|
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):
|
class Callback(models.Model):
|
||||||
hook = models.ForeignKey(Hook, on_delete=models.CASCADE)
|
hook = models.ForeignKey(Hook, on_delete=models.CASCADE)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% include 'partials/notify.html' %}
|
||||||
|
{% endblock %}
|
|
@ -4,10 +4,11 @@
|
||||||
<th>account</th>
|
<th>account</th>
|
||||||
<th>asset</th>
|
<th>asset</th>
|
||||||
<th>price</th>
|
<th>price</th>
|
||||||
<th>value in base</th>
|
<th>units</th>
|
||||||
<th>value in quote</th>
|
<th>quote</th>
|
||||||
<th>P/L</th>
|
<th>P/L</th>
|
||||||
<th>side</th>
|
<th>side</th>
|
||||||
|
<th>stored</th>
|
||||||
<th>actions</th>
|
<th>actions</th>
|
||||||
</thead>
|
</thead>
|
||||||
{% for item in items %}
|
{% for item in items %}
|
||||||
|
@ -21,7 +22,18 @@
|
||||||
<td>{{ item.units }}</td>
|
<td>{{ item.units }}</td>
|
||||||
<td>{{ item.value }}</td>
|
<td>{{ item.value }}</td>
|
||||||
<td>{{ item.unrealized_pl }}</td>
|
<td>{{ item.unrealized_pl }}</td>
|
||||||
<td>{{ item.side }}</td>
|
<td>
|
||||||
|
{% if item.side == 'long' %}
|
||||||
|
<span class="icon has-text-success" data-tooltip="long">
|
||||||
|
<i class="fa-solid fa-up"></i>
|
||||||
|
</span>
|
||||||
|
{% elif item.side == 'short' %}
|
||||||
|
<span class="icon has-text-danger" data-tooltip="short">
|
||||||
|
<i class="fa-solid fa-down"></i>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ item.trades|length }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -59,7 +59,8 @@
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
{% if type == 'page' %}
|
{% if type == 'page' %}
|
||||||
<a href="#"><button
|
<a href="{% url 'trade_action' type=type trade_id=item.id %}">
|
||||||
|
<button
|
||||||
class="button is-success">
|
class="button is-success">
|
||||||
<span class="icon-text">
|
<span class="icon-text">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
|
@ -71,7 +72,7 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<button
|
<button
|
||||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||||
hx-get="#"
|
hx-get="{% url 'trade_action' type=type trade_id=item.id %}"
|
||||||
hx-trigger="click"
|
hx-trigger="click"
|
||||||
hx-target="#{{ type }}s-here"
|
hx-target="#{{ type }}s-here"
|
||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML"
|
||||||
|
|
|
@ -1,3 +1,53 @@
|
||||||
TRADE DETAILS
|
{% load pretty %}
|
||||||
{{ items }}
|
{% include 'partials/notify.html' %}
|
||||||
{{ status }}
|
|
||||||
|
<h1 class="title">Live information</h1>
|
||||||
|
<table class="table is-fullwidth is-hoverable">
|
||||||
|
<thead>
|
||||||
|
<th>attribute</th>
|
||||||
|
<th>value</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for key, item in live_info.items %}
|
||||||
|
<tr>
|
||||||
|
<th>{{ key }}</th>
|
||||||
|
<td>
|
||||||
|
{% if item is not None %}
|
||||||
|
{{ item }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h1 class="title">Stored information</h1>
|
||||||
|
<table class="table is-fullwidth is-hoverable">
|
||||||
|
<thead>
|
||||||
|
<th>attribute</th>
|
||||||
|
<th>value</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for key, item in db_info.items %}
|
||||||
|
{% if key == 'instruments' %}
|
||||||
|
<tr>
|
||||||
|
<th>{{ key }}</th>
|
||||||
|
<td>
|
||||||
|
{% if item is not None %}
|
||||||
|
<pre>{{ item|pretty }}</pre>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<th>{{ key }}</th>
|
||||||
|
<td>
|
||||||
|
{% if item is not None %}
|
||||||
|
{{ item }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
|
@ -1,5 +1,6 @@
|
||||||
{% include 'partials/notify.html' %}
|
{% include 'partials/notify.html' %}
|
||||||
<h1 class="title">Live information</h1>
|
<h1 class="title">Live information</h1>
|
||||||
|
{{ valid_trade_ids }}
|
||||||
<table class="table is-fullwidth is-hoverable">
|
<table class="table is-fullwidth is-hoverable">
|
||||||
<thead>
|
<thead>
|
||||||
<th>attribute</th>
|
<th>attribute</th>
|
||||||
|
@ -8,12 +9,31 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for key, item in items.items %}
|
{% for key, item in items.items %}
|
||||||
<tr>
|
<tr>
|
||||||
|
{% if key == 'trade_ids' %}
|
||||||
|
<th>{{ key }}</th>
|
||||||
|
<td>
|
||||||
|
{% if item is not None %}
|
||||||
|
{% for trade_id in item %}
|
||||||
|
<button
|
||||||
|
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||||
|
hx-get="{% url 'trade_action' type=type trade_id=trade_id %}"
|
||||||
|
hx-trigger="click"
|
||||||
|
hx-target="#modals-here"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
class="button is-small {% if trade_id in valid_trade_ids %}is-primary{% else %}is-warning{% endif %}">
|
||||||
|
{{ trade_id }}
|
||||||
|
</button>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% else %}
|
||||||
<th>{{ key }}</th>
|
<th>{{ key }}</th>
|
||||||
<td>
|
<td>
|
||||||
{% if item is not None %}
|
{% if item is not None %}
|
||||||
{{ item }}
|
{{ item }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -227,7 +227,9 @@ class ObjectCreate(RestrictedViewMixin, ObjectNameMixin, CreateView):
|
||||||
context["submit_url"] = submit_url
|
context["submit_url"] = submit_url
|
||||||
context["list_url"] = list_url
|
context["list_url"] = list_url
|
||||||
context["type"] = type
|
context["type"] = type
|
||||||
return self.render_to_response(context)
|
response = self.render_to_response(context)
|
||||||
|
# response["HX-Trigger"] = f"{self.context_object_name_singular}Event"
|
||||||
|
return response
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.request = request
|
self.request = request
|
||||||
|
@ -298,7 +300,9 @@ class ObjectUpdate(RestrictedViewMixin, ObjectNameMixin, UpdateView):
|
||||||
context["context_object_name_singular"] = self.context_object_name_singular
|
context["context_object_name_singular"] = self.context_object_name_singular
|
||||||
context["submit_url"] = submit_url
|
context["submit_url"] = submit_url
|
||||||
context["type"] = type
|
context["type"] = type
|
||||||
return self.render_to_response(context)
|
response = self.render_to_response(context)
|
||||||
|
# response["HX-Trigger"] = f"{self.context_object_name_singular}Event"
|
||||||
|
return response
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.request = request
|
self.request = request
|
||||||
|
|
|
@ -8,7 +8,7 @@ from rest_framework.parsers import FormParser
|
||||||
from two_factor.views.mixins import OTPRequiredMixin
|
from two_factor.views.mixins import OTPRequiredMixin
|
||||||
|
|
||||||
from core.exchanges import GenericAPIError
|
from core.exchanges import GenericAPIError
|
||||||
from core.models import Account
|
from core.models import Account, Trade
|
||||||
from core.util import logs
|
from core.util import logs
|
||||||
|
|
||||||
log = logs.get_logger(__name__)
|
log = logs.get_logger(__name__)
|
||||||
|
@ -28,6 +28,32 @@ def get_positions(user, account_id=None):
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def annotate_positions(positions, user, return_order_ids=False):
|
||||||
|
"""
|
||||||
|
Annotate positions with trade information.
|
||||||
|
If return_order_ids is True, yield a list of order_ids instead of a list of trades.
|
||||||
|
:param positions: list of positions
|
||||||
|
:param user: user
|
||||||
|
:param return_order_ids: whether to return a generator of order_ids
|
||||||
|
:return: None or list of order_ids
|
||||||
|
:rtype: None or generator
|
||||||
|
"""
|
||||||
|
for item in positions:
|
||||||
|
try:
|
||||||
|
if "trade_ids" in item:
|
||||||
|
if return_order_ids:
|
||||||
|
for trade_id in Trade.objects.filter(
|
||||||
|
user=user, order_id__in=item["trade_ids"]
|
||||||
|
):
|
||||||
|
yield trade_id.order_id
|
||||||
|
else:
|
||||||
|
item["trades"] = Trade.objects.filter(
|
||||||
|
user=user, order_id__in=item["trade_ids"]
|
||||||
|
)
|
||||||
|
except Trade.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Positions(LoginRequiredMixin, OTPRequiredMixin, View):
|
class Positions(LoginRequiredMixin, OTPRequiredMixin, View):
|
||||||
allowed_types = ["modal", "widget", "window", "page"]
|
allowed_types = ["modal", "widget", "window", "page"]
|
||||||
window_content = "window-content/objects.html"
|
window_content = "window-content/objects.html"
|
||||||
|
@ -41,6 +67,7 @@ class Positions(LoginRequiredMixin, OTPRequiredMixin, View):
|
||||||
template_name = f"wm/{type}.html"
|
template_name = f"wm/{type}.html"
|
||||||
unique = str(uuid.uuid4())[:8]
|
unique = str(uuid.uuid4())[:8]
|
||||||
items = get_positions(request.user, account_id)
|
items = get_positions(request.user, account_id)
|
||||||
|
annotate_positions(items, request.user, return_order_ids=False)
|
||||||
if type == "page":
|
if type == "page":
|
||||||
type = "modal"
|
type = "modal"
|
||||||
context = {
|
context = {
|
||||||
|
@ -66,12 +93,15 @@ class PositionAction(LoginRequiredMixin, OTPRequiredMixin, View):
|
||||||
Get live information for a trade.
|
Get live information for a trade.
|
||||||
"""
|
"""
|
||||||
if type not in self.allowed_types:
|
if type not in self.allowed_types:
|
||||||
return HttpResponseBadRequest
|
return HttpResponseBadRequest()
|
||||||
template_name = f"wm/{type}.html"
|
template_name = f"wm/{type}.html"
|
||||||
unique = str(uuid.uuid4())[:8]
|
unique = str(uuid.uuid4())[:8]
|
||||||
|
|
||||||
account = Account.get_by_id(account_id, request.user)
|
account = Account.get_by_id(account_id, request.user)
|
||||||
info = account.client.get_position_info(symbol)
|
info = account.client.get_position_info(symbol)
|
||||||
|
valid_trade_ids = list(
|
||||||
|
annotate_positions([info], request.user, return_order_ids=True)
|
||||||
|
)
|
||||||
|
|
||||||
if type == "page":
|
if type == "page":
|
||||||
type = "modal"
|
type = "modal"
|
||||||
|
@ -81,6 +111,7 @@ class PositionAction(LoginRequiredMixin, OTPRequiredMixin, View):
|
||||||
"window_content": self.window_content,
|
"window_content": self.window_content,
|
||||||
"type": type,
|
"type": type,
|
||||||
"items": info,
|
"items": info,
|
||||||
|
"valid_trade_ids": valid_trade_ids,
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, template_name, context)
|
return render(request, template_name, context)
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.http import HttpResponseBadRequest
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from two_factor.views.mixins import OTPRequiredMixin
|
from two_factor.views.mixins import OTPRequiredMixin
|
||||||
|
|
||||||
|
from core.exchanges import GenericAPIError
|
||||||
from core.forms import TradeForm
|
from core.forms import TradeForm
|
||||||
from core.models import Trade
|
from core.models import Trade
|
||||||
from core.util import logs
|
from core.util import logs
|
||||||
|
@ -17,6 +21,44 @@ from core.views import (
|
||||||
log = logs.get_logger(__name__)
|
log = logs.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TradeAction(LoginRequiredMixin, OTPRequiredMixin, View):
|
||||||
|
allowed_types = ["modal", "widget", "window", "page"]
|
||||||
|
window_content = "window-content/trade.html"
|
||||||
|
|
||||||
|
def get(self, request, type, trade_id):
|
||||||
|
"""
|
||||||
|
Get live information for a trade.
|
||||||
|
"""
|
||||||
|
if type not in self.allowed_types:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
template_name = f"wm/{type}.html"
|
||||||
|
unique = str(uuid.uuid4())[:8]
|
||||||
|
|
||||||
|
db_info = Trade.get_by_id_or_order(trade_id, request.user)
|
||||||
|
if db_info is None:
|
||||||
|
return HttpResponseBadRequest("Trade not found.")
|
||||||
|
if db_info.order_id is not None:
|
||||||
|
try:
|
||||||
|
live_info = db_info.account.client.get_trade(db_info.order_id)
|
||||||
|
except GenericAPIError as e:
|
||||||
|
live_info = {"error": e}
|
||||||
|
else:
|
||||||
|
live_info = {}
|
||||||
|
|
||||||
|
if type == "page":
|
||||||
|
type = "modal"
|
||||||
|
context = {
|
||||||
|
"title": f"Trade info ({type})",
|
||||||
|
"unique": unique,
|
||||||
|
"window_content": self.window_content,
|
||||||
|
"type": type,
|
||||||
|
"db_info": db_info.__dict__,
|
||||||
|
"live_info": live_info,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, template_name, context)
|
||||||
|
|
||||||
|
|
||||||
class TradeList(LoginRequiredMixin, OTPRequiredMixin, ObjectList):
|
class TradeList(LoginRequiredMixin, OTPRequiredMixin, ObjectList):
|
||||||
list_template = "partials/trade-list.html"
|
list_template = "partials/trade-list.html"
|
||||||
model = Trade
|
model = Trade
|
||||||
|
|
Loading…
Reference in New Issue