[{"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"}]
"910_plusieurs_nodes_dans_le_graphe"
Le poste de contrôle (PC) /
Les graphes /
Plusieurs nodes dans le graphe
Plusieurs nodes permettent les interconnexions
Avant-propos
Python - Apprendre ou réviser : Tri d'une liste d'objets selon un attribut.
Apprendre ou réviser QGraphicsItem.zValue(), QGraphicsItem.setZValue(), QGraphicsView.setDragMode.
Refactoring : Emploi fréquent, dans la classe UiNode, de l'attribut self.o_node.o_scene. Pour simplifier :
Créer dans UiNode.__init__() l'attribut self.o_scene = self.o_node.o_scene
Dans tout le code de la classe UiNode, remplacer chaque occurrence de self.o_node.o_scene par self.o_scene.
Vérifier que tout continue de fonctionner.
Correction d'un oubli :
Lors du dernier tuto, la case à cocher On / Off a été ajoutée aux nodes mais n'a pas été mémorisée. Réparons cet oubli.
La méthode CtrlNode.set_checked() devient :
La déconnexion du timer des fonctions retardées ne se faisait pas systématiquement, provoquant des répétitions inutiles de code.
La méthode utils.py > DateTime.delay() devient :
def delay(self, client_function, delay=20, *args, **kwargs):
""" Asynchrone (ne prend pas la main).
:param client_function: Cette fonction sera exécutée après un délai.
:param delay: Délai en ms (20ms par défaut).
:param args: Optionnel. Arguments dans l'appel de la fonction (tuple ou liste).
:param kwargs: Optionnel. Arguments dans l'appel de la fonction (dictionnaire).
:return: NA
"""
key = client_function.__name__
if key in self.d_tempo:
try:
self.d_tempo[key].disconnect()
except TypeError:
pass
else:
self.d_tempo[key] = QTimer()
self.d_tempo[key].setSingleShot(True)
self.d_tempo[key].timeout.connect(lambda: client_function(*args, **kwargs))
self.d_tempo[key].start(delay)
Description
L'ajout de nodes apporte son lot de problèmes à résoudre : chevauchements, passage au premier plan, sélection multiple, etc.
1 - Ajout de 2 autres nodes dans le code provisoire.
Modifier la méthode provisoire dans CtrlScene pour ajouter 2 nodes. CtrlScene.provisoire() :
def provisoire(self):
""" Provisoire. Ajout de 3 nodes pour la mise au point. A supprimer après le coding du drag & drop. """
from nodes.generateurs.signaux import Node
Node(self, 'Node1')
from nodes.afficheurs.plot import Node
Node(self, 'Node2')
from nodes.indicateurs.macd import Node
Node(self, 'Node3')
plot.py et macd.py doivent posséder la classe Node (voir signaux.py).
Modifiez les tailles et les couleurs à votre convenance.
Vérifiez que les positions ont été correctement mémorisées.
2 - Z-index - Le node cliqué doit passer au premier plan.
Ajouter du code à la méthode-événement UiNode.mouseReleaseEvent(), pour donner au node un Z-Index supérieur à tous les autres. UiNode.mouseReleaseEvent() :
def mouseReleaseEvent(self, ev):
super().mouseReleaseEvent(ev)
""" Demande à la scène d'enregistrer tous les nodes, en cas de multiselection déplacée à la souris.
- La fonction broadcastée par la scène (à chaque node) est save_pos(), ci-dessus. """
self.save_pos()
""" Passe au 1er plan. """
z_max = self.o_scene.get_z_max()
self.setZValue(z_max + 1)
Oui mais ... la méthode CtrlScene.get_z_max() n'existe pas. Créons-la :
def get_z_max(self):
"""
- Code appelant : un node, sur 'mouseReleaseEvent()'.
- Réaffecte tous les Zindex à partir de 0, retourne le plus grand.
"""
z_max = -1
# A coder ...
return z_max
Il vous appartient de coder cette méthode (voir les prérequis).
3 - Multi-sélection de nodes :
La multi-sélection peut être effectuée en cliquant sur chaque node, tout en gardant la touche Ctrl appuyée.
Plusieurs clics avec la touche Ctrl appuyée.
On désire aussi faire une multi-sélection par lasso à la souris.
Cliqué-glissé (lasso) à la souris.
A vous de coder (voir prérequis). 2 choses : Vous devez trouver le code (une seule ligne) et où le placer.
Si l'on déplace la sélection, tous les nodes font bloc et se déplacent.
Oui mais ... il y a un double-bug !
Au relâchement du bouton de la souris (fin du déplacement), seul le node cliqué bénéficie du magnétisme.
Au relancement du programme, on s'aperçoit que seul ce node a eu sa position mémorisée.
A vous de coder (Indice : il faut demander la propagation au contrôleur-scène CtrlScene).
4 - Affichage des paramètres généraux du node sélectionné :
Lorsqu'on clique n'importe où dans le graphe, la méthode CtrlScene.show_params() doit être appelée.
La multi-sélection par drag n'est connue qu'au relâchement du bouton gauche de la souris. UiView.mouseReleaseEvent() devient :
def mouseReleaseEvent(self, ev):
if ev.button() in [Qt.RightButton, Qt.MiddleButton]:
""" Molette ou bouton droit relâché. """
self.right_button_released(ev)
elif ev.button() in [Qt.LeftButton]:
""" Affichage des paramètres retardé pour laisser le temps à l'UI de s'actualiser. """
self.dt.delay(self.o_scene.show_params)
super().mouseReleaseEvent(ev)
La méthode CtrlScene.show_params() doit être complétée pour traiter le cas d'un seul node et celui de la multi-sélection. CtrlScene.show_params() :
def show_params(self):
"""
Si aucun élément du graphe n'est sélectionné, affiche les paramètres de la vue.
Si un élémént est sélectionné, affiche les paramètres de cet élément.
Si plusieurs éléménts sont sélectionnés, affiche une image 'multi-sélection'.
:return: NA
"""
l_selected = self.o_gr_scene.selectedItems()
nb_selected = len(l_selected)
if nb_selected == 0:
self.o_params.show_params()
elif nb_selected == 1:
if l_selected[0].__class__.__name__ == 'UiNode': # o_gr_node
o_node = l_selected[0].o_node
o_node.o_params.show_params()
else: # > 1
self.o_params.show_params(b_multi=True)
Oui mais ... Si le clic a lieu sur un node, bug ! => CtrlNode n'a pas d'objet o_params -> On l'ajoute.
- Déclaration. Code de CtrlNode.__init__(), complet :
- Instanciation. Code de CtrlNode.setup(), complet :
def setup(self):
""" Paramètres. """
self.o_params = Parameters(self)
""" Code appelant = classe dérivée. """
self.o_gr_node = UiNode(self) # Gestion de l'UI (Interface utilisateur) : dessin couleurs, ...
self.o_scene.o_gr_scene.addItem(self.o_gr_node) # Incorporation dans la scène.
Ne pas oublier d'importer Parameters !
Oui mais ...o_params a besoin des paramètres par défaut, fournis par CtrlNode.get_default(), qui n'existe pas -> on l'ajoute. CtrlNode.get_default() :
def get_default(self):
return {
self.main_key: {
'Titre du node': 'Choisir un titre',
}
}
Maintenant, les paramètres s'affichent correctement.
Si l'on clique dans la vue, en dehors d'un node, les paramètres de la scène s'affichent.
5 - Paramètres d'une multi-sélection :
Si l'on fait une multi-sélection, on obtient une erreur : Parameters.show_params() reçoit le paramètre inconnu b_multi.
En effet, ce paramètre est un flag qui conditionnera l'affichage d'une image illustrant une multi-sélection. Nous allons réécrire cette méthode. Parameters.show_params() :
def show_params(self, b_multi=False):
"""
Code appelant : CtrlScene.show_params() - Affichage dans le dockable 'Params'.
:param b_multi: Conditionne l'affichage d'une image.
:return: NA.
"""
""" 1 - Nettoyage de l'arbre des paramètres dans le dockable 'params'. """
self.clear_params()
""" 2 - Multi-sélection. """
if b_multi:
self.multi_selection()
return
""" 3 - Liste des paramètres 'l_params' créée à partir de od_real et od_default. """
l_params = self.dic2params()
try:
p = Parameter.create(name='params', type='group', children=l_params) # GroupParameter
except (Exception,):
print("Parameters.show_params() :\nLa liste 'l_params' n'est pas conforme.")
return
""" 4 - Mise en place de la liste des paramètres dans un widget 'ParameterTree'. """
pt = ParameterTree(showHeader=False) # ParameterTree
pt.setParameters(p, showTop=False) # Titre 'params' masqué.
""" 5 - Affichage à l'écran, en plaçant l'arbre de paramètres dans un layout propre. """
self.layout.addWidget(pt)
""" 6 - Événement : change() est appelée à chaque modification. """
p.sigTreeStateChanged.connect(self.change)
Oui mais ... on fait appel à la méthode multi_selection(), chargée d'afficher l'image, qui n'existe pas. Créons la. Parameters.multi_selection() :
def multi_selection(self):
""" Ajoute 2 éléments dans le dockable des paramètres :
- L'image multi.png, récupérée dans les ressources (voir exemple de code dans fen_mere.py).
- Le texte "Multi-sélection" en arial 20px, ou à votre convenance.
"""
# à coder ...
A vous de coder cette méthode.
6 - Modification et affichage du titre du node :
Sélectionner un node pour afficher ses paramètres. Modifier le titre.
Oui mais ... cette modification provoque une erreur : la méthode refresh() n'existe pas -> on la crée. CtrlNode.refresh() :
def refresh(self, l_keys):
""" Titre du node. """
if l_keys[-1] == 'Titre du node':
self.o_gr_node.set_title()
Oui mais ... cette méthode fait appel à la méthode set_title() qui n'existe pas -> On la crée. UiNode.set_title() :
def set_title(self):
title = self.o_node.get_param('Titre du node', 'Le titre')
self.title_item.setPlainText(title)
Oui mais ... cette méthode fait appel à la méthode get_param() qui n'existe pas -> On la crée. CtrlNode.get_param() :
Oui mais ... au lancement, le titre affiché sur les nodes reste "Le titre". C'est normal, il reste à modifier le code qui écrit le titre. Dans UiNode.init_ui(), section Titre, remplacez la ligne self.title_item.setPlainText('Le titre') par self.set_title()
Options : Vous pouvez modifier le code à votre convenance. Exemple - Ajout d'une tooltip.
UiNode.init_ui(), section Flags, ajoutez cette ligne :
self.setToolTip(f"{self.o_node.get_param('Titre du node', 'Le titre')}\n{self.o_node.pos}")
7 - Paramètres spécifiques :
Le paramètre "Titre du node" est commun à tous les nodes.
De plus, chaque node doit ajouter ses propres paramètres.
Il lui suffit pour celà de surcharger la méthode-parent get_default(). À titre d'exemple, nous allons traiter signaux.py.