La versione originale di questo articolo chiamava funzioni come alpaca.PlaceOrder, alpaca.GetQuote e alpaca.ListTrades come funzioni a livello di package. Nessuna di queste esiste nell’SDK corrente. La vera libreria usa una struct client. Questo articolo usa il pacchetto reale github.com/alpacahq/alpaca-trade-api-go/v3/alpaca.
Inizia sempre con il paper trading. L’ambiente paper (https://paper-api.alpaca.markets) usa dati di mercato reali con denaro simulato. Testa la tua strategia a fondo prima di passare a https://api.alpaca.markets.
Setup: Account e Inizializzazione del Client#
Registrati su alpaca.markets, vai su Paper Trading e genera le chiavi API. Gli ambienti paper e live usano coppie di chiavi separate.
package main
import (
"fmt"
"log"
"os"
"github.com/alpacahq/alpaca-trade-api-go/v3/alpaca"
)
func newClient() *alpaca.Client {
return alpaca.NewClient(alpaca.ClientOpts{
APIKey: os.Getenv("APCA_API_KEY_ID"),
APISecret: os.Getenv("APCA_API_SECRET_KEY"),
BaseURL: "https://paper-api.alpaca.markets",
})
}
func printAccount(client *alpaca.Client) error {
account, err := client.GetAccount()
if err != nil {
return fmt.Errorf("get account: %w", err)
}
fmt.Printf("equity: $%s\n", account.Equity)
fmt.Printf("buying power: $%s\n", account.BuyingPower)
fmt.Printf("cash: $%s\n", account.Cash)
return nil
}
func main() {
client := newClient()
if err := printAccount(client); err != nil {
log.Fatal(err)
}
}Non hardcodare mai le chiavi API. Leggile da variabili d’ambiente o da un secrets manager.
Stato dell’Account e del Portfolio#
package main
import (
"fmt"
"strings"
"github.com/alpacahq/alpaca-trade-api-go/v3/alpaca"
)
func printPortfolio(client *alpaca.Client) error {
positions, err := client.GetPositions()
if err != nil {
return fmt.Errorf("get positions: %w", err)
}
if len(positions) == 0 {
fmt.Println("nessuna posizione aperta")
return nil
}
fmt.Printf("%-8s %8s %12s %12s %10s\n", "SYMBOL", "QTY", "ENTRY", "CURRENT", "P&L")
fmt.Println(strings.Repeat("-", 54))
for _, p := range positions {
fmt.Printf(
"%-8s %8s %12s %12s %10s\n",
p.Symbol,
p.Qty,
p.AvgEntryPrice,
p.CurrentPrice,
p.UnrealizedPL,
)
}
return nil
}Dati di Mercato: Barre Storiche#
Il client per i dati di mercato e una struct separata nel pacchetto marketdata con il proprio base URL.
package main
import (
"fmt"
"os"
"time"
"github.com/alpacahq/alpaca-trade-api-go/v3/marketdata"
)
func getHistoricalBars(symbol string, days int) ([]marketdata.Bar, error) {
mdClient := marketdata.NewClient(marketdata.ClientOpts{
APIKey: os.Getenv("APCA_API_KEY_ID"),
APISecret: os.Getenv("APCA_API_SECRET_KEY"),
})
end := time.Now()
start := end.AddDate(0, 0, -days)
bars, err := mdClient.GetBars(symbol, marketdata.GetBarsRequest{
TimeFrame: marketdata.OneDay,
Start: start,
End: end,
})
if err != nil {
return nil, fmt.Errorf("get bars for %s: %w", symbol, err)
}
return bars, nil
}Piazzare Ordini#
package main
import (
"fmt"
"github.com/alpacahq/alpaca-trade-api-go/v3/alpaca"
)
func placeMarketOrder(client *alpaca.Client, symbol string, qty float64, side alpaca.Side) (*alpaca.Order, error) {
order, err := client.PlaceOrder(alpaca.PlaceOrderRequest{
Symbol: symbol,
Qty: alpaca.RoundToFractionOrWhole(qty),
Side: side,
Type: alpaca.Market,
TimeInForce: alpaca.Day,
})
if err != nil {
return nil, fmt.Errorf("place market order %s %s: %w", side, symbol, err)
}
return order, nil
}
func placeLimitOrder(client *alpaca.Client, symbol string, qty, limitPrice float64, side alpaca.Side) (*alpaca.Order, error) {
lp := alpaca.RoundToFractionOrWhole(limitPrice)
order, err := client.PlaceOrder(alpaca.PlaceOrderRequest{
Symbol: symbol,
Qty: alpaca.RoundToFractionOrWhole(qty),
Side: side,
Type: alpaca.Limit,
LimitPrice: &lp,
TimeInForce: alpaca.GTC,
})
if err != nil {
return nil, fmt.Errorf("place limit order %s %s: %w", side, symbol, err)
}
return order, nil
}
func cancelAllOrders(client *alpaca.Client) error {
status := "open"
orders, err := client.GetOrders(alpaca.GetOrdersRequest{
Status: &status,
Limit: 50,
})
if err != nil {
return fmt.Errorf("get orders: %w", err)
}
for _, o := range orders {
if err := client.CancelOrder(o.ID); err != nil {
fmt.Printf("warn: cancel order %s: %v\n", o.ID, err)
}
}
return nil
}Una Semplice Strategia Momentum#
Incrocio di medie mobili: compra quando la SMA a 5 giorni supera la SMA a 20 giorni, vendi quando la supera al ribasso. E un segnale standard usato per dimostrare la struttura di un loop di strategia completo – testalo in paper prima di trarre conclusioni sul suo edge.
package main
import (
"fmt"
"log"
"math"
"github.com/alpacahq/alpaca-trade-api-go/v3/alpaca"
"github.com/alpacahq/alpaca-trade-api-go/v3/marketdata"
)
func sma(bars []marketdata.Bar, period int) float64 {
if len(bars) < period {
return 0
}
var sum float64
for _, b := range bars[len(bars)-period:] {
sum += b.Close
}
return sum / float64(period)
}
func positionSize(buyingPower, price, riskFraction float64) float64 {
capital := buyingPower * riskFraction
return capital / price
}
func runMomentumStrategy(client *alpaca.Client, symbol string) error {
bars, err := getHistoricalBars(symbol, 30)
if err != nil {
return err
}
if len(bars) < 20 {
return fmt.Errorf("dati insufficienti: %d barre, ne servono 20", len(bars))
}
sma5 := sma(bars, 5)
sma20 := sma(bars, 20)
currentPrice := bars[len(bars)-1].Close
log.Printf("%s: price=%.2f sma5=%.2f sma20=%.2f", symbol, currentPrice, sma5, sma20)
account, err := client.GetAccount()
if err != nil {
return fmt.Errorf("get account: %w", err)
}
buyingPower, _ := account.BuyingPower.Float64()
equity, _ := account.Equity.Float64()
lastEquity, _ := account.LastEquity.Float64()
// Guardia al drawdown: interrompi il trading se siamo giu piu del 5% dalla sessione precedente
if lastEquity > 0 && (equity/lastEquity-1) < -0.05 {
return fmt.Errorf("limite di drawdown raggiunto, skip del trade")
}
positions, _ := client.GetPositions()
hasPosition := false
for _, p := range positions {
if p.Symbol == symbol {
hasPosition = true
break
}
}
switch {
case sma5 > sma20 && !hasPosition:
qty := math.Floor(positionSize(buyingPower, currentPrice, 0.02))
if qty < 1 {
log.Printf("dimensione posizione troppo piccola, skip")
return nil
}
order, err := placeMarketOrder(client, symbol, qty, alpaca.Buy)
if err != nil {
return err
}
log.Printf("ordine di acquisto piazzato: %s", order.ID)
case sma5 < sma20 && hasPosition:
order, err := placeMarketOrder(client, symbol, 0, alpaca.Sell)
if err != nil {
return err
}
log.Printf("ordine di vendita piazzato: %s", order.ID)
default:
log.Println("nessun segnale, mantengo la posizione")
}
return nil
}Gestione del Rischio#
Prima di ogni sessione di trading, verifica lo stato dell’account e gli orari di mercato:
package main
import (
"fmt"
"github.com/alpacahq/alpaca-trade-api-go/v3/alpaca"
)
func preTradeChecks(client *alpaca.Client) error {
clock, err := client.GetClock()
if err != nil {
return fmt.Errorf("get clock: %w", err)
}
if !clock.IsOpen {
return fmt.Errorf("mercato chiuso, prossima apertura: %s", clock.NextOpen)
}
account, err := client.GetAccount()
if err != nil {
return err
}
if account.TradingBlocked {
return fmt.Errorf("il trading dell'account e bloccato")
}
return nil
}Aggiungi sempre un ordine stop-loss immediatamente dopo l’esecuzione di un acquisto. Un ordine day senza stop puo rimanere aperto durante le ore extended, creando perdite piu grandi del previsto.
Disciplina del Paper Trading#
La sequenza corretta prima di rischiare denaro reale:
- Esegui la strategia in paper per almeno 30 giorni di trading
- Registra ogni trade: prezzo di entrata, prezzo di uscita, dimensione, motivo
- Calcola lo Sharpe ratio e il drawdown massimo nel periodo di test
- Valida che l’edge regga in diverse condizioni di mercato (trend, range, volatilita)
- Solo allora passa al live con una piccola allocazione iniziale
Una strategia che sembra funzionare in 5 giorni di paper trading non e stata testata – e stata fortunata.
Errori comuni e come evitarli
Saltare il paper trading Il paper trading non e opzionale. L’ambiente paper esegue contro dati di mercato reali con fill simulati. Esegui la tua strategia per almeno 30 giorni di trading prima di impegnare capitale reale. Il backtesting da solo e insufficiente – l’esecuzione live ha slippage, fill parziali e latenza che i backtest nascondono.
Nessun position sizing Piazzare un numero fisso di azioni indipendentemente dalle dimensioni dell’account non e gestione del rischio. Dimensiona sempre le posizioni come frazione del capitale o del buying power. 1-2% del buying power per trade e un punto di partenza comune.
Ignorare gli orari di mercato
Gli ordini piazzati fuori dall’orario di mercato vengono messi in coda fino alla prossima apertura per impostazione predefinita. Un flusso di ordini in coda da un’esecuzione notturna puo triggerare fill inaspettati all’apertura. Controlla sempre clock.IsOpen prima di fare trading.
Non gestire i fill parziali
Un ordine di mercato per 100 azioni puo essere eseguito in tre lotti separati a prezzi leggermente diversi. Il codice di gestione degli ordini deve gestire lo stato partially_filled e tracciare il prezzo medio di fill, non solo il prezzo richiesto.
Chiavi API hardcoded Le chiavi API Alpaca danno accesso completo all’account, inclusi i prelievi in alcune configurazioni. Conservale in variabili d’ambiente o in un secrets manager, mai nel codice sorgente o nel version control.
Se vuoi approfondire questi argomenti, offro sessioni di coaching 1:1 per ingegneri che lavorano su integrazione AI, architettura cloud e platform engineering. Prenota una sessione (50 EUR / 60 min) o scrivimi a manuel.fedele+website@gmail.com.