Implement viewing open positions

This commit is contained in:
Mark Veidemanis 2022-10-22 00:15:27 +01:00
parent 572b839c2c
commit 30d516ebf9
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
8 changed files with 81 additions and 72 deletions

View File

@ -21,7 +21,7 @@ from django.urls import include, path
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django_otp.forms import OTPAuthenticationForm from django_otp.forms import OTPAuthenticationForm
from core.views import accounts, base, callbacks, hooks, trades, positions from core.views import accounts, base, callbacks, hooks, positions, trades
from core.views.stripe_callbacks import Callback from core.views.stripe_callbacks import Callback
urlpatterns = [ urlpatterns = [

View File

@ -1,4 +1,5 @@
from serde import Model, fields from serde import Model, fields
# { # {
# "id": "92f0b26b-4c98-4553-9c74-cdafc7e037db", # "id": "92f0b26b-4c98-4553-9c74-cdafc7e037db",
# "clientOrderId": "ccxt_26adcbf445674f01af38a66a15e6f5b5", # "clientOrderId": "ccxt_26adcbf445674f01af38a66a15e6f5b5",
@ -60,6 +61,7 @@ from serde import Model, fields
# "lastTradeTimestamp": null # "lastTradeTimestamp": null
# } # }
class CCXTInfo(Model): class CCXTInfo(Model):
id = fields.Uuid() id = fields.Uuid()
client_order_id = fields.Str() client_order_id = fields.Str()
@ -97,7 +99,6 @@ class CCXTInfo(Model):
source = fields.Optional(fields.Str()) source = fields.Optional(fields.Str())
class CCXTRoot(Model): class CCXTRoot(Model):
id = fields.Uuid() id = fields.Uuid()
clientOrderId = fields.Str() clientOrderId = fields.Str()

View File

@ -1,4 +1,5 @@
# Trade handling # Trade handling
def sync_trades_with_db(user): def sync_trades_with_db(user):
pass pass

View File

@ -1,5 +1,5 @@
from alpaca.trading.client import TradingClient
import stripe import stripe
from alpaca.trading.client import TradingClient
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
@ -69,9 +69,7 @@ class User(AbstractUser):
class Account(models.Model): class Account(models.Model):
EXCHANGE_CHOICES = ( EXCHANGE_CHOICES = (("alpaca", "Alpaca"),)
("alpaca", "Alpaca"),
)
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
exchange = models.CharField(choices=EXCHANGE_CHOICES, max_length=255) exchange = models.CharField(choices=EXCHANGE_CHOICES, max_length=255)
@ -80,7 +78,9 @@ class Account(models.Model):
sandbox = models.BooleanField(default=False) sandbox = models.BooleanField(default=False)
def get_account(self): def get_account(self):
trading_client = TradingClient(self.api_key, self.api_secret, paper=self.sandbox) trading_client = TradingClient(
self.api_key, self.api_secret, paper=self.sandbox
)
return trading_client.get_account() return trading_client.get_account()
@ -121,9 +121,7 @@ class Trade(models.Model):
stop_loss = models.FloatField(null=True, blank=True) stop_loss = models.FloatField(null=True, blank=True)
take_profit = models.FloatField(null=True, blank=True) take_profit = models.FloatField(null=True, blank=True)
status = models.CharField(max_length=255, null=True, blank=True) status = models.CharField(max_length=255, null=True, blank=True)
direction = models.CharField( direction = models.CharField(choices=DIRECTION_CHOICES, max_length=255)
choices=DIRECTION_CHOICES, max_length=255
)
# To populate from the trade # To populate from the trade
order_id = models.CharField(max_length=255, null=True, blank=True) order_id = models.CharField(max_length=255, null=True, blank=True)
@ -141,7 +139,9 @@ class Trade(models.Model):
if self.response is None: if self.response is None:
# the trade is not placed yet # the trade is not placed yet
if self.account.exchange == "alpaca": if self.account.exchange == "alpaca":
trading_client = TradingClient(self.account.api_key, self.account.api_secret, paper=self.sandbox) trading_client = TradingClient(
self.account.api_key, self.account.api_secret, paper=self.sandbox
)
if self.type == "market": if self.type == "market":
order = account.create_order( order = account.create_order(
self.symbol, self.type, self.direction, self.amount self.symbol, self.type, self.direction, self.amount

View File

@ -1,38 +1,33 @@
{% include 'partials/notify.html' %} {% include 'partials/notify.html' %}
<table class="table is-fullwidth is-hoverable" id="accounts-table"> <table class="table is-fullwidth is-hoverable" id="positions-table">
<thead> <thead>
<th>id</th> <th>account</th>
<th>user</th> <th>asset</th>
<th>name</th> <th>price</th>
<th>exchange</th> <th>quantity</th>
<th>API key</th> <th>value</th>
<th>sandbox</th> <th>P/L</th>
<th>side</th>
<th>actions</th> <th>actions</th>
</thead> </thead>
{% for item in items %} {% for item in items %}
<tr> <tr class="
<td>{{ item.id }}</td> {% if item.unrealized_pl > 0 %}has-background-success-light
<td>{{ item.user }}</td> {% elif item.unrealized_pl < 0 %}has-background-danger-light
<td>{{ item.name }}</td> {% endif %}">
<td>{{ item.exchange }}</td> <td>{{ item.account_id }}</td>
<td>{{ item.api_key }}</td> <td>{{ item.symbol }}</td>
<td> <td>{{ item.current_price }}</td>
{% if item.sandbox %} <td>{{ item.qty }}</td>
<span class="icon"> <td>{{ item.market_value }}</td>
<i class="fa-solid fa-check"></i> <td>{{ item.unrealized_pl }}</td>
</span> <td>{{ item.side }}</td>
{% else %}
<span class="icon">
<i class="fa-solid fa-xmark"></i>
</span>
{% endif %}
</td>
<td> <td>
<div class="buttons"> <div class="buttons">
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'position_action' type=type order_id=item.id %}" hx-get="#trade-edit"
hx-trigger="click" hx-trigger="click"
hx-target="#{{ type }}s-here" hx-target="#{{ type }}s-here"
class="button is-info"> class="button is-info">
@ -44,30 +39,31 @@
</button> </button>
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-delete="{% url 'position_action' type=type order_id=item.id %}" hx-delete="#trade-close-confirm"
hx-trigger="click" hx-trigger="click"
hx-target="#positions-table" hx-target="#accounts-table"
class="button is-danger"> class="button is-danger">
<span class="icon-text"> <span class="icon-text">
<span class="icon" data-tooltip="Close"> <span class="icon">
<i class="fa-solid fa-trash"></i> <i class="fa-solid fa-trash"></i>
</span> </span>
</span> </span>
</button> </button>
{% if type == 'page' %} {% if type == 'page' %}
<a href="{% url 'position_action' type=type account_id=item.id %}"><button <a href="#trade-info">
class="button is-success"> <button
<span class="icon-text"> class="button is-success">
<span class="icon"> <span class="icon-text">
<i class="fa-solid fa-eye"></i> <span class="icon">
<i class="fa-solid fa-eye"></i>
</span>
</span> </span>
</span> </button>
</button>
</a> </a>
{% else %} {% else %}
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'trades' type=type account_id=item.id %}" hx-get="#trade-info"
hx-trigger="click" hx-trigger="click"
hx-target="#{{ type }}s-here" hx-target="#{{ type }}s-here"
class="button is-success"> class="button is-success">

View File

@ -1,20 +1,19 @@
<div class="buttons"> <div class="buttons">
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="#" hx-get="#"
hx-trigger="click" hx-trigger="click"
hx-target="#modals-here" hx-target="#modals-here"
class="button is-info"> class="button is-info">
<span class="icon-text"> <span class="icon-text">
<span class="icon"> <span class="icon">
<i class="fa-solid fa-plus"></i> <i class="fa-solid fa-plus"></i>
</span>
<span>Trade</span>
</span> </span>
</button> <span>Trade</span>
</div> </span>
</button>
{% include 'partials/notify.html' %} </div>
{% include 'partials/position-list.html' %}
{% include 'partials/notify.html' %}
{% include 'partials/position-list.html' %}

View File

@ -18,6 +18,7 @@ def get_accounts(user):
accounts = Account.objects.filter(user=user) accounts = Account.objects.filter(user=user)
return accounts return accounts
class AccountInfo(LoginRequiredMixin, View): class AccountInfo(LoginRequiredMixin, View):
VIEWABLE_FIELDS_MODEL = ["name", "exchange", "api_key", "sandbox"] VIEWABLE_FIELDS_MODEL = ["name", "exchange", "api_key", "sandbox"]
allowed_types = ["modal", "widget", "window", "page"] allowed_types = ["modal", "widget", "window", "page"]
@ -46,7 +47,9 @@ class AccountInfo(LoginRequiredMixin, View):
live_info = dict(account.get_account()) live_info = dict(account.get_account())
account_info = account.__dict__ account_info = account.__dict__
account_info = {k:v for k,v in account_info.items() if k in self.VIEWABLE_FIELDS_MODEL} account_info = {
k: v for k, v in account_info.items() if k in self.VIEWABLE_FIELDS_MODEL
}
if type == "page": if type == "page":
type = "modal" type = "modal"
@ -61,6 +64,7 @@ class AccountInfo(LoginRequiredMixin, View):
return render(request, template_name, context) return render(request, template_name, context)
class Accounts(LoginRequiredMixin, View): class Accounts(LoginRequiredMixin, View):
allowed_types = ["modal", "widget", "window", "page"] allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/accounts.html" window_content = "window-content/accounts.html"

View File

@ -1,6 +1,7 @@
import uuid import uuid
import orjson import orjson
from alpaca.trading.client import TradingClient
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse, HttpResponseBadRequest from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import render from django.shortcuts import render
@ -8,13 +9,12 @@ from django.views import View
from rest_framework.parsers import FormParser, JSONParser from rest_framework.parsers import FormParser, JSONParser
from rest_framework.views import APIView from rest_framework.views import APIView
from serde import ValidationError from serde import ValidationError
from alpaca.trading.client import TradingClient
from core.forms import HookForm from core.forms import HookForm
from core.lib.serde import drakdoo_s from core.lib.serde import drakdoo_s
from core.models import Callback, Hook, Account from core.models import Account, Callback, Hook
from core.util import logs from core.util import logs
log = logs.get_logger(__name__) log = logs.get_logger(__name__)
@ -23,10 +23,17 @@ def get_positions(user, account_id=None):
accounts = Account.objects.filter(user=user) accounts = Account.objects.filter(user=user)
for account in accounts: for account in accounts:
if account.exchange == "alpaca": if account.exchange == "alpaca":
cast = {"api-key": account.api_key, "secret-key": account.api_secret, "paper": account.sandbox} trading_client = TradingClient(
trading_client = TradingClient(**cast) account.api_key, account.api_secret, paper=account.sandbox
)
positions = trading_client.get_all_positions() positions = trading_client.get_all_positions()
print("POSITIONS", positions) print("POSITIONS", positions)
for item in positions:
item = dict(item)
item["account_id"] = account.id
item["unrealized_pl"] = float(item["unrealized_pl"])
items.append(item)
# try: # try:
# parsed = ccxt_s.CCXTRoot.from_dict(order) # parsed = ccxt_s.CCXTRoot.from_dict(order)
# except ValidationError as e: # except ValidationError as e:
@ -34,6 +41,7 @@ def get_positions(user, account_id=None):
# return False # return False
# self.status = parsed.status # self.status = parsed.status
# self.response = order # self.response = order
return items
class Positions(LoginRequiredMixin, View): class Positions(LoginRequiredMixin, View):
@ -55,4 +63,4 @@ class Positions(LoginRequiredMixin, View):
"items": items, "items": items,
"type": type, "type": type,
} }
return render(request, template_name, context) return render(request, template_name, context)