delete protfolio folder

This commit is contained in:
Phuoc Cao
2026-01-05 18:59:47 +01:00
parent 181d8d0d21
commit bbc75d0b12
14 changed files with 0 additions and 1169 deletions

View File

@@ -7,7 +7,6 @@ import zope.sqlalchemy
# Base.metadata prior to any initialization routines
from .user import User
from .blog_record import BlogRecord
from .portfolio import Actifs
# run configure_mappers after defining all of the models to ensure
# all relationships can be setup

View File

@@ -1,63 +0,0 @@
import datetime #<- will be used to set default dates on models
from ctp_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,
Float,
Unicode, #<- will provide Unicode field
UnicodeText, #<- will provide Unicode text field
DateTime, #<- time abstraction field
Index,
ForeignKey,
)
class Actifs(Base):
__tablename__ = 'actifs'
no_id = Column(Integer, primary_key=True)
symbole = Column(Unicode(45), unique=True, nullable=False)
libelle = Column(Unicode(45), nullable=False)
classe = Column(Unicode(45), nullable=False)
nombre = Column(Integer)
cours = Column(Float)
pru = Column(Float)
valeur = Column(Float)
plus_value = Column(Float)
pc_plusvalue = Column(Float)
rendement = Column(Float)
pc_rdt = Column(Float)
pc_allocation = Column(Float)
ter = Column(Float)
ter_pondere = Column(Float)
devise = Column(Unicode(45), default='EUR')
parite = Column(Float)
website = Column(Unicode(100))
modif_le = Column(DateTime, default=datetime.datetime.utcnow)
__table_args__ = (Index('symbole_index', 'symbole'),)
class Allocation(Base):
__tablename__ = 'allocation'
no_cat = Column(Integer, primary_key=True)
classe = Column(Unicode(45), ForeignKey('actifs.classe'))
pc_cible = Column(Integer)
pc_atteint = Column(Float)
pc_ecart = Column(Float)
valeur = Column(Float)
type = Column(Unicode(45), default='ACTION')
ordre = Column(Integer)
bg_color = Column(Unicode(45))
__table_args__ = (Index('classe_index', 'classe'), Index('ordre_index', 'ordre'),)
class Histo(Base):
__tablename__ = 'histo'
no_id = Column(Integer, primary_key=True)
date = Column(DateTime, default=datetime.datetime.utcnow)
mvt_cash = Column(Float)
valeur_pf = Column(Float)
nb_part = Column(Float)
val_part = Column(Float)
cours_ref = Column(Float)
val_part_ref = Column(Float)

View File

@@ -1,106 +0,0 @@
import sqlalchemy as sa
from sqlalchemy import func
from ..models.portfolio import Actifs, Allocation, Histo
class PFService(object):
@classmethod
def get_actifs(cls, request, no_id):
if no_id == '0':
items = request.dbsession.query(Actifs, Allocation).join(Allocation).order_by(Allocation.ordre, 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)
query = query.order_by(sa.asc(Allocation.ordre)).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_allocation_byType(cls, request, type):
# lire une allocation par le no_id
query = request.dbsession.query(Allocation).filter(Allocation.type == type).all()
return query
@classmethod
def get_histo(cls, request, no_id):
if no_id == '0':
items = request.dbsession.query(Histo).order_by(sa.asc(Histo.date)).all()
elif no_id == '-1':
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.no_id == no_id).first()
return items
@classmethod
def get_last_histo(cls, request):
# lire le dernier histo créé
items = request.dbsession.query(Histo).order_by(sa.desc(Histo.date)).first()
return items
@classmethod
def delete_allocation(cls, request, no_cat):
request.dbsession.query(Allocation).filter(Allocation.no_cat == no_cat).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(cls, request, devise, taux):
request.dbsession.query(Actifs).filter(Actifs.devise == devise).update({'parite': taux})
return
def update_actif_valeur(request, symbole, cours, valeur, plus_value, pc_plusvalue):
request.dbsession.query(Actifs).filter(Actifs.symbole == symbole
).update({'cours': cours,
'valeur': valeur,
'plus_value':plus_value,
'pc_plusvalue': pc_plusvalue,
})
return
def update_portefeuille(request, today):
# cumuler la valeur totale des actifs
result = request.dbsession.query(Actifs, func.sum(Actifs.valeur).label("TotalValue")).first()
TotalValue = result.TotalValue
# maj du pourcentage d'allocation des lignes du portefeuille
items = PFService.get_actifs(request, '0')
for item in items:
pc_allocation = round(item.Actifs.valeur / TotalValue * 100, 3)
request.dbsession.query(Actifs).filter(Actifs.no_id == item.Actifs.no_id).update({'pc_allocation': pc_allocation,
'modif_le': today})
# maj des allocations
items = PFService.get_allocation(request, '0')
for item in items:
# cumuler la valeur totale des actifs de cette classe
result = request.dbsession.query(Actifs, func.sum(Actifs.valeur).label("TotalClasse")
).filter(Actifs.classe == item.classe).first()
TotalClasse = result.TotalClasse
pc_atteint = round(TotalClasse / TotalValue * 100, 3)
# maj du pourcentage d'allocation de cette classe
request.dbsession.query(Allocation).filter(Allocation.classe == item.classe
).update({'valeur': TotalClasse,
'pc_atteint': pc_atteint})
return
def delete_actif(request, no_id):
request.dbsession.query(Actifs).filter(Actifs.no_id == no_id).delete(synchronize_session=False)
return

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

View File

@@ -1,88 +0,0 @@
{% extends "ctp_blogr:templates/layout.jinja2" %}
{% block content %}
<form action="{{ url }}" method="post" class="form">
<div class="form-group">
<label class="required-field">{{ form.classe.label }}</label>
{{ form.classe(class_='form-control') }}
</div>
{% for error in form.symbole.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
<div class="form-group">
<label class="required-field" for="symbole">{{form.symbole.label}}</label>
{{form.symbole(class_='form-control')}}
</div>
{% for error in form.libelle.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
<div class="form-group">
<label class="required-field" for="libelle">{{form.libelle.label}}</label>
{{form.libelle(class_='form-control')}}
</div>
<div class="form-group">
<label class="required-field" for="pru">{{form.pru.label}}</label>
<div class="input-group">
<div class="input-group-addon">{{ item.devise }}</div>
{{form.pru(class_='form-control')}}
</div>
</div>
<div class="form-group">
<label class="required-field" for="cours">{{form.cours.label}}</label>
<div class="input-group">
<div class="input-group-addon">{{ item.devise }}</div>
{{form.cours(class_='form-control')}}
</div>
</div>
<div class="form-group">
<p class="form-control-static"><b>Dernière modif. :</b> {{ item.modif_le }}</p>
</div>
<br>
<div class="form-group">
<a class="btn btn-default" href="{{ request.route_url('portfolio') }}">
<span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
<button class="btn btn-primary" type="submit" name="form.submitted">
<span class="glyphicon glyphicon-ok"></span> Enregistrer</button>
{% if form.no_id.data %}
<button class="btn btn-danger" type="button" data-toggle="modal" data-target="#confirmDelete">
<span class="glyphicon glyphicon-remove"></span> Supprimer</button>
{% endif %}
</div>
</form>
<!-- Modal : Confirmation SUPRESSION -->
<div id="confirmDelete" class="modal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Supprimer cette allocation</h4>
</div>
<div class="modal-body">
<!-- The form is placed inside the body of modal -->
<p>Etes-vous certain(e) de vouloir supprimer la ligne <b>{{ form.symbole.data }}</b> ?</p>
</div>
<div class="modal-footer">
<div class="form-group">
<div class="text-center">
<form id="confirmForm" method="post">
<button type="submit" class="btn btn-danger" name="form.deleted">Supprimer</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,111 +0,0 @@
{% extends "ctp_blogr:templates/layout.jinja2" %}
{% block content %}
<form action="{{ url }}" method="post" class="form">
<div class="form-group">
<label class="required-field">{{ form.classe.label }}</label>
{{ form.classe(class_='form-control') }}
</div>
{% for error in form.symbole.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
<div class="form-group">
<label class="required-field" for="symbole">{{form.symbole.label}}</label>
{{form.symbole(class_='form-control')}}
</div>
{% for error in form.libelle.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
<div class="form-group">
<label class="required-field" for="libelle">{{form.libelle.label}}</label>
{{form.libelle(class_='form-control')}}
</div>
<div class="form-group">
<label class="required-field" for="nombre">{{form.nombre.label}}</label>
{{form.nombre(class_='form-control')}}
</div>
<div class="form-group">
<label class="control-label required-field" for="pru">{{form.pru.label}}</label>
<div class="input-group">
<div class="input-group-addon">{{ item.devise }}</div>
{{form.pru(class_='form-control')}}
</div>
</div>
<div class="form-group">
<label class="required-field" for="ter">{{form.ter.label}}</label>
<div class="input-group">
<div class="input-group-addon">%</div>
{{form.ter(class_='form-control')}}
</div>
</div>
<div class="form-group">
<label class="required-field" for="pc_rdt">{{form.pc_rdt.label}}</label>
<div class="input-group">
<div class="input-group-addon">%</div>
{{form.pc_rdt(class_='form-control')}}
</div>
</div>
<div class="form-group">
<label class="required-field" for="website">{{form.website.label}}</label>
<div class="input-group">
<div class="input-group-addon">
<a href="{{ item.website }}" target="_blank"><span class="glyphicon glyphicon-globe"></span></a>
</div>
{{form.website(class_='form-control')}}
</div>
</div>
<div class="form-group">
<p class="form-control-static"><b>Dernière modif. :</b> {{ item.modif_le }}</p>
</div>
<br>
<div class="form-group">
<a class="btn btn-default" href="{{ request.route_url('portfolio') }}">
<span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
<button class="btn btn-primary" type="submit" name="form.submitted">
<span class="glyphicon glyphicon-ok"></span> Enregistrer</button>
{% if form.no_id.data %}
<button class="btn btn-danger" type="button" data-toggle="modal" data-target="#confirmDelete">
<span class="glyphicon glyphicon-remove"></span> Supprimer</button>
{% endif %}
</div>
</form>
<!-- Modal : Confirmation SUPRESSION -->
<div id="confirmDelete" class="modal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Supprimer cette allocation</h4>
</div>
<div class="modal-body">
<!-- The form is placed inside the body of modal -->
<p>Etes-vous certain(e) de vouloir supprimer la ligne <b>{{ form.symbole.data }}</b> ?</p>
</div>
<div class="modal-footer">
<div class="form-group">
<div class="text-center">
<form id="confirmForm" method="post">
<button type="submit" class="btn btn-danger" name="form.deleted">Supprimer</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,69 +0,0 @@
{% extends "ctp_blogr:templates/layout.jinja2" %}
{% block content %}
<form action="{{ url }}" method="post" class="form">
<div class="form-group">
<label class="required-field">{{ form.classe.label }}</label>
{{ form.classe(class_='form-control') }}
</div>
{% for error in form.pc_cible.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
<div class="form-group">
<label class="required-field" for="tag">{{form.pc_cible.label}}</label>
{{form.pc_cible(class_='form-control')}}
</div>
<div class="form-group">
<label class="required-field" for="tag">{{form.type.label}}</label>
{{form.type(class_='form-control')}}
</div>
<div class="form-group">
<label class="required-field" for="tag">{{form.bg_color.label}}</label>
{{form.bg_color(class_='form-control')}}
</div>
<br>
<div class="form-group">
<a class="btn btn-default" href="{{ request.route_url('portfolio') }}">
<span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
<button class="btn btn-primary" type="submit" name="form.submitted">
<span class="glyphicon glyphicon-ok"></span> Enregistrer</button>
{% if form.no_cat.data %}
<button class="btn btn-danger" type="button" data-toggle="modal" data-target="#confirmDelete">
<span class="glyphicon glyphicon-remove"></span> Supprimer</button>
{% endif %}
</div>
</form>
<!-- Modal : Confirmation SUPRESSION -->
<div id="confirmDelete" class="modal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Supprimer cette allocation</h4>
</div>
<div class="modal-body">
<!-- The form is placed inside the body of modal -->
<p>Etes-vous certain(e) de vouloir supprimer la ligne <b>{{ form.classe.data }}</b> ?</p>
</div>
<div class="modal-footer">
<div class="form-group">
<div class="text-center">
<form id="confirmForm" method="post">
<button type="submit" class="btn btn-danger" name="form.deleted">Supprimer</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,64 +0,0 @@
{% extends "ctp_blogr:templates/layout.jinja2" %}
{% block content %}
<form action="{{ url }}" method="post" class="form">
<div class="form-group">
<p class="form-control-static">Date : <b>{{ item.date.strftime('%d-%m-%Y') }}</b></p>
</div>
{% for error in form.mvt_cash.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
<div class="form-group">
<label class="required-field" for="tag">{{form.mvt_cash.label}}</label>
<div class="input-group">
<div class="input-group-addon">€</div>
{{form.mvt_cash(class_='form-control')}}
</div>
<p class="form-control-static">(si le montant est négatif, le montant est retiré du portefeuille)</b></p>
</div>
<br>
<div class="form-group">
<a class="btn btn-default" href="{{ request.route_url('histo_list') }}">
<span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
{% if form.no_id.data %}
<button class="btn btn-danger" type="button" data-toggle="modal" data-target="#confirmDelete">
<span class="glyphicon glyphicon-remove"></span> Supprimer</button>
{% else %}
<button class="btn btn-primary" type="submit" name="form.submitted">
<span class="glyphicon glyphicon-ok"></span> Enregistrer</button>
{% endif %}
</div>
</form>
<!-- Modal : Confirmation SUPRESSION -->
<div id="confirmDelete" class="modal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Supprimer cet historique</h4>
</div>
<div class="modal-body">
<!-- The form is placed inside the body of modal -->
<p>Etes-vous certain(e) de vouloir supprimer la ligne <b>{{ form.no_id.data }}</b> ?</p>
</div>
<div class="modal-footer">
<div class="form-group">
<div class="text-center">
<form id="confirmForm" method="post">
<button type="submit" class="btn btn-danger" name="form.deleted">Supprimer</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,49 +0,0 @@
{% extends "ctp_blogr:templates/layout.jinja2" %}
{% block content %}
<p>
<a class="btn btn-default" href="{{ request.route_url('portfolio') }}">
<span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
<a href="{{ request.route_url('histo_edit', no_id='0') }}" class="btn btn-success" role="button">
<span class="glyphicon glyphicon-plus"></span> Entrée / Sortie cash</a>
</p>
<table id="histo_list" class="table table-striped table-bordered table-condensed">
<thead>
<tr>
<th>Date</th>
<th align='right'>E/S Cash</th>
<th align='right'>Valeur Pf</th>
<th align='right'>Nb Part</th>
<th align='right'>Valeur Part</th>
<th align='right'>Cours ref</th>
<th align='right'>Nb Part ref</th>
<th align='center'>No Id</th>
</tr>
</thead>
{% for item in items %}
<tr>
<td>{{ item.date.strftime('%d/%m/%Y') }}</td>
<td align='right'>{{ '{0:0.2f} €'.format(item.mvt_cash) }}</td>
<td align='right'>{{ '{0:0.2f} €'.format(item.valeur_pf) }}</td>
<td align='right'>{{ '{0:0.2f}'.format(item.nb_part) }}</td>
<td align='right'>{{ '{0:0.2f} €'.format(item.val_part) }}</td>
<td align='right'>{{ '{0:0.2f} €'.format(item.cours_ref) }}</td>
<td align='right'>{{ '{0:0.2f}'.format(item.val_part_ref) }}</td>
<td align='center'>
<a href="{{ request.route_url('histo_edit', no_id=item.no_id) }}">{{ item.no_id }}</a>
</td>
</tr>
{% endfor %}
</table>
<br />
<br />
{% endblock %}

View File

@@ -1,245 +0,0 @@
{% extends "ctp_blogr:templates/layout.jinja2" %}
{% block content %}
<p>
<a href="allocation_edit/0">[ Nouvelle classe ]</a>
</p>
<div class="row">
<div class="col-md-6">
<table class="table table-condensed table-bordered">
<thead>
<tr>
<th>Classe</th>
<th class="text-right">% cible</th>
<th class="text-right">% actuel</th>
<th class="text-right">Ecart</th>
<th class="text-right">Valeur</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td class="{{ item.bg_color }}"><a href="allocation_edit/{{ item.no_cat }}">{{ item.classe }}</a></td>
<td class="text-right">{{ item.pc_cible }} %</td>
<td class="text-right">{{ '{0:0.1f}'.format(item.pc_atteint) }} %</td>
{% if (item.pc_atteint - item.pc_cible) >= 0 %}
<td class="text-right" style="color: green;">{{ '{0:0.1f}'.format(item.pc_ecart) }}</td>
{% else %}
<td class="text-right" style="color: red;">{{ '{0:0.1f}'.format(item.pc_ecart) }}</td>
{% endif %}
<td class="text-right">{{ '{0:0,.0f} €'.format(item.valeur).replace(',',' ') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="col-md-6">
<table id="portfolio" class="table table-condensed table-bordered">
<thead>
<tr>
<th>Portefeuille</th>
<th class="text-right">Montant</th>
<th class="text-right">%</th>
</tr>
</thead>
<tbody>
<tr>
<td>Valorisation</td>
<td class="text-right">{{ '{0:0.2f} €'.format(total_valeur).replace(',',' ') }}</td>
<td></td>
</tr>
<tr>
<td>Plus value</td>
<td class="text-right">{{ '{0:0,.0f} €'.format(total_pv).replace(',',' ') }}</td>
<td class="text-right">{{ '{0:0.1f} %'.format(total_pc_value) }}</td>
</tr>
<tr>
<td>Safe Withdrawal Rate</td>
<td class="text-right text-success"><b>{{ '{0:0,.0f} €'.format(swr_amount) }}</b></td>
<td class="text-right text-success"><b>{{ '{0:0.1f} %'.format(swr_rate) }}</b></td>
</tr>
</tbody>
</table>
<p>
<b>Allocation globale</b> : 70% actions + 30% obligations<br />
[Inspirée du
<a href="https://www.nbim.no/en/the-fund/how-we-invest/benchmark-index/" target="_blank">Fond souverain Norvégien</a>]<br />
<b>Allocation actions</b> : 80% Monde (56%) + 20% Croissance (14%)<br />
</p>
</div>
</div>
<div class="row">
<div class="col-md-6">
<!-- graphique donut cible -->
<div id="donutchart_cible" style="width: 100%; height: 500px;"></div>
</div>
<div class="col-md-6">
<!-- graphique donut actuel -->
<div id="donutchart_actuel" style="width: 100%; height: 500px;"></div>
</div>
</div>
<div class="row">
<h2>Mes actifs</h2>
<p>"<i>Diversification is not determined by the number of securities held.</i>"
<a href="http://www.etf.com/sections/index-investor-corner" target="_blank">Larry Swedroe</a></p>
<form id="actif_list-form" action="{{ url }}" method="post">
<div class="form-group">
<button id="updateButton" class="btn btn-primary" type="submit" name="form.submitted">
<i class="glyphicon glyphicon-refresh"></i> MAJ du portefeuille</button>
<a href="#" data-toggle="modal" data-target="#choixTypeActif">[ Nouvel actif ]</a>
<a href="/histo_list">[ Historique ]</a>
<a href="/blog/2/mouvements-du-portefeuille">[ Mouvements ]</a>
</div>
</form>
<table class="table table-condensed table-bordered">
<thead>
<tr>
<th>Classe</th>
<th>Libellé</th>
<th class="text-right">Cours</th>
<th class="text-right">Nb</th>
<th class="text-right">Valeur</th>
<th class="text-right">+/- Valeur</th>
<th class="text-right">% de +/-</th>
<th class="text-right">% TER</th>
<th class="text-right">% PF</th>
</tr>
</thead>
<tbody>
{% for ligne in actifs %}
<tr>
<td class="{{ ligne.Allocation.bg_color }}">{{ ligne.Allocation.classe }}</td>
{% if ligne.Allocation.type=='ACTION' %}
<td><a href="actif_edit/{{ ligne.Actifs.no_id }}">{{ ligne.Actifs.libelle }}</a></td>
{% else %}
<td><a href="actif2_edit/{{ ligne.Actifs.no_id }}">{{ ligne.Actifs.libelle }}</a></td>
{% endif %}
{% if ligne.Actifs.devise=='EUR' %}
<td class="text-right">{{ '{0:0.2f} €'.format(ligne.Actifs.cours) }}</td>
{% else %}
<td class="text-right">{{ '{0:0.2f} $'.format(ligne.Actifs.cours) }}</td>
{% endif %}
<td class="text-right">{{ ligne.Actifs.nombre }}</td>
<td class="text-right">{{ '{0:0,.2f} €'.format(ligne.Actifs.valeur).replace(',',' ') }}</td>
{% if ligne.Actifs.plus_value >= 0 %}
<td class="text-right" style="color: green;">{{ '{0:0,.2f} €'.format(ligne.Actifs.plus_value).replace(',',' ') }}</td>
<td class="text-right" style="color: green;">{{ '{0:0.1f}'.format(ligne.Actifs.pc_plusvalue) }}</td>
{% else %}
<td class="text-right" style="color: red;">{{ '{0:0,.2f} €'.format(ligne.Actifs.plus_value).replace(',',' ') }}</td>
<td class="text-right" style="color: red;">{{ '{0:0.1f}'.format(ligne.Actifs.pc_plusvalue) }}</td>
{% endif %}
<td class="text-right">{{ '{0:0.1f}'.format(ligne.Actifs.ter) }}</td>
<td class="text-right">{{ '{0:0.1f}'.format(ligne.Actifs.pc_allocation) }}</td>
</tr>
{% endfor %}
<tr>
<td class="text-right" colspan="4"><b>Total au {{ maj_pf_le.strftime('%d/%m/%Y') }}</b></td>
<td>{{ '{0:0,.2f} €'.format(total_valeur).replace(',',' ') }}</td>
{% if total_pv >= 0 %}
<td class="text-right" style="color: green;">{{ '{0:0,.2f} €'.format(total_pv).replace(',',' ') }}</td>
<td class="text-right" style="color: green;">{{ '{0:0.1f}'.format(total_pc_value) }}</td>
{% else %}
<td class="text-right" style="color: red;">{{ '{0:0,.2f} €'.format(total_pv).replace(',',' ') }}</td>
<td class="text-right" style="color: red;">{{ '{0:0.1f}'.format(total_pc_value) }}</td>
{% endif %}
<td></td>
<td class="text-right"><b>100.0</b></td>
</tr>
</tbody>
</table>
</div>
<div class="row">
<div>
<!-- graphique evolution -->
<div id="curve_chart" style="width: 100%; height: 500px;"></div>
</div>
</div>
<!-- MODAL POPUP : Affichage de la FAQ -->
<div class="modal fade" id="choixTypeActif" tabindex="-1" role="dialog" aria-labelledby="choixTypeActif" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span class="glyphicon glyphicon-remove"></span></button>
<h4 class="modal-title" id="choixTypeActif">Choix du type</h4>
</div>
<div class="modal-body">
<p>
Voulez-vous créer un actif de type <br />
<br />
<a href="/actif_edit/0" class="btn btn-primary" role="button">
ACTION</a> ou
<a href="/actif2_edit/0" class="btn btn-warning" role="button">
AUTRE</a>
<br />
<br />
<p>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$('#updateButton').on('click', function(){
$('i.gly-spin').removeClass('gly-spin');
$('i').addClass('gly-spin');
});
</script>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load("current", {packages:["corechart"]});
google.charts.setOnLoadCallback(drawChart);
var dataSet_cible = {{ donut_cible | safe }};
var dataSet_actuel = {{ donut_actuel | safe }};
var dataSet_evoln = {{ courbe_evoln | safe }};
function drawChart() {
var data_cible = google.visualization.arrayToDataTable(dataSet_cible);
var data_actuel = google.visualization.arrayToDataTable(dataSet_actuel);
var data_evoln = google.visualization.arrayToDataTable(dataSet_evoln);
var options_cible = {
title: 'Allocation cible',
pieHole: 0.4,
slices: {
0: {color: 'SteelBlue'}, 1: {color: 'LightSteelBlue'},
2: {color: 'Maroon'}, 3: {color: 'Brown'},
5: {offset: 0.2, color: 'DarkGreen'}, 6: {offset: 0.3, color: 'Green'},
},
};
var options_actuel = {
title: 'Allocation actuelle',
pieHole: 0.4,
slices: {
0: {color: 'SteelBlue'}, 1: {color: 'LightSteelBlue'},
2: {color: 'Maroon'}, 3: {color: 'Brown'},
5: {offset: 0.2, color: 'DarkGreen'}, 6: {offset: 0.3, color: 'Green'},
},
};
var options_evoln = {
title: 'Evolution du portefeuille VS fond Carmignac Investissement',
curveType: 'function',
legend: { position: 'top' }
};
var chart_cible = new google.visualization.PieChart(document.getElementById('donutchart_cible'));
chart_cible.draw(data_cible, options_cible);
var chart_actuel = new google.visualization.PieChart(document.getElementById('donutchart_actuel'));
chart_actuel.draw(data_actuel, options_actuel);
var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));
chart.draw(data_evoln, options_evoln);
}
</script>
</div><!-- content -->
</div>
{% endblock %}

View File

@@ -1,373 +0,0 @@
from pyramid.view import (
view_config,
)
from pyramid.httpexceptions import HTTPFound
from ..services.portfolio import PFService
from ..forms import ActifForm, Actif2Form, AllocationForm, HistoForm
from ..models.portfolio import Actifs, Histo, Allocation
import datetime #<- will be used to set default dates on models
import yfinance as yf
import json
import html
@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')
# 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
taux = ticker.fast_info.get('lastPrice')
PFService.update_actif_devise(request, 'USD', taux)
for item in actifs:
if item.Allocation.type == 'ACTION':
symbole = item.Actifs.symbole
# lire le cours de l'action
ticker = yf.Ticker(symbole)
# ticker delisted ?
if symbole == 'SHLDQ':
cours = 0.1
else:
cours = round(ticker.fast_info.get('lastPrice'), 3)
valeur = round(cours * item.Actifs.parite * item.Actifs.nombre, 3)
plus_value = round(valeur - (item.Actifs.pru * item.Actifs.nombre),2)
pc_plusvalue = round(valeur * 100 / (item.Actifs.pru * item.Actifs.nombre) - 100, 3)
PFService.update_actif_valeur(request, symbole, cours, valeur, plus_value, pc_plusvalue)
# time.sleep(1) # attendre 2 secondes
# update du portefeuille
today = datetime.datetime.now()
PFService.update_portefeuille(request, today)
# 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
maj_pf_le = None
for item in actifs:
maj_pf_le = item.Actifs.modif_le
total_valeur += item.Actifs.valeur
total_pv += item.Actifs.plus_value
if total_valeur == 0:
total_pc_value = 0
else:
total_pc_value = total_pv / total_valeur * 100
total_rdt += item.Actifs.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,
'maj_pf_le' : maj_pf_le,
'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')
def histo_list(request):
# lire l historique
items = PFService.get_histo(request, '-1')
return {
'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)
if no_cat == '0':
# create a new allocation
entry = Allocation()
form = AllocationForm(request.POST, entry)
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)
page_title = "Modifier Allocation : " + str(entry.no_cat)
if 'form.submitted' in request.params :
if no_cat == '0':
form.populate_obj(entry)
entry.pc_atteint = 0
entry.pc_ecart = 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.mvt_cash = 0
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():
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 = round(ticker.fast_info.last_price, 3)
# lire le dernier histo
last = PFService.get_last_histo(request)
# lire les actifs et cumuler leurs valeurs
actifs = PFService.get_actifs(request, '0')
valeur_pf = 0
for item in actifs:
valeur_pf += item.Actifs.valeur
entry.valeur_pf = valeur_pf
# nlle valeur part = ancienne + nouvelle ratio
entry.nb_part = round(last.nb_part + (float(entry.mvt_cash) / (valeur_pf - float(entry.mvt_cash))/last.nb_part), 3)
entry.val_part = round(entry.valeur_pf / entry.nb_part, 3)
entry.val_part_ref = round(float(entry.cours_ref) * last.val_part_ref / last.cours_ref, 3)
request.dbsession.add(entry)
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,
}
@view_config(route_name='actif_edit', renderer='../templates/portfolio/actif_edit.jinja2', permission='view')
def actif_edit(request):
no_id = request.matchdict['no_id']
url = request.route_url('actif_edit', no_id=no_id)
# get the list of classes
classes = PFService.get_allocation_byType(request, 'ACTION')
if no_id == '0':
# create a new allocation
entry = Actifs()
entry.pru = 0
entry.cours = 0
entry.pc_rdt = 0
entry.ter = 0
form = ActifForm(request.POST, entry)
form.classe.choices = [(row.classe, row.classe) for row in classes]
page_title = "Nouvel actif"
else:
# modify post
entry = PFService.get_actifs(request, no_id)
if not entry:
request.session.flash(u"Actif non trouvé : %s" % no_id, 'warning')
return HTTPFound(location=request.route_url('portfolio'))
form = ActifForm(request.POST, entry)
form.classe.choices = [(row.classe, row.classe) for row in classes]
page_title = "Modifier Actif : " + entry.symbole
if 'form.submitted' in request.params :
if no_id == '0':
form.populate_obj(entry)
# récupérer le cours du symbole de Yahoo finance
ticker = yf.Ticker(entry.symbole)
entry.cours = ticker.fast_info.get('lastPrice')
entry.devise = ticker.fast_info.get('currency')
# raccourcir le libelle
entry.pc_allocation = 1.0
entry.valeur = round(float(entry.cours) * entry.parite * entry.nombre, 3)
entry.plus_value = round(entry.valeur - float(entry.pru * entry.nombre), 3)
entry.pc_plusvalue = round(entry.plus_value / entry.valeur * 100, 3)
entry.rendement = 0 # round(entry.valeur * float(entry.pc_rdt) / 100, 3)
request.dbsession.add(entry)
return HTTPFound(location=request.route_url('portfolio'))
else:
if entry.symbole != 'SHLDQ':
del form.no_id # SECURITY: prevent overwriting of primary key
form.populate_obj(entry)
# récupérer le cours du symbole de Yahoo finance
ticker = yf.Ticker(entry.symbole)
entry.cours = ticker.fast_info.get('lastPrice')
entry.devise = ticker.fast_info.get('currency')
# raccourcir le libelle
entry.valeur = round(float(entry.cours) * entry.parite * entry.nombre, 3)
entry.plus_value = round(entry.valeur - float(entry.pru * entry.nombre), 3)
entry.pc_plusvalue = round(entry.plus_value / entry.valeur * 100, 3)
entry.rendement = 0 # round(entry.valeur * float(entry.pc_rdt) / 100, 3)
return HTTPFound(location=request.route_url('portfolio'))
if 'form.deleted' in request.params:
PFService.delete_actif(request, entry.no_id)
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='actif2_edit', renderer='../templates/portfolio/actif2_edit.jinja2', permission='view')
def actif2_edit(request):
no_id = request.matchdict['no_id']
url = request.route_url('actif2_edit', no_id=no_id)
# get the list of classes
classes = PFService.get_allocation_byType(request, 'AUTRE')
if no_id == '0':
# create a new allocation
entry = Actifs()
entry.pru = 0
entry.cours = 0
entry.pc_rdt = 0
entry.ter = 0
entry.devise = 'EUR'
form = Actif2Form(request.POST, entry)
form.classe.choices = [(row.classe, row.classe) for row in classes]
page_title = "Nouvel actif"
else:
# modify post
entry = PFService.get_actifs(request, no_id)
if not entry:
request.session.flash(u"Actif non trouvé : %s" % no_id, 'warning')
return HTTPFound(location=request.route_url('portfolio'))
form = Actif2Form(request.POST, entry)
form.classe.choices = [(row.classe, row.classe) for row in classes]
page_title = "Modifier Actif : " + entry.symbole
if 'form.submitted' in request.params :
if no_id == '0':
form.populate_obj(entry)
entry.nombre = 1000
entry.devise = 'EUR'
entry.parite = 1.0
entry.pc_allocation = 1.0
entry.valeur = float(entry.cours) * entry.parite * entry.nombre;
entry.plus_value = entry.valeur - float(entry.pru * entry.nombre);
entry.pc_plusvalue = entry.plus_value / entry.valeur * 100;
entry.rendement = entry.valeur * float(entry.pc_rdt) / 100;
request.dbsession.add(entry)
return HTTPFound(location=request.route_url('portfolio'))
else:
del form.no_id # SECURITY: prevent overwriting of primary key
form.populate_obj(entry)
entry.valeur = float(entry.cours) * entry.parite * entry.nombre;
entry.plus_value = entry.valeur - float(entry.pru * entry.nombre);
entry.pc_plusvalue = entry.plus_value / entry.valeur * 100;
entry.rendement = entry.valeur * float(entry.pc_rdt) / 100;
return HTTPFound(location=request.route_url('portfolio'))
if 'form.deleted' in request.params:
PFService.delete_actif(request, entry.no_id)
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,
}