A Simple Mean Reversion Stock Trading Script in C#
A code snippet for a simple mean reversion stock trading in C#. We use fundamental analysis in this algorithmic trading.
Full code for this project is here.
Python is not the only language for algorithmic trading
In the past, I’ve published stories on Medium showing how to write algorithms that trade stocks based on company fundamentals and how to run a technical analysis day trading algorithm in the cloud. Both of those articles assumed that:
- Python was the language the reader wanted to use.
- You had access to an Alpaca brokerage account and could therefore use Polygon’s premium data feed.
This meant that those outside the US were out of luck, as you need to be a US citizen to trade with Alpaca. In this article, though, I’ll show an example implementation of a new C# trading script. Because it uses their free paper-trading API, anyone can run it. You‘ll be able to test it out in their paper trading environment, whether or not you have money in an account with them.
Using the Paper Trading API with a C# Script
To get access to Alpaca’s free paper trading API, sign up at Alpaca’s website. Once you’ve done that, you should have access to the dashboard, where you can watch the performance and contents of your portfolio change as your algorithms run. For now, you’ll just need to hit the “Generate Keys” button to get started.
The Code
Once you’ve got your API keys, we can go ahead and use them to get our code running. I’ll post the code first, then explain what it’s doing below.
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.
}
}
}
}
We’ll be using the C# SDK for Alpaca’s trade API, which you can install from NuGet. (The SDK is hosted on GitHub and is open source, so feel free to take a look at the underlying code for yourself.)
To connect to the API, we simply create a new REST client, giving it our keys and the API URL. (All of these pieces of info can be taken from the Alpaca dashboard, and you should fill in the REPLACEMEs near the top of the file with your own keys.) Using the REST client, we check Alpaca’s clock and calendar API endpoints to figure out when the market will open and when we’re getting too near to close. (We don’t want to hold the position overnight, so we liquidate the position before close to be safe.)
Trade Logic of this Algorithm
The trading logic of the algorithm is simple. It looks at data for one symbol — set here to SPY, as an example — and each minute, it checks on its current price and its average price over the last 20 minutes. If the current price is higher than the average, it doesn’t want to hold any position. However, if the current price is lower than average, it wants to allocate a certain percentage of your portfolio to holding shares. It’s following the economic theory of mean reversion, assuming that when the price is below the average, it’s more likely to come back up.
The “scale” variable at the top determines how much of your portfolio goes into the position. You can see exactly how it factors in with this chunk of code:
Decimal avg = bars.Average(item => item.Close);
Decimal currentPrice = bars.Last().Close;
Decimal diff = avg - currentPrice;
// ...
Decimal portfolioShare = diff / currentPrice * scale;
The scale is by default set to 200, and if you follow the code above, you’ll see that means that a change of .5% from the 20 minute average would cause 100% of your portfolio to be invested.
I encourage you to play around with the scale and different symbols, and see if you can find a combination that works for you. SPY is used as an example because it has a high trading volume and does not tend to move too dramatically during market hours. I encourage you to try different symbols and scales and see if you can find an edge on any stocks with this approach. If you’d like to augment the code, too, you might practice by extending the algorithm to check the EMA as well and factor that indicator into its purchasing decisions.
Later, I’ll post another article showing how we can use Polygon’s premium data feed to improve this script. If you’re interested in giving that version a try, you’ll need a brokerage account with Alpaca. With a live trading account, you’ll be ready to give the other version a try as well as apply this code to your own trading ideas.
Technology and services are offered by AlpacaDB, Inc. Brokerage services are provided by Alpaca Securities LLC (alpaca.markets), member FINRA/SIPC. Alpaca Securities LLC is a wholly-owned subsidiary of AlpacaDB, Inc.
You can find us @AlpacaHQ, if you use twitter.