Implement strategies and posting trades
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
188
core/views/strategies.py
Normal 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)
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user