You've successfully subscribed to Alpaca Learn | Developer-First API for Crypto and Stocks
Great! Next, complete checkout for full access to Alpaca Learn | Developer-First API for Crypto and Stocks
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.
Success! Your billing info is updated.
Billing info update failed.
Crypto

On-Chain Metric Investing Strategy with Glassnode API

Gareth Wu
Gareth Wu

Background

Cryptocurrency valuation is tricky. Unlike investing in traditional companies, fundamental investors cannot rely on metrics like year-over-year revenue, profitability, or other income sheet statistics, since blockchains aren’t exactly companies. There are a number of unclear factors that can influence a cryptocurrency’s price. What can investors look to instead of income sheets or cash flow statements then? The answer is on-chain metrics: financial indicators for blockchains. Instead of looking at customer retention rates or quantity of products sold, we can look at daily active addresses or daily transaction volume.

Glassnode API for On-Chain Metrics

How do we integrate on-chain metrics into algorithmic trading? We can use Glassnode API to fetch on-chain metric data, perform calculations on the data, and implement trading with our APIs. We'll also show off our new python SDK: Alpaca-py.

Strategy Overview

Let's implement a simple algorithm by analyzing three specific on-chain metrics:

  • Active Addresses
  • Transaction Volume
  • Gas Fees

We'll perform linear regressions of these 3 statistics over time, and then trade based on volatility. For example, a risk-averse algo would only trade if all three metrics are positive over a long period of time. A more risk-seeking algo would trade even if one or two metrics are positive over a short period of time. For the purposes of this guide, we are going to implement a risk-averse strategy, although parameters can be changed to make the algo riskier.

Implementation

To create an algorithmic strategy using on-chain metrics, fetch data from Glassnode API, a service that provides on-chain data. We then want to extract a certain number of rows from the dataframe, and store tuples of on-chain data paired with timeframes. Then, we run a linear regression analysis on these tuples of data. Next, we find the slope of the line of best fit. Finally, we make trades using Alpaca based on the sign (positive or negative) and magnitude of the slope. For the purposes of this algorithm (do your own research as well!) we will operate under the following investment theses (this is not financial advice):

  1. An upward sloping trend in active addresses could indicate an increase in crypto adoption/interest, and opens up the potential for increased transaction volume and overall activity on the network.
  2. An upward sloping trend in transaction volume indicates trust in the network to execute transactions, and or excitement regarding applications built on the network, and thus may spur increased network growth and adoption.
  3. An upward sloping trend in gas fees means higher network activity, indicating signs of potential future growth. We note that lower gas fees doesn’t necessarily mean less developer activity - cheap gas is great for Blockchain Devs!

By trading when multiple of these slopes are above a certain threshold, we can hope to have better indicators and predictors of future cryptocurrency value.

Getting Started

import requests
from scipy.stats import linregress
import pandas as pd
import config
from alpaca.trading.client import TradingClient
from alpaca.trading.requests import MarketOrderRequest
from alpaca.trading.enums import OrderSide, TimeInForce
import asyncio

Import the above packages. Notice that alpaca-py follows an object-oriented approach. We will use asyncio and requests to get data from GlassNode. SciPy and pandas will help us store the data and perform calculations on the data. Lastly, the alpaca-py module helps us place the final trades.

GLASSNODE_KEY = config.GLASSNODE_KEY
API_KEY = config.API_KEY
SECRET_KEY = config.SECRET_KEY
 
HEADERS = {'APCA-API-KEY-ID': API_KEY,'APCA-API-SECRET-KEY': SECRET_KEY}
 
trading_client = TradingClient(API_KEY, SECRET_KEY)

Head over to https://glassnode.com if you don’t already have an account, and save your API key within GLASSNODE_KEY. We do this here in a config.py file, but where you keep the key is up to you. Do the same for your Alpaca API and Secret keys. Create an Alpaca account here if you haven’t already: https://app.alpaca.markets/login.

Lastly, we create a connection to alpaca-py, using trading_client.

MARGIN, COUNT, asset = 1.05, 0, 'ETH'
 
values = {
  'add' : 0,
  'fee' : 0,
  'tx' : 0
}
 
assets = {'ETH': 'ETH/USD'}
 
waitTime = 200
 
rows = 100

Finally, we initialize these values. MARGIN represents the minimum slope of the metrics that we will trade on. Asset will be initialized as ‘ETH’ but can be changed depending on which cryptocurrency you wish to investigate. The notation we will use to fetch data from GlassNode is different from the notation used for Alpaca (‘ETH’ for GlassNode and ‘ETH/USD’ for Alpaca). Thus we use a dictionary for efficiency’s sake. If you plan to investigate, for example, Bitcoin instead, add something like ‘BTC’: ‘BTC/USD’ to the dictionary.


Getting Data

def submit_req(URL: str, asset : str):
    request = requests.get('https://api.glassnode.com/v1/metrics/' + URL,
    params={'a': asset, 'api_key': GLASSNODE_KEY})
    return request

This is the function we will use to submit requests to GlassNode’s API. Notice we input a URL as well as an asset. This is because each of the three statistics we want to request will need a different url. Asset will remain Ethereum for this strategy, but you can change this to any asset you wish that is supported by both Glassnode and Alpaca.


Algorithm

async def get_dfs(symbol: str):
  # Make requests for each group of data and put them into dataframes
  address_df = pd.read_json(submit_req('addresses/active_count', symbol).text)
  address_df = address_df.tail(rows)
  fee_df = pd.read_json(submit_req('fees/gas_price_median', symbol).text)
  fee_df = fee_df.tail(rows)
  tx_df = pd.read_json(submit_req('transactions/count', symbol).text)
  tx_df = tx_df.tail(rows)

The above code sends requests for active addresses, fees, and transaction data. We store that data in Pandas dataframes.

# Extracting the values we need to run Linear Regressions
  add_tuple = (address_df.t, address_df.v)
  fee_tuple = (fee_df.t, fee_df.v)
  tx_tuple = (tx_df.t, tx_df.v)

We extract the time and value data into tuples - which we can then perform linear regressions on.

# Getting slope values and updating dictionary
  add_stat = linregress(add_tuple)
  values['add'] = add_stat.slope
  fee_stat = linregress(fee_tuple)
  values['fee'] = fee_stat.slope
  tx_stat = linregress(tx_tuple)
  values['tx']= tx_stat.slope
  print(values)

This essentially determines the line of best fit given the data - if we take the slope of that regression, we can get a sense of the general positive or negative direction of change over time.

We update our dictionary values with these slope values, as we’ll be trading based on these values.

Trading

# Updating COUNT value
async def calculate():
  tmp = 0
  for element in values:
    if values[element] > MARGIN:
      tmp += 1
  COUNT = tmp

We then use a helper function to update our COUNT value, which will be set to the number of statistics that are greater than the Margin we’ve set. Note: one way to improve this algorithm is to consider more sophisticated margin values rather than a hard-coded one (perhaps previous average + some percentage).

def owns_asset(asset: str):
  if TradingClient.get_open_position(assets[asset]):
    return False
  return True

This helper function is optional - it will tell us whether we currently own this asset using Alpaca-py notation.

def post_order(symbol: str):
  # prepare order
  try:
    market_order_data = MarketOrderRequest(
      symbol = assets[symbol],
      qty=0.01,
      side=OrderSide.BUY,
      time_in_force=TimeInForce.DAY)
 
    market_order = trading_client.submit_order(
      order_data=market_order_data)
   
    print("Bought {}".format(assets[symbol]))
    return market_order
 
  except Exception as e:
    print("Issue posting order to Alpaca: {}".format(e))
    return False

This final helper function allows us to place a trade using Alpaca-py. Notice the object-oriented nature of our new python SDK. This function abstracts the trading logic away and requires just that we need the symbol of our order, in this case ETH/USD.

async def trade(symbol: str):
  if owns_asset(symbol):
    print("None"); return False
  if COUNT >= 2:
    post_order(“ETH/USD”)
    print("Done"); return True
  return False

Finally, this trade function places a trade if and only if we don’t currently own this asset, and 2 or more of the metrics have a slope greater than the margin. Editing this logic impacts the risk of this algorithm, with a higher number implying less risk.

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

At the bottom of your code, include this sequence, helping us run our code asynchronously.

Main Function

async def main():
        while True:
            task = loop.create_task(get_dfs(asset))
            task2 = loop.create_task(calculate())
            task3 = loop.create_task(trade(asset))
            await asyncio.wait([task, task2, task3])
            # Wait for the tasks to finish
            # # Wait for the value of waitTime between each quote request
            await asyncio.sleep(waitTime)

Our main function creates tasks for each of the necessary functions we have implemented. If we run this file, our code will fetch the most recent on-chain data from Glassnode API, perform calculations on that data, and finally trade if the conditions are right.

Summary

Now you’ve learned how to incorporate on-chain metrics into your crypto trading algos! Some potential improvements to the current algo to fit your needs:

  • Update the waitTime (know how often you want to run this algo, or another)
  • If using our linear regression approach, use a more sophisticated margin (perhaps some weighted/moving average)
  • Compare activity across different blockchains

Please note that this article is for informational purposes only and not investment advice. Alpaca Crypto LLC does not recommend any specific cryptocurrencies.

Cryptocurrency is highly speculative in nature, involves a high degree of risks, such as volatile market price swings, market manipulation, flash crashes, and cybersecurity risks. Cryptocurrency is not regulated or is lightly regulated in most countries. Cryptocurrency trading can lead to large, immediate and permanent loss of financial value. You should have appropriate knowledge and experience before engaging in cryptocurrency trading. For additional information please click here.

Cryptocurrency services are made available by Alpaca Crypto LLC ("Alpaca Crypto"), a FinCEN registered money services business (NMLS # 2160858), and a wholly-owned subsidiary of AlpacaDB, Inc. Alpaca Crypto is not a member of SIPC or FINRA. Cryptocurrencies are not stocks and your cryptocurrency investments are not protected by either FDIC or SIPC. Depending on your location, cryptocurrency services may be provided by West Realm Shires Services, Inc., d/b/a FTX US (NMLS #1957771). Please see the Disclosure Library for more information.

This is not an offer, solicitation of an offer, or advice to buy or sell cryptocurrencies, or open a cryptocurrency account in any jurisdiction where Alpaca Crypto, or FTX US respectively, are not registered or licensed, as applicable.

CryptoPythonUse Cases

Gareth Wu

Applied Math (CS + Econ) at Harvard. Interested in crypto, quant finance, product management and software engineering.