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 ee68cd8..53bf70d 100644 Binary files a/cao_blogr.sqlite and b/cao_blogr.sqlite differ 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 %} + + + + +Date : {{ item.date.strftime('%d-%m-%Y') }}
+| Classe | +% cible | +% actuel | +Ecart | +Valeur | +|
|---|---|---|---|---|---|
| {{ item.classe }} | +{{ item.pc_cible }} % | +{{ item.pc_atteint }} | + {% if (item.pc_atteint - item.pc_cible) >= 0 %} +{{ item.pc_atteint }} | + {% else %} +{{ item.pc_atteint }} | + {% endif %} +{{ '{0:0.2f} €'.format(item.valeur) }} | +
| Portefeuille | +Montant | +% | +
|---|---|---|
| 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%)
+
"Diversification is not determined by the number of securities held." + Larry Swedroe
+ + + +| Classe | +Libellé | +Cours | +Nb | +Valeur | ++/- Valeur | +% de +/- | +% TER | +% PF | +||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ ligne.classe }} | + {% if ligne.type=='ACTION' %} +{{ ligne.libelle }} | + {% else %} +{{ ligne.libelle }} | + {% endif %} + {% if ligne.devise=='EUR' %} +{{ '{0:0.2f} €'.format(ligne.cours) }} | + {% else %} +{{ '{0:0.2f} $'.format(ligne.cours) }} | + {% endif %} +{{ ligne.nombre }} | +{{ '{0:0.2f} €'.format(ligne.valeur) }} | + {% if ligne.plus_value >= 0 %} +{{ '{0:0.2f} €'.format(ligne.plus_value) }} | +{{ '{0:0.1f}'.format(ligne.pc_plusvalue) }} | + {% else %} +{{ '{0:0.2f} €'.format(ligne.plus_value) }} | +{{ '{0:0.1f}'.format(ligne.pc_plusvalue) }} | + {% endif %} +{{ '{0:0.1f}'.format(ligne.ter) }} | +{{ '{0:0.1f}'.format(ligne.pc_allocation) }} | +
| Total | +{{ '{0:0.2f} €'.format(total_valeur) }} | + {% if total_pv >= 0 %} +{{ '{0:0.1f}'.format(total_pv) }} | +{{ '{0:0.2f} €'.format(total_pc_value) }} | + {% else %} +{{ '{0:0.1f}'.format(total_pv) }} | +{{ '{0:0.2f} €'.format(total_pc_value) }} | + {% endif %} ++ | 100.0 | +|||||