Skip to content

Instantly share code, notes, and snippets.

@ttt733
Last active July 17, 2022 16:19
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save ttt733/3d5b47950a65157cf3c78dc7c6aab071 to your computer and use it in GitHub Desktop.
Save ttt733/3d5b47950a65157cf3c78dc7c6aab071 to your computer and use it in GitHub Desktop.
Mean Reversion Trading Algorithm (Alpaca)
using Alpaca.Markets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace Examples
{
class MeanReversionPaperOnly
{
private string API_KEY = "REPLACEME";
private string API_SECRET = "REPLACEME";
private string API_URL = "https://paper-api.alpaca.markets";
private string symbol = "SPY";
private Decimal scale = 200;
private RestClient restClient;
private Guid lastTradeId = Guid.NewGuid();
private List<Decimal> closingPrices = new List<Decimal>();
public void Run()
{
restClient = new RestClient(API_KEY, API_SECRET, API_URL);
// First, cancel any existing orders so they don't impact our buying power.
var orders = restClient.ListOrdersAsync().Result;
foreach (var order in orders)
{
restClient.DeleteOrderAsync(order.OrderId);
}
// Figure out when the market will close so we can prepare to sell beforehand.
var calendars = restClient.ListCalendarAsync(DateTime.Today).Result;
var calendarDate = calendars.First().TradingDate;
var closingTime = calendars.First().TradingCloseTime;
closingTime = new DateTime(calendarDate.Year, calendarDate.Month, calendarDate.Day, closingTime.Hour, closingTime.Minute, closingTime.Second);
Console.WriteLine("Waiting for market open...");
AwaitMarketOpen();
Console.WriteLine("Market opened.");
// Check every minute for price updates.
TimeSpan timeUntilClose = closingTime - DateTime.UtcNow;
while (timeUntilClose.TotalMinutes > 15)
{
// Cancel old order if it's not already been filled.
restClient.DeleteOrderAsync(lastTradeId);
// Get information about current account value.
var account = restClient.GetAccountAsync().Result;
Decimal buyingPower = account.BuyingPower;
Decimal portfolioValue = account.PortfolioValue;
// Get information about our existing position.
int positionQuantity = 0;
Decimal positionValue = 0;
try
{
var currentPosition = restClient.GetPositionAsync(symbol).Result;
positionQuantity = currentPosition.Quantity;
positionValue = currentPosition.MarketValue;
}
catch (Exception e)
{
// No position exists. This exception can be safely ignored.
}
var barSet = restClient.GetBarSetAsync(new String[] { symbol }, TimeFrame.Minute, 20).Result;
var bars = barSet[symbol];
Decimal avg = bars.Average(item => item.Close);
Decimal currentPrice = bars.Last().Close;
Decimal diff = avg - currentPrice;
if (diff <= 0)
{
// Above the 20 minute average - exit any existing long position.
if (positionQuantity > 0)
{
Console.WriteLine("Setting position to zero.");
SubmitOrder(positionQuantity, currentPrice, OrderSide.Sell);
}
else
{
Console.WriteLine("No position to exit.");
}
}
else
{
// Allocate a percent of our portfolio to this position.
Decimal portfolioShare = diff / currentPrice * scale;
Decimal targetPositionValue = portfolioValue * portfolioShare;
Decimal amountToAdd = targetPositionValue - positionValue;
if (amountToAdd > 0)
{
// Buy as many shares as we can without going over amountToAdd.
// Make sure we're not trying to buy more than we can.
if (amountToAdd > buyingPower)
{
amountToAdd = buyingPower;
}
int qtyToBuy = (int)(amountToAdd / currentPrice);
SubmitOrder(qtyToBuy, currentPrice, OrderSide.Buy);
}
else
{
// Sell as many shares as we can without going under amountToAdd.
// Make sure we're not trying to sell more than we have.
amountToAdd *= -1;
int qtyToSell = (int)(amountToAdd / currentPrice);
if (qtyToSell > positionQuantity)
{
qtyToSell = positionQuantity;
}
SubmitOrder(qtyToSell, currentPrice, OrderSide.Sell);
}
}
// Wait another minute.
Thread.Sleep(60000);
timeUntilClose = closingTime - DateTime.Now;
}
Console.WriteLine("Market nearing close; closing position.");
ClosePositionAtMarket();
}
private void AwaitMarketOpen()
{
while (!restClient.GetClockAsync().Result.IsOpen)
{
Thread.Sleep(60000);
}
}
// Submit an order if quantity is not zero.
private void SubmitOrder(int quantity, Decimal price, OrderSide side)
{
if (quantity == 0)
{
Console.WriteLine("No order necessary.");
return;
}
Console.WriteLine($"Submitting {side} order for {quantity} shares at ${price}.");
var order = restClient.PostOrderAsync(symbol, quantity, side, OrderType.Limit, TimeInForce.Day, price).Result;
lastTradeId = order.OrderId;
}
private void ClosePositionAtMarket()
{
try
{
var positionQuantity = restClient.GetPositionAsync(symbol).Result.Quantity;
restClient.PostOrderAsync(symbol, positionQuantity, OrderSide.Sell, OrderType.Market, TimeInForce.Day);
}
catch (Exception e)
{
// No position to exit.
}
}
}
}
@ImaginaryDevelopment
Copy link

// F# version - compiles, not tested
// alpaca.markets 3.0.3 seemed to compile
open System.Threading.Tasks

let API_KEY = "REPLACEME"
let API_SECRET = "REPLACEME"
let API_URL = "https://paper-api.alpaca.markets"

let symbol = "SPY"
let scale = 200m
let mutable restClient: RestClient = null

let mutable lastTradeId = Guid.NewGuid()
let closingPrices = ResizeArray<Decimal>();

let awaitMarketOpen() =
    while not (restClient.GetClockAsync().Result.IsOpen) do
        Thread.Sleep(60000)
        
// Submit an order if quantity is not zero.
let submitOrder(quantity:int, price:Decimal, side:OrderSide ) =
    if quantity = 0 then
        Console.WriteLine("No order necessary.");
    else
        printfn "Submitting %A order for %A shares at $%A." side quantity price
        let order = restClient.PostOrderAsync(symbol, int64 quantity, side, OrderType.Limit, TimeInForce.Day, Nullable price).Result;
        lastTradeId <- order.OrderId;
        


let closePositionAtMarket() =
    try
        let positionQuantity = restClient.GetPositionAsync(symbol).Result.Quantity
        restClient.PostOrderAsync(symbol, int64 positionQuantity, OrderSide.Sell, OrderType.Market, TimeInForce.Day)
        |> ignore<Task<_>>
    with e -> () // No position to exit.
    
let run () =
    restClient <- new RestClient(API_KEY, API_SECRET, API_URL);

    // First, cancel any existing orders so they don't impact our buying power.
    let orders = restClient.ListOrdersAsync().Result;
    orders
    |> Seq.iter(fun order ->
        restClient.DeleteOrderAsync(order.OrderId)
        |> ignore<Task<_>>
    )

    // Figure out when the market will close so we can prepare to sell beforehand.
    let calendars = restClient.ListCalendarAsync(Nullable DateTime.Today).Result
    let calendarDate = calendars.First().TradingDate
    let mutable closingTime = calendars.First().TradingCloseTime
    closingTime <- new DateTime(calendarDate.Year, calendarDate.Month, calendarDate.Day, closingTime.Hour, closingTime.Minute, closingTime.Second)

    printfn "Waiting for market open..."
    awaitMarketOpen()
    printfn "Market opened."

    // Check every minute for price updates.
    let mutable timeUntilClose = closingTime - DateTime.UtcNow;
    while timeUntilClose.TotalMinutes > 15. do
        // Cancel old order if it's not already been filled.
        restClient.DeleteOrderAsync(lastTradeId)
        |> ignore<Task<bool>>

        // Get information about current account value.
        let account = restClient.GetAccountAsync().Result
        let buyingPower = account.BuyingPower
        let portfolioValue = account.PortfolioValue

        // Get information about our existing position.
        let mutable positionQuantity = 0
        let mutable positionValue = 0m
        try
            let currentPosition = restClient.GetPositionAsync(symbol).Result
            positionQuantity <- currentPosition.Quantity
            positionValue <- currentPosition.MarketValue
        with e ->
            // No position exists. This exception can be safely ignored.
            ()

        let barSet = restClient.GetBarSetAsync(Array.singleton symbol, TimeFrame.Minute, Nullable 20).Result
        let bars = barSet.[symbol]
        let avg = bars.Average(fun item -> item.Close)
        let currentPrice = bars.Last().Close
        let diff = avg - currentPrice
        if diff <= 0m then
            // Above the 20 minute average - exit any existing long position.
            if (positionQuantity > 0) then
                Console.WriteLine("Setting position to zero.");
                submitOrder(int positionQuantity, currentPrice, OrderSide.Sell);
            else
                Console.WriteLine("No position to exit.");
        else
            // Allocate a percent of our portfolio to this position.
            let portfolioShare = diff / currentPrice * scale;
            let targetPositionValue = portfolioValue * portfolioShare;
            let mutable amountToAdd = targetPositionValue - positionValue;

            if (amountToAdd > 0m) then
                // Buy as many shares as we can without going over amountToAdd.

                // Make sure we're not trying to buy more than we can.
                if amountToAdd > buyingPower then
                    amountToAdd <- buyingPower
                let qtyToBuy = int (amountToAdd / currentPrice)

                submitOrder(int qtyToBuy, currentPrice, OrderSide.Buy)
            else
                // Sell as many shares as we can without going under amountToAdd.

                // Make sure we're not trying to sell more than we have.
                amountToAdd <- amountToAdd * -1m
                let mutable qtyToSell = int (amountToAdd / currentPrice)
                if qtyToSell > positionQuantity then
                    qtyToSell <- positionQuantity

                submitOrder(int qtyToSell, currentPrice, OrderSide.Sell)

        // Wait another minute.
        Thread.Sleep(60000)
        timeUntilClose <- closingTime - DateTime.Now;

    Console.WriteLine("Market nearing close; closing position.");
    closePositionAtMarket()

@Gabowatt
Copy link

type RestClient isn't found. what package or namespace should it be in?

I'm having the same issue.

@ImaginaryDevelopment
Copy link

type RestClient isn't found. what package or namespace should it be in?

I'm having the same issue.

It was an older version try alpaca.markets 3.0.3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment