Implement closing positions

This commit is contained in:
Mark Veidemanis 2022-12-02 07:20:37 +00:00
parent 5aac60a7ee
commit 1ce6c3fafa
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
5 changed files with 68 additions and 6 deletions

View File

@ -143,6 +143,11 @@ urlpatterns = [
positions.Positions.as_view(), positions.Positions.as_view(),
name="positions", name="positions",
), ),
path(
"positions/close/<str:account_id>/<str:side>/<str:symbol>/",
positions.PositionAction.as_view(),
name="position_action",
),
path( path(
"positions/<str:type>/<str:account_id>/<str:symbol>/", "positions/<str:type>/<str:account_id>/<str:symbol>/",
positions.PositionAction.as_view(), positions.PositionAction.as_view(),

View File

@ -218,6 +218,10 @@ class BaseExchange(ABC):
def get_all_positions(self): def get_all_positions(self):
pass pass
@abstractmethod
def close_position(self, side, symbol):
pass
@abstractmethod @abstractmethod
def close_all_positions(self): def close_all_positions(self):
pass pass

View File

@ -130,3 +130,9 @@ class AlpacaExchange(BaseExchange):
item["unrealized_pl"] = float(item["unrealized_pl"]) item["unrealized_pl"] = float(item["unrealized_pl"])
items.append(item) items.append(item)
return items return items
def close_position(self, side, symbol):
pass
def close_all_positions(self):
pass

View File

@ -518,7 +518,7 @@ class LongOrderFillTransaction(BaseModel):
longPositionCloseout: LongPositionCloseout longPositionCloseout: LongPositionCloseout
class OrderTransation(BaseModel): class OrderTransaction(BaseModel):
id: str id: str
accountID: str accountID: str
userID: int userID: int
@ -531,8 +531,8 @@ class OrderTransation(BaseModel):
timeInForce: str | None timeInForce: str | None
positionFill: str | None positionFill: str | None
reason: str reason: str
longPositionCloseout: LongPositionCloseout longPositionCloseout: LongPositionCloseout | None
longOrderFillTransaction: dict longOrderFillTransaction: dict | None
class PositionClose(BaseModel): class PositionClose(BaseModel):
@ -578,7 +578,7 @@ class TradeDetailsTrade(BaseModel):
dividendAdjustment: str dividendAdjustment: str
closeTime: str closeTime: str
averageClosePrice: str averageClosePrice: str
clientExtensions: ClientExtensions clientExtensions: ClientExtensions | None
class TradeDetails(BaseModel): class TradeDetails(BaseModel):

View File

@ -3,6 +3,7 @@ import uuid
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseBadRequest from django.http import HttpResponseBadRequest
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse
from django.views import View from django.views import View
from rest_framework.parsers import FormParser from rest_framework.parsers import FormParser
from two_factor.views.mixins import OTPRequiredMixin from two_factor.views.mixins import OTPRequiredMixin
@ -64,12 +65,20 @@ class Positions(LoginRequiredMixin, OTPRequiredMixin, View):
def get(self, request, type, account_id=None): def get(self, request, type, account_id=None):
if type not in self.allowed_types: if type not in self.allowed_types:
return HttpResponseBadRequest return HttpResponseBadRequest
template_name = f"wm/{type}.html" self.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) annotate_positions(items, request.user, return_order_ids=False)
orig_type = type
if type == "page": if type == "page":
type = "modal" type = "modal"
cast = {
"type": orig_type,
}
if account_id:
cast["account_id"] = account_id
list_url = reverse("positions", kwargs={**cast})
context = { context = {
"title": f"Positions ({type})", "title": f"Positions ({type})",
"unique": unique, "unique": unique,
@ -79,8 +88,17 @@ class Positions(LoginRequiredMixin, OTPRequiredMixin, View):
"type": type, "type": type,
"page_title": self.page_title, "page_title": self.page_title,
"page_subtitle": self.page_subtitle, "page_subtitle": self.page_subtitle,
"list_url": list_url,
"context_object_name_singular": "position",
"context_object_name": "positions",
} }
return render(request, template_name, context) # Return partials for HTMX
if self.request.htmx:
if orig_type == "page":
self.template_name = self.list_template
else:
context["window_content"] = self.list_template
return render(request, self.template_name, context)
class PositionAction(LoginRequiredMixin, OTPRequiredMixin, View): class PositionAction(LoginRequiredMixin, OTPRequiredMixin, View):
@ -103,6 +121,10 @@ class PositionAction(LoginRequiredMixin, OTPRequiredMixin, View):
annotate_positions([info], request.user, return_order_ids=True) annotate_positions([info], request.user, return_order_ids=True)
) )
# Remove some fields from the info dict
del info["long"]
del info["short"]
if type == "page": if type == "page":
type = "modal" type = "modal"
context = { context = {
@ -115,3 +137,28 @@ class PositionAction(LoginRequiredMixin, OTPRequiredMixin, View):
} }
return render(request, template_name, context) return render(request, template_name, context)
def delete(self, request, account_id, side, symbol):
"""
Close a position.
"""
template_name = "partials/notify.html"
account = Account.get_by_id(account_id, request.user)
try:
api_response = account.client.close_position(side, symbol)
except GenericAPIError as e:
context = {"message": e, "class": "danger"}
return render(request, template_name, context)
if "longOrderCreateTransaction" in api_response:
context = {
"message": f"Long position closed on {symbol}",
"class": "success",
}
elif "shortOrderCreateTransaction" in api_response:
context = {
"message": f"Short position closed on {symbol}",
"class": "success",
}
response = render(request, template_name, context)
response["HX-Trigger"] = "positionEvent"
return response