diff options
author | lxf <sng@luxagraf.net> | 2022-01-03 17:02:50 -0500 |
---|---|---|
committer | lxf <sng@luxagraf.net> | 2022-01-03 17:02:50 -0500 |
commit | 617b485a0147266d93ec7db22afd2cd9055b0c09 (patch) | |
tree | 93462a0cf8c93c211c3db7223df48f94f94f3b96 | |
parent | 1123d655bd6708fba056d9800af61e9f2e8bd6eb (diff) |
trad: new options tracker that works better for WON system
-rw-r--r-- | app/trading/admin.py | 25 | ||||
-rw-r--r-- | app/trading/forms.py | 25 | ||||
-rw-r--r-- | app/trading/migrations/0017_luxoptioncontact_luxoptionpurchase.py | 34 | ||||
-rw-r--r-- | app/trading/migrations/0018_luxoptionpurchase_pl.py | 18 | ||||
-rw-r--r-- | app/trading/migrations/0019_auto_20220103_1458.py | 32 | ||||
-rw-r--r-- | app/trading/migrations/0020_auto_20220103_1517.py | 23 | ||||
-rw-r--r-- | app/trading/models.py | 73 | ||||
-rw-r--r-- | app/trading/templates/trading/create_luxoptions_form.html | 68 | ||||
-rw-r--r-- | app/trading/templates/trading/list.html | 77 | ||||
-rw-r--r-- | app/trading/urls.py | 7 | ||||
-rw-r--r-- | app/trading/views.py | 39 |
11 files changed, 414 insertions, 7 deletions
diff --git a/app/trading/admin.py b/app/trading/admin.py index 2e4e05c..338c924 100644 --- a/app/trading/admin.py +++ b/app/trading/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin from django.db import models -from .models import TradeJrnl, LuxOptionsTrade, LuxTrade +from .models import TradeJrnl, LuxOptionsTrade, LuxTrade, LuxOptionPurchase, LuxOptionContact from utils.widgets import AdminImageWidget, LGEntryForm @@ -34,3 +34,26 @@ class LuxTradeAdmin(admin.ModelAdmin): "all": ("my_styles.css",) } + +class LuxOptionContactInline(admin.StackedInline): + model = LuxOptionContact + extra = 0 + fieldsets = ( + (None, { + 'fields': ( + 'contract_open_price', + 'contract_close_price' + ) + }), + ) + + +@admin.register(LuxOptionPurchase) +class LuxOptionPurchaseAdmin(admin.ModelAdmin): + list_display = ('symbol',) + inlines = [LuxOptionContactInline,] + class Media: + js = ('image-loader.js', 'next-prev-links.js') + css = { + "all": ("my_styles.css",) + } diff --git a/app/trading/forms.py b/app/trading/forms.py new file mode 100644 index 0000000..60c6de6 --- /dev/null +++ b/app/trading/forms.py @@ -0,0 +1,25 @@ +from django import forms +from django.forms.utils import ValidationError + +from .models import LuxOptionContact + +class LuxOptionsForm(forms.ModelForm): + contracts = forms.IntegerField() + + class Meta: + model = LuxOptionContact + fields = [ + 'symbol', + 'strike_price', + 'expiration_date', + 'contract_open_price', + 'call_put', + 'contracts' + ] + + def save(self, commit=True): + i = 0 + while i < int(self.cleaned_data['contracts']): + print(i, "contracts") + i = i+1 + return super(LuxOptionsForm, self).save(commit=commit) diff --git a/app/trading/migrations/0017_luxoptioncontact_luxoptionpurchase.py b/app/trading/migrations/0017_luxoptioncontact_luxoptionpurchase.py new file mode 100644 index 0000000..2258466 --- /dev/null +++ b/app/trading/migrations/0017_luxoptioncontact_luxoptionpurchase.py @@ -0,0 +1,34 @@ +# Generated by Django 3.2.7 on 2022-01-03 13:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('trading', '0016_alter_luxoptionstrade_pl'), + ] + + operations = [ + migrations.CreateModel( + name='LuxOptionContact', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('symbol', models.CharField(max_length=256)), + ('strike_price', models.FloatField()), + ('expiration_date', models.DateField()), + ('contract_open_price', models.FloatField()), + ('contract_close_price', models.FloatField(blank=True, null=True)), + ('call_put', models.IntegerField(choices=[(0, 'Call'), (1, 'Put')], default=0)), + ], + ), + migrations.CreateModel( + name='LuxOptionPurchase', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('symbol', models.CharField(max_length=256)), + ('open_date', models.DateTimeField(auto_now_add=True)), + ('contracts', models.ManyToManyField(to='trading.LuxOptionContact')), + ], + ), + ] diff --git a/app/trading/migrations/0018_luxoptionpurchase_pl.py b/app/trading/migrations/0018_luxoptionpurchase_pl.py new file mode 100644 index 0000000..46d3b66 --- /dev/null +++ b/app/trading/migrations/0018_luxoptionpurchase_pl.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.7 on 2022-01-03 14:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('trading', '0017_luxoptioncontact_luxoptionpurchase'), + ] + + operations = [ + migrations.AddField( + model_name='luxoptionpurchase', + name='pl', + field=models.FloatField(blank=True, null=True), + ), + ] diff --git a/app/trading/migrations/0019_auto_20220103_1458.py b/app/trading/migrations/0019_auto_20220103_1458.py new file mode 100644 index 0000000..d0d6529 --- /dev/null +++ b/app/trading/migrations/0019_auto_20220103_1458.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.7 on 2022-01-03 14:58 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('trading', '0018_luxoptionpurchase_pl'), + ] + + operations = [ + migrations.AlterModelOptions( + name='luxoptionpurchase', + options={'get_latest_by': 'open_date', 'ordering': ('-open_date',)}, + ), + migrations.RemoveField( + model_name='luxoptionpurchase', + name='contracts', + ), + migrations.AddField( + model_name='luxoptioncontact', + name='options_purchase', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='trading.luxoptionpurchase'), + ), + migrations.AlterField( + model_name='luxoptioncontact', + name='call_put', + field=models.IntegerField(choices=[(0, 'C'), (1, 'P')], default=0), + ), + ] diff --git a/app/trading/migrations/0020_auto_20220103_1517.py b/app/trading/migrations/0020_auto_20220103_1517.py new file mode 100644 index 0000000..73c4b5f --- /dev/null +++ b/app/trading/migrations/0020_auto_20220103_1517.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.7 on 2022-01-03 15:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('trading', '0019_auto_20220103_1458'), + ] + + operations = [ + migrations.AddField( + model_name='luxoptionpurchase', + name='close_date', + field=models.DateTimeField(null=True), + ), + migrations.AddField( + model_name='luxoptionpurchase', + name='status', + field=models.IntegerField(choices=[(0, 'Open'), (1, 'Closed')], default=1), + ), + ] diff --git a/app/trading/models.py b/app/trading/models.py index f0f332c..212d196 100644 --- a/app/trading/models.py +++ b/app/trading/models.py @@ -205,6 +205,77 @@ class LuxOptionsTradeStatsManager(models.Manager): return self.filter(close_date__range=(start_date, end_date)).aggregate(Sum('pl')) +class LuxOptionPurchase(models.Model): + symbol = models.CharField(max_length=256) + open_date = models.DateTimeField(auto_now_add=True) + close_date = models.DateTimeField(null=True, blank=True) + pl = models.FloatField(null=True, blank=True) + STATUS = ( + (0, 'Open'), + (1, 'Closed'), + ) + status = models.IntegerField(choices=STATUS, default=1) + + class Meta: + ordering = ('-open_date',) + get_latest_by = 'open_date' + + def __str__(self): + return str(self.symbol) + + def get_contract_count(self): + return self.luxoptioncontact_set.count() + + @property + def stop_price(self): + return (self.contract_price*.75) + + @property + def total_invested(self): + return round(self.get_contract_count()*(self.luxoptioncontact_set.first().contract_open_price*100)) + + @property + def trade_risk(self): + return round(self.total_invested*.25) + + @property + def portfolio_risk(self): + return (self.trade_risk/10000)*100 + + @property + def sell_half_at(self): + return self.luxoptioncontact_set.first().contract_open_price*1.25 + + @property + def contract_price(self): + return self.luxoptioncontact_set.first().contract_open_price + + def save(self, *args, **kwargs): + if self.status == 1 and not self.close_date: + self.close_date = timezone.now() + if self.status == 1 and not self.pl: + pass + #self.pl = round((self.close_price*self.shares)-(self.entry_price*self.shares), 2) + super(LuxOptionPurchase, self).save() + + +class LuxOptionContact(models.Model): + symbol = models.CharField(max_length=256) + strike_price = models.FloatField() + expiration_date = models.DateField() + contract_open_price = models.FloatField() + contract_close_price = models.FloatField(null=True, blank=True) + CALL_PUT = ( + (0, 'Calls'), + (1, 'Puts'), + ) + call_put = models.IntegerField(choices=CALL_PUT, default=0) + options_purchase = models.ForeignKey(LuxOptionPurchase, null=True, on_delete=models.SET_NULL) + + def __str__(self): + return "%s - %s %s" %(self.symbol, round(self.strike_price), self.get_call_put_display()) + + class LuxOptionsTrade(models.Model): symbol = models.CharField(max_length=256) date = models.DateTimeField(auto_now_add=True) @@ -322,8 +393,6 @@ class LuxOptionsTrade(models.Model): super(LuxOptionsTrade, self).save() - - class TradeJrnl(models.Model): date = models.DateTimeField(auto_now_add=True) body_markdown = models.TextField(blank=True) diff --git a/app/trading/templates/trading/create_luxoptions_form.html b/app/trading/templates/trading/create_luxoptions_form.html new file mode 100644 index 0000000..5fff0ae --- /dev/null +++ b/app/trading/templates/trading/create_luxoptions_form.html @@ -0,0 +1,68 @@ +{% extends 'trading/base.html' %} +{% load typogrify_tags %} + {% block content %} + <form id="id_form" action="{% url 'luxtrade:testoptions' %}" method="post" class="big">{% csrf_token %} + {% for field in form %} + <fieldset> + {{ field.errors }} + {% if field.name == 'status' or field.name == 'call_put' %} + <label class="hide" for="id_status">Status:</label>{{ field }} + {% else %} + {{ field.label_tag }} {{ field }} + {% endif %} + {% if field.help_text %} + <p class="help">{{ field.help_text|safe }}</p> + {% endif %} + </fieldset> +{% endfor %} + <dl> + <dt>R/R: </dt><dd id="id_rr"></dd> + <dt>% Portfolio: </dt><dd id="id_p_portfolio"></dd> + <dt>Risk per contract: </dt><dd id="id_risk_contract"></dd> + <dt>Total Risk: </dt><dd id="id_risk_total"></dd> + <dt>Total Invested: </dt><dd id="id_total"></dd> + </dl> + <div class="flex"> + <input type="submit" name="post" class="btn" value="record purchase"/> + </div> + </form> + {% endblock %} + + {% block js %} +<script> +function calcPercentPortfolio() { + var entry_price = document.getElementById("id_entry_price").value; + var stop_price = document.getElementById("id_stop_price").value; + var target_price = document.getElementById("id_target_price").value; + var contract_price = document.getElementById("id_contract_price").value; + var number_contracts = document.getElementById("id_number_contracts").value; + var delta = document.getElementById("id_delta").value; + var pp = (contract_price*number_contracts)*100/10000; + var total = (contract_price*number_contracts)*100; + var rr = (target_price-entry_price)/(entry_price-stop_price); + var risk_per = ((entry_price-stop_price)*delta/contract_price)*contract_price*100 + var total_risk = (risk_per*number_contracts); + document.getElementById("id_p_portfolio").innerText = (pp*100).toFixed(2); + document.getElementById("id_risk_contract").innerText = "$"+risk_per.toFixed(2); + document.getElementById("id_risk_total").innerText = "$"+total_risk.toFixed(2); + document.getElementById("id_rr").innerText = rr.toFixed(2); + document.getElementById("id_total").innerText = "$"+total.toFixed(2); +} +id_form.addEventListener("input", function (e) { + calcPercentPortfolio(); +}); +var form = document.getElementById('id_form'); +function processForm(e) { + if (e.preventDefault) e.preventDefault(); + if(!confirm("Do you really want to do this?")) { + return false; + } + form.submit(); +} +if (form.attachEvent) { + form.attachEvent("submit", processForm); +} else { + form.addEventListener("submit", processForm); +} +</script> + {% endblock %} diff --git a/app/trading/templates/trading/list.html b/app/trading/templates/trading/list.html index 390b64e..bf8104b 100644 --- a/app/trading/templates/trading/list.html +++ b/app/trading/templates/trading/list.html @@ -1,6 +1,55 @@ {% extends 'trading/base.html' %} {% block content %} + <h3>WON Options Trades</h3> + <table class="options"> + <thead> + <tr> + <th>Symbol</th> + <th>Open Date</th> + <th>Contract</th> + <th>Total Invested</th> + <th>$ Trade Risk</th> + <th>% Portfolio Risk</th> + <th>25% profit at</th> + <th>Stop</th> + <th>Notes</th> + </tr> + </thead> + {% for object in luxoptions_purchases %} + <tr> + <td><a href="https://www.tradingview.com/chart/?symbol={{object.symbol}}" target="_blank">{{object.symbol}}</a></td> + <td><a href="{{object.get_absolute_url}}">{{object.open_date|date:"Y-m-d"}}</a></td> + <td> + <div class="data_calc"> + {% for contract in object.luxoptioncontact_set.all %}{% if forloop.first%} + <span id="contract" data-num-contracts="{{object.get_contract_count}}">{{object.get_contract_count}}</span> + <span>{{contract.expiration_date|date:"Mj"}}</span> + <span>${{contract.strike_price|floatformat:0}} {{contract.get_call_put_display|title}}</span> + <span>@ ${{object.contract_price|floatformat:2}}</span> +{%endif%} +{%endfor%} + </div> + + <td>${{object.total_invested}}</td> + <td>${{object.trade_risk}}</td> + <td>{{object.portfolio_risk}}%</td> + <td>${{object.sell_half_at|floatformat:2}}</td> + <td>${{object.stop_price|floatformat:2}}</td> + <td class="notes"> + {% if object.notes %} + <div class="wrapper"> + {{object.notes_html|safe}} + </div> + {% endif %} + </td> + {%if object.status == 2%}<td> + <a href="https://live.luxagraf.net/admin/trading/luxoptionstrade/{{object.pk}}/delete/">∅</a> + </td>{% endif %} + </tr> + {% endfor %} + </table> + {% comment %} <h3>Options Trades</h3> <table class="options"> <thead> @@ -66,11 +115,12 @@ </table> + {% endcomment %} - <h3>Stock Trades</h3> + <h3>Open Stock Trades</h3> <table> <thead> <tr> @@ -146,7 +196,7 @@ - <h3>Trade History</h3> + <h3>Stock History</h3> <table> <thead> <tr> @@ -336,6 +386,29 @@ for (var i = 0; i < elements.length; i++) { elements[i].addEventListener('input', getCurrentProfit, false); } + var els = document.getElementsByClassName("data_calc"); + var getData = function() { + var options = this.getAttribute("data-options"); + var eprice = this.getAttribute("data-eprice"); + var sprice = this.getAttribute("data-sprice"); + var shares = this.getAttribute("data-shares"); + var contract_price = this.getAttribute("data-cprice"); + var number_contracts = this.getAttribute("data-ncontracts"); + var total = this.getAttribute("data-total"); + var delta = this.getAttribute("data-delta"); + var cprice = this.value; + if (options) { + var profit = ((cprice*number_contracts)*100)-((contract_price*number_contracts)*100); + this.nextSibling.innerHTML = "$"+profit.toFixed(2); + } else { + var profit = (cprice*shares)-(shares*eprice) + this.nextSibling.innerHTML = "$"+profit.toFixed(2); + } + }; + + for (var i = 0; i < elements.length; i++) { + els[i].addEventListener('input', getData, false); + } </script> {% endblock %} diff --git a/app/trading/urls.py b/app/trading/urls.py index 2a70e13..6bb486a 100644 --- a/app/trading/urls.py +++ b/app/trading/urls.py @@ -10,9 +10,14 @@ urlpatterns = [ views.TradeModelFormView.as_view(), name='testtrade' ), + #path( + # 'options-calc', + # views.OptionsModelFormView.as_view(), + # name='testoptions' + #), path( 'options-calc', - views.OptionsModelFormView.as_view(), + views.LuxOptionPurchaseCreateView.as_view(), name='testoptions' ), path( diff --git a/app/trading/views.py b/app/trading/views.py index 2fb960a..686a344 100644 --- a/app/trading/views.py +++ b/app/trading/views.py @@ -1,8 +1,10 @@ from datetime import datetime +from django.http import HttpResponseRedirect from django.views.generic.edit import CreateView, UpdateView from utils.views import PaginatedListView -from .models import LuxTrade, LuxOptionsTrade +from .models import LuxTrade, LuxOptionsTrade, LuxOptionContact, LuxOptionPurchase +from .forms import LuxOptionsForm class LuxTradeListView(PaginatedListView): @@ -21,6 +23,7 @@ class LuxTradeListView(PaginatedListView): context['options_monthly_pl'] = LuxOptionsTrade.stats.get_month_pl() context['options_year_pl'] = LuxOptionsTrade.stats.get_year_pl() context['month'] = datetime.now().strftime('%h') + context['luxoptions_purchases'] = LuxOptionPurchase.objects.filter(status=0) return context def get_queryset(self): @@ -82,3 +85,37 @@ class OptionsModelFormView(CreateView): ] success_url = '/trading/' template_name = 'trading/create_options_form.html' + + +class LuxOptionPurchaseCreateView(CreateView): + model = LuxOptionContact + fields = ['symbol'] + success_url = '/trading/' + template_name = 'trading/create_luxoptions_form.html' + + + def get_context_data(self, **kwargs): + context = super(LuxOptionPurchaseFormView, self).get_context_data(**kwargs) + context = {'form': LuxOptionsForm()} + return context + + def post(self, request, *args, **kwargs): + form = LuxOptionsForm(request.POST) + if form.is_valid(): + i = 0 + p = LuxOptionPurchase.objects.create( + symbol = form.cleaned_data['symbol'], + ) + print(form.cleaned_data['contracts']) + while i < int(form.cleaned_data['contracts']): + c = LuxOptionContact.objects.create( + symbol = form.cleaned_data['symbol'], + strike_price = form.cleaned_data['strike_price'], + expiration_date = form.cleaned_data['expiration_date'], + contract_open_price = form.cleaned_data['contract_open_price'], + call_put = form.cleaned_data['call_put'], + options_purchase = p + ) + i = i+1 + return HttpResponseRedirect('/trading/') + return render(request, 'trading/create_luxoptions_form.html', {'form': form}) |