Bitcoin has reached a new level of publicity in the last month. If you have spent any time looking at Bitcoin, you know it is very volatile and prices can fluctuate up and down in minutes. I’m a fairly risk-averse person so I’m not putting much of my personal money into Bitcoin, but was fortunate enough to buy a small amount back in 2013 as an experiment which has grown quite a bit. I’ve been doing a little bit of trading and exchanging to make the most of it — but struggle to stay up with current prices given heavy variability.
Instead of having to constantly check Coinbase or Bittrex, I wanted to get alerts on the coins I had or wanted. I wanted a way to automate purchase or sale of coins when the right thresholds were crossed. What I set out to build the other night was a serverless cryptocurrency watcher — one that I could assign new watchers just by sending a text like “watch btc 18000” from my phone — and it would alert me when the coin crossed that limit. I’m happy to say I built just that this week, and have been using successfully ever since. Here’s how it came together.
Watching for events in serverless can be fairly straight forward when you have a single or static number of watchers. For example, the other week I built an Azure Function to watch for new events from my ring.com doorbell. The pattern there was a single function on a timer that would wake up and check for items. However, in this case I wanted to have a dynamic ‘pool’ of watchers. One day I may request a watcher to watch Ethereum if it crosses $813, and three hours later want a parallel watcher to be checking Litecoin. While the task itself seems simple enough — building this hosted durably in the cloud can be tricky. How do you spin up new watchers? How long can a watcher run? Are they durable? Can a watcher be terminated? And additionally when working with serverless: How can I make my watcher exist for longer than the traditional 5–10 minutes?
I considered a few options to build this out on the serverless stack:
Both 2 and 3 are perfectly valid, but since I wanted a little more flexibility into things like the “success condition” for a watcher, I went with Durable Functions. It was the most complex orchestration I’d built but it worked without any hiccups and happy with my choice.
The first function receives the request to start watching a coin, and starts a new watcher process. I created an HttpWebhook function which is listening to a Twilio webhook to notify my function whenever I send a text to a specific number. I parse the command sent (“watch” or “stop”), and then spin up a new orchestration for a watcher.
A coin watcher is an instance of a durable function. In this case the durable function is a while loop: “While the current price is less than the threshold price, keep checking the price.” To make sure I don’t have watchers checking for years, I also have a limit on the while loop (settable via an app setting, right now I have a single watcher timing out after 1 week if threshold isn’t hit). I also add in a delay so I don’t keep checking the price in rapid succession.
Here’s the code to the durable orchestrator for a coin watcher:
A few callouts worth amplifying: When a durable function runs, the way it manages running long-term processes in short-lived functions is by taking advantage of state retrieved in the context
and replaying the function to resume at the next step. This means you need to be very careful in your durable function so that replays provide deterministic and consistent results. You’ll notice to get the current time, I’m actually leveraging the context with the expression context.CurrentUtcDateTime
instead of something like DateTime.Now
as the context
will return the same value on replays so not to throw off other conditions.
I’m also adding in a delay during the while loop, so I’m not getting throttled by the exchange API (in this case, Bittrex). I add a 15 minute delay with the following line which pulls down the delay interval from my Azure App Settings (so I can customize without having to re-deploy).
await context.CreateTimer(context.CurrentUtcDateTime.AddMinutes(double.Parse(Constants.DelayInterval)), CancellationToken.None);
What are the actions being called like “send_message” and “watcher_getticker?” Those are just simple Azure Functions like below:
Now whenever I text something like “watch eth 900” a durable instance will spin up and continue to check the price of Ethereum until it crosses $900 or the while loop times out (and sends me a nice text message to inform me it timed out if I want to renew).
One of the most pleasant surprises when building this was how easy it was to terminate an active instance. You may notice from the screenshot above I provide a way to cancel a watcher by texting “STOP” and some ID. Whenever a durable orchestrator starts, it returns an instance ID. I easily could have just texted back “stop {instanceId}” and been done. But an instance ID is a GUID like fe5cb9b39e0445f4b751d95fc6410ade
which would be a pain to type on a phone. So whenever I create an instance, I create an alias for it as well in Azure Table Storage. In my case, I generate a number between 1–1000. I know, I know.. this means collisions will happen 1/1000 times per phone number, but I only ever plan to have about 4 watchers active at a time and opted for ease of typing instead of high level of entropy. If someone wants to open a pull request to generate a 4 character case-sensitive alphanumeric alias I will gladly accept it 😀.
Once a stop command is received, I map it to the instance ID and can terminate the instance with a single line of code from my master function:
await starter.TerminateAsync(((Alias)result.Result).Id, “User requested terminate”);
Just what I needed and was super easy to implement.
This was really just the beginning of what I want to build out. I plan to extend the “send_event” function above to also emit an event to Azure Event Grid. As I supplement my request with more info, it could also trigger an automated purchase or sale of coins when a threshold is crossed.
I can’t state enough how much I love that I can create these little pieces of logic up in the cloud, have them run “all day, every day,” but only ever pay for the minutes of compute I actively consume.
The entire solution is checked into GitHub here, and should be compatible with the Azure Functions tools in Visual Studio 2017 if you want to deploy your own. It is written in .NET Core so compilable and runnable cross-platform as well.
jeffhollan/functions-durable-csharp-bittrex-watcher_Azure Function to monitor the price of coins on Bittrex and notify via SMS when a threshold is crossed. Leverages Azure…_github.com