[{"id":1,"nom":"Pr\u00e9requis","level":1,"parent_id":null},{"id":2,"nom":"Le poste de contr\u00f4le (PC)","level":1,"parent_id":null},{"id":3,"nom":"Calculs & affichages","level":1,"parent_id":null},{"id":4,"nom":"Annexe","level":1,"parent_id":null},{"id":11,"nom":"La fen\u00eatre principale","level":2,"parent_id":2},{"id":12,"nom":"Les onglets","level":2,"parent_id":2},{"id":14,"nom":"Les param\u00e8tres","level":2,"parent_id":2},{"id":13,"nom":"Les graphes","level":2,"parent_id":2},{"id":15,"nom":"Infrastructure","level":2,"parent_id":2},{"id":16,"nom":"Matplotlib","level":2,"parent_id":3},{"id":17,"nom":"Coding des nodes","level":2,"parent_id":3},{"id":18,"nom":"Historiques","level":2,"parent_id":3},{"id":19,"nom":"Le mode geek","level":2,"parent_id":3}]
[{"partie_id":1,"nom":"Connaissances","slug":"8_connaissances","utilite":"Elles vont vous permettre d'avancer plus rapidement"},{"partie_id":1,"nom":"Environnement de travail","slug":"7_environnement_de_travail","utilite":"Mat\u00e9riel n\u00e9cessaire"},{"partie_id":1,"nom":"Logiciels utilis\u00e9s","slug":"1_logiciels_utilises","utilite":"Install\u00e9s avant de commencer le cours"},{"partie_id":1,"nom":"Installation d'Anaconda","slug":"2_installation_danaconda","utilite":"Package contenant Python et bien d'autres utilitaires."},{"partie_id":1,"nom":"Environnement virtuel Python","slug":"4_environnement_virtuel_python","utilite":"S\u00e9curit\u00e9 : s\u00e9parez vos interpr\u00e9teurs Python."},{"partie_id":1,"nom":"Installation de Pycharm","slug":"3_installation_de_pycharm","utilite":"IDE pour une programmation agr\u00e9able en Python."},{"partie_id":1,"nom":"Structure du projet 'Robot'","slug":"5_structure_du_projet_robot","utilite":"Organisation des fichiers sur disque dur."},{"partie_id":1,"nom":"Fen\u00eatre du poste de contr\u00f4le","slug":"6_fenetre_du_poste_de_controle","utilite":"Cr\u00e9ation et affichage de la fen\u00eatre du PC."},{"partie_id":1,"nom":"TDD : D\u00e9veloppement dirig\u00e9 par les tests","slug":"60f_tdd_developpement_dirige_par_les_tests","utilite":"\u00c9crire du code robuste, de qualit\u00e9 et maintenable."},{"partie_id":11,"nom":"Pr\u00e9sentation","slug":"efb_presentation","utilite":"Aper\u00e7u des fonctionnalit\u00e9s"},{"partie_id":11,"nom":"Dessiner la fen\u00eatre avec Qt Designer","slug":"a1e_dessiner_la_fenetre_avec_qt_designer","utilite":"Programmation graphique d'une fen\u00eatre"},{"partie_id":11,"nom":"Designer \u2192 Python - Compilation manuelle","slug":"cb8_designer_python_compilation_manuelle","utilite":"Utiliser en Python les fichiers cr\u00e9\u00e9s avec Qt Designer"},{"partie_id":11,"nom":"Designer \u2192 Python - Compilation automatique","slug":"7c5_designer_python_compilation_automatique","utilite":"Utiliser en Python les fichiers cr\u00e9\u00e9s avec Qt Designer"},{"partie_id":11,"nom":"Bug de r\u00e9gression","slug":"b9a_bug_de_regression","utilite":"Intercepter les bugs le plus t\u00f4t possible"},{"partie_id":11,"nom":"Persistance de toutes les g\u00e9om\u00e9tries","slug":"46d_persistance_de_toutes_les_geometries","utilite":"G\u00e9om\u00e9trie de la fen\u00eatre principale et des dockables"},{"partie_id":11,"nom":"Refactoring","slug":"0fa_refactoring","utilite":"Am\u00e9lioration de la structure du projet"},{"partie_id":12,"nom":"Cr\u00e9ation d'un graphe","slug":"64a_creation_dun_graphe","utilite":"Base visuelle de la programmation graphique"},{"partie_id":12,"nom":"Ouvrir \/ fermer un graphe","slug":"1df_ouvrir_fermer_un_graphe","utilite":"Afficher dans des onglets les graphes choisis"},{"partie_id":12,"nom":"M\u00e9morisation de l'\u00e9tat des onglets","slug":"49d_memorisation_de_letat_des_onglets","utilite":"Persistance des onglets ouverts : ordre et s\u00e9lection"},{"partie_id":12,"nom":"Entrep\u00f4t des nodes","slug":"5d3_entrepot_des_nodes","utilite":"Rangement des mod\u00e8les de nodes dans des dossiers."},{"partie_id":12,"nom":"Peuplement des nodes","slug":"569_peuplement_des_nodes","utilite":"Affiche les icones de nodes dans toutes les cat\u00e9gories"},{"partie_id":14,"nom":"La sc\u00e8ne et la vue : r\u00e9ticule","slug":"e95_la_scene_et_la_vue_reticule","utilite":"Affichage des nodes et des edges sur un \u00e9cran r\u00e9ticul\u00e9"},{"partie_id":14,"nom":"Param\u00e8tres : 1 - Dictionnaires tri\u00e9s","slug":"3dd_parametres_1_dictionnaires_tries","utilite":"N\u00e9cessaires pour la gestion des param\u00e8tres"},{"partie_id":14,"nom":"Param\u00e8tres : 2 - Persistance","slug":"220_parametres_2_persistance","utilite":"Param\u00e8tres enregistr\u00e9s dans un fichier pickle"},{"partie_id":14,"nom":"Param\u00e8tres : 3 - \u00c9dition dans le dockable","slug":"661_parametres_3_edition_dans_le_dockable","utilite":"Confort dans l'\u00e9dition des param\u00e8tres"},{"partie_id":13,"nom":"Entr\u00e9e en sc\u00e8ne du premier node","slug":"350_entree_en_scene_du_premier_node","utilite":"Affichage du node au lancement de l'application"},{"partie_id":13,"nom":"Plusieurs nodes dans le graphe","slug":"910_plusieurs_nodes_dans_le_graphe","utilite":"Plusieurs nodes permettent les interconnexions"},{"partie_id":13,"nom":"Ajout et suppression de nodes depuis l'UI","slug":"d22_ajout_et_suppression_de_nodes_depuis_lui","utilite":"\u00c9dition simplifi\u00e9e de graphes, \u00e0 la souris et au clavier"},{"partie_id":13,"nom":"Sockets : ports d'entr\u00e9e-sortie","slug":"077_sockets_ports_dentree_sortie","utilite":"N\u00e9cessaires pour l'interconnexion des nodes"},{"partie_id":13,"nom":"Edges : 1 - Interconnexions des nodes.","slug":"3f6_edges_1_interconnexions_des_nodes","utilite":"Permet la mise en cascade de calculs"},{"partie_id":13,"nom":"Edges : 2 - Cr\u00e9ation par drag & drop, \u00e9dition","slug":"146_edges_2_creation_par_drag_drop_edition","utilite":"Cr\u00e9ation simple et ludique, par drag & drop"},{"partie_id":13,"nom":"Edges : 3 - Couleurs, court-circuits, croisements","slug":"791_edges_3_couleurs_court_circuits_croisements","utilite":"Customisation des liens"},{"partie_id":13,"nom":"Droit \u00e0 l'erreur : Undo & Redo","slug":"cef_droit_a_lerreur_undo_redo","utilite":"Parcours de l'historique par undo & redo"},{"partie_id":15,"nom":"Param\u00e8tres dynamiques d'un node","slug":"2ec_parametres_dynamiques_dun_node","utilite":"Transmission de signaux entre nodes"},{"partie_id":15,"nom":"Mise en conformit\u00e9 des types de node","slug":"e77_mise_en_conformite_des_types_de_node","utilite":"Standardiser la cr\u00e9ation de nodes"},{"partie_id":15,"nom":"Infrastructure","slug":"7cf_infrastructure","utilite":"Base de travail pour les graphes et les calculs"},{"partie_id":15,"nom":"G\u00e9n\u00e9ralisation des afficheurs","slug":"f00_generalisation_des_afficheurs","utilite":"Finalit\u00e9 du poste de contr\u00f4le"},{"partie_id":15,"nom":"Mod\u00e8les de calculs","slug":"6ef_modeles_de_calculs","utilite":"C'est la finalit\u00e9 du poste de contr\u00f4le."},{"partie_id":16,"nom":"Matplotlib","slug":"1df_matplotlib","utilite":"Compl\u00e9ment basique du poste de contr\u00f4le."},{"partie_id":16,"nom":"Interactions PC \u2192 Graphiques","slug":"207_interactions_pc_graphiques","utilite":"Modifications depuis le PC appliqu\u00e9es en direct"},{"partie_id":16,"nom":"Param\u00e9trage \u00e9tendu","slug":"8ed_parametrage_etendu","utilite":"D\u00e9cupler la puissance des nodes"},{"partie_id":16,"nom":"Plusieurs graphiques \u00e0 plusieurs signaux","slug":"555_plusieurs_graphiques_a_plusieurs_signaux","utilite":"Utile lorsque les signaux sont compl\u00e9mentaires"},{"partie_id":16,"nom":"Refactoring - am\u00e9liorations","slug":"a24_refactoring_ameliorations","utilite":"Moins de code, plus de rapidit\u00e9."},{"partie_id":17,"nom":"Moyennes mobiles - Calculs en cascade","slug":"716_moyennes_mobiles_calculs_en_cascade","utilite":"Propagation des calculs dans le graphe"},{"partie_id":17,"nom":"MACD - RSI - Mod\u00e8les yaml","slug":"657_macd_rsi_modeles_yaml","utilite":"Mod\u00e8les = Gain de temps lors des param\u00e9trages"},{"partie_id":17,"nom":"Check-list pour la cr\u00e9ation d'un node","slug":"752_check_list_pour_la_creation_dun_node","utilite":"Optimiser la cr\u00e9ation d'un node"},{"partie_id":17,"nom":"Calculs globaux, directs, diff\u00e9r\u00e9s","slug":"77b_calculs_globaux_directs_differes","utilite":"R\u00e9action rapide lors d'une modification des param\u00e8tres"},{"partie_id":18,"nom":"T\u00e9l\u00e9chargement d'historiques","slug":"e92_telechargement_dhistoriques","utilite":"Cr\u00e9er des strat\u00e9gies avec des donn\u00e9es r\u00e9elles"},{"partie_id":18,"nom":"La base de donn\u00e9es Candles","slug":"92f_la_base_de_donnees_candles","utilite":"Stockage local des cours de devises 'Candles'"},{"partie_id":18,"nom":"La base de donn\u00e9es Ticks","slug":"4b2_la_base_de_donnees_ticks","utilite":"Stockage local des cours en 'ticks' et en 'Renko'"},{"partie_id":18,"nom":"Le node g\u00e9n\u00e9rateur d'historiques","slug":"1c2_le_node_generateur_dhistoriques","utilite":"Serveur d'historiques r\u00e9els"},{"partie_id":19,"nom":"\u00c9laboration d'une strat\u00e9gie de trading","slug":"510_elaboration_dune_strategie_de_trading","utilite":"Programmation avanc\u00e9e. Suppression des limites."},{"partie_id":19,"nom":"Partie 1 : Voir les ouvertures et les fermetures","slug":"ccf_partie_1_voir_les_ouvertures_et_les_fermetures","utilite":"Aper\u00e7u visuel des gains d'une strat\u00e9gie de trading"},{"partie_id":19,"nom":"Partie 2 : Cr\u00e9er ses indicateurs","slug":"52c_partie_2_creer_ses_indicateurs","utilite":"Repousser les limites"},{"partie_id":19,"nom":"Partie 3 : Algorithmes g\u00e9n\u00e9tiques","slug":"e4b_partie_3_algorithmes_genetiques","utilite":"Optimise la recherche des meilleurs param\u00e8tres"},{"partie_id":19,"nom":"Indicateurs dynamiques","slug":"96c_indicateurs_dynamiques","utilite":"Ajout d'une dimension \u00e0 l'espace des recherches"},{"partie_id":19,"nom":"Prochainement ...","slug":"ad6_prochainement","utilite":"Tutos en pr\u00e9paration"}]
"220_parametres_2_persistance"
Le poste de contrôle (PC) /
Les paramètres /
Paramètres : 2 - Persistance
Paramètres enregistrés dans un fichier pickle
Avant-propos
Python : module pickle.
Réviser la classe QGraphicsView, ainsi que sa classe parente QAbstractScrollArea.
Changement de repère scène → vue (mapFromScene) et vue → scène (mapToScene)
Changement d'icone du curseur de la souris.
Voir les flags :
setRenderHints()
setViewportUpdateMode()
setTransformationAnchor()
Voir les événements :
mousePressEvent()
mouseReleaseEvent()
wheelEvent()
Description
Objectif:
La scène contient des widgets : nodes, edges, zônes de texte.
Vous pourrez y ajouter du texte, des boutons ... et divers autres widgets.
Une partie de cet ensemble est affiché dans une vue : ce que l'on voit à l'écran, dans un onglet.
On se propose dans ce tuto de tout mémoriser en temps réel dans un fichier pkl ...
... afin de tout retrouver dans le même état lors de la réouverture de cette vue.
La scène a ses paramètres : nom de l'onglet, couleur de fond, couleur des traits, pas de la grille, etc.
La vue a ses paramètres : zoom, position.
Chaque node a ses paramètres généraux : datas, position, activé/désactivé, couleurs diverses, ...
Chaque node a ses paramètres spécifiques. Pour une MM (moyenne mobile) par exemple :
Son type : Arithmétique, exponentielle, pondérée, ...
Sa liste de signaux en entrée, traités. Nous verrons, en effet, que les nodes reçoivent non pas un signal, mais un faisceau de signaux.
Sa liste de longueurs (nombre de points moyennés), pour chacun des signaux traités. Nous verrons, en effet, que les nodes fournissent non pas un signal, mais un faisceau de signaux.
Conventions :
Le fichier pkl mémorise un super-dictionnaire, instance de la classe Dictionary étudiée précédemment.
Il contiendra plusieurs grappes à la racine : Scène, Vue, Node1, Node2, ... Edges.
Chaque grappe est elle-même arborescente (imbrication de dictionnaires).
Nous voyons donc qu'il ne s'agit pas d'une simple liste de tuples (clé / valeur).
Exemple traité, la vue :
Sa position dans la scène : déplacement de la souris, bouton droit appuyé.
Son zoom : molette souris.
Enregistrement en live.
Lecture et restitution des paramètres mémorisés, au lancement de la fenêtre.
Le déplacement :
Créez le fichier /pc/pkl.py, copiez-y le code suivant :
from functions.utils import Dictionary
import pickle
import os
class Pkl:
def __init__(self, o_scene):
self.o_scene = o_scene
self.fic_pkl = None
self.od_pkl = None
self.restore()
def restore(self):
""" Passage unique, à l'instantiation.
- Tentative de lecture du fichier params.pkl, produisant le dictionnaire de paramètres.
- Si échec (inexistant ou non conforme), on utilise un dictionnaire vide.
- Transformation en super-dictionnaire (self.od_pkl), grace à la classe Dictionary.
"""
self.fic_pkl = f'{os.getcwd()[:-2]}backups{os.sep}{self.o_scene.graph_name}{os.sep}params.pkl'
try:
""" Lecture du fichier pkl. """
with open(self.fic_pkl, 'rb') as pk: # Pas d'encoding pour les fichiers binaires.
d_pkl = pickle.load(pk)
except (Exception,):
d_pkl = {}
self.od_pkl = Dictionary(d_pkl) # OrderedDict de tout le graphe (vue + nodes + edges + groupes + labels)
def backup(self):
""" Méthode appelée chaque fois qu'une modification intervient dans le graphe. """
try:
with open(self.fic_pkl, 'wb') as pk: # 1) Ouveture ET écrasement du fichier pkl.
pickle.dump(self.od_pkl, pk, pickle.HIGHEST_PROTOCOL) # 2) Écriture dans un fichier vierge.
except (Exception,):
return False
return True
Dans le fichier ctrl_scene.py, déclarez le super-dictionnaire unique :
Import de la classe Pkl que nous venons de créer. Dans les imports : ctrl_scene.ui > imports
from pkl import Pkl
Déclaration de l'attribut self.o_pkl. Dans ctrl_scene.py > CtrlScene.__init__(), avant self.setup() :
self.o_pkl = None # Instance de Pkl (Une seule instance par graphe)
Instanciation de la classe Pkl (code ci-dessus) dans l'objet self.o_pkl (ligne ci-dessus). Dans la méthode self.setup(), au début : ctrl_scene.py > CtrlScene.setup()
""" Paramètres. """
self.o_pkl = Pkl(self)
Dans le fichier ui_view.py,
Déclarez le super-dictionnaire dans __init__() : ui_view.py > UiView.__init__()
self.od_pkl = self.o_scene.o_pkl.od_pkl
Ajoutez ces 5 méthodes à la classe ui_view.py > UiView :
def _get_position(self):
""" Centre de la vue par rapport au centre de la scène => Négatifs si le centre-scène est en bas à droite. """
p_centre = self.width() // 2, self.height() // 2 # p_ = point
p_scene = self.mapToScene(*p_centre) # Convertisseur => Changement de coordonnées.
return round(p_scene.x()), round(p_scene.y())
def right_button_pressed(self, ev):
fake_event = QMouseEvent(ev.type(), ev.localPos(), ev.screenPos(),
Qt.LeftButton, ev.buttons() | Qt.LeftButton, ev.modifiers())
self.setDragMode(QGraphicsView.ScrollHandDrag) # Icone-Curseur souris => main fermée.
super().mousePressEvent(fake_event) # Après setDragMode() -> Obligatoire !
def right_button_released(self, ev):
fake_event = QMouseEvent(ev.type(), ev.localPos(), ev.screenPos(),
Qt.LeftButton, ev.buttons() | Qt.LeftButton, ev.modifiers())
super().mouseReleaseEvent(fake_event) # Avant setDragMode() -> Obligatoire !
self.setDragMode(QGraphicsView.RubberBandDrag) # Curseur = pointeur par défaut.
""" Enregistrement. """
self.od_pkl.write(['UiView', 'position'], self._get_position())
self.o_scene.o_pkl.backup()
def mousePressEvent(self, ev):
if ev.button() in [Qt.RightButton, Qt.MiddleButton]:
""" Molette ou bouton droit appuyé. """
self.right_button_pressed(ev)
super().mousePressEvent(ev)
def mouseReleaseEvent(self, ev):
if ev.button() in [Qt.RightButton, Qt.MiddleButton]:
""" Molette ou bouton droit relâché. """
self.right_button_released(ev)
super().mouseReleaseEvent(ev)
Bien sûr, ne pas oublier d'importer les classes nécessaires. Ici QMouseEvent : ui_view.py > imports
from PyQt5.QtGui import QMouseEvent
Le déplacement de la vue est effectué en bougeant la souris, bouton-droit (ou molette) appuyé.
L'enregistrement de sa position a lieu au relâchement du bouton, à la fin de la méthode right_button_released().
Pour retrouver la vue à cette position au lancement, il faut lire la valeur mémorisée et la restituer :
Nous avions corrigé, lors du tuto "La scène et la vue : réticule", le défaut de centrage de la vue au lancement → (0, 0)
Modifions celà pour obtenir un centrage non pas à (0, 0), mais au tuple mémorisé.
Recherchez la ligne : self.dt.delay(self.centerOn, 20, 0, 0), remplacez-la par :
""" Le centrage est retardé, pour laisser le temps au processur graphique de tout dessiner. """
p_center = self.od_pkl.read(['UiView', 'position'], (0, 0)) # (0, 0) = tuple par défaut.
self.dt.delay(self.centerOn, 20, *p_center) # 20ms, x et y de p_center.
Tester.
Le zoom :
Dans le fichier ui_view.py :
Ajouter des attributs à la classe UiView. Dans la méthode __init__(), avant self.init_ui(), insérer : ui_view.py > UiView.__init__()
Ajouter la méthode wheelEvent() (surcharge de la méthode-parent wheelEvent()) : ui_view.py > UiView
def wheelEvent(self, ev):
angle = ev.angleDelta()
if angle.x() != 0:
""" Inhibition des 2 switches latéraux dans la molette de la souris. """
return
if angle.y() > 0 and self.zoom < self.zoom_range[1]: # Limite.
""" Molette roule vers le haut. """
zoom_factor = self.zoom_factor
self.zoom += self.zoom_step
elif angle.y() < 0 and self.zoom > self.zoom_range[0]: # Limite.
""" Molette roule vers le bas. """
zoom_factor = 1 / self.zoom_factor
self.zoom -= self.zoom_step
else:
""" Hors limites. """
return
""" Zoom dans les limites. """
self.scale(zoom_factor, zoom_factor)
""" Enregistrement. """
self.od_pkl.write(['UiView', 'zoom'], self.zoom)
""" Enregistrement de la position du centre de la vue nécessaire, car le zoom l'a modifié. """
self.od_pkl.write(['UiView', 'position'], self._get_position())
self.dt.delay(self.o_scene.o_pkl.backup, 500) # Évite la répétition excessive (mitraillage).
Le centre du zoom devrait se situer exactement à l'emplacement de la souris, mais ce n'est pas le cas.
Nous allons remedédier à celà en ajoutant un flag. Ajouter cette ligne dans la méthode init_ui(), section "flags." : ui_view.py > UiView.init_ui()
Notez que l'enregistrement, à la fin du code, mémorise le zoom, mais aussi la position. Pourquoi ?
Tout simplement parce que lorsqu'on modifie le zoom, on modifie aussi la position ... logique !
Pour retrouver la vue à cette position et avec ce zoom au lancement, il faut lire la valeur mémorisée et la restituer :
Ajouter ce code dans init_ui(), juste après celui du centrage : ui_view.py > UiView.init_ui()
""" Zoom. """
self.zoom = self.od_pkl.read(['UiView', 'zoom'], 0)
""" La raison étant géométrique, il faut procéder à une élévation à la puissance. """
zoom_factor = self.zoom_factor**self.zoom # Rappel x^0 = 1
self.scale(zoom_factor, zoom_factor)