Skip to content

Commit 5279e7a

Browse files
committed
feat: implement financial market data DAG and add new automated data updater DAGs for technical indicators, funding rates, and news.
1 parent d622238 commit 5279e7a

8 files changed

Lines changed: 693 additions & 231 deletions

calculate_technical_indicators.py

Lines changed: 7 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
python calculate_technical_indicators.py
1313
1414
Input:
15-
- CSV file with columns: UNIX_TIMESTAMP, DATETIME, OPEN, HIGH, CLOSE, LOW, VOLUME
15+
- CSV file with columns: UNIX_TIMESTAMP, DATETIME, OPEN, HIGH, CLOSE, LOW, VOLUME (or VOLUME_USD)
1616
1717
Output:
1818
- CSV file with OHLCV + 90+ technical indicators
@@ -24,15 +24,8 @@
2424

2525

2626
def load_ohlcv_data(filepath='bitcoin-hourly-ohlcv.csv'):
27-
"""
28-
Load OHLCV data from CSV file
29-
30-
Args:
31-
filepath: Path to CSV file with OHLCV data
32-
33-
Returns:
34-
pandas DataFrame with OHLCV data
35-
"""
27+
# Load OHLCV data from CSV file
28+
3629
print(f"📥 Loading data from {filepath}...")
3730
df = pd.read_csv(filepath)
3831

@@ -45,15 +38,8 @@ def load_ohlcv_data(filepath='bitcoin-hourly-ohlcv.csv'):
4538

4639

4740
def calculate_technical_indicators(df):
48-
"""
49-
Calculate comprehensive technical indicators using TA-Lib
50-
51-
Args:
52-
df: DataFrame with OHLCV data (columns: OPEN, HIGH, CLOSE, LOW, VOLUME)
53-
54-
Returns:
55-
DataFrame with all original columns + technical indicators
56-
"""
41+
# Calculate comprehensive technical indicators using TA-Lib
42+
5743
print("🔧 Calculating comprehensive technical indicators using TA-Lib...")
5844

5945
# Convert to numpy arrays for TA-Lib
@@ -252,27 +238,13 @@ def calculate_technical_indicators(df):
252238

253239

254240
def save_to_csv(df, filepath='bitcoin-hourly-technical-indicators.csv'):
255-
"""
256-
Save DataFrame to CSV file
257-
258-
Args:
259-
df: DataFrame to save
260-
filepath: Output file path
261-
"""
241+
# Save DataFrame to CSV file
262242
print(f"💾 Saving to {filepath}...")
263243
df.to_csv(filepath, index=False, float_format='%.8f')
264244
print(f"✅ Saved {len(df):,} records to {filepath}")
265245

266246

267-
def main():
268-
"""
269-
Main function to run the technical indicators calculation
270-
"""
271-
print("=" * 60)
272-
print("Bitcoin Technical Indicators Calculator")
273-
print("=" * 60)
274-
print()
275-
247+
def main():
276248
# Step 1: Load OHLCV data
277249
df = load_ohlcv_data('bitcoin-hourly-ohlcv.csv')
278250
print()
@@ -284,20 +256,6 @@ def main():
284256
# Step 3: Save to CSV
285257
save_to_csv(df_with_indicators, 'bitcoin-hourly-technical-indicators.csv')
286258
print()
287-
288-
print("=" * 60)
289-
print("✅ Process Complete!")
290-
print("=" * 60)
291-
print()
292-
print("📁 Output Files:")
293-
print(" • bitcoin-hourly-technical-indicators.csv")
294-
print()
295-
print("📊 Summary:")
296-
print(f" • Total Records: {len(df_with_indicators):,}")
297-
print(f" • Total Columns: {len(df_with_indicators.columns)}")
298-
print(f" • OHLCV Columns: 7")
299-
print(f" • Technical Indicators: {len(df_with_indicators.columns) - 7}")
300-
print()
301259

302260

303261
if __name__ == "__main__":

dags/OpenInterestFutures_data_updater_dag.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,48 @@
4848
tags=['bitcoin', 'futures', 'open-interest', 'daily', 'snowflake']
4949
)
5050

51+
def ensure_schema_and_table(**context):
52+
"""Create database, schema, stage and table if they don't exist"""
53+
54+
snowflake_hook = SnowflakeHook(snowflake_conn_id='snowflake_default')
55+
56+
# Create database if not exists
57+
snowflake_hook.run("CREATE DATABASE IF NOT EXISTS BITCOIN_DATA")
58+
print("✅ Database BITCOIN_DATA ensured")
59+
60+
# Create schemas if not exists
61+
snowflake_hook.run("CREATE SCHEMA IF NOT EXISTS BITCOIN_DATA.DATA")
62+
snowflake_hook.run("CREATE SCHEMA IF NOT EXISTS BITCOIN_DATA.FORECASTER")
63+
print("✅ Schemas ensured")
64+
65+
# Create stage and file format
66+
snowflake_hook.run("""
67+
CREATE FILE FORMAT IF NOT EXISTS BITCOIN_DATA.FORECASTER.json_format
68+
TYPE = 'JSON' STRIP_OUTER_ARRAY = TRUE
69+
""")
70+
snowflake_hook.run("""
71+
CREATE STAGE IF NOT EXISTS BITCOIN_DATA.FORECASTER.my_stage
72+
FILE_FORMAT = BITCOIN_DATA.FORECASTER.json_format
73+
""")
74+
print("✅ Stage and file format ensured")
75+
76+
# Create table if not exists
77+
snowflake_hook.run("""
78+
CREATE TABLE IF NOT EXISTS BITCOIN_DATA.DATA.OPEN_INTEREST_FUTURES (
79+
date DATE,
80+
unix_ts BIGINT,
81+
binance FLOAT, bybit FLOAT, okx FLOAT, bitget FLOAT,
82+
deribit FLOAT, bitmex FLOAT, huobi FLOAT, bitfinex FLOAT,
83+
gate_io FLOAT, kucoin FLOAT, kraken FLOAT, crypto_com FLOAT,
84+
dydx FLOAT, delta_exchange FLOAT, total_open_interest FLOAT,
85+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
86+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
87+
PRIMARY KEY (date)
88+
)
89+
""")
90+
print("✅ Table BITCOIN_DATA.DATA.OPEN_INTEREST_FUTURES ensured")
91+
92+
5193
def download_and_upload_open_interest(**context):
5294
"""
5395
Download open futures interest JSON data from API and upload to Snowflake stage
@@ -237,6 +279,13 @@ def cleanup_stage_files(**context):
237279

238280
return result
239281

282+
# Ensure DB infrastructure exists
283+
ensure_db_task = PythonOperator(
284+
task_id='ensure_schema_and_table',
285+
python_callable=ensure_schema_and_table,
286+
dag=dag
287+
)
288+
240289
# Create file format task
241290
create_file_format = SnowflakeOperator(
242291
task_id='create_file_format',
@@ -271,4 +320,5 @@ def cleanup_stage_files(**context):
271320
)
272321

273322
# Set task dependencies
274-
create_file_format >> download_task >> merge_task >> cleanup_task
323+
ensure_db_task >> create_file_format >> download_task >> merge_task >> cleanup_task
324+

dags/bitcoin_news_dag.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Bitcoin News DAG - Production Version
33
Fetches Bitcoin news from multiple APIs and stores in Snowflake with duplicate prevention
4+
Includes database initialization with schema/table creation for data recovery
45
"""
56

67
from datetime import datetime, timedelta
@@ -35,6 +36,42 @@
3536
tags=['bitcoin', 'news', 'snowflake', 'production'],
3637
)
3738

39+
# ─── Database Initialization ───────────────────────────────────────────
40+
41+
def ensure_schema_and_table(**context):
42+
"""Create database, schema and table in Snowflake if they don't exist"""
43+
44+
hook = SnowflakeHook(snowflake_conn_id='snowflake_default')
45+
46+
# Create database if not exists
47+
hook.run("CREATE DATABASE IF NOT EXISTS BITCOIN_DATA")
48+
print("✅ Database BITCOIN_DATA ensured")
49+
50+
# Create RAW schema if not exists (news goes to RAW schema)
51+
hook.run("CREATE SCHEMA IF NOT EXISTS BITCOIN_DATA.RAW")
52+
print("✅ Schema BITCOIN_DATA.RAW ensured")
53+
54+
# Create table if not exists
55+
create_table_sql = """
56+
CREATE TABLE IF NOT EXISTS BITCOIN_DATA.RAW.BITCOIN_NEWS (
57+
datetime TIMESTAMP,
58+
headline VARCHAR(2000),
59+
summary VARCHAR(10000),
60+
source VARCHAR(500),
61+
url VARCHAR(1000),
62+
categories VARCHAR(1000),
63+
tags VARCHAR(1000),
64+
api_source VARCHAR(100),
65+
file_name VARCHAR(500),
66+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP()
67+
)
68+
"""
69+
hook.run(create_table_sql)
70+
print("✅ Table BITCOIN_DATA.RAW.BITCOIN_NEWS ensured")
71+
72+
73+
# ─── Data Functions ────────────────────────────────────────────────────
74+
3875
def get_last_datetime_from_snowflake():
3976
"""Get the latest datetime from Snowflake to avoid duplicates"""
4077

@@ -399,7 +436,15 @@ def send_production_notification(**context):
399436
except Exception as e:
400437
print(f"Failed to send notification: {str(e)}")
401438

402-
# Define tasks
439+
# ─── Task Definitions ──────────────────────────────────────────────────
440+
441+
# Step 1: Ensure DB infrastructure exists
442+
ensure_db_task = PythonOperator(
443+
task_id='ensure_schema_and_table',
444+
python_callable=ensure_schema_and_table,
445+
dag=dag,
446+
)
447+
403448
fetch_cryptocompare_task = PythonOperator(
404449
task_id='fetch_cryptocompare_news',
405450
python_callable=fetch_cryptocompare_news,
@@ -436,5 +481,7 @@ def send_production_notification(**context):
436481
dag=dag,
437482
)
438483

439-
# Set task dependencies
440-
[fetch_cryptocompare_task, fetch_finnhub_task] >> merge_deduplicate_task >> insert_snowflake_task >> notification_task
484+
# ─── Task Dependencies ─────────────────────────────────────────────────
485+
# ensure_schema_and_table >> [fetch_*] >> merge >> insert >> notification
486+
487+
ensure_db_task >> [fetch_cryptocompare_task, fetch_finnhub_task] >> merge_deduplicate_task >> insert_snowflake_task >> notification_task

0 commit comments

Comments
 (0)