Les programmes précédents conservent prix, compteurs et réglages dans des variables séparées. Cela suffit pour créer de nombreux indicateurs et stratégies. Dans un programme plus grand, il peut être utile de regrouper des données liées, comme les deux limites d’une bande de prix ou le prix et l’instant d’un signal.

C’est le rôle d’un type défini par l’utilisateur, ou user-defined type, abrégé UDT. Une méthode est une opération associée à un type donné. Tous deux font partie de Pyne code, mais les exemples suivants ne nécessitent pas leur utilisation. Si vous souhaitez d’abord aborder les stratégies, passez au débogage et revenez ici plus tard.

Regrouper des données dans un type

Deux nombres décrivent une bande de prix : ses limites inférieure et supérieure. Nous pouvons nommer cette paire avec un type personnalisé :

@udt
class PriceRange:
    bottom: float = 0.0
    top: float = 0.0

class définit un nouveau type nommé PriceRange. bottom et top sont ses champs. Ils acceptent des nombres décimaux et valent zéro par défaut.

@udt en fait un type utilisateur Pyne. Nous importons udt depuis pynecore.core.pine_udt. En Edge, la définition du type contient les champs ; les méthodes associées sont définies séparément. Nous n’utilisons pas l’héritage dans ce cadre.

Le type et l’objet créé à partir de lui

Le type décrit les données à conserver ensemble. Une paire concrète est stockée dans un objet, aussi appelé instance, créé à partir de ce type :

current_range = PriceRange()
current_range.bottom = low
current_range.top = high

PriceRange() crée une nouvelle instance avec les valeurs par défaut des champs. Les deux lignes suivantes affectent le plus bas de la bougie actuelle à sa limite inférieure et le plus haut à sa limite supérieure. Vous lisez les champs avec current_range.bottom et current_range.top. Le point désigne ici l’accès à un champ.

Vous pouvez créer plusieurs objets du même type. Un autre PriceRange peut contenir une autre bande de prix : les noms des champs sont identiques, leurs valeurs indépendantes.

Une méthode : une opération sur un type donné

Le milieu de la bande est la moyenne de ses deux limites. Nous pouvons l’écrire comme une méthode associée au type :

@method
def midpoint(self: PriceRange) -> float:
    return (self.bottom + self.top) / 2

@method marque la fonction comme méthode. Le type du premier paramètre indique les données traitées, ici un objet PriceRange. self est le nom, dans la méthode, de l’objet dont nous calculons le milieu. C’est un nom de paramètre ; PriceRange en indique le type.

La définition se trouve hors du bloc class, au niveau extérieur du fichier. Nous importons method depuis pynecore.core.pine_method.

L’appel :

center = midpoint(current_range)

Nous appelons la méthode connue par son nom et transmettons l’objet traité comme premier argument. self reçoit donc l’objet current_range. Le nombre renvoyé est placé dans center.

Une fonction ordinaire pourrait aussi effectuer ce calcul. Une méthode aide à organiser le programme lorsque plusieurs opérations sont associées à un type et que vous souhaitez rendre explicites les données traitées.

Le programme complet

Créez udt_ranges.py dans le groupe Scripts :

"""
@pyne edge
"""
from pynecore.core.pine_method import method
from pynecore.core.pine_udt import udt
from pynecore.lib import color, high, low, plot, script


@udt
class PriceRange:
    bottom: float = 0.0
    top: float = 0.0


@method
def midpoint(self: PriceRange) -> float:
    return (self.bottom + self.top) / 2


@script.indicator("Price Range Objects", overlay=True)
def main():
    current_range = PriceRange()
    current_range.bottom = low
    current_range.top = high
    center = midpoint(current_range)
    plot(current_range.bottom, "Lower", color=color.blue)
    plot(current_range.top, "Upper", color=color.orange)
    plot(center, "Midpoint", color=color.green, linewidth=2)

Exécutez-le sur les données de démonstration. La ligne bleue montre les plus bas, l’orange les plus hauts et la verte leur milieu. À chaque bougie, le point vert se situe entre les deux autres valeurs.

PyneIDE affiche le code du type utilisateur PriceRange et de la méthode midpoint ; le graphique présente les trois lignes des limites inférieure et supérieure et du prix médian.
Les deux champs de PriceRange conservent les limites de la bougie ; la méthode midpoint calcule leur milieu.

Le programme crée un nouvel objet PriceRange à chaque bougie. Ici, les champs ne conservent pas les valeurs de la bougie précédente : ils contiennent toujours les deux limites actuelles.

Affectation et copie

Affecter un objet à une autre variable ne le copie pas :

other_range = current_range
other_range.top = other_range.top + 1.0

Les deux variables font référence au même objet. La seconde ligne change donc aussi current_range.top.

Pour obtenir une instance distincte avec les mêmes valeurs de champs, créez un nouvel objet :

other_range = PriceRange()
other_range.bottom = current_range.bottom
other_range.top = current_range.top
other_range.top = other_range.top + 1.0

Pour ce type contenant deux nombres, modifier la limite supérieure de la nouvelle instance ne change pas l’originale. Les deux nombres sont conservés dans un objet distinct. Si un champ contient ensuite un autre objet ou un tableau, le transmettre à une nouvelle instance transmet aussi une référence sans copier son contenu.

Conserver un objet entre les bougies

Pour réutiliser le même objet, vous pouvez appliquer l’annotation Persistent déjà apprise. Importez Persistent depuis pynecore.

current_range: Persistent[PriceRange] = PriceRange()
current_range.bottom = low
current_range.top = high

L’objet est créé une seule fois ; ses deux champs sont actualisés à chaque bougie. Vous pouvez remplacer la création d’objet et les deux affectations au début de main() par ces trois lignes. Le calcul du milieu et le tracé restent inchangés.

Le graphique ne change pas ici, car nous attribuons les mêmes valeurs aux champs. Pour un autre besoin, vous pourriez ne les modifier qu’à un événement, pour conserver une bande de prix précédemment repérée, par exemple. Le type ne rend pas à lui seul l’objet persistant.

À vous de jouer

Créez une méthode width qui renvoie la largeur de la bande. Sur le modèle de midpoint, donnez-lui un paramètre self: PriceRange et calculez le résultat à partir des deux champs.

Solution de l’exercice
@method
def width(self: PriceRange) -> float:
    return self.top - self.bottom

Son appel dans main() :

range_width = width(current_range)

Si le plus haut d’une bougie vaut 108 et son plus bas 101, la largeur vaut 7. La méthode lit seulement les champs, sans modifier aucune limite.