J'ai regroupé le yen dollar en utilisant la méthode k-medoids en python et j'ai trouvé le taux de réponse correct.

introduction

En commençant par "La prévision des cours boursiers et des taux de change semble être intéressante" et "Lisons quelques articles", si vous téléchargez et lisez des articles et des documents académiques susceptibles de vous intéresser personnellement J'ai été impressionné de trouver un matériau qui semble extrêmement intéressant. Quand ça s'arrête là, madame. Je n'avais pas touché au cours de l'action et aux données d'échange jusqu'à présent parce que j'ai essayé de l'implémenter à ma manière, pas seulement en le lisant, mais j'ai obtenu les données et essayé d'en faire un formulaire.

Cible: Pour ceux qui comprennent les bases telles que les quatre règles de programmation, if statement et for statement, et qui s'intéressent à l'analyse des cours boursiers et des taux de change. Il est écrit de manière à ce que les élèves de première et de deuxième année qui ont commencé à programmer à l'université puissent le lire. Ce n'est pas pour les personnes qui ont des connaissances avancées et peuvent faire diverses prédictions et analyses (je ne peux pas écrire en premier ... lol))

Aperçu: Le flux de regroupement au-delà du dollar-yen en utilisant la méthode des k-médoïdes et en trouvant le taux de réponse correct est décrit.

1 Obtenir des données

Cette fois, nous avons acquis des données quotidiennes sur le yen dollar pour les 15 dernières années et des données horaires pour les 5 dernières années. Je ne savais même pas que je pouvais obtenir les données, donc j'ai pris beaucoup de temps ici, mais il m'est arrivé de découvrir l'existence de "oanda api" et j'ai réussi à l'obtenir en faisant référence à Qiita, qui résume comment l'utiliser. Les données quotidiennes acquises ressemblent à ceci (voir la figure ci-dessous). スクリーンショット 2020-03-03 18.32.24.png L'excellent se sent plus intime.

Obtenir des données de l'API OANDA

Je me réfère à Comment obtenir une grande quantité de données d'échange passées de l'API FX (pour l'apprentissage automatique) J'ai les données. Il a fallu beaucoup de temps pour utiliser l'API, voici donc les étapes. Tout d'abord, oanda api est une api fournie par le fournisseur FX oanda. Vous avez besoin d'un identifiant et d'une clé pour l'utiliser, et vous devez ouvrir un compte de démonstration pour oanda. "Page d'accueil d'Oanda" https://www.oanda.jp Sélectionnez "Ouvrir un nouveau compte" -> "Ouvrir un nouveau compte démo" depuis la page d'accueil. Entrez diverses informations sur le formulaire d'ouverture de compte démo gratuit et émettez un identifiant de compte démo. Après cela, un e-mail avec l'identifiant et le mot de passe vous sera envoyé, alors connectez-vous au compte démo. Il y a «ID de compte» dans les informations de compte dans la partie centrale de la figure ci-dessous. Entrez dans "Gérer l'accès à l'API" en bas à droite et obtenez un jeton d'accès personnel. Si vous pouvez obtenir le AccountID et AccessKey (PersonalAccessToken, vous pouvez utiliser l'API.

Installez un package appelé oandapy

pip install git+https://github.com/oanda/oandapy.git

Obtenez des informations d'échange. Chargez la bibliothèque requise et essayez d'obtenir le taux actuel dollar-yen.


import pandas as pd
import oandapy
import configparser
import datetime
from datetime import datetime, timedelta
import pytz

account_id = "xxxxx"
access_key = "xxxxx"

#Appel API oanda
oanda = oanda.API(access_token = access_key, environment = "practice")

#Obtenez le taux dollar-yen pour l'heure actuelle
res = oanda.get_prices(instruments = "USD_JPY")

Résultat de sortie ↓ ↓ ↓ {'prices': [{'ask': 107.321, 'bid': 107.317, 'instrument': 'USD_JPY', 'time': '2020-03-05T06:12:23.365940Z'}]} En raison de l'influence du virus corona, il est passé du niveau de 112 yens au niveau de 107 yens. À partir de là, accédez à Comment obtenir une grande quantité de données d'échange passées de l'API FX (pour l'apprentissage automatique). Il est rédigé de manière très simple à comprendre, veuillez donc vous référer à cet article pour obtenir les données pour la période souhaitée.

2 Méthode d'analyse

Effectuez l'analyse selon les étapes suivantes. Étape 1: Déterminez la période de prévision, la période de collecte des données et la période de vérification Étape 2: regrouper la période de collecte de données Étape 3: de la période de vérification, commerce pendant la période de prévision Étape 4: Ajoutez les données de la période de transaction que vous avez effectuée à la période de collecte de données Étape 5: Lorsque la période de vérification n'est pas terminée, revenez à l'étape 2 et terminez lorsque la période de vérification est terminée.

<img width = "600"src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512508/db4b3cf1-8577-914c-fc19-7580deeab002.png ">

Chaque période a la signification suivante. Période de collecte des données: Période de référence aux mouvements de prix antérieurs (généralement données de formation) Période de vérification: Période d'évaluation du taux de réponse correct (généralement des données de test) Période de prévision: que se passera-t-il le mois prochain, la semaine prochaine, demain? Période à prévoir (nous en avons préparé trois mensuellement, hebdomadairement et quotidiennement)

À titre d'exemple, la période de prévision est mensuelle, la période de collecte des données est de 156 mois de 2003 à fin 2015 et la période de vérification est de 36 mois de 2016 à fin 2018 (une fois que vous l'avez comprise, changez simplement la période). Cela ressemble à ceci sur la figure. (Figure ci-dessous)

Appliquez la méthode k-médoïde à la période de collecte de données à l'étape 2. L'image est "Lorsque la période de collecte de données est divisée en plusieurs modèles, quel modèle sera la période prévue?" Nous classons donc d'abord la période de collecte de données en plusieurs modèles. Étant donné que les mouvements de prix du graphique sont essentiellement classés en trois modèles de hausse, de nivellement et de baisse, nous les classons ici en trois classes.

Voici un résumé des mouvements de prix pendant 156 mois (voir la figure ci-dessous).

Le nombre de séries est de 156, et la raison pour laquelle le nombre d'éléments varie est qu'il existe des différences telles qu'un mois se terminant par 30 jours, un mois se terminant par 31 jours et février. De plus, les devises ne sont pas négociées les jours fériés et le Nouvel An, de sorte que le nombre d'éléments dans chaque série est d'environ 18 à 22.

Afin de comprendre les caractéristiques de chaque série, divisez chaque élément par la valeur au début de chaque période (élément [0]). Ensuite, cela ressemble à ceci (voir la figure ci-dessous).

Distance DTW

Ensuite, je classerai ces séries confuses en trois. Mais la question ici est de savoir comment classer? Est-il possible d'évaluer quantitativement chaque forme dans des séries chronologiques? J'ai pensé, mais cela peut être résolu en utilisant une méthode de mesure appelée distance DTW. Il s'agit d'une valeur renvoyée lorsque vous placez les deux séries temporelles que vous souhaitez comparer dans la fonction DTW, et vous pouvez l'utiliser pour évaluer quantitativement entre chaque série chronologique.

Par exemple, lorsqu'il existe la série chronologique suivante de A à E

Si vous mettez les séries chronologiques de A et B dans la fonction DTW, ce sera 297. スクリーンショット 2020-03-03 20.28.25.png Le tableau ci-dessous montre la fonction DTW pour toutes les combinaisons de A à E. スクリーンショット 2020-03-03 20.29.25.png Les distances DTW des séries temporelles les plus proches A et B sont 297, et les distances DTW des séries chronologiques les plus éloignées D et E sont 2900. ..

Algorithme DTW, code

L'algorithme ressemble à ceci: Étape 1: Créez une matrice de coût de longueur M x longueur N pour la différence entre les valeurs absolues de chaque point. Étape 2: Créez une matrice M × N Dist, $ Dist {(1, 1)} = Cost {(1,1)} $, initialisez en remplaçant $ ∞ $ par d'autres éléments. Étape 3: Dans la 1ère ligne et la 1ère colonne de la matrice Dist, ajoutez la valeur de la valeur précédente et la valeur de la matrice Coût à la même position. Étape 4: Créez une matrice Dist selon la formule suivante.

Dist_{(i, j)} = Cost{(i, j)} + min(Dist{(i, j-1)}, Dist{(i-1, j)}, Dist{(i-1, j-1)})

Étape 5: Soit $ Dist {(M, N)} $ la distance DTW. Tout d'abord, faites attention aux séries chronologiques de A et B, et que se passe-t-il lors de la mesure de la distance DTW? Si vous créez une matrice de coût qui trouve la différence entre les valeurs absolues de chaque point de l'étape 1, initialisez la matrice Dist des étapes 2 et 3 et créez une matrice Dist selon l'étape 4, le résultat sera le suivant. À partir de l'étape 5, la distance DTW entre les séries temporelles A et B est calculée à 297. Ce qui suit est une fonction de ceux-ci.

#Fonction dtw pour trouver la distance
def dtw(x, y):
    #Créez une matrice de distance.
    X, Y = np.meshgrid(x, y)
    dist = abs(X-Y)  #Distance euclidienne
    #print(dist)

    #Initialisation de la matrice qui calcule dtw
    #Sur l'algorithme dtw, dtw[-1][-1]Se référer d'abord,ligne,Longueur de la série chronologique d'origine pour les deux colonnes+Sécurisez une matrice dtw de 1
    dtw = np.full((len(y) + 1, len(x) + 1), np.inf)
    dtw[0, 0] = 0

    for i in range(1, len(y) + 1):
        for j in range(1, len(x) + 1):
            dtw[i, j] = min(
                dtw[i - 1, j],
                dtw[i, j - 1],
                dtw[i - 1, j - 1]) + dist[i - 1, j - 1]
    return dtw[-1, -1]

Utilisez la fonction dtw pour créer une matrice dtw qui stocke les valeurs de distance dtw entre chaque série temporelle (un tableau qui contient toutes les combinaisons A à E ci-dessus).

def make_dtw_matrix(data):
    dtw_matrix = [[0 for i in range(len(data))] for j in range(len(data))]
    for i in range(0, len(data)):
        for j in range(0, len(data)):
            dtw_matrix[i][j] = dtw(data[i], data[j])
    return dtw_matrix

méthode k-medoids

Ensuite, nous allons enfin effectuer le clustering en utilisant la méthode k-medoids. La méthode k-medoids est une méthode de classification par optimisation de division similaire à la méthode k-means. Dans la méthode k-means, le point de base est défini sur le centroïde, mais dans la méthode k-medoids, d'autres éléments sont inclus dans la classe attribuée. Les points qui minimisent la somme des distances à tous les points sont utilisés comme points de base (médoïdes). Par conséquent, la méthode des k-moyennes a l'inconvénient d'être facilement affectée par les valeurs d'écart par rapport à la méthode de calcul du centre de gravité, mais k- La méthode des médoïdes a l'avantage de réduire l'effet des valeurs aberrantes car l'une des données non classées est attribuée aux médoïdes (j'ai fait référence à la méthode des k-means, mais cet article sans le savoir. Peut être lu, donc ça va.)

La distance entre les séries temporelles est quantifiée par la distance DTW, et la matrice dtw quantifiée est classée en utilisant la méthode des k-médoïdes.

Il ressemble à ceci lorsqu'il est classé en trois classes. (Figure ci-dessous) image.pngimage.pngimage.png D'une manière ou d'une autre, il est divisé en une classe plate (53 séries), une classe descendante (66 séries) et une classe ascendante (37 séries).

algorithme de méthode k-medoids, code

L'algorithme procédera à l'étape suivante. Étape 1: sélectionnez au hasard des points pour les classes k (médoïde) Étape 2: Attribuez chaque point à la classe médoïde la plus proche. Étape 3: Faites le point où la distance totale à tous les autres points dans chaque classe est le minimum en tant que nouveau médoïde. Étape 4: s'il n'y a pas de changement, il se termine et s'il y a un changement, il revient à l'étape 2. Le code est affiché ci-dessous.

À titre d'exemple, que se passe-t-il si nous classons les séries chronologiques A à E en deux classes? Je le ferai. La matrice dtw est une matrice symétrique de composante 0 en diagonale 5 × 5 dans le tableau ci-dessus.

Tout d'abord, sélectionnez deux séries au hasard à l'étape 1. Ici, sélectionnons les séries A et B comme médoïde. Correspond à initialize_medoids dans le code. Un médoïde est celui de la classe qui minimise la distance totale à tous les points de la classe. Donc cette fois, nous avons deux médoïdes. Les médoïdes qui stockent les médoïdes sont les médoïdes = \ [A, B ](par programmation, médoïdes = [0, 1]).

Étape 2 Attribuez chaque série (A à E) à la classe médoïde la plus proche. Correspond à assign_to_nearest dans le code. Ici, la classe assignée est appelée étiquette, et comme elle est classée en deux classes, nous préparerons deux étiquettes, "0" et "1". Comme nous n'avons encore rien fait, chacune des étiquettes actuelles Le label attribué à la série est label = \ [?,?,?,?,? ](Label = [∞, ∞, ∞, ∞, ∞] sur le programme).

En se concentrant sur la matrice dtw, les séries sélectionnées pour le médoïde sont A et B. Le médoïde avec l'étiquette 0 est maintenant la série A, et le médoïde avec l'étiquette 1 est maintenant la série B. Si vous regardez de quel médoïde chaque série est plus proche, vous verrez le cercle rouge ci-dessous.

Par conséquent, l'étiquette à laquelle le médoïde le plus proche de chaque série est attribué est label = \ [0, 1, 0, 0, 1 ].

Étape 3: Le point où la distance totale à tous les autres points de chaque classe est minimisée est nouvellement défini comme médoïde. Correspond à update_medoids dans le code. Concentrez-vous sur chaque série de la classe, préparez le mindiste à stocker la valeur qui minimise la distance totale entre cette série et toutes les autres séries de la classe, et les médoïdes pour stocker le nouveau médoïde. Il est à l'état de mindist = \ [?,? ], Medoids = \ [?,? ]. Alors, quelle étiquette est attribuée à A à E? , Quelle est la distance totale par rapport aux autres étiquettes? Mettons à jour la série avec la plus petite distance totale entre les autres étiquettes et le nouveau médoïde. Première série A. La série A correspond à l'étiquette 0 et les autres séries affectées à 0 correspondent au cercle rouge ci-dessous lors de la vérification des valeurs de la série C et de la série D. dtw matrice. Depuis 363 + 1388 = 1751, mindist = \ [1751 ,? ], Medoids = \ [0 ,?]. Série B. La série B est étiquetée 1 et la seule autre série affectée à 1 est la série E, donc si vous vérifiez la valeur de la matrice dtw, 1156. Par conséquent, mindist = \ [1751, 1156 ], medoids = \ [0, 1 ](pour l'instant, cela n'a pas changé) Série C. La série C est étiquetée 0 et les autres séries affectées à 0 sont la série A et la série D, donc si vous vérifiez les valeurs de la matrice dtw, le total est de 1447 (363 + 1084). Comme il est inférieur à 1751, mettez à jour mindist = \ [1447, 1156 ], medoids = \ [2, 1 ]. La série D a l'étiquette 0, et si vous vérifiez les valeurs de la matrice dtw, le total est de 2472, ce qui est dû au fait que le mindist n'est pas mis à jour. La série E vérifie la valeur de la matrice dtw ainsi que l'étiquette 1 et ne met pas à jour le mindist, donc c'est fini.

À partir de ce qui précède, le médoïde a été mis à jour de \ [0, 1 ] à \ [2, 1 ]. Attribuez ce nouveau médoïde au médoïde le plus proche de chaque série en utilisant la même procédure qu'à l'étape 2. Il peut être divisé en bons sentiments.

Je pense que l'explication est redondante et difficile à comprendre, mais la méthode k-medoids consiste à répéter la procédure ci-dessus jusqu'à ce que le médoïde ne soit pas mis à jour et à le classer.

Vous trouverez ci-dessous un programme de la méthode k-medoids écrite en python (je l'ai écrit jusqu'à présent, mais il est plus facile de l'utiliser car la bibliothèque est préparée ...)

#algorithme de la méthode kmedoids
def kmedoids(dtw_matrix, total_class_num):
    medoids = initialize_medoids(dtw_matrix, total_class_num)
    label = [0 for i in range(len(dtw_matrix))] #len(dtw_matrix)Contient 0 de la longueur de,Alors maintenant, toutes les étiquettes de séries temporelles sont 0
    for i in range(0, 100):
        new_label = assign_to_nearest(dtw_matrix, medoids)
        if new_label == label:
            break
        label = new_label
        medoids = update_medoids(dtw_matrix, label, total_class_num)
    return (label, medoids)

def update_medoids(dtw_matrix, label, total_class_num):
    n = len(dtw_matrix)
    mindists = [np.inf for i in range(total_class_num)]  #Tableau avec inf pour le nombre de classes. k=Si 3[inf, inf, inf]
    medoids = [np.inf for i in range(total_class_num)]
    for i in range(0, n):
        ts_label = label[i]
        dist_total = 0
        for j in range(0, n):
            if label[j] == ts_label:
                dist_total += dtw_matrix[i][j]
        if dist_total < mindists[ts_label]:
            mindists[ts_label] = dist_total
            medoids[ts_label] = i
    return medoids

def assign_to_nearest(dtw_matrix, medoids):
    total_class_num = len(medoids)
    label = [0 for i in range(len(dtw_matrix))]
    for i in range(0, len(dtw_matrix)):
        mindist = np.inf
        nearest = 0
        for j in range(0, total_class_num):
            if dtw_matrix[i][medoids[j]] < mindist:
                mindist = dtw_matrix[i][medoids[j]]
                nearest = j
        label[i] = nearest
    return label

def initialize_medoids(dtw_matrix, total_class_num):
    medoids = list(range(len(dtw_matrix)))
    return medoids[0:total_class_num]

transaction

Alors faites un marché. Concentrez-vous sur la série immédiatement avant la série prévue et vérifiez à quelle classe appartient la série. Si plus de la moitié des séries de cette catégorie augmentent (diminuent), nous prévoyons qu'elles augmenteront (diminueront) dans le futur et prendrons une décision d'achat (vente). S'il augmente réellement même un peu, il est compté comme un succès, et la période prédite est stockée dans la période de collecte de données et le regroupement est à nouveau effectué.

Dans l'exemple précédent, lors de la prédiction de janvier 2016, qui est la première de la période de vérification (2016 à fin 2018), la classe classifiée de la série de décembre 2015, qui est la série précédente, est utilisée. Confirmation Transaction selon la classe, après la fin, la série de janvier 2016 est stockée dans la période de collecte des données et à nouveau regroupée, puis la série de février 2016 est prédite. <img width = "600"src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512508/db4b3cf1-8577-914c-fc19-7580deeab002.png ">

De cette manière, le taux de réponse correcte a été calculé en concevant diverses périodes de collecte de données et de vérification, et en modifiant la période de prédiction.

3 Exécution de l'analyse

Lorsque vous avez terminé d'écrire le code, vous pouvez devenir riche si vous pouvez l'analyser avec précision! J'étais excité. Mais ce n'était pas si doux. Les résultats sont les suivants.

Période de prévision: mensuelle

image.png image.png image.png image.png image.png

Période prévue: hebdomadaire

image.png image.png image.png image.png image.png

Période de prévision: tous les jours

image.png image.png image.png image.png image.png

Le nombre de classes était limité à 5 modèles de 3 à 7 (cela n'a pas beaucoup changé même si je l'ai augmenté) et le résultat a été obtenu. Tous les résultats ont un taux de réponse correcte d'environ 40 à 50%, ce qui est un peu décevant. Mais il reste encore beaucoup à faire.

Après avoir examiné la méthode de négociation et pris en compte et mis en œuvre deux hypothèses, nous avons pu atteindre un taux de réponse correcte moyen d'environ 65% pour chaque semaine. Je vais résumer cela dans un autre article.

Le temps d'exécution est long!

C'est très long. Dans les cas où la période de prédiction est quotidienne, il faut environ 100 minutes pour s'exécuter une fois. Je voulais obtenir des résultats plus tôt. J'utilise un MacBook Air (début 2015), un processeur Intel Core i5 bicœur à 1,6 GHz et 8 Go de mémoire que je n'ai pas personnalisés, mais j'en ai acheté un nouveau l'autre jour.

Les spécifications sont CPU: Core i7-9850H (2,6 GHz, 6 cœurs), mémoire: 16 Go, NVIDIA GeForce MX150 (GPU).

En plus de ce programme, je pense à comparer le décalage horaire lorsqu'un programme d'apprentissage automatique basé sur des livres, etc. est exécuté sur un MacBook Air et un ordinateur portable équipé d'un GPU.

Les références

Comment obtenir une grande quantité de données d'échange passées depuis l'API FX (pour l'apprentissage automatique)Prévision du cours de l'action en utilisant la similitude des modèles de fluctuation du cours de l'actionPrévisions de marché utilisant des modèles de fluctuation de prix Clustering k-Medoids avec indexation Dynamic Time Warping appliqué au marché boursier / _pdf) ・ Analyse du marché des valeurs mobilières / des changes / des devises virtuelles par modèle de fluctuation des prix

Recommended Posts

J'ai regroupé le yen dollar en utilisant la méthode k-medoids en python et j'ai trouvé le taux de réponse correct.
J'ai essayé la méthode des moindres carrés en Python
Déterminer le seuil à l'aide de la méthode P-tile en python
J'ai vérifié la vitesse de référence lors de l'utilisation de la liste python, du dictionnaire et du type de jeu.
J'ai eu un AttributeError en me moquant de la méthode ouverte en python
J'ai comparé Node.js et Python lors de la création d'une miniature à l'aide d'AWS Lambda
J'ai écrit la file d'attente en Python
J'ai essayé de calculer la "distance de Lebenstein" en utilisant Python
J'ai écrit la pile en Python
Comment écrire le bon shebang dans les scripts Perl, Python et Ruby
J'ai défini des variables d'environnement dans Docker et je les ai affichées en Python.
Le nom du fichier était mauvais en Python et j'étais accro à l'importation
Notes utilisant cChardet et python3-chardet dans Python 3.3.1.
Essayez d'utiliser l'API Wunderlist en Python
Essayez d'utiliser l'API Kraken avec Python
Tweet à l'aide de l'API Twitter en Python
J'ai essayé d'utiliser l'optimisation bayésienne de Python
CheckIO (Python)> La lettre la plus recherchée que j'ai essayée> Recherche des alphabets les plus fréquents> Utilisation de Counter dans les collections, en utilisant filter () et sorted (), méthode par saisie max (), string.ascii_lowercase
J'ai comparé la vitesse des expressions régulières en Ruby, Python et Perl (version 2013)
Notez que je comprends l'algorithme des moindres carrés. Et je l'ai écrit en Python.
Je veux obtenir le nom du fichier, le numéro de ligne et le nom de la fonction dans Python 3.4
vprof - J'ai essayé d'utiliser le profileur pour Python
J'ai essayé le web scraping en utilisant python et sélénium
J'ai essayé la détection d'objets en utilisant Python et OpenCV
La réponse de "1/2" est différente entre python2 et 3
Apprenez le modèle de conception "Méthode de modèle" en Python
Essayez d'utiliser l'API BitFlyer Ligntning en Python
J'ai essayé de simuler "Birthday Paradox" avec Python
J'ai écrit une classe en Python3 et Java
Pour remplacer dynamiquement la méthode suivante en python
Apprenez le modèle de conception "Méthode d'usine" en Python
À propos de la différence entre "==" et "is" en python
Corriger les fluctuations de notation demi-largeur et pleine largeur en Python
Pré-traiter l'index en Python à l'aide de ScriptUpdateProcessor de Solr
J'ai essayé d'implémenter la fonction gamma inverse en python
Essayez d'implémenter la méthode Monte Carlo en Python
Essayez d'utiliser l'API ChatWork et l'API Qiita en Python
Je veux afficher la progression en Python!
Essayez d'utiliser l'API DropBox Core avec Python
Suivi d'objets à l'aide d'OpenCV3 et de Python3 (suivi des points caractéristiques spécifiés par la souris à l'aide de la méthode Lucas-Kanade)
Approximer une courbe de Bézier à travers un point spécifié en utilisant la méthode des moindres carrés en Python
J'ai comparé la vitesse de la référence du python dans la liste et la référence de l'inclusion du dictionnaire faite à partir de la liste dans.
Je veux remplacer les variables dans le fichier de modèle python et le produire en masse dans un autre fichier
Dans Python3.8 et versions ultérieures, le mod inverse peut être calculé avec la fonction intégrée pow.
J'ai essayé la version python de "Prise en compte de la réponse de Conner Davis" Impression de nombres de 1 à 100 sans utiliser de boucle, récursive, goto "