From 4a55f945514db08ee15d4114dc00f564a01b10dc Mon Sep 17 00:00:00 2001 From: Phuoc CAO Date: Thu, 26 Jan 2023 16:22:58 +0100 Subject: [PATCH] added portfolio view --- allocation.sql | 6 + cao_blogr.sqlite | Bin 532480 -> 532480 bytes .../alembic/versions/20230126_42a297861f20.py | 30 +++ .../alembic/versions/20230126_bbfb79cb9dad.py | 34 +++ cao_blogr/forms.py | 11 +- cao_blogr/models/portfolio.py | 8 +- cao_blogr/services/portfolio.py | 83 +++++- cao_blogr/static/theme.css | 4 +- cao_blogr/templates/blog.jinja2 | 2 +- .../portfolio/allocation_edit.jinja2 | 60 +++++ .../templates/portfolio/histo_edit.jinja2 | 116 ++++---- .../templates/portfolio/histo_list.jinja2 | 15 +- .../templates/portfolio/portfolio.jinja2 | 249 ++++++++++++++++++ cao_blogr/views/blog.py | 10 +- cao_blogr/views/portfolio.py | 208 ++++++++++++++- setup.py | 1 + 16 files changed, 750 insertions(+), 87 deletions(-) create mode 100644 allocation.sql create mode 100644 cao_blogr/alembic/versions/20230126_42a297861f20.py create mode 100644 cao_blogr/alembic/versions/20230126_bbfb79cb9dad.py create mode 100644 cao_blogr/templates/portfolio/allocation_edit.jinja2 create mode 100644 cao_blogr/templates/portfolio/portfolio.jinja2 diff --git a/allocation.sql b/allocation.sql new file mode 100644 index 0000000..f29d4b2 --- /dev/null +++ b/allocation.sql @@ -0,0 +1,6 @@ +INSERT INTO "allocation" VALUES(5,'Obligations',20,28.899999999999998578,74481.0); +INSERT INTO "allocation" VALUES(10,'Actions World',60,54.0,139404.14999999999418); +INSERT INTO "allocation" VALUES(12,'Cash',2,2.2000000000000001776,5591.0000000000000001); +INSERT INTO "allocation" VALUES(16,'Actions REITS',2,6.0999999999999996447,15607.200000000000727); +INSERT INTO "allocation" VALUES(17,'Actions Moment',15,8.1999999999999992894,21223.43999999999869); +INSERT INTO "allocation" VALUES(19,'Crypto',1,0.59999999999999997779,1623.349999999999909); diff --git a/cao_blogr.sqlite b/cao_blogr.sqlite index ee68cd89d182eeb12361372a8fdf504cca1680fa..53bf70df61fbdac6bdda5fc2efe2b568396f1486 100644 GIT binary patch delta 486 zcmZoTpwMtYVS=<^1p@;^6A;6I;6xo`?Ft4x|5{#-L&e+1Vf^u#DGFhZLCzkIK^i8e znv<__WG3qS;Y!&{z0yu z?tTj1u8|sWSxto?S2x!nS3hUhUw?DlS^~61)C-l3ts`lgUP8JS2)-N*ucKp z?8v!Uj3+56Ey>(6Imt35F=dm&7k*R$1AlozGX+BfD80x#RT-J4&tA_M$tcqP zbv+{xGfn@xo|&IfZ#wG+X07Rl8<x=#IDO`u~|@H9qVQbHccj$##nCl$*CMyCZA_zW8%`>?8v!UjK|P2 z#nRX^B`Mh~#bA@d7k*R$1AlozV+BJ~D+5a_14BJSBLhoIv*rZ+nx^ zSkGv}$ThumJ)-5>{86z3R+P|)61Y)M?U)MA9GwM%g-N3BH7&u*T1GCxov<=K0 zEKCdx+Y7cZKV)NZ5#eNL58cfS#4JF}3dC&NLwB>k4*?qgk!`!FI7bhoFgrWDk%5u1 bu7RPhfsultft7*5bUsNA>Fw_&I0V@NAS6*g diff --git a/cao_blogr/alembic/versions/20230126_42a297861f20.py b/cao_blogr/alembic/versions/20230126_42a297861f20.py new file mode 100644 index 0000000..ef8807b --- /dev/null +++ b/cao_blogr/alembic/versions/20230126_42a297861f20.py @@ -0,0 +1,30 @@ +"""added relationship allocation-classe + +Revision ID: 42a297861f20 +Revises: 19d939dbc6d0 +Create Date: 2023-01-26 14:23:42.771763 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '42a297861f20' +down_revision = '19d939dbc6d0' +branch_labels = None +depends_on = None + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('allocation', sa.Column('classe_id', sa.Unicode(length=45), nullable=True)) + op.create_foreign_key(op.f('fk_allocation_classe_id_classes'), 'allocation', 'classes', ['classe_id'], ['classe']) + op.drop_column('allocation', 'classe') + # ### end Alembic commands ### + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('allocation', sa.Column('classe', sa.VARCHAR(length=45), nullable=False)) + op.drop_constraint(op.f('fk_allocation_classe_id_classes'), 'allocation', type_='foreignkey') + op.drop_column('allocation', 'classe_id') + # ### end Alembic commands ### diff --git a/cao_blogr/alembic/versions/20230126_bbfb79cb9dad.py b/cao_blogr/alembic/versions/20230126_bbfb79cb9dad.py new file mode 100644 index 0000000..bfadaf4 --- /dev/null +++ b/cao_blogr/alembic/versions/20230126_bbfb79cb9dad.py @@ -0,0 +1,34 @@ +"""added relationship allocation-classe + +Revision ID: bbfb79cb9dad +Revises: 42a297861f20 +Create Date: 2023-01-26 14:41:29.955558 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'bbfb79cb9dad' +down_revision = '42a297861f20' +branch_labels = None +depends_on = None + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('allocation', + sa.Column('no_cat', sa.Integer(), nullable=False), + sa.Column('classe_id', sa.Unicode(length=45), nullable=True), + sa.Column('pc_cible', sa.Integer(), nullable=True), + sa.Column('pc_atteint', sa.Float(), nullable=True), + sa.Column('valeur', sa.Float(), nullable=True), + sa.ForeignKeyConstraint(['classe_id'], ['classes.classe'], name=op.f('fk_allocation_classe_id_classes')), + sa.PrimaryKeyConstraint('no_cat', name=op.f('pk_allocation')) + ) + # ### end Alembic commands ### + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('allocation') + # ### end Alembic commands ### diff --git a/cao_blogr/forms.py b/cao_blogr/forms.py index c0bd4b1..35fee4c 100644 --- a/cao_blogr/forms.py +++ b/cao_blogr/forms.py @@ -1,4 +1,4 @@ -from wtforms import Form, StringField, TextAreaField, SelectField, validators +from wtforms import Form, StringField, TextAreaField, SelectField, DecimalField from wtforms import IntegerField, PasswordField from wtforms.validators import InputRequired, Length from wtforms.widgets import HiddenInput @@ -22,7 +22,6 @@ class BlogSearchForm(Form): class TagForm(Form): id = IntegerField(widget=HiddenInput()) - tag = StringField('Tag', validators=[InputRequired(), Length(min=1, max=25)], filters=[strip_filter]) @@ -32,3 +31,11 @@ class UserCreateForm(Form): filters=[strip_filter]) password = PasswordField('Mot de passe', validators=[InputRequired(), Length(min=6)]) +class HistoForm(Form): + no_id = IntegerField(widget=HiddenInput()) + mvt_cash = DecimalField(places=2, validators=[InputRequired()]) + +class AllocationForm(Form): + no_cat = IntegerField(widget=HiddenInput()) + classe = SelectField('Classe') + pc_cible = IntegerField(validators=[InputRequired()]) diff --git a/cao_blogr/models/portfolio.py b/cao_blogr/models/portfolio.py index 6945c50..e9b2a56 100644 --- a/cao_blogr/models/portfolio.py +++ b/cao_blogr/models/portfolio.py @@ -1,5 +1,6 @@ import datetime #<- will be used to set default dates on models from cao_blogr.models.meta import Base #<- we need to import our sqlalchemy metadata from which model classes will inherit +from sqlalchemy.orm import relationship from sqlalchemy import ( Column, Integer, @@ -38,14 +39,17 @@ class Actifs(Base): class Allocation(Base): __tablename__ = 'allocation' no_cat = Column(Integer, primary_key=True) - classe = Column(Unicode(45), nullable=False) + classe_id = Column(Unicode(45), ForeignKey('classes.classe')) pc_cible = Column(Integer) pc_atteint = Column(Float) valeur = Column(Float) + # relationship + classe = relationship('Classes', backref="allocation") + class Classes(Base): __tablename__ = 'classes' - classe = Column(Unicode, primary_key=True) + classe = Column(Unicode(45), primary_key=True) type = Column(Unicode(45), default='ACTION') ordre = Column(Integer) bg_color = Column(Unicode(45)) diff --git a/cao_blogr/services/portfolio.py b/cao_blogr/services/portfolio.py index 9b18ff3..55c7252 100644 --- a/cao_blogr/services/portfolio.py +++ b/cao_blogr/services/portfolio.py @@ -1,15 +1,90 @@ import sqlalchemy as sa -from ..models.portfolio import Histo - +from sqlalchemy import func +from ..models.portfolio import Actifs, Allocation, Classes, Histo class PFService(object): + + @classmethod + def get_actifs(cls, request, no_id): + if no_id == '0': + items = request.dbsession.query(Actifs).order_by(Actifs.classe, Actifs.libelle).all() + else: + # lire une allocation par le no_id + items = request.dbsession.query(Actifs).filter(Actifs.no_id == no_id).first() + return items + + @classmethod + def get_allocation(cls, request, no_cat): + if no_cat == '0': + query = request.dbsession.query(Allocation).join(Classes).filter(Classes.classe == Allocation.classe_id) + query = query.order_by(sa.asc(Allocation.classe_id)).all() + else: + # lire une allocation par le no_id + query = request.dbsession.query(Allocation).filter(Allocation.no_cat == no_cat).first() + return query + + @classmethod + def get_classes(cls, request, classe): + if classe == '0': + items = request.dbsession.query(Classes).order_by(sa.asc(Classes.ordre)).all() + else: + # lire une allocation par le no_id + items = request.dbsession.query(Classes).filter(Classes.classe == classe).first() + return items + @classmethod def get_histo(cls, request, no_id): if no_id == '0': - items = request.dbsession.query(Histo).order_by(sa.asc(Histo.date)).all() + items = request.dbsession.query(Histo).order_by(sa.desc(Histo.date)).all() else: # lire le histo par le no_id - items = request.dbsession.query(Histo).filter(Histo.id == id).first() + items = request.dbsession.query(Histo).filter(Histo.no_id == no_id).first() return items + @classmethod + def delete_allocation(cls, request, no_id): + request.dbsession.query(Allocation).filter(Histo.no_ == no_id).delete(synchronize_session=False) + return + + @classmethod + def delete_histo(cls, request, no_id): + request.dbsession.query(Histo).filter(Histo.no_id == no_id).delete(synchronize_session=False) + return + + @classmethod + def update_actif_devise(request, devise, taux): + request.dbsession.query(Actifs).filter(Actifs.devise == devise).update({'parite': taux}) + request.dbsession.commit() + return + + def update_actif_valeur(request, symbole, cours, dividends): + request.dbsession.query(Actifs).filter(Actifs.symbole == symbole).update({'symbole': symbole, 'cours': cours, 'dividends': dividends}) + request.dbsession.commit() + return + + def update_portefeuille(request): + TotalValue = request.dbsession.query(Actifs, func.sum(Actifs.valeur).label("TotalValue")).first() + + # maj du pourcentage d'allocation des lignes du portefeuille + request.dbsession.query(Actifs).update({'pc_allocation': Actifs.valeur / TotalValue * 100}) + request.dbsession.commit() + + # maj des allocations + items = PFService.get_allocation(request, '0') + for item in items: + TotalClasse = request.dbsession.query(Actifs, func.sum(Actifs.valeur).label("TotalValue") + ).filter(Actifs.classe == item.classe).first() + item.valeur = TotalClasse, + item.pc_atteint = item.valeur / TotalValue * 100; + request.dbsession.commit() + + return + + def delete_actif(request, no_id): + request.dbsession.query(Actifs).filter(Actifs.no_id == no_id).delete(synchronize_session=False) + request.dbsession.commit() + return + + + diff --git a/cao_blogr/static/theme.css b/cao_blogr/static/theme.css index 302815a..9470c68 100644 --- a/cao_blogr/static/theme.css +++ b/cao_blogr/static/theme.css @@ -2,8 +2,8 @@ /* https://www.w3schools.com/bootstrap/bootstrap_theme_band.asp */ body { - font: 400 19px/1.8 Georgia, serif; - color: #777; + font: 200 16px/1.8 Georgia, serif; + color: rgb(90, 89, 89); } h3, h4 { margin: 10px 0 30px 0; diff --git a/cao_blogr/templates/blog.jinja2 b/cao_blogr/templates/blog.jinja2 index 531903f..203544b 100644 --- a/cao_blogr/templates/blog.jinja2 +++ b/cao_blogr/templates/blog.jinja2 @@ -3,7 +3,7 @@ {% block content %} {% if request.authenticated_userid %}

- [ Retour ] + [ Retour ] [ Modifier ]

diff --git a/cao_blogr/templates/portfolio/allocation_edit.jinja2 b/cao_blogr/templates/portfolio/allocation_edit.jinja2 new file mode 100644 index 0000000..3d56cb8 --- /dev/null +++ b/cao_blogr/templates/portfolio/allocation_edit.jinja2 @@ -0,0 +1,60 @@ +{% extends "cao_blogr:templates/layout.jinja2" %} + +{% block content %} + +
+ +
+ + {{ form.classe(class_='form-control') }} +
+ + {% for error in form.pc_cible.errors %} +
{{ error }}
+ {% endfor %} +
+ + {{form.pc_cible(class_='form-control')}} +
+ +
+
+ + Retour + + {% if form.no_cat.data %} + + {% endif %} +
+ + +
+ + + + +{% endblock %} diff --git a/cao_blogr/templates/portfolio/histo_edit.jinja2 b/cao_blogr/templates/portfolio/histo_edit.jinja2 index 568b76f..e6536ab 100644 --- a/cao_blogr/templates/portfolio/histo_edit.jinja2 +++ b/cao_blogr/templates/portfolio/histo_edit.jinja2 @@ -1,67 +1,59 @@ -
-
+{% extends "cao_blogr:templates/layout.jinja2" %} -
-
-
-
+{% block content %} -
- -
-

${item.date.strftime('%d-%m-%Y')}

-
-
-
- -
-
-
- -
-
-
-
- -
-

${item.nb_part}

-
-
- -
-
-
-
- - Retour - - -
-
-
-
+
-
-
-
+
+

Date : {{ item.date.strftime('%d-%m-%Y') }}

+
- + {% for error in form.mvt_cash.errors %} +
{{ error }}
+ {% endfor %} +
+ + {{form.mvt_cash(class_='form-control')}} +
-
-
+
+
+ + Retour + + {% if form.no_id.data %} + + {% endif %} +
+ + + + + + + +{% endblock %} diff --git a/cao_blogr/templates/portfolio/histo_list.jinja2 b/cao_blogr/templates/portfolio/histo_list.jinja2 index 8136fa9..cfababf 100644 --- a/cao_blogr/templates/portfolio/histo_list.jinja2 +++ b/cao_blogr/templates/portfolio/histo_list.jinja2 @@ -15,19 +15,19 @@ Nb Part Valeur Part Cours ref - Valeur Part ref + Nb Part ref No Id {% for item in items %} {{ item.date.strftime('%d/%m/%Y') }} - {{ item.mvt_cash }} € - {{ item.valeur_pf }} - {{ item.nb_part }} - {{ item.val_part }} - {{ item.nb_part_ref }} - {{ item.val_part_ref }} + {{ '{0:0.2f} €'.format(item.mvt_cash) }} + {{ '{0:0.2f} €'.format(item.valeur_pf) }} + {{ '{0:0.2f}'.format(item.nb_part) }} + {{ '{0:0.2f} €'.format(item.val_part) }} + {{ '{0:0.2f} €'.format(item.cours_ref) }} + {{ '{0:0.2f}'.format(item.val_part_ref) }} {{ item.no_id }} @@ -37,6 +37,7 @@

+ {% endblock %} diff --git a/cao_blogr/templates/portfolio/portfolio.jinja2 b/cao_blogr/templates/portfolio/portfolio.jinja2 new file mode 100644 index 0000000..a6eb793 --- /dev/null +++ b/cao_blogr/templates/portfolio/portfolio.jinja2 @@ -0,0 +1,249 @@ +{% extends "cao_blogr:templates/layout.jinja2" %} + +{% block content %} + +

+ + Nouvelle classe +

+
+
+ + + + + + + + + + + + {% for item in items %} + + + + + {% if (item.pc_atteint - item.pc_cible) >= 0 %} + + {% else %} + + {% endif %} + + + {% endfor %} + +
Classe% cible% actuelEcartValeur
{{ item.classe }}{{ item.pc_cible }} %{{ item.pc_atteint }}{{ item.pc_atteint }}{{ item.pc_atteint }}{{ '{0:0.2f} €'.format(item.valeur) }}
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
PortefeuilleMontant%
Valorisation{{ '{0:0.2f} €'.format(total_valeur) }}
Plus value{{ '{0:0.2f} €'.format(total_pv) }}{{ '{0:0.1f} %'.format(total_pc_value) }}
Safe Withdrawal Rate{{ '{0:0.1f} %'.format(swr_amount) }}{{ '{0:0.1f} %'.format(swr_rate) }}
+

+ Allocation globale : 70% actions + 30% obligations
+ [Inspirée du + Fond souverain Norvégien]
+ Allocation actions : 80% Monde (56%) + 20% Croissance (14%)
+

+
+ +
+ +
+
+ +
+
+
+ +
+
+
+ +
+

Mes actifs

+

"Diversification is not determined by the number of securities held." + Larry Swedroe

+ +
+
+ + Nouvel actif + + Historique + + Mouvements +
+
+ + + + + + + + + + + + + + + + + {% for ligne in actifs %} + + + {% if ligne.type=='ACTION' %} + + {% else %} + + {% endif %} + {% if ligne.devise=='EUR' %} + + {% else %} + + {% endif %} + + + {% if ligne.plus_value >= 0 %} + + + {% else %} + + + {% endif %} + + + + {% endfor %} + + + + + {% if total_pv >= 0 %} + + + {% else %} + + + {% endif %} + + + + +
ClasseLibelléCoursNbValeur+/- Valeur% de +/-% TER% PF
{{ ligne.classe }}{{ ligne.libelle }}{{ ligne.libelle }}{{ '{0:0.2f} €'.format(ligne.cours) }}{{ '{0:0.2f} $'.format(ligne.cours) }}{{ ligne.nombre }}{{ '{0:0.2f} €'.format(ligne.valeur) }}{{ '{0:0.2f} €'.format(ligne.plus_value) }}{{ '{0:0.1f}'.format(ligne.pc_plusvalue) }}{{ '{0:0.2f} €'.format(ligne.plus_value) }}{{ '{0:0.1f}'.format(ligne.pc_plusvalue) }}{{ '{0:0.1f}'.format(ligne.ter) }}{{ '{0:0.1f}'.format(ligne.pc_allocation) }}
Total{{ '{0:0.2f} €'.format(total_valeur) }}{{ '{0:0.1f}'.format(total_pv) }}{{ '{0:0.2f} €'.format(total_pc_value) }}{{ '{0:0.1f}'.format(total_pv) }}{{ '{0:0.2f} €'.format(total_pc_value) }}100.0
+
+
+
+ +
+
+
+ + + + + + + + + +
+ + +{% endblock %} diff --git a/cao_blogr/views/blog.py b/cao_blogr/views/blog.py index 3b7adc8..22ef063 100644 --- a/cao_blogr/views/blog.py +++ b/cao_blogr/views/blog.py @@ -6,8 +6,7 @@ from ..forms import BlogCreateForm, BlogUpdateForm, BlogSearchForm, TagForm import markdown import datetime #<- will be used to set default dates on models -@view_config(route_name='blog', - renderer='cao_blogr:templates/blog.jinja2') +@view_config(route_name='blog', renderer='cao_blogr:templates/blog.jinja2') def blog(request): # get post id from request blog_id = request.matchdict['id'] @@ -21,11 +20,18 @@ def blog(request): body = entry.body.replace('static/', "%s/static/" % request.application_url) # convertir de markdown en HTML body_html = markdown.markdown(body, extensions=['footnotes']) + + # si page mouvement de portfolio + if blog_id == '2': + return_url = "/portfolio" + else: + return_url = "/" return { 'page_title': entry.title, 'entry': entry, 'body_html': body_html, + 'return_url': return_url, } diff --git a/cao_blogr/views/portfolio.py b/cao_blogr/views/portfolio.py index 9ccbba8..bf2ec56 100644 --- a/cao_blogr/views/portfolio.py +++ b/cao_blogr/views/portfolio.py @@ -1,12 +1,113 @@ from pyramid.view import ( view_config, - forbidden_view_config, ) from pyramid.httpexceptions import HTTPFound -from pyramid.security import remember, forget from ..services.portfolio import PFService -from ..forms import UserCreateForm -from ..models.portfolio import Histo +from ..forms import AllocationForm, HistoForm +from ..models.portfolio import Histo, Allocation + +import datetime #<- will be used to set default dates on models +import yfinance as yf +import json + +@view_config(route_name='portfolio', renderer='../templates/portfolio/portfolio.jinja2', permission='view') +def portfolio(request): + logged_in = request.authenticated_userid + url = request.route_url('portfolio') + + # lire les categories + items = PFService.get_allocation(request, '0') + import pdb;pdb.set_trace() + # construire la liste pour donut + donut_cible=[] + donut_cible.append(('Allocation cible', 'Pourcent')) + donut_actuel=[] + donut_actuel.append(('Allocation actuelle', 'Pourcent')) + + # calculer % total + total = 0 + for item in items: + # construire la liste pour donut cible + d = (item.classe, item.pc_cible) + donut_cible.append(d) + # construire la liste pour donut actuel + d = (item.classe, int(item.pc_atteint * 10)) + donut_actuel.append(d) + # totaliser les pourcentages + total += item.pc_cible + + swr_rate = 3.5 + if total != 100: + request.session.flash('Attention, le total de votre répartition cible dépasse 100% : ' + str(total), 'warning') + + # lire les actifs + actifs = PFService.get_actifs(request, '0') + + # MAJ du prtefeuille + if 'form.submitted' in request.params: + # lire le cours de EURUSD + ticker = yf.Ticker('EUR=X') + + # maj des parités des devises + PFService.update_actif_devise(request, 'USD', ticker.info.get('regularMarketPrice')) + + for item in actifs: + if item.type == 'ACTION': + # lire le cours de l'action + ticker = yf.Ticker(item.symbole) + # ticker delisted ? + if ticker.info == None: + price = 0 + else: + price = ticker.info.get('regularMarketPrice') + # caluler son rendement + dividends = PFService.get_dividends(ticker) + PFService.update_actif_valeur(request, item.symbole, price, dividends) + # time.sleep(1) # attendre 2 secondes + + # update du portefeuille + PFService.update_portefeuille(request, logged_in) + # relire les actifs + actifs = PFService.get_actifs(request, '0') + request.session.flash('Le portefeuille est mis à jour avec succès.', 'success') + + total_valeur = 0 + total_pv = 0 + total_rdt = 0 + for item in actifs: + total_valeur += item.valeur + total_pv += item.plus_value + if total_valeur == 0: + total_pc_value = 0 + else: + total_pc_value = total_pv / total_valeur * 100 + total_rdt += item.rendement + + # lire l'historique + histos = PFService.get_histo(request,'0') + courbe_evoln=[] + courbe_evoln.append(('Date', 'Valeur part PF', 'Valeur part CARINVT:FP')) + + for item in histos: + # construire la liste pour donut cible + d = (item.date.strftime('%d/%m/%Y'), int(item.val_part * 1000), int(item.val_part_ref * 1000)) + courbe_evoln.append(d) + + return { + 'page_title': "Portefeuille", + 'url': url, + 'items': items, + 'donut_cible': json.dumps(donut_cible), + 'donut_actuel': json.dumps(donut_actuel), + 'courbe_evoln': json.dumps(courbe_evoln), + 'actifs': actifs, + 'total_valeur': total_valeur, + 'total_pv': total_pv, + 'total_pc_value': total_pc_value, + 'total_rdt': total_rdt, + 'swr_rate' : swr_rate, + 'swr_amount': float(total_valeur) * swr_rate / 100, + } @view_config(route_name='histo_list', renderer='../templates/portfolio/histo_list.jinja2', permission='view') @@ -15,8 +116,105 @@ def histo_list(request): items = PFService.get_histo(request, '0') return { - 'page_title': 'Historique', + 'page_title': 'Historique des parts', 'items': items, } +@view_config(route_name='allocation_edit', renderer='../templates/portfolio/allocation_edit.jinja2', permission='view') +def allocation_edit(request): + no_cat = request.matchdict['no_cat'] + url = request.route_url('allocation_edit', no_cat=no_cat) + + # lire les classes + classes_list = PFService.get_classes(request, '0') + + if no_cat == '0': + # create a new allocation + entry = Allocation() + form = AllocationForm(request.POST, entry) + form.classe.choices = [(row.classe, row.classe) for row in classes_list] + page_title = "Nouvelle allocation" + + else: + # modify post + entry = PFService.get_allocation(request, no_cat) + if not entry: + request.session.flash(u"Allocation non trouvée : %s" % no_cat, 'warning') + return HTTPFound(location=request.route_url('portfolio')) + form = AllocationForm(request.POST, entry) + form.classe.choices = [(row.classe, row.classe) for row in classes_list] + page_title = "Modifier Allocation : " + str(entry.no_cat) + + if 'form.submitted' in request.params and form.validate(): + if no_cat == '0': + form.populate_obj(entry) + entry.pc_atteint = 0 + entry.valeur = 0 + request.dbsession.add(entry) + return HTTPFound(location=request.route_url('portfolio')) + else: + del form.no_cat # SECURITY: prevent overwriting of primary key + form.populate_obj(entry) + + return HTTPFound(location=request.route_url('portfolio')) + + if 'form.deleted' in request.params: + PFService.delete_allocation(request, entry.no_cat) + request.session.flash("La fiche a été supprimée avec succès.", 'success') + return HTTPFound(location=request.route_url('portfolio')) + + return { + 'page_title': page_title, + 'url': url, + 'form': form, + 'item': entry, + } + + +@view_config(route_name='histo_edit', renderer='../templates/portfolio/histo_edit.jinja2', permission='view') +def histo_edit(request): + no_id = request.matchdict['no_id'] + url = request.route_url('histo_edit', no_id=no_id) + + if no_id == '0': + # create a new tag + entry = Histo() + entry.date = datetime.datetime.now() + form = HistoForm(request.POST, entry) + page_title = "Nouveau Histo" + + else: + # modify post + entry = PFService.get_histo(request, no_id) + if not entry: + request.session.flash(u"Histo non trouvé : %s" % no_id, 'warning') + return HTTPFound(location=request.route_url('histo_list')) + form = HistoForm(request.POST, entry) + page_title = "Modifier histo no " + str(entry.no_id) + + if 'form.submitted' in request.params and form.validate(): + if no_id == '0': + form.populate_obj(entry) + request.dbsession.add(entry) + return HTTPFound(location=request.route_url('histo_list')) + else: + del form.no_id # SECURITY: prevent overwriting of primary key + form.populate_obj(entry) + # lire le cours de l'indice de réfence : Carmignac Investissement A EUR Acc + ticker = yf.Ticker('0P00000FB2.F') + entry.cours_ref = ticker.info.get('regularMarketPrice') + + return HTTPFound(location=request.route_url('histo_list')) + + if 'form.deleted' in request.params: + PFService.delete_histo(request, entry.no_id) + request.session.flash("La fiche a été supprimée avec succès.", 'success') + return HTTPFound(location=request.route_url('histo_list')) + + return { + 'page_title': page_title, + 'url': url, + 'form': form, + 'item': entry, + } diff --git a/setup.py b/setup.py index 8320403..3a88d0c 100644 --- a/setup.py +++ b/setup.py @@ -25,6 +25,7 @@ requires = [ 'webhelpers2', # various web building related helpers 2.0 'passlib', 'markdown', + 'yfinance', ] tests_require = [