Implement strategies and posting trades

This commit is contained in:
2022-10-27 18:08:40 +01:00
parent 7e4f3f52d1
commit 061c6f6ca7
32 changed files with 1060 additions and 178 deletions

View File

@@ -34,7 +34,7 @@ class AccountInfo(LoginRequiredMixin, View):
template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8]
try:
account = Account.objects.get(id=account_id, user=request.user)
account = Account.get_by_id(account_id, request.user)
except Account.DoesNotExist:
message = "Account does not exist"
message_class = "danger"
@@ -45,7 +45,7 @@ class AccountInfo(LoginRequiredMixin, View):
}
return render(request, template_name, context)
live_info = dict(account.get_account())
live_info = dict(account.client.get_account())
account_info = account.__dict__
account_info = {
k: v for k, v in account_info.items() if k in self.VIEWABLE_FIELDS_MODEL
@@ -141,7 +141,8 @@ class AccountAction(LoginRequiredMixin, APIView):
if account_id:
try:
form = AccountForm(
request.data, instance=Account.objects.get(id=account_id)
request.data,
instance=Account.objects.get(id=account_id, user=request.user),
)
except Account.DoesNotExist:
message = "Account does not exist"

View File

@@ -91,6 +91,11 @@ class Signup(CreateView):
success_url = reverse_lazy("login")
template_name = "registration/signup.html"
def get(self, request, *args, **kwargs):
if not settings.REGISTRATION_OPEN:
return render(request, "registration/registration_closed.html")
super().get(request, *args, **kwargs)
class Portal(LoginRequiredMixin, View):
async def get(self, request):

View File

@@ -1,6 +1,8 @@
import re
import uuid
import orjson
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import render
@@ -10,6 +12,7 @@ from rest_framework.views import APIView
from serde import ValidationError
from core.forms import HookForm
from core.lib import market
from core.lib.serde import drakdoo_s
from core.models import Callback, Hook
from core.util import logs
@@ -22,6 +25,18 @@ def get_hooks(user):
return hooks
def extract_price(message):
result = re.findall("\d+\.\d+", message) # noqa
if len(result) != 1:
log.error(f"Could not extract price from message: {message}")
return False
try:
log.debug(f"Extracted {result[0]} from '{message}'")
return float(result[0])
except ValueError:
return False
class HookAPI(APIView):
parser_classes = [JSONParser]
@@ -41,17 +56,31 @@ class HookAPI(APIView):
log.error(f"HookAPI POST: {e}")
return HttpResponseBadRequest(e)
price = extract_price(hook_resp.message)
if not price:
log.debug(f"Could not extract price from message: {hook_resp.message}")
return HttpResponseBadRequest("Could not extract price from message")
base = hook_resp.market.item
quote = hook_resp.market.currency
symbol = f"{base.upper()}/{quote.upper()}"
if symbol not in settings.ASSET_FILTER:
log.debug(f"Skipping {symbol} because it is not in the asset filter")
return HttpResponseBadRequest("Invalid symbol")
data = {
"title": hook_resp.title,
"message": hook_resp.message,
"period": hook_resp.period,
"timestamp_sent": hook_resp.timestamp.sent,
"sent": hook_resp.timestamp.sent,
"timestamp_trade": hook_resp.timestamp.trade,
"market_exchange": hook_resp.market.exchange,
"market_item": hook_resp.market.item,
"market_currency": hook_resp.market.currency,
"market_contract": hook_resp.market.contract,
"trade": hook_resp.market.exchange,
"base": hook_resp.market.item,
"quote": hook_resp.market.currency,
"symbol": symbol,
"contract": hook_resp.market.contract,
"price": price,
}
log.debug("HookAPI callback: data: %s", data)
# Try getting the hook
@@ -63,6 +92,7 @@ class HookAPI(APIView):
# Create the callback object
callback = Callback.objects.create(hook=hook, **data)
callback.save()
market.process_callback(callback)
# Bump received count
hook.received = hook.received + 1
hook.save()

View File

@@ -1,18 +1,13 @@
import uuid
import orjson
from alpaca.trading.client import TradingClient
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse, HttpResponseBadRequest
from django.http import HttpResponseBadRequest
from django.shortcuts import render
from django.views import View
from rest_framework.parsers import FormParser, JSONParser
from rest_framework.views import APIView
from serde import ValidationError
from rest_framework.parsers import FormParser
from core.forms import HookForm
from core.lib.serde import drakdoo_s
from core.models import Account, Callback, Hook
from core.lib import trades
from core.models import Account
from core.util import logs
log = logs.get_logger(__name__)
@@ -22,25 +17,21 @@ def get_positions(user, account_id=None):
items = []
accounts = Account.objects.filter(user=user)
for account in accounts:
if account.exchange == "alpaca":
trading_client = TradingClient(
account.api_key, account.api_secret, paper=account.sandbox
)
positions = trading_client.get_all_positions()
print("POSITIONS", positions)
positions = account.client.get_all_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:
# parsed = ccxt_s.CCXTRoot.from_dict(order)
# except ValidationError as e:
# log.error(f"Error creating trade: {e}")
# return False
# self.status = parsed.status
# self.response = order
for item in positions:
item = dict(item)
item["account_id"] = account.id
item["unrealized_pl"] = float(item["unrealized_pl"])
items.append(item)
# try:
# parsed = ccxt_s.CCXTRoot.from_dict(order)
# except ValidationError as e:
# log.error(f"Error creating trade: {e}")
# return False
# self.status = parsed.status
# self.response = order
return items
@@ -64,3 +55,42 @@ class Positions(LoginRequiredMixin, View):
"type": type,
}
return render(request, template_name, context)
class PositionAction(LoginRequiredMixin, View):
allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/view-position.html"
parser_classes = [FormParser]
async def get(self, request, type, account_id, asset_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]
account = Account.get_by_id(account_id, request.user)
success, info = trades.get_position_info(account, asset_id)
print("INFO", info)
if not success:
message = "Position does not exist"
message_class = "danger"
items = get_positions(request.user, account_id)
if type == "page":
type = "modal"
context = {
"title": f"Hooks ({type})",
"unique": unique,
"window_content": self.window_content,
"items": items,
"type": type,
}
if success:
context["items"] = info
else:
context["message"] = message
context["class"] = message_class
return render(request, template_name, context)

188
core/views/strategies.py Normal file
View File

@@ -0,0 +1,188 @@
import uuid
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseBadRequest
from django.shortcuts import render
from django.views import View # , CreateView, UpdateView, DeleteView
from rest_framework.parsers import FormParser
from rest_framework.views import APIView
from core.forms import StrategyForm
from core.models import Strategy
from core.util import logs
# from django.urls import reverse
log = logs.get_logger(__name__)
def get_strategies(user):
strategies = Strategy.objects.filter(user=user)
return strategies
class Strategies(LoginRequiredMixin, View):
allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/strategies.html"
async def get(self, request, type):
if type not in self.allowed_types:
return HttpResponseBadRequest
template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8]
strategies = get_strategies(request.user)
if type == "page":
type = "modal"
context = {
"title": f"Accounts ({type})",
"unique": unique,
"window_content": self.window_content,
"items": strategies,
"type": type,
}
return render(request, template_name, context)
# class AddStrategy(CreateView):
# model = Strategy
# form_class = StrategyForm
# template_name = "window-content/add-strategy.html"
# success_url = reverse("strategies")
# def form_valid(self, form):
# form.instance.user = self.request.user
# return super().form_valid(form)
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context["title"] = "Add Strategy"
# context["window_content"] = "window-content/add_strategy.html"
# return context
class StrategiesAction(LoginRequiredMixin, APIView):
allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/add-strategy.html"
parser_classes = [FormParser]
def get(self, request, type, strategy_id=None):
"""
Get the form for adding or editing a strategy.
:param strategy_id: The id of the strategy to edit. Optional.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8]
if strategy_id:
try:
account = Strategy.objects.get(id=strategy_id)
form = StrategyForm(instance=account)
except Strategy.DoesNotExist:
message = "Strategy does not exist"
message_class = "danger"
context = {
"message": message,
"message_class": message_class,
"window_content": self.window_content,
}
return render(request, template_name, context)
else:
form = StrategyForm()
if type == "page":
type = "modal"
context = {
"form": form,
"strategy_id": strategy_id,
"type": type,
"unique": unique,
"window_content": self.window_content,
}
return render(request, template_name, context)
def put(self, request, type, strategy_id=None):
"""
Add or edit a account.
:param account_id: The id of the strategy to edit. Optional.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
message = None
message_class = "success"
if strategy_id:
try:
form = StrategyForm(
request.data,
instance=Strategy.objects.get(
id=strategy_id, account__user=request.user
),
)
except Strategy.DoesNotExist:
message = "Strategy does not exist"
message_class = "danger"
context = {
"message": message,
"class": message_class,
}
return render(request, self.template_name, context)
else:
form = StrategyForm(request.data)
if form.is_valid():
hooks = list(form.cleaned_data.get("hooks"))
strategy = form.save(commit=False)
strategy.user = request.user
strategy.hooks.set(hooks)
strategy.save()
print("HOOKS SET", strategy.hooks)
if strategy_id:
message = f"Strategy {strategy_id} edited successfully"
else:
message = f"Strategy {strategy.id} added successfully"
else:
message = "Error adding strategy"
message_class = "danger"
accounts = get_strategies(request.user)
context = {
"items": accounts,
"type": type,
}
if message:
context["message"] = message
context["class"] = message_class
template_name = "partials/strategy-list.html"
return render(request, template_name, context)
def delete(self, request, type, strategy_id):
"""
Delete a strategy.
:param strategy_id: The id of the strategy to delete.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
message = None
message_class = "success"
try:
strategy = Strategy.objects.get(id=strategy_id, user=request.user)
strategy.delete()
message = "Strategy deleted successfully"
except Strategy.DoesNotExist:
message = "Error deleting strategy"
message_class = "danger"
strategies = get_strategies(request.user)
context = {
"items": strategies,
"type": strategies,
}
if message:
context["message"] = message
context["class"] = message_class
template_name = "partials/strategy-list.html"
return render(request, template_name, context)

View File

@@ -111,7 +111,10 @@ class TradeAction(LoginRequiredMixin, APIView):
if trade_id:
try:
form = TradeForm(request.data, instance=Trade.objects.get(id=trade_id))
form = TradeForm(
request.data,
instance=Trade.objects.get(id=trade_id, account__user=request.user),
)
except Trade.DoesNotExist:
message = "Trade does not exist"
message_class = "danger"
@@ -125,12 +128,18 @@ class TradeAction(LoginRequiredMixin, APIView):
if form.is_valid():
trade = form.save(commit=False)
print("PRESAVE TRADE", trade)
trade.user = request.user
trade.save()
print("SAVED TRADE", trade)
if trade_id:
message = f"Trade {trade_id} edited successfully"
success, returned = trade.post()
if success:
print("SAVED TRADE", trade)
if trade_id:
message = f"Trade {trade_id} edited successfully"
else:
message = f"Trade {trade.id} added successfully"
else:
message = f"Trade {trade.id} added successfully"
message = f"Error adding trade: {returned}"
message_class = "danger"
else:
message = "Error adding trade"
message_class = "danger"