Converti il tuo Raspberry Pi in un bot commerciale con Pythonic
Riduci il consumo energetico configurando il tuo bot di trading di criptovaluta su un Raspberry Pi.
L’attuale popolarità delle criptovalute include anche il loro scambio. L'anno scorso ho scritto un articolo Come automatizzare le tue operazioni di criptovaluta con Python che trattava la configurazione di un bot di trading basato sul framework di programmazione grafica Pythonic, che ho sviluppato nel mio tempo libero. A quel tempo, per eseguire Pythonic era ancora necessario un sistema desktop basato su x86. Nel frattempo, ho riconsiderato il concetto (GUI basata sul web). Oggi è possibile eseguire Pythonic su un Raspberry Pi, il che avvantaggia soprattutto il consumo energetico perché un bot di trading di questo tipo deve essere costantemente acceso.
L'articolo precedente è ancora valido. Se vuoi creare un bot di trading basato sulla vecchia versione di Pythonic (0.x), puoi installarlo con pip3 install Pythonic==0.19
.
Questo articolo illustra la configurazione di un bot di trading in esecuzione su un Raspberry Pi e l'esecuzione di un algoritmo di trading basato sulla strategia crossover EMA.
Installa Pythonic sul tuo Raspberry Pi
Qui toccherò solo brevemente l'argomento dell'installazione perché puoi trovare istruzioni dettagliate sull'installazione di Pythonic nel mio ultimo articolo Controlla il tuo Raspberry Pi da remoto con il tuo smartphone. In poche parole: scarica l'immagine Raspberry Pi da sourceforge.net e esegui il flashing sulla scheda SD.
L'immagine PythonicRPI non ha un desktop grafico preinstallato, quindi per procedere dovresti essere in grado di accedere alla GUI web di programmazione (http : //PythonicRPI:7000/):
(Stephan Avenwedde, CC BY-SA 4.0)
Codice di esempio
Scarica il codice di esempio per il bot di trading da GitHub (link per il download diretto) e decomprimi l'archivio. L'archivio contiene tre diversi tipi di file:
\*.py-files
: contiene l'effettiva implementazione di determinate funzionalitàcurrent_config.json
: questo file descrive gli elementi configurati, i collegamenti tra gli elementi e la configurazione variabile degli elementijupyter/backtest.ipynb
: un taccuino Jupyter per il backtestjupyter/ADAUSD_5m.df
: un set di dati OHLCV minimo che utilizzo in questo esempio
Con il pulsante delineato in verde, carica current_config.json
sul Raspberry Pi. Puoi caricare solo file di configurazione validi. Con il pulsante delineato in giallo, carica tutti i file \*.py
.
(Stephan Avenwedde, CC BY-SA 4.0)
I file \*.py
vengono caricati su /home/pythonic/Pythonic/executables
mentre current_config.json
viene caricato su / home/pythonic/Pythonic/current_config.json
. Dopo aver caricato current_config.json
, dovresti vedere una schermata come questa:
(Stephan Avenwedde, CC BY-SA 4.0)
Ora esaminerò passo dopo passo ogni parte del bot di trading.
Acquisizione dei dati
Come nell’ultimo articolo, inizio con l’acquisizione dei dati:
(Stephan Avenwedde, CC BY-SA 4.0)
L'acquisizione dei dati si trova nella scheda Area 2 e viene eseguita indipendentemente dal resto del bot. Implementa le seguenti funzionalità:
- AcqusitionScheduler: attiva gli elementi successivi ogni cinque minuti
- OHLCV_Query: prepara il metodo di query OHLCV
- KrakenConnector: stabilisce una connessione con lo scambio di criptovaluta Kraken
- DataCollector: raccogli ed elabora i nuovi dati OHLCV
Il DataCollector ottiene un elenco Python di dati OHLCV con un timestamp prefissato e lo converte in un DataFrame Pandas. Pandas è una libreria popolare per l'analisi e la manipolazione dei dati. Un DataFrame è il tipo di base per dati di qualsiasi tipo a cui è possibile applicare operazioni aritmetiche.
Il compito del DataCollector (generic_pipe_3e059017.py
) è caricare un DataFrame esistente da un file, aggiungere i dati OHLCV più recenti e salvarli nuovamente nel file.
import time, queue
import pandas as pd
from pathlib import Path
try:
from element_types import Record, Function, ProcCMD, GuiCMD
except ImportError:
from Pythonic.element_types import Record, Function, ProcCMD, GuiCMD
class Element(Function):
def __init__(self, id, config, inputData, return_queue, cmd_queue):
super().__init__(id, config, inputData, return_queue, cmd_queue)
def execute(self):
df_in = pd.DataFrame(self.inputData, columns=['close_time', 'open', 'high', 'low', 'close', 'volume'])
df_in['close_time'] = df_in['close_time'].floordiv(1000) # remove milliseconds from timestamp
file_path = Path.home() / 'Pythonic' / 'executables' / 'ADAUSD_5m.df'
try:
# load existing dataframe
df = pd.read_pickle(file_path)
# count existing rows
n_row_cnt = df.shape[0]
# concat latest OHLCV data
df = pd.concat([df,df_in], ignore_index=True).drop_duplicates(['close_time'])
# reset the index
df.reset_index(drop=True, inplace=True)
# calculate number of new rows
n_new_rows = df.shape[0] - n_row_cnt
log_txt = '{}: {} new rows written'.format(file_path, n_new_rows)
except Exception as e:
log_txt = 'File error - writing new one'
df = df_in
# save dataframe to file
df.to_pickle(file_path)
logInfo = Record(None, log_txt)
self.return_queue.put(logInfo)
Questo codice viene eseguito ogni cinque minuti interi poiché anche i dati OHLCV sono a intervalli di 5 minuti.
Per impostazione predefinita, l'elemento OHLCV_Query scarica solo il set di dati per l'ultimo periodo. Per avere alcuni dati per lo sviluppo dell'algoritmo di trading, fai clic con il pulsante destro del mouse sull'elemento OHLCV_Query per aprire la configurazione, imposta il Limite su 500 e attiva AcquisitionScheduler. Ciò provoca il download di 500 valori OHLCV:
(Stephan Avenwedde, CC BY-SA 4.0)
Strategia di trading
La nostra strategia di trading sarà la popolare strategia crossover EMA. L'indicatore EMA è una media mobile ponderata sugli ultimi n prezzi di chiusura che attribuisce maggiore peso ai dati sui prezzi recenti. Calcoli due serie EMA, una per un periodo più lungo (ad esempio, n = 21, linea blu) e una per un periodo più breve (ad esempio, n = 10, linea gialla).
(Stephan Avenwedde, CC BY-SA 4.0)
Il bot dovrebbe effettuare un ordine di acquisto (cerchio verde) quando l'EMA a breve termine supera l'EMA a lungo termine. Il bot dovrebbe effettuare un ordine di vendita quando l'EMA a breve termine incrocia al di sotto dell'EMA a lungo termine (cerchio arancione).
Backtest con Jupyter
Il codice di esempio su GitHub (link per il download diretto) contiene anche un file Jupyter Notebook (backtesting.ipynb
) che utilizzi per testare e sviluppare l'algoritmo di trading.
Nota: Jupyter non è preinstallato sull'immagine Pythonic Raspberry Pi. Puoi installarlo anche sul Raspberry Pi o installarlo sul tuo normale PC. Io consiglio quest'ultimo, poiché effettuerai alcuni calcoli di numeri che sono molto più veloci su una normale CPU x86.
Avvia Jupyter e apri il taccuino. Assicurati di avere a disposizione un DataFrame, scaricato da DataCollector. Con Maiusc+Invio puoi eseguire ciascuna cella individualmente. Dopo aver eseguito le prime tre celle, dovresti ottenere un output come questo:
(Stephan Avenwedde, CC BY-SA 4.0)
Ora calcola i valori EMA-10 ed EMA-21. Fortunatamente, Pandas ti offre la funzione ewm
, che fa esattamente ciò di cui hai bisogno. I valori EMA vengono aggiunti come colonne separate al DataFrame:
(Stephan Avenwedde, CC BY-SA 4.0)
Per determinare se una condizione di acquisto o di vendita è soddisfatta, devi considerare queste quattro variabili:
- emaLong0: valore EMA attuale a lungo termine (ema-21)
- emaLong1: ultimo valore EMA a lungo termine (ema-21) (il valore prima di emaLong0)
- emaShort0: valore EMA attuale a breve termine (ema-10)
- emaShort1: ultimo valore EMA a breve termine (ema-10) (il valore prima di emaShort0)
Quando si verifica la seguente situazione, viene soddisfatta una condizione di acquisto:
(Stephan Avenwedde, CC BY-SA 4.0)
Nel codice Python:
emaLong1 > emaShort1 and emaShort0 > emaLong0
Una condizione di vendita è soddisfatta nella seguente situazione:
(Stephan Avenwedde, CC BY-SA 4.0)
Nel codice Python:
emaShort1 > emaLong1 and emaLong0 > emaShort0
Per testare DataFrame e valutare il possibile profitto che potresti ottenere, potresti eseguire l'iterazione su ogni riga e verificare queste condizioni oppure, con un approccio più intelligente, filtrare il set di dati solo sulle righe pertinenti con i metodi integrati di Pandas.
Sotto il cofano, Pandas utilizza NumPy, che è il metodo preferito per operazioni di dati veloci ed efficienti sugli array. Questo è ovviamente conveniente perché l'uso successivo avverrà su un Raspberry Pi con una CPU ARM.
Per motivi di chiarezza, negli esempi seguenti viene utilizzato il DataFrame dell'esempio (ADAUSD_5m.df
) con solo 20 voci. Il codice seguente aggiunge una colonna di valori booleani dipendenti dalla condizione emaShort0 > emaLong0
:
(Stephan Avenwedde, CC BY-SA 4.0)
Il luogo di interesse è quando un False passa a True (acquista) o quando True passa a False. Per filtrarli, applica un'operazione diff
alla colonna condizione. L'operazione diff
calcola la differenza tra la riga corrente e quella precedente. In termini di valori booleani, il risultato è:
- Falso
diff
Falso = Falso - Falso
diff
Vero=Vero - Vero
diff
Vero = Falso - Vero
diff
Falso=Vero
Con il seguente codice, applichi l'operazione diff
come filtro alla colonna condizione senza modificarla:
(Stephan Avenwedde, CC BY-SA 4.0)
Di conseguenza, ottieni i dati desiderati: la prima riga (indice 2) segnala una condizione di acquisto e la seconda riga (indice 8) segnala una condizione di vendita. Poiché ora disponi di un modo efficiente per estrarre dati rilevanti, puoi calcolare il possibile profitto.
Per fare ciò, devi scorrere le righe e calcolare il possibile profitto in base alle operazioni simulate. La variabile bBought
salva lo stato se hai già acquistato e buyPrice
memorizza il prezzo che hai acquistato tra le iterazioni. Salti anche il primo indicatore di vendita poiché non ha senso vendere prima ancora di aver acquistato.
profit = 0.0
buyPrice = 0.0
bBought = False
for index, row, in trades.iterrows():
# skip first sell-indicator
if not row['condition'] and not bBought:
continue
# buy-indication
if row['condition'] and not bBought:
bBought = True
buyPrice = row['close']
# sell-indication
if not row['condition'] and bBought:
bBought = False
sellPrice = row['close']
orderProfit = (sellPrice * 100) / buyPrice - 100
profit += orderProfit
Il tuo mini set di dati a singola operazione ti fornirebbe il seguente profitto:
(Stephan Avenwedde, CC BY-SA 4.0)
Nota: come puoi vedere, la strategia avrebbe dato un risultato pessimo poiché avresti acquistato a $2,5204 e venduto a $2,5065, causando una perdita dello 0,55% (commissioni sugli ordini non incluse). Tuttavia, questo è uno scenario reale: una strategia non funziona per ogni scenario. Sta a te trovare i parametri più promettenti (ad esempio, utilizzare OHLCV su base oraria avrebbe più senso in generale).
Implementazione
Puoi trovare l'attuazione della decisione nella scheda Area 1.
(Stephan Avenwedde, CC BY-SA 4.0)
Implementa le seguenti funzionalità:
- BotScheduler: uguale a AcquisitionScheduler: attiva gli elementi successivi ogni cinque minuti
- Ritardo: ritarda l'esecuzione di 30 secondi per garantire che i dati OHLCV più recenti siano stati scritti nel file
- Valutazione: prendi la decisione di trading in base alla strategia crossover EMA
Ora sai come funzionano i processi decisionali, quindi puoi dare un'occhiata all'implementazione effettiva. Apri il file generic_pipe_29dfc189.py
. Corrisponde all'elemento Valutazione sullo schermo:
@dataclass
class OrderRecord:
orderType: bool # True = Buy, False = Sell
price: float # close price
profit: float # profit in percent
profitCumulative: float # cumulative profit in percent
class OrderType(Enum):
Buy = True
Sell = False
class Element(Function):
def __init__(self, id, config, inputData, return_queue, cmd_queue):
super().__init__(id, config, inputData, return_queue, cmd_queue)
def execute(self):
### Load data ###
file_path = Path.home() / 'Pythonic' / 'executables' / 'ADAUSD_5m.df'
# only the last 21 columsn are considered
self.ohlcv = pd.read_pickle(file_path)[-21:]
self.bBought = False
self.lastPrice = 0.0
self.profit = 0.0
self.profitCumulative = 0.0
self.price = self.ohlcv['close'].iloc[-1]
# switches for simulation
self.bForceBuy = False
self.bForceSell = False
# load trade history from file
self.trackRecord = ListPersist('track_record')
try:
lastOrder = self.trackRecord[-1]
self.bBought = lastOrder.orderType
self.lastPrice = lastOrder.price
self.profitCumulative = lastOrder.profitCumulative
except IndexError:
pass
### Calculate indicators ###
self.ohlcv['ema-10'] = self.ohlcv['close'].ewm(span = 10, adjust=False).mean()
self.ohlcv['ema-21'] = self.ohlcv['close'].ewm(span = 21, adjust=False).mean()
self.ohlcv['condition'] = self.ohlcv['ema-10'] > self.ohlcv['ema-21']
### Check for Buy- / Sell-condition ###
tradeCondition = self.ohlcv['condition'].iloc[-1] != self.ohlcv['condition'].iloc[-2]
if tradeCondition or self.bForceBuy or self.bForceSell:
orderType = self.ohlcv['condition'].iloc[-1] # True = BUY, False = SELL
if orderType and not self.bBought or self.bForceBuy: # place a buy order
msg = 'Placing a Buy-order'
newOrder = self.createOrder(True)
elif not orderType and self.bBought or self.bForceSell: # place a sell order
msg = 'Placing a Sell-order'
sellPrice = self.price
buyPrice = self.lastPrice
self.profit = (sellPrice * 100) / buyPrice - 100
self.profitCumulative += self.profit
newOrder = self.createOrder(False)
else: # Something went wrong
msg = 'Warning: Condition for {}-order met but bBought is {}'.format(OrderType(orderType).name, self.bBought)
newOrder = None
recordDone = Record(newOrder, msg)
self.return_queue.put(recordDone)
def createOrder(self, orderType: bool) -> OrderRecord:
newOrder = OrderRecord(
orderType=orderType,
price=self.price,
profit=self.profit,
profitCumulative=self.profitCumulative
)
self.trackRecord.append(newOrder)
return newOrder
Poiché il processo generale non è così complicato, voglio evidenziare alcune peculiarità:
Dati in ingresso
Il bot di trading elabora solo gli ultimi 21 elementi poiché questo è l'intervallo da considerare quando si calcola la media mobile esponenziale:
self.ohlcv = pd.read_pickle(file_path)[-21:]
Record di pista
Il tipo ListPersist è un oggetto elenco Python esteso che si scrive nel file system quando viene modificato (quando gli elementi vengono aggiunti o rimossi). Crea il file track_record.obj
sotto ~/Pythonic/executables/
dopo averlo eseguito per la prima volta.
self.trackRecord = ListPersist('track_record')
Mantenere un track record aiuta a mantenere lo stato delle attività recenti del bot.
Plausibilità
L'algoritmo restituisce un oggetto del tipo OrderRecord nel caso in cui le condizioni per un'operazione siano soddisfatte. Tiene inoltre traccia della situazione generale: ad esempio, se è stato ricevuto un segnale di acquisto, ma bBought
indica che hai già acquistato in precedenza, qualcosa deve essere andato storto:
else: # Something went wrong
msg = 'Warning: Condition for {}-order met but bBought is {}'.format(OrderType(orderType).name, self.bBought)
newOrder = None
In questo scenario, viene restituito None con un messaggio di log corrispondente.
Simulazione
L'elemento Valutazione (generic_pipe_29dfc189.py
) contiene queste opzioni che ti consentono di forzare l'esecuzione di un ordine di acquisto o di vendita:
self.bForceBuy = False
self.bForceSell = False
Apri l'IDE del server di codice (http : //PythonicRPI:8000/), carica generic_pipe_29dfc189.py
e imposta uno degli interruttori su True. Collegati al debugger e aggiungi un punto di interruzione in cui il percorso di esecuzione entra nelle condizioni inner if.
(Stephan Avenwedde, CC BY-SA 4.0)
Ora apri la GUI di programmazione, aggiungi un elemento ManualScheduler (configurato su single fire) e collegalo direttamente all'elemento Evaluation per attivarlo manualmente:
(Stephan Avenwedde, CC BY-SA 4.0)
Fare clic sul pulsante di riproduzione. L'elemento Evaluation viene attivato direttamente e il debugger si ferma al punto di interruzione precedentemente impostato. Ora puoi aggiungere, rimuovere o modificare manualmente gli ordini dal track record per simulare determinati scenari:
(Stephan Avenwedde, CC BY-SA 4.0)
Aprire la finestra del messaggio di registro (pulsante con contorno verde) e la finestra dei dati di output (pulsante con contorno arancione):
(Stephan Avenwedde, CC BY-SA 4.0)
Vedrai i messaggi di registro e l'output dell'elemento Valutazione e quindi il comportamento dell'algoritmo decisionale in base al tuo input:
(Stephan Avenwedde, CC BY-SA 4.0)
Riepilogo
L'esempio si ferma qui. L'implementazione finale potrebbe avvisare l'utente su un'indicazione commerciale, effettuare un ordine su una borsa o interrogare in anticipo il saldo del conto. A questo punto dovresti sentire che tutto si connette ed essere in grado di procedere da solo.
Usare Pythonic come base per il tuo bot di trading è una buona scelta perché funziona su un Raspberry Pi, è interamente accessibile da un browser web e dispone già di funzionalità di registrazione. È anche possibile fermarsi su un punto di interruzione senza disturbare l'esecuzione di altre attività utilizzando le capacità multiprocessing di Pythonic.