Comment récupérer facilement des transactions historiques Binance en utilisant Python »wiki utile Meilleure programmation
Analyser les arguments
Le script utilisera les arguments suivants:
- symbole: Le symbole de la paire de trading, défini par Binance. Il peut être interrogé ici ou il peut être copié à partir de l’URL de l’application Web Binance, à l’exclusion du caractère «_».
-
date de début et fin: Auto-explicatif. le format attendu est
mm/dd/yyyy
, ou, en argot Python,%m/%d/%Y
Pour obtenir les arguments, nous allons utiliser la fonction intégrée sys
(rien de trop sophistiqué ici), et pour analyser la date, nous utiliserons le datetime
bibliothèque.
symbol = sys.argv[1]
starting_date = datetime.strptime(sys.argv[2], '%m/%d/%Y')
ending_date = datetime.strptime(sys.argv[3], '%m/%d/%Y') + timedelta(days=1) - timedelta(microseconds=1)
Nous ajoutons un jour et soustrayons une microseconde pour que le ending_date
la portion de temps est toujours à 23:59:59.999
, ce qui rend plus pratique l’obtention d’intervalles le jour même.
Récupération des métiers
Avec API de Binance, en utilisant le aggTrades point de terminaison, nous pouvons obtenir au plus 1000 transactions en une seule demande, et si nous utilisons les paramètres de début et de fin, ils peuvent être séparés d’au plus 1 heure. Après quelques échecs, en récupérant en utilisant des intervalles de temps (à un moment ou à un autre, la liquidité deviendrait folle et je perdrais de précieux échanges), j’ai décidé d’essayer le from_id
stratégie.
Le point de terminaison aggTrades est choisi car il renvoie les transactions compressées, de cette façon, nous ne perdrons aucune information précieuse.
Obtenez des transactions compressées et agrégées. Les transactions qui se remplissent à l’époque, à partir de la même commande, avec le même prix verront la quantité agrégée.
le from_id
stratégie va comme ceci: nous allons obtenir le premier métier du date de début en envoyant des intervalles de date au point final. Après cela, nous allons récupérer 1000 transactions en commençant par le premier ID de transaction récupéré. Ensuite, nous vérifierons si le dernier échange a eu lieu après notre ending_date
. Si c’est le cas, nous avons traversé toute la période et nous pouvons enregistrer les résultats dans un fichier. Sinon, nous mettrons à jour notre from_id
variable pour obtenir le dernier ID de transaction et recommencer la boucle.
Ugh, assez parlé, codons.
Récupération du premier identifiant de transaction
new_ending_date = from_date + timedelta(seconds=60)
r = requests.get('https://api.binance.com/api/v3/aggTrades',
params = {
"symbol" : symbol,
"startTime": get_unix_ms_from_date(from_date),
"endTime": get_unix_ms_from_date(new_ending_date)
})response = r.json()
if len(response) > 0:
return response[0]['a']
else:
raise Exception('no trades found')
Tout d’abord, nous créons un new_end_date
. C’est parce que nous utilisons les aggTrades en passant un startTime
Et un endTime
paramètre. Pour l’instant, nous n’avons besoin que de connaître le premier ID de transaction de la période, nous ajoutons donc 60 secondes à la période. Dans les paires à faible liquidité, ce paramètre peut être modifié car il n’y a aucune garantie qu’un échange a eu lieu dans la première minute de la journée demandée.
Ensuite, analysez la date à l’aide de notre fonction d’assistance, convertissez-la en une représentation en millisecondes Unix, en utilisant le calendar.timegm
une fonction. le timegm
La fonction est préférée car elle conserve la date en UTC.
def get_unix_ms_from_date(date):
return int(calendar.timegm(date.timetuple()) * 1000 + date.microsecond/1000)
La réponse de la demande est une liste d’objets commerciaux triés par date, au format suivant:
[
{
"a": 26129, // Aggregate tradeId
"p": "0.01633102", // Price
"q": "4.70443515", // Quantity
"f": 27781, // First tradeId
"l": 27781, // Last tradeId
"T": 1498793709153, // Timestamp
"m": true, // Was the buyer the maker?
"M": true // Was the trade the best price match?
}
]
Donc, comme nous avons besoin du première Commerce id, nous retournerons le response[0]["a"]
valeur.
Boucle principale
Maintenant que nous avons le premier ID de transaction, nous pouvons récupérer 1000 transactions à la fois, jusqu’à ce que nous atteignions notre ending_date
. Le code suivant sera appelé dans notre boucle principale. Il exécutera notre demande en utilisant le from_id
paramètre, abandonner le startDate
et endDate
paramètres.
def get_trades(symbol, from_id):
r = requests.get("https://api.binance.com/api/v3/aggTrades",
params = {
"symbol": symbol,
"limit": 1000,
"fromId": from_id
}) return r.json()
Et maintenant, notre boucle principale, qui exécutera les demandes et créera notre Trame de données.
from_id = get_first_trade_id_from_start_date(symbol, from_date)
current_time = 0
df = pd.DataFrame()while current_time < get_unix_ms_from_date(to_date):
trades = get_trades(symbol, from_id)
from_id = trades[-1]['a']
current_time = trades[-1]['T']
print(f'fetched {len(trades)} trades from id {from_id} @ {datetime.utcfromtimestamp(current_time/1000.0)}')
df = pd.concat([df, pd.DataFrame(trades)]) #dont exceed request limits
time.sleep(0.5)
Nous vérifions donc si le current_time
qui contient la date de la dernière transaction récupérée est supérieure à notre to_date
et si oui, nous:
- récupérer les métiers en utilisant le
from_id
paramètre; - mettre à jour le
from_id
etcurrent_time
paramètres, à la fois avec des informations de la dernière transaction récupérée; - imprimer un agréable message de débogage;
- pd.concat les transactions récupérées avec les transactions précédentes dans notre DataFrame;
- et dormir un peu, pour que Binance ne nous donne pas une réponse HTTP laid 429.
Nettoyage et économie
Après avoir assemblé notre DataFrame, nous devons effectuer un simple nettoyage des données. Nous supprimerons les doublons, et réduire les métiers qui se sont produits après notre to_date
(nous avons ce «problème» parce que nous récupérons des blocs de 1 000 transactions, donc nous nous attendons à ce que certaines transactions soient exécutées après notre date de fin cible).
Nous pouvons encapsuler notre fonctionnalité «trim»:
def trim(df, to_date):
return df[df['T'] <= get_unix_ms_from_date(to_date)]
Et effectuez notre nettoyage des données:
df.drop_duplicates(subset='a', inplace=True)
df = trim(df, to_date)
Maintenant, nous pouvons l’enregistrer dans un fichier en utilisant le to_csv méthode:
filename = f'binance__{symbol}__trades__from__{sys.argv[2].replace("/", "_")}__to__{sys.argv[3].replace("/", "_")}.csv'
df.to_csv(filename)
Nous pouvons également utiliser d’autres mécanismes de stockage de données, tels que l’Arctique.
Vous pouvez consulter mon tutoriel sur l’Arctique si vous n’avez pas le temps d’attendre que les Pandas chargent un fichier CSV encore et encore pendant le test de vos stratégies.
Il est important que nous puissions faire confiance à nos données lorsque nous travaillons avec des stratégies de trading. Nous pouvons facilement le faire avec les données commerciales récupérées en appliquant ce qui suit pour vérification:
df = pd.read_csv(file_name)
values = df.to_numpy()
last_id = values[0][1]for row in values[1:]:
trade_id = row[1]
if last_id + 1 != trade_id:
print('last_id', last_id)
print('trade_id', trade_id)
print('inconsistent data')
exit()
last_id = trade_idprint('data is OK!')
Dans l’extrait de code, nous convertissons notre DataFrame en Numpy Array et itérer ligne par ligne, en vérifiant si l’ID de transaction est incrémenté de 1 chaque ligne.
Les identifiants commerciaux Binance sont numérotés de manière incrémentielle et sont créés pour chaque symbole, il est donc très facile de vérifier si vos données sont correctes.
La première étape pour créer une stratégie de trading réussie est d’avoir les bonnes données.
Ma série Algotrading est un travail en cours, donc je me réjouis de tout commentaire ou suggestion que vous me laissez dans la section des commentaires. Vous pouvez consulter le code complet de ce petit tutoriel dans mon GitHub dépôt.
J’espère que vous avez apprécié la lecture de ce post. Merci pour votre temps.
Faites attention et continuez à coder!