crypto-etl-analytics-core
End-to-end Python and MySQL data engineering pipeline with OOP analytics library for cryptocurrency price data
How do I install this agent skill?
npx skills add https://github.com/aradotso/data-skills --skill crypto-etl-analytics-coreIs this agent skill safe to install?
- Gen Agent Trust Hubwarn
The skill establishes a data pipeline that requires downloading code from an unverified GitHub repository and installing external libraries. Additionally, it processes external data from a database, creating a potential surface for indirect prompt injection.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
crypto-etl-analytics-core
Skill by ara.so — Data Skills collection
A foundational data engineering pipeline and object-oriented analytics library for cryptocurrency market data. This project demonstrates pure Python financial calculations (without Pandas/NumPy) using MySQL as the data warehouse, OHLCV candlestick objects, and computed metrics like returns, drawdowns, and volatility.
What It Does
- ETL Pipeline: Extracts crypto price data, transforms via staging tables, loads into strict MySQL schema
- Data Validation: Enforces integrity constraints (no negative volumes, valid wicks, chronological order)
- OOP Design: Encapsulates OHLCV data in
Candleobjects with built-in validation - Financial Analytics: Calculates simple/log returns, drawdowns, rolling volatility without external math libraries
- Visualization: Generates matplotlib charts for price action and computed metrics
Installation
git clone https://github.com/Jad-srifi/crypto-etl-analytics-core.git
cd crypto-etl-analytics-core
pip install mysql-connector-python matplotlib
Database Setup
- Execute the schema creation script in your MySQL instance:
mysql -u your_user -p < 00_table_creation.sql
- Create environment variables for database credentials:
export DB_HOST=localhost
export DB_USER=your_user
export DB_PASSWORD=your_password
export DB_NAME=crypto_db
Core Architecture
1. Database Schema
The crypto_1d_candles table enforces strict data integrity:
CREATE TABLE crypto_1d_candles (
candle_id INT AUTO_INCREMENT PRIMARY KEY,
asset_id INT NOT NULL,
candle_date DATE NOT NULL,
open_price DECIMAL(18,8) NOT NULL CHECK (open_price > 0),
high_price DECIMAL(18,8) NOT NULL CHECK (high_price > 0),
low_price DECIMAL(18,8) NOT NULL CHECK (low_price > 0),
close_price DECIMAL(18,8) NOT NULL CHECK (close_price > 0),
volume DECIMAL(20,8) NOT NULL CHECK (volume >= 0),
CONSTRAINT valid_wick CHECK (high_price >= open_price
AND high_price >= close_price
AND low_price <= open_price
AND low_price <= close_price),
UNIQUE KEY unique_asset_date (asset_id, candle_date)
);
2. Candle Object (OOP Model)
The core data structure encapsulating OHLCV data:
class Candle:
def __init__(self, date, open_price, high, low, close, volume):
self.date = date
self.open = float(open_price)
self.high = float(high)
self.low = float(low)
self.close = float(close)
self.volume = float(volume)
# Validation
if not (self.high >= self.open and self.high >= self.close):
raise ValueError(f"Invalid wick on {date}: high must be >= open and close")
if not (self.low <= self.open and self.low <= self.close):
raise ValueError(f"Invalid wick on {date}: low must be <= open and close")
if self.volume < 0:
raise ValueError(f"Negative volume on {date}")
def __repr__(self):
return f"Candle({self.date}, O:{self.open}, H:{self.high}, L:{self.low}, C:{self.close}, V:{self.volume})"
3. ETL Pipeline
Extract from MySQL using CTEs:
import mysql.connector
import os
def extract_candles(asset_name):
conn = mysql.connector.connect(
host=os.getenv('DB_HOST', 'localhost'),
user=os.getenv('DB_USER'),
password=os.getenv('DB_PASSWORD'),
database=os.getenv('DB_NAME')
)
cursor = conn.cursor()
query = """
WITH asset_filter AS (
SELECT asset_id FROM assets WHERE asset_name = %s
)
SELECT candle_date, open_price, high_price, low_price, close_price, volume
FROM crypto_1d_candles
WHERE asset_id = (SELECT asset_id FROM asset_filter)
ORDER BY candle_date ASC
"""
cursor.execute(query, (asset_name,))
rows = cursor.fetchall()
candles = []
for row in rows:
candle = Candle(
date=row[0],
open_price=row[1],
high=row[2],
low=row[3],
close=row[4],
volume=row[5]
)
candles.append(candle)
cursor.close()
conn.close()
return candles
Analytics Library
Returns Calculation
Simple Returns:
def calculate_simple_returns(candles):
"""Calculate period-over-period simple returns."""
if len(candles) < 2:
return []
returns = []
for i in range(1, len(candles)):
prev_close = candles[i-1].close
curr_close = candles[i].close
simple_return = (curr_close - prev_close) / prev_close
returns.append(simple_return)
return returns
Log Returns:
import math
def calculate_log_returns(candles):
"""Calculate natural logarithm returns for statistical analysis."""
if len(candles) < 2:
return []
log_returns = []
for i in range(1, len(candles)):
prev_close = candles[i-1].close
curr_close = candles[i].close
log_return = math.log(curr_close / prev_close)
log_returns.append(log_return)
return log_returns
Cumulative Returns:
def calculate_cumulative_returns(returns):
"""Calculate cumulative returns from simple return series."""
cumulative = []
cum_value = 1.0
for ret in returns:
cum_value *= (1 + ret)
cumulative.append(cum_value - 1) # Convert back to return format
return cumulative
Drawdown Calculation
def calculate_drawdowns(candles):
"""Calculate drawdown from rolling peak for each candle."""
if not candles:
return []
drawdowns = []
running_peak = candles[0].close
for candle in candles:
if candle.close > running_peak:
running_peak = candle.close
drawdown = (candle.close - running_peak) / running_peak
drawdowns.append(drawdown)
return drawdowns
def max_drawdown(drawdowns):
"""Find the maximum drawdown from a drawdown series."""
if not drawdowns:
return 0.0
return min(drawdowns) # Most negative value
Rolling Volatility
def calculate_rolling_volatility(log_returns, window=30):
"""Calculate rolling standard deviation of log returns."""
if len(log_returns) < window:
return []
volatilities = []
for i in range(window - 1, len(log_returns)):
window_returns = log_returns[i - window + 1 : i + 1]
# Calculate mean
mean_return = sum(window_returns) / len(window_returns)
# Calculate variance
variance = sum((r - mean_return) ** 2 for r in window_returns) / (len(window_returns) - 1)
# Standard deviation
volatility = math.sqrt(variance)
volatilities.append(volatility)
return volatilities
Visualization
import matplotlib.pyplot as plt
def plot_price_and_returns(candles, returns):
"""Generate dual-axis plot for price and returns."""
dates = [c.date for c in candles]
closes = [c.close for c in candles]
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
# Price chart
ax1.plot(dates, closes, label='Close Price', color='blue', linewidth=1.5)
ax1.set_ylabel('Price (USD)')
ax1.set_title('Cryptocurrency Price Analysis')
ax1.legend()
ax1.grid(True, alpha=0.3)
# Returns chart
return_dates = dates[1:] # Offset by 1 since returns start at t=1
ax2.plot(return_dates, returns, label='Daily Returns', color='green', linewidth=1)
ax2.axhline(y=0, color='red', linestyle='--', alpha=0.5)
ax2.set_ylabel('Return')
ax2.set_xlabel('Date')
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('crypto_analysis.png', dpi=300)
plt.show()
Complete Usage Example
import os
from candle import Candle
from etl import extract_candles
from analytics import (
calculate_log_returns,
calculate_cumulative_returns,
calculate_drawdowns,
calculate_rolling_volatility
)
from visualizations import plot_price_and_returns
def main():
# Extract data
asset = 'BTC'
candles = extract_candles(asset)
print(f"Extracted {len(candles)} candles for {asset}")
# Calculate metrics
log_returns = calculate_log_returns(candles)
cumulative_returns = calculate_cumulative_returns(log_returns)
drawdowns = calculate_drawdowns(candles)
volatility = calculate_rolling_volatility(log_returns, window=30)
# Print summary statistics
print(f"Total Return: {cumulative_returns[-1]:.2%}")
print(f"Max Drawdown: {max(drawdowns):.2%}")
print(f"Avg 30-day Volatility: {sum(volatility)/len(volatility):.4f}")
# Generate visualizations
plot_price_and_returns(candles, log_returns)
if __name__ == '__main__':
main()
Data Validation Patterns
Chronological Order Check:
def validate_chronological_order(candles):
"""Ensure candles are in ascending date order."""
for i in range(1, len(candles)):
if candles[i].date <= candles[i-1].date:
raise ValueError(f"Candles not in chronological order at index {i}")
return True
Completeness Check:
from datetime import timedelta
def check_missing_dates(candles):
"""Identify gaps in daily data."""
if len(candles) < 2:
return []
missing = []
for i in range(1, len(candles)):
date_diff = (candles[i].date - candles[i-1].date).days
if date_diff > 1:
missing.append((candles[i-1].date, candles[i].date, date_diff - 1))
return missing
Common Patterns
Staging Table Pattern
Use a staging table to dynamically map asset names to IDs:
CREATE TEMPORARY TABLE staging_candles (
asset_name VARCHAR(50),
candle_date DATE,
open_price DECIMAL(18,8),
high_price DECIMAL(18,8),
low_price DECIMAL(18,8),
close_price DECIMAL(18,8),
volume DECIMAL(20,8)
);
INSERT INTO crypto_1d_candles (asset_id, candle_date, open_price, high_price, low_price, close_price, volume)
SELECT a.asset_id, s.candle_date, s.open_price, s.high_price, s.low_price, s.close_price, s.volume
FROM staging_candles s
JOIN assets a ON s.asset_name = a.asset_name;
Batch Processing Pattern
def process_multiple_assets(asset_list):
"""Process analytics for multiple cryptocurrencies."""
results = {}
for asset in asset_list:
try:
candles = extract_candles(asset)
returns = calculate_log_returns(candles)
cum_returns = calculate_cumulative_returns(returns)
results[asset] = {
'total_return': cum_returns[-1] if cum_returns else 0,
'num_candles': len(candles)
}
except Exception as e:
print(f"Error processing {asset}: {e}")
results[asset] = {'error': str(e)}
return results
Troubleshooting
Issue: MySQL connection fails
- Verify environment variables are set:
echo $DB_USER - Check MySQL service is running:
systemctl status mysql - Test connection:
mysql -u $DB_USER -p$DB_PASSWORD
Issue: Invalid wick constraint violation
- Check raw data before insertion:
high >= max(open, close)andlow <= min(open, close) - Use staging table to filter invalid rows before final insert
Issue: Returns calculation produces NaN
- Ensure candles list has at least 2 elements
- Check for zero or negative prices in data
- Validate chronological order with
validate_chronological_order()
Issue: Plotting fails with index mismatch
- Remember returns series is length n-1 for n candles
- Use
dates[1:]when plotting returns alongside prices
Issue: Slow queries for large datasets
- Add indexes:
CREATE INDEX idx_asset_date ON crypto_1d_candles(asset_id, candle_date) - Use date range filters in WHERE clauses
- Consider materialized views for frequently accessed aggregations
Next Steps
This pure-Python implementation demonstrates core concepts. For production scale:
- Migrate to Pandas for vectorized operations
- Use NumPy for optimized mathematical computations
- Implement async database connections for concurrent asset processing
- Add caching layer (Redis) for frequently accessed candle series
- Integrate Airflow/Prefect for orchestrated ETL scheduling
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/aradotso/data-skills/crypto-etl-analytics-core">View crypto-etl-analytics-core on skillZs</a>