Optimisation de portefeuille financier : les macros

Sommaire

Les macros

Préambule

Mes langages de prédilection étant plutôt le C ou l'assembleur que Visual Basic pour Application, les macros ci-dessous sont probablement perfectibles et ne constituent en aucune manière des modèles de programmation VBA. Si vous avez des remarques à ce sujet, je suis preneur, merci de bien vouloir me laisser un message sur le forum par exemple.

Création de la matrice de variances/covariances

Cette macro génère la table de variances/covariances de la feuille "VarCovar" à partir des cours de la feuille "Données".
Au passage, elle remplit aussi la liste des valeurs de la feuille "Portefeuille", ainsi que leurs volatilités et leurs rendements historiques.

'****************************************************************************
'*                     Macro CreeMatriceVarCovar                            *
'****************************************************************************
'*                                                                          *
'* DESCRIPTION : cree une matrice de variances/covariances a partir des     *
'*               donnees de la feuille 'Données'. Les valeurs dont le       *
'*               nombre de periodes est inférieur a 36 sont affichees sur   *
'*               fond rouge pour signifier leur manque de maturite.         *
'*                                                                          *
'* ENTREE..... : Indirectement les rendements de toutes les periodes de     *
'*               de toutes les valeurs de la feuille 'Données'.             *
'*                                                                          *
'* SORTIE..... : Feuille 'VarCovar' remplie.                                *
'*                                                                          *
'* RETOUR..... : Neant.                                                     *
'*                                                                          *
'****************************************************************************
Sub CreeMatriceVarCovar()

'----- Constantes -----
Const LIGNE_TITRE_DONNEES = 1
Const PREMIERE_COLONNE_SRC = 3
Const PREMIERE_LIGNE_SRC = 4
Const PREMIERE_COLONNE_DEST = 2
Const PREMIERE_LIGNE_DEST = 5
Const NB_DATAS_MINI = 36

Const COLONNE_NOM_VALEURS_PF = 2
Const PREMIERE_LIGNE_DEST_PF = 2
Const COLONNE_VOLATILITE_VALEURS_PF = 5
Const COLONNE_RENDEMENT_VALEURS_PF = 4

'----- Variables -----
iLigSrc = PREMIERE_LIGNE_SRC
iColSrc = PREMIERE_COLONNE_SRC
iLigDest = PREMIERE_LIGNE_DEST
iColDest = PREMIERE_COLONNE_DEST
iNbSeries = 0     ' Nombre de series de donnees
Dim iTabLigDeb(200, 2) As Integer

' ----- On cherche le nombre de series -----
While (Not IsEmpty(Worksheets("Données").Cells(LIGNE_TITRE_DONNEES, iColSrc - 1)))
  iNbSeries = iNbSeries + 1
  iColSrc = iColSrc + 2
Wend
iColSrc = PREMIERE_COLONNE_SRC

' ----- Pour chaque serie de donnees, on va stocker le debut et la fin -----
For i = PREMIERE_COLONNE_SRC To PREMIERE_COLONNE_SRC + (iNbSeries - 1) * 2 Step 2
  iLigSrc = PREMIERE_LIGNE_SRC
  While (IsEmpty(Worksheets("Données").Cells(iLigSrc, i)))
    iLigSrc = iLigSrc + 1
  Wend
  iTabLigDeb((i - PREMIERE_COLONNE_SRC) / 2, 0) = iLigSrc
  While (Not IsEmpty(Worksheets("Données").Cells(iLigSrc, i)))
    iLigSrc = iLigSrc + 1
  Wend
  iTabLigDeb((i - PREMIERE_COLONNE_SRC) / 2, 1) = iLigSrc - 1
Next

' ----- On va generer la matrice de variance/covariance -----
iLigSrc = PREMIERE_LIGNE_SRC
iColSrc = PREMIERE_COLONNE_SRC
For i = 0 To iNbSeries - 1
  iColDest = PREMIERE_COLONNE_DEST + i

  ' Nom valeur
  Worksheets("VarCovar").Cells(PREMIERE_LIGNE_DEST, iColDest).Formula = "=Données!" + _
    Worksheets("Données").Cells(iLigSrc - 3, i * 2 + PREMIERE_COLONNE_SRC - 1).Address
  Worksheets("VarCovar").Cells(PREMIERE_LIGNE_DEST + iColDest - PREMIERE_COLONNE_DEST + 1, _
    PREMIERE_COLONNE_DEST - 1).Formula = "=Données!" + _
    Worksheets("Données").Cells(iLigSrc - 3, i * 2 + PREMIERE_COLONNE_SRC - 1).Address
  If (iTabLigDeb(i, 1) - iTabLigDeb(i, 0) < NB_DATAS_MINI) Then
    ' On met le fond en rouge
    Worksheets("VarCovar").Cells(PREMIERE_LIGNE_DEST, iColDest).Interior.Color = RGB(255, 0, 0)
    Worksheets("VarCovar").Cells(PREMIERE_LIGNE_DEST + iColDest - PREMIERE_COLONNE_DEST + 1, PREMIERE_COLONNE_DEST - 1).Interior.Color = RGB(255, 0, 0)
  End If
  
  ' Variance
  Worksheets("VarCovar").Cells(iLigDest + 1, iColDest).Formula = "=VARP(Données!" + _
    Worksheets("Données").Cells(iTabLigDeb(i, 0), i * 2 + PREMIERE_COLONNE_SRC).Address + ":" + _
    Worksheets("Données").Cells(iTabLigDeb(i, 1), i * 2 + PREMIERE_COLONNE_SRC).Address + ")"
  Worksheets("VarCovar").Cells(iLigDest + 1, iColDest).Interior.Color = RGB(255, 255, 128)
  
  If (iTabLigDeb(i, 0) > iTabLigDeb(j, 0)) Then iLigneDeb = iTabLigDeb(i, 0) Else iLigneDeb = iTabLigDeb(j, 0)
  If (iTabLigDeb(i, 1) > iTabLigDeb(j, 1)) Then iLigneFin = iTabLigDeb(i, 1) Else iLigneFin = iTabLigDeb(j, 1)
  Set plage1 = Range(Worksheets("Données").Cells(iLigneDeb, i * 2 + PREMIERE_COLONNE_SRC), Worksheets("Données").Cells(iLigneFin, i * 2 + PREMIERE_COLONNE_SRC))
  
  'Covariances
  For j = i + 1 To iNbSeries - 1
    iColDest = iColDest + 1
    Set plage2 = Range(Worksheets("Données").Cells(iLigneDeb, j * 2 + PREMIERE_COLONNE_SRC), Worksheets("Données").Cells(iLigneFin, j * 2 + PREMIERE_COLONNE_SRC))
    If (iLigneFin - iLigneDeb < NB_DATAS_MINI) Then lColorTexte = RGB(128, 128, 128) Else lColorTexte = RGB(0, 0, 0)

    Worksheets("VarCovar").Cells(iLigDest + 1, iColDest).Value = Application.WorksheetFunction.Covar(plage1, plage2)
    Worksheets("VarCovar").Cells(iLigDest + 1, iColDest).Interior.Color = RGB(255, 255, 255)
    Worksheets("VarCovar").Cells(iLigDest + 1, iColDest).Font.Color = lColorTexte
    
    '-----Seconde partie de la matrice-----
    Worksheets("VarCovar").Cells(PREMIERE_LIGNE_DEST + iColDest - PREMIERE_COLONNE_DEST + 1, _
      PREMIERE_COLONNE_DEST + iLigDest - PREMIERE_LIGNE_DEST).Formula = "=" + Worksheets("VarCovar").Cells(iLigDest + 1, iColDest).Address
    Worksheets("VarCovar").Cells(PREMIERE_LIGNE_DEST + iColDest - PREMIERE_COLONNE_DEST + 1, _
      PREMIERE_COLONNE_DEST + iLigDest - PREMIERE_LIGNE_DEST).Interior.Color = RGB(200, 200, 200)
    Worksheets("VarCovar").Cells(PREMIERE_LIGNE_DEST + iColDest - PREMIERE_COLONNE_DEST + 1, _
      PREMIERE_COLONNE_DEST + iLigDest - PREMIERE_LIGNE_DEST).Font.Color = lColorTexte
  Next j
  iLigDest = iLigDest + 1

  '-----------------------------------------------------------------------------
  ' On va recopier les noms des valeurs dans la seconde colonne du portefeuille
  '-----------------------------------------------------------------------------
  ' ----- Nom valeur -----
  Worksheets("Portefeuille").Cells(PREMIERE_LIGNE_DEST_PF + i, COLONNE_NOM_VALEURS_PF).Formula = "=Données!" + _
    Worksheets("Données").Cells(iLigSrc - 3, i * 2 + PREMIERE_COLONNE_SRC - 1).Address
  If (iTabLigDeb(i, 1) - iTabLigDeb(i, 0) < NB_DATAS_MINI) Then
    ' On met le fond en rouge
    Worksheets("Portefeuille").Cells(PREMIERE_LIGNE_DEST_PF + i, COLONNE_NOM_VALEURS_PF).Interior.Color = RGB(255, 0, 0)
  Else
    Worksheets("Portefeuille").Cells(PREMIERE_LIGNE_DEST_PF + i, COLONNE_NOM_VALEURS_PF).Interior.ColorIndex = xlColorIndexNone
  End If
  
  ' ----- Rendements historiques -----
  Worksheets("Portefeuille").Cells(PREMIERE_LIGNE_DEST_PF + i, COLONNE_RENDEMENT_VALEURS_PF).Formula = "=(Données!" + Worksheets("Données").Cells(iLigneFin + 2, i * 2 + PREMIERE_COLONNE_SRC).Address + ")^12-1"

  ' ----- Volatilité mensuelle -----
  Worksheets("Portefeuille").Cells(PREMIERE_LIGNE_DEST_PF + i, COLONNE_VOLATILITE_VALEURS_PF).Formula = "=StDevP(Données!" + _
    Worksheets("Données").Cells(iLigneDeb, i * 2 + PREMIERE_COLONNE_SRC).Address + ":" + _
    Worksheets("Données").Cells(iLigneFin, i * 2 + PREMIERE_COLONNE_SRC).Address + ")"

Next i
End Sub

Optimisation d'un portefeuille

Le module "Optimisation" contient plusieurs macros :

  • OptimiseVolPF cherche pour un rendement donné, le portefeuille présentant la variance la plus faible ;
  • OptimiseRendementPF cherche pour un écart-type donné, le portefeuille présentant le rendement le plus élevé ;
  • TracerFrontiereEfficiente calcule une série de couples (rendement,ecart-type) pour permettre de tracer la frontiere efficiente.
'****************************************************************************
'*                         Module Optimisation                              *
'****************************************************************************
'*                                                                          *
'* DESCRIPTION........ : Module comprenant les fonctions necessaires à      *
'*                       l'optimisation d'un portefeuille, ainsi qu'au      *
'*                       tracé de la frontière efficiente.                  *
'*                                                                          *
'* AUTEUR............. : "La Bourse pour les nains"                         *
'*                       http://wwww.bnains.org/                            *
'*                                                                          *
'* DATE DE CREATION... : 2000                                               *
'*                                                                          *
'* FONCTIONS EXPORTEES :                                                    *
'*   Neant.                                                                 *
'*                                                                          *
'* MACROS EXPORTEES... :                                                    *
'*   OptimiseVolPF()                                                        *
'*     Cherche la composition de portefeuille permettant d'obtenir la       *
'*     volatilité la plus faible pour un rendement donné.                   *
'*   OptimiseRendementPF()                                                  *
'*     Cherche la composition de portefeuille permettant d'obtenir le       *
'*     rendement le plus élevé pour une volatilité donnée.                  *
'*   TracerFrontiereEfficiente()                                            *
'*     Pour une liste de rendements donnée, cherche les volatilités les     *
'*     plus faibles de manière à produire une liste de couples (rendement,  *
'*     volatilité) permettant de tracer la frontière efficiente d'une liste *
'*     de valeurs.                                                          *
'*                                                                          *
'****************************************************************************
'* MODIFIE LE ../../.... PAR ......................                         *
'* DESCRIPTION DE LA MODIFICATION :                                         *
'*                                                                          *
'****************************************************************************
Const iNbTitres = 48

Const szCelluleMaximumParLigne = "$56"
Const szCelluleMinimumParLigne = "$55"
Const szCelluleObjectifRendement = "$52"
Const szCelluleRendementCalcule = "C51"
Const szCelluleSommePoidsValeurs = "C50"
Const szCelluleVariance = "C52"
Const szCelluleObjectifEcartType = "E53"
Const szCelluleEcartTypeAnnuelCalcule = "C54"

'****************************************************************************
'*                            Macro OptimiseVolPF                           *
'****************************************************************************
'*                                                                          *
'* DESCRIPTION : cherche les proportions de valeur permettant d'obtenir la  *
'*               variance la plus petite pour le rendement donné.           *
'*                                                                          *
'* ENTREE..... : indirectement, liste des valeurs, minimum et maximum de    *
'*               proportion dans le portefeuille et rendement souhaite.     *
'*                                                                          *
'* SORTIE..... : indirectement, la variance calculée.                       *
'*                                                                          *
'* RETOUR..... : Neant.                                                     *
'*                                                                          *
'****************************************************************************
Public Sub OptimiseVolPF()

'************************************************************************
'* On programme le solveur pour minimiser la variance en jouant sur les *
'* proportions des valeurs                                              *
'************************************************************************

' ----- Préparation de l'environnement de travail -----
' On se positionne dans la bonne feuille
Worksheets("Portefeuille").Activate
' Et dans la bonne cellule (pour éviter un bug du solver avec certaines versions d'Excel)
Range(szCelluleVariance).Select
' Reset du solveur
SolverReset
' On met toutes les proportions de valeur à 0
For iValeur = 2 To iNbTitres + 1
  Cells(iValeur, 3).Value = 0
Next iValeur
' Objectif : Minimiser la variance en faisant varier les proportions des valeurs
SolverOk szCelluleVariance, maxMinVal:=2, byChange:=Range("C2:C49")

' ----- On cree les contraintes -----
'Somme des poids des valeurs = 1 (soit 100%)
SolverAdd cellRef:=szCelluleSommePoidsValeurs, relation:=2, formulaText:=1
' Valeurs interdites (toutes les valeurs non cochées)
For iValeur = 1 To iNbTitres
  If Cells(iValeur, 1).Value = "N" Then SolverAdd Cells(iValeur, 3), relation:=2, formulaText:=0
Next iValeur
'Poids de chaque valeur <= maximum par ligne
SolverAdd cellRef:="C2:C49", relation:=1, formulaText:=szCelluleMaximumParLigne
'Poids de chaque valeur >= minimum par ligne
SolverAdd cellRef:="C2:C49", relation:=3, formulaText:=szCelluleMinimumParLigne
'Rendement attendu = celui demandé
SolverAdd cellRef:=szCelluleRendementCalcule, relation:=2, formulaText:=szCelluleObjectifRendement

' ----- On indique maintenant au solveur qu'il doit bosser... -----
SolverSolve (True)

SolverFinish

End Sub


'****************************************************************************
'*                         Macro OptimiseRendementPF                        *
'****************************************************************************
'*                                                                          *
'* DESCRIPTION : cherche les proportions de valeur permettant d'obtenir le  *
'*               rendement le plus élevé pour la variance donnée.           *
'*                                                                          *
'* ENTREE..... : indirectement, liste des valeurs, minimum et maximum de    *
'*               proportion dans le portefeuille et variance souhaitée.     *
'*                                                                          *
'* SORTIE..... : indirectement, la rendement calculé.                       *
'*                                                                          *
'* RETOUR..... : Neant.                                                     *
'*                                                                          *
'****************************************************************************
Public Sub OptimiseRendementPF()

' ----- Préparation de l'environnement de travail -----
' On se positionne dans la bonne feuille
Worksheets("Portefeuille").Activate
' Et dans la bonne cellule (pour éviter un bug du solver avec certaines versions d'Excel)
Range(szCelluleVariance).Select
' Reset du solveur
SolverReset
' On met toutes les proportions de valeur à 0
For iValeur = 1 To iNbTitres
  Cells(iValeur, 3).Value = 0
Next iValeur
' Objectif : Maximiser le rendement en faisant varier les proportions des valeurs
SolverOk szCelluleRendementCalcule, maxMinVal:=1, byChange:=Range("C2:C49")

' ----- On cree les contraintes -----
'Somme des poids des valeurs = 1 (soit 100%)
SolverAdd cellRef:=szCelluleSommePoidsValeurs, relation:=2, formulaText:=1

' Valeurs interdites (toutes les valeurs non cochées)
For iValeur = 2 To iNbTitres + 1
  If Cells(iValeur, 1).Value = "N" Then SolvAdd Cells(iValeur, 3), relation:=2, formulaText:=0
Next iValeur
'Poids de chaque valeur <= maximum par ligne
SolverAdd cellRef:="C2:C49", relation:=1, formulaText:=szCelluleMaximumParLigne
'Poids de chaque valeur >= minimum par ligne
SolverAdd cellRef:="C2:C49", relation:=3, formulaText:=szCelluleMinimumParLigne
'Volatilité attendue = celle demandée
SolverAdd cellRef:=szCelluleEcartTypeAnnuelCalcule, relation:=2, formulaText:=szCelluleObjectifEcartType

' ----- On indique maintenant au solveur qu'il doit bosser... -----
SolverSolve (True)

SolverFinish

End Sub


'****************************************************************************
'*                       Macro TracerFrontiereEfficiente                    *
'****************************************************************************
'*                                                                          *
'* DESCRIPTION : cherche la variance la plus petite pour une liste de       *
'*               rendements donnés.                                         *
'*                                                                          *
'* ENTREE..... : indirectement, liste des valeurs, minimum et maximum de    *
'*               proportion dans le portefeuille et liste des rendements    *
'*               souhaités.                                                 *
'*                                                                          *
'* SORTIE..... : indirectement, les couples (rendement,variance) calculés.  *
'*                                                                          *
'* RETOUR..... : Neant.                                                     *
'*                                                                          *
'****************************************************************************
Public Sub TracerFrontiereEfficiente()

  Const COLONNE_RENDEMENTS_SOUHAITES = 13

  '****************************************************************
  '* Maintenant, on va itérer pour tracer la frontière efficiente *
  '****************************************************************
  bMax = False
  i = 2
  j = COLONNE_RENDEMENTS_SOUHAITES
  Do While (Not (IsEmpty(Cells(i, j))) And bMax = False)
    ' On fixe le rendement souhaite
    Cells(52, 5).Value = Cells(i, j)
    ' On optimise la variance en consequence
    Call OptimiseVolPF
    ' On recupere le rendement
    Cells(i, j + 1).Value = Cells(51, 3)
    ' On recupere la variance annuelle
    Cells(i, j + 2).Value = Cells(54, 3)
    i = i + 1
    ' Si on n'est pas parvenu a ameliorer, alors on arrete
    If i > 3 And Cells(i - 2, j + 1).Value = Cells(i - 1, j + 1).Value Then bMax = True
  Loop
End Sub

Remarques :
La constante "iNbTitres" pourait avantageusement être remplacée par une petite fonction comptant les titres.

Voilà, c'est tout. Tout ceci peut bien sûr être amélioré. A vous de jouer...

Vous avez des questions ? Des remarques ? Des félicitations ou des reproches ? Laissez un message dans un des forums mis à votre disposition. Tout le monde pourra ainsi en profiter.


Puce rouge Début de la page   Puce rouge Sommaire du risque   Puce rouge Sommaire de l'optimisation   Puce rouge Sommaire du site