You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
12 KiB
323 lines
12 KiB
|
|
from django.http import HttpResponse, HttpResponseRedirect, Http404 |
|
from django.template import loader |
|
from django.shortcuts import render, get_object_or_404 |
|
from django.urls import reverse |
|
from django.contrib import messages |
|
from django.contrib.auth.decorators import login_required |
|
from django.db import transaction |
|
from django.contrib.auth import authenticate, login, logout |
|
|
|
from .models import RobotInstance, Trade, ClosedTrade |
|
from .forms import NewTradeForm, ClosedTradeFilterForm, LoginForm |
|
import redis |
|
import json |
|
import datetime |
|
|
|
def login_view(request): |
|
if request.method == 'POST': |
|
form = LoginForm(request.POST) |
|
nextlink = request.POST.get('next', '') |
|
if form.is_valid(): |
|
username = request.POST['username'] |
|
password = request.POST['password'] |
|
user = authenticate(username=username, password=password) |
|
if user is not None: |
|
login(request, user) |
|
if nextlink == "": |
|
return HttpResponseRedirect(reverse('overview')) |
|
else: |
|
return HttpResponseRedirect(nextlink) |
|
else: |
|
return HttpResponseRedirect(reverse('login')) |
|
else: |
|
template = loader.get_template('dashboard/login.html') |
|
|
|
context = { |
|
'login_form' : form, |
|
'next' : nextlink |
|
} |
|
return HttpResponse(template.render(context, request)) |
|
else: |
|
form = LoginForm() |
|
template = loader.get_template('dashboard/login.html') |
|
nextlink = request.GET.get('next', '') |
|
context = { |
|
'login_form' : form, |
|
'next' : nextlink |
|
} |
|
return HttpResponse(template.render(context, request)) |
|
raise Http404("Invalid method") |
|
|
|
def logout_view(request): |
|
logout(request) |
|
return HttpResponseRedirect(reverse('login')) |
|
|
|
@login_required |
|
def overview(request): |
|
r = redis.StrictRedis(unix_socket_path='/var/run/redis/redis') |
|
robot_instances = RobotInstance.objects.order_by('instanceId') |
|
robot_states = [] |
|
index = 0 |
|
for robot in robot_instances: |
|
entry = {} |
|
entry['positions'] = dict() |
|
raw_state = r.get(robot.instanceId) |
|
if raw_state is not None: |
|
state = json.loads(str(raw_state, 'utf-8')) |
|
try: |
|
entry['positions'] = state['positions'] |
|
del state['positions'] |
|
except KeyError: |
|
entry['positions'] = dict() |
|
else: |
|
state = dict() |
|
|
|
open_pos_counter = 0 |
|
pending_pos_counter = 0 |
|
for pos in entry['positions']: |
|
if pos['posState']['tag'] == 'PositionOpen': |
|
open_pos_counter += 1 |
|
elif pos['posState']['tag'] == 'PositionWaitingOpen': |
|
pending_pos_counter += 1 |
|
|
|
entry['open_pos_counter'] = open_pos_counter |
|
entry['pending_pos_counter'] = pending_pos_counter |
|
entry['state'] = json.dumps(state, sort_keys=True, indent=2, separators=(',', ': ')) |
|
|
|
last_store = r.get(robot.instanceId + ":last_store") |
|
if last_store is not None: |
|
entry['last_store'] = datetime.datetime.utcfromtimestamp(float(str(last_store, 'utf-8')[:-1])) |
|
index += 1 |
|
entry['index'] = index |
|
entry['instance_id'] = robot.instanceId |
|
|
|
robot_states.append(entry) |
|
|
|
template = loader.get_template('dashboard/overview.html') |
|
context = { |
|
'robot_instances' : robot_instances, |
|
'robot_states' : robot_states, |
|
'user' : request.user |
|
} |
|
return HttpResponse(template.render(context, request)) |
|
|
|
@login_required |
|
def add_instance(request): |
|
instance_id = request.POST['instance_id'] |
|
if instance_id == "" or RobotInstance.objects.filter(instanceId=instance_id).count() > 0: |
|
messages.error(request, 'Invalid instance ID specified') |
|
else: |
|
new_instance = RobotInstance(instanceId=instance_id) |
|
new_instance.save() |
|
return HttpResponseRedirect(reverse('overview')) |
|
|
|
@login_required |
|
def delete_instance(request, instance_id): |
|
instance = get_object_or_404(RobotInstance, instanceId=instance_id) |
|
instance.delete() |
|
return HttpResponseRedirect(reverse('overview')) |
|
|
|
@login_required |
|
def trades_index(request): |
|
now = datetime.datetime.utcnow() |
|
new_trade_form = NewTradeForm(initial={'timestamp' : now}) |
|
trades = Trade.objects.all().order_by('-timestamp') |
|
template = loader.get_template('dashboard/trades.html') |
|
context = { |
|
'trades' : trades, |
|
'new_trade_form' : new_trade_form, |
|
'user' : request.user |
|
} |
|
return HttpResponse(template.render(context, request)) |
|
|
|
@login_required |
|
def delete_trade(request, trade_id): |
|
trade = get_object_or_404(Trade, pk=trade_id) |
|
trade.delete() |
|
return HttpResponseRedirect(reverse('trades_index')) |
|
|
|
@login_required |
|
def add_trade(request): |
|
if request.method == 'POST': |
|
form = NewTradeForm(request.POST) |
|
if form.is_valid(): |
|
d = form.cleaned_data |
|
quantity_multiplier = 1 |
|
if d['operation'] == 'sell': |
|
quantity_multiplier = -1 |
|
trade = Trade(account=d['account'], security=d['security'], price=d['price'], quantity=quantity_multiplier * d['quantity'], |
|
volume=d['volume'], volumeCurrency=d['volumeCurrency'], strategyId=d['strategyId'], signalId=d['signalId'], timestamp=d['timestamp'], balanced=False) |
|
trade.save() |
|
return HttpResponseRedirect(reverse('trades_index')) |
|
else: |
|
trades = Trade.objects.all() |
|
template = loader.get_template('dashboard/trades.html') |
|
context = { |
|
'trades' : trades, |
|
'new_trade_form' : form, |
|
'user' : request.user |
|
} |
|
return HttpResponse(template.render(context, request)) |
|
raise Http404("Invalid method") |
|
|
|
@transaction.atomic |
|
def aggregate_unbalanced_trades(): |
|
unbalanced_trades = Trade.objects.filter(balanced=False).order_by('timestamp') |
|
balanced_trades = [] |
|
balances = {} |
|
for trade in unbalanced_trades: |
|
balance_key = '/'.join([trade.account, trade.security, trade.strategyId]) |
|
try: |
|
balance_entry = balances[balance_key] |
|
except KeyError: |
|
balance_entry = { 'balance' : 0} |
|
|
|
print('ts:', trade.timestamp) |
|
if balance_entry['balance'] == 0: |
|
print('new entry: ', balance_key) |
|
balance_entry['balance'] = trade.quantity |
|
direction = '' |
|
if trade.quantity > 0: |
|
direction='long' |
|
else: |
|
direction='short' |
|
balance_entry['trade'] = ClosedTrade(account=trade.account, security=trade.security, entryTime=trade.timestamp, profitCurrency=trade.volumeCurrency, |
|
profit=(-trade.price * trade.quantity), strategyId=trade.strategyId, direction=direction) |
|
balance_entry['ks'] = trade.volume / (trade.price * abs(trade.quantity)) |
|
balance_entry['trade_ids'] = [trade.pk] |
|
else: |
|
print('update entry: ', balance_key) |
|
balance_entry['balance'] += trade.quantity |
|
balance_entry['trade'].profit += -trade.price * trade.quantity |
|
balance_entry['ks'] += trade.volume / (trade.price * abs(trade.quantity)) |
|
balance_entry['ks'] /= 2 |
|
balance_entry['trade_ids'].append(trade.pk) |
|
|
|
print('updated: ', balance_entry['balance']) |
|
if balance_entry['balance'] == 0: |
|
balance_entry['trade'].profit *= balance_entry['ks'] |
|
balance_entry['trade'].exitTime = trade.timestamp |
|
balanced_trades.append((balance_entry['trade'], balance_entry['trade_ids'])) |
|
balances[balance_key] = balance_entry |
|
|
|
for trade, trade_ids in balanced_trades: |
|
trade.save() |
|
for trade_id in trade_ids: |
|
tr = Trade.objects.get(pk=trade_id) |
|
tr.balanced = True |
|
tr.save() |
|
|
|
def make_cumulative_profits(closed_trades): |
|
result = {} |
|
for trade in closed_trades: |
|
try: |
|
result[trade.account]['value'] += trade.profit |
|
except KeyError: |
|
result[trade.account] = { 'name' : trade.account, |
|
'value' : trade.profit, |
|
'elements' : [] } |
|
|
|
element = {'year' : trade.exitTime.year, |
|
'month' : trade.exitTime.month - 1, |
|
'day' : trade.exitTime.day, |
|
'hour' : trade.exitTime.hour, |
|
'minute' : trade.exitTime.minute, |
|
'second' : trade.exitTime.second, |
|
'value' : result[trade.account]['value']} |
|
result[trade.account]['elements'].append(element) |
|
return result |
|
|
|
@login_required |
|
def closed_trades_index(request): |
|
aggregate_unbalanced_trades() |
|
form = ClosedTradeFilterForm(request.GET) |
|
if form.is_valid(): |
|
d = form.cleaned_data |
|
if len(d['accounts']) == 0: |
|
if len(d['strategies']) == 0: |
|
closed_trades = ClosedTrade.objects.all() |
|
else: |
|
closed_trades = ClosedTrade.objects.filter(strategyId__in=list(d['strategies'])) |
|
else: |
|
if len(d['strategies']) == 0: |
|
closed_trades = ClosedTrade.objects.filter(account__in=list(d['accounts'])) |
|
else: |
|
closed_trades = ClosedTrade.objects.filter(account__in=list(d['accounts']), strategyId__in=list(d['strategies'])) |
|
else: |
|
closed_trades = ClosedTrade.objects.all() |
|
form = ClosedTradeFilterForm() |
|
|
|
closed_trades = closed_trades.order_by('-entryTime') |
|
|
|
closed_trades_prime = closed_trades.order_by('exitTime') |
|
|
|
cum_profits = make_cumulative_profits(closed_trades_prime) |
|
|
|
template = loader.get_template('dashboard/closed_trades.html') |
|
context = { |
|
'closed_trades' : closed_trades, |
|
'closed_trades_filter_form' : form, |
|
'cumulative_profits' : cum_profits, |
|
'user' : request.user } |
|
return HttpResponse(template.render(context, request)) |
|
|
|
@transaction.atomic |
|
def do_rebalance(): |
|
ClosedTrade.objects.all().delete() |
|
Trade.objects.all().update(balanced=False) |
|
|
|
@login_required |
|
def rebalance_closed_trades(request): |
|
do_rebalance() |
|
return HttpResponseRedirect(reverse('closed_trades_index')) |
|
|
|
@login_required |
|
def performance(request): |
|
aggregate_unbalanced_trades() |
|
all_accounts = set() |
|
for trade in ClosedTrade.objects.all(): |
|
if trade.account != 'demo': |
|
all_accounts.add(trade.account) |
|
|
|
closed_trades = ClosedTrade.objects.exclude(account='demo').order_by('exitTime') |
|
dates = [] |
|
columns = {} |
|
for account in all_accounts: |
|
columns[account] = [] |
|
prev_day = None |
|
for trade in closed_trades: |
|
if prev_day != trade.exitTime.date(): |
|
prev_day = trade.exitTime.date() |
|
dates.append(prev_day) |
|
for account in all_accounts: |
|
columns[account].append(0) |
|
columns[trade.account][-1] += trade.profit |
|
|
|
results = { 'total' : { 'pnl' : 0, 'profit' : 0, 'loss' : 0 } } |
|
|
|
for account in all_accounts: |
|
results[account] = { 'pnl' : 0, 'profit' : 0, 'loss' : 0 } |
|
|
|
for trade in closed_trades: |
|
if trade.account != 'demo': |
|
results['total']['pnl'] += trade.profit |
|
results[trade.account]['pnl'] += trade.profit |
|
|
|
if trade.profit > 0: |
|
results['total']['profit'] += trade.profit |
|
results[trade.account]['profit'] += trade.profit |
|
else: |
|
results['total']['loss'] -= trade.profit |
|
results[trade.account]['loss'] -= trade.profit |
|
|
|
results['total']['pf'] = results['total']['profit'] / results['total']['loss'] |
|
|
|
template = loader.get_template('dashboard/performance.html') |
|
context = { |
|
'user' : request.user, |
|
'dates' : dates, |
|
'columns' : columns, |
|
'results' : results |
|
} |
|
return HttpResponse(template.render(context, request))
|
|
|