373 lines
14 KiB
Python
373 lines
14 KiB
Python
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
|
|
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.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,
|
|
}
|