Using OHLCV Data to Generate Trading Signals

Open-High-Low-Close-Volume (OHLCV) data is the foundation of most financial market analysis. This data structure summarizes price and activity over a set period, offering a clear view of market behavior. For developers building trading or analytics applications, turning this raw time-series data into actionable signals is a frequent requirement.

This guide presents a method for using OHLCV data to generate trading signals based on a moving average crossover system. You will see how to fetch historical data, calculate technical indicators, and identify buy or sell signals programmatically.

This guide covers:

  • Fetching historical OHLCV data for a currency pair.

  • Calculating short-term and long-term simple moving averages (SMAs).

  • Identifying crossover points between the two moving averages.

  • Generating buy and sell signals based on these crossovers.

  • Visualizing the price, indicators, and signals on a chart.

What you need:

  • Python 3.x with pandas and matplotlib.

  • The api-bricks-fx-api-rest library for the FinFeedAPI.

  • Your personal FinFeedAPI key.

1. Understanding OHLCV Data

Before writing code, it's good to know what each piece of OHLCV data represents for a given time period (e.g., one day):

  • Open: The first price at which the asset traded.

  • High: The highest price reached.

  • Low: The lowest price reached.

  • Close: The final price at which the asset traded.

  • Volume: The total amount of the asset traded.

We will use the Close price for our calculations, as it represents the final market consensus for the period.

2. Setting Up the Environment

First, install the FinFeedAPI client library if it is not already on your system.

pip install api-bricks-fx-api-rest

Next, prepare your Python script by importing the necessary libraries and configuring the API client.

# Import libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import api_bricks_fx_api_rest
from datetime import datetime

# --- API Configuration ---
# IMPORTANT: Replace "YOUR_API_KEY_HERE" with your actual key.
API_KEY = "YOUR_API_KEY_HERE"
api_client_config = api_bricks_fx_api_rest.Configuration()
api_client_config.api_key['Authorization'] = API_KEY
api_client = api_bricks_fx_api_rest.ApiClient(configuration=api_client_config)

# --- Plotting Configuration ---
plt.style.use('seaborn-v0_8-darkgrid')
plt.rcParams['figure.figsize'] = (15, 7)

3. Fetching Historical OHLCV Data

We will use the /v1/exchangerate/{base}/{quote}/history endpoint to get daily OHLCV data for the BTC/USD pair. This data will then be loaded into a pandas DataFrame for analysis.

# Initialize the ExchangeRatesApi
exchange_rates_api = api_bricks_fx_api_rest.ExchangeRatesApi(api_client)

# Define parameters for the data request
base_currency = "BTC"
quote_currency = "USD"
start_time = "2023-01-01"
end_time = "2024-12-31"
period = "1DAY"
data_limit = 500

print(f"Fetching historical data for {base_currency}/{quote_currency}...")

try:
    historical_data = exchange_rates_api.v1_exchangerate_base_quote_history_get(
        base=base_currency,
        quote=quote_currency,
        time_start=start_time,
        time_end=end_time,
        period_id=period,
        limit=data_limit
    )
    print(f"Fetched {len(historical_data)} data points.")
except api_bricks_fx_api_rest.ApiException as e:
    print(f"API Exception occurred: {e}")
    historical_data = []

# Convert the data to a pandas DataFrame
if historical_data:
    ohlcv_df = pd.DataFrame.from_records([vars(d) for d in historical_data])
    ohlcv_df['time_period_start'] = pd.to_datetime(ohlcv_df['time_period_start'])
    ohlcv_df = ohlcv_df.set_index('time_period_start')
    ohlcv_df = ohlcv_df.sort_index()
    # We will work with the closing price
    ohlcv_df = ohlcv_df[['price_close']].rename(columns={'price_close': 'close'})
else:
    ohlcv_df = pd.DataFrame()

4. Calculating Moving Averages

A simple moving average (SMA) smooths out price data by calculating the average price over a specified number of periods. We will calculate a short-term SMA (20 days) and a long-term SMA (50 days).

if not ohlcv_df.empty:
    short_window = 20
    long_window = 50

    # Calculate SMAs
    ohlcv_df['short_sma'] = ohlcv_df['close'].rolling(window=short_window, min_periods=1).mean()
    ohlcv_df['long_sma'] = ohlcv_df['close'].rolling(window=long_window, min_periods=1).mean()

    print("Moving averages calculated.")
    print(ohlcv_df.tail())

5. Generating Crossover Signals

A trading signal can be generated when the short-term SMA crosses the long-term SMA.

  • Buy Signal: The short-term SMA crosses above the long-term SMA. This suggests upward momentum is building.

  • Sell Signal: The short-term SMA crosses below the long-term SMA. This suggests downward momentum is building.

We can identify these points by observing where the relationship between the two SMAs changes from one day to the next.

if 'short_sma' in ohlcv_df.columns:
    # Create a column to identify the signal
    ohlcv_df['signal'] = 0.0
    # Generate signal when short SMA crosses long SMA
    ohlcv_df['signal'] = np.where(ohlcv_df['short_sma'] > ohlcv_df['long_sma'], 1.0, 0.0)

    # Find the exact crossover points
    ohlcv_df['position'] = ohlcv_df['signal'].diff()

    print("\nTrading signals generated.")
    print(ohlcv_df[ohlcv_df['position'] != 0].head())

In the DataFrame, a position value of 1.0 indicates a buy signal (the short SMA just crossed above the long SMA), and -1.0 indicates a sell signal.

6. Visualizing the Signals

A chart makes it easy to see the price action, the indicators, and the resulting signals together.

if 'position' in ohlcv_df.columns:
    plt.figure(figsize=(15, 8))

    # Plot closing price and SMAs
    plt.plot(ohlcv_df['close'], label='Close Price', alpha=0.5)
    plt.plot(ohlcv_df['short_sma'], label='20-Day SMA', alpha=0.75)
    plt.plot(ohlcv_df['long_sma'], label='50-Day SMA', alpha=0.75)

    # Plot buy signals
    plt.plot(ohlcv_df[ohlcv_df['position'] == 1.0].index, 
             ohlcv_df['short_sma'][ohlcv_df['position'] == 1.0],
             '^', markersize=10, color='g', lw=0, label='Buy Signal')

    # Plot sell signals
    plt.plot(ohlcv_df[ohlcv_df['position'] == -1.0].index, 
             ohlcv_df['short_sma'][ohlcv_df['position'] == -1.0],
             'v', markersize=10, color='r', lw=0, label='Sell Signal')

    plt.title(f'{base_currency}/{quote_currency} Price with SMA Crossover Signals')
    plt.xlabel('Date')
    plt.ylabel('Price')
    plt.legend()
    plt.show()

The resulting chart displays the closing price, both moving averages, and clear markers for each buy (green up-arrow) and sell (red down-arrow) signal.

Final Thoughts

This guide showed a complete process for turning raw OHLCV data into a visual trading strategy. You can build on this foundation by exploring other technical indicators, such as the Relative Strength Index (RSI) or MACD, combining different types of signals, and adding risk management rules. The methods shown here provide a solid starting point for developing more advanced analytical tools.

0
Subscribe to my newsletter

Read articles from Maciej Józefowicz directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Maciej Józefowicz
Maciej Józefowicz