19ed77f4a0
Downsize GNN hidden channels/heads (128→64, 16→4) to match the 30-stock price-only universe before alternative data processors are ready. Add USE_ALTERNATIVE_DATA flag (default False) that skips news/social branches so the model trains on real data rather than zero-filled stubs. Standardize target returns per-batch in the data pipeline to stabilize training. Introduce walk-forward cross-validation with expanding windows: configurable fold count and out-of-sample years. Add IC loss weighting (0.7) to complement MSE, and wire it into the trainer alongside the new loss function.
226 lines
7.2 KiB
Python
226 lines
7.2 KiB
Python
import os
|
||
from datetime import datetime, timedelta
|
||
|
||
import torch
|
||
from dotenv import load_dotenv
|
||
|
||
load_dotenv()
|
||
|
||
|
||
def _check_amd_gpu() -> bool:
|
||
"""Detect AMD GPU via ROCm device name."""
|
||
if not torch.cuda.is_available():
|
||
return False
|
||
try:
|
||
name = torch.cuda.get_device_name(0)
|
||
return any(kw in name for kw in ("AMD", "Radeon", "gfx"))
|
||
except Exception:
|
||
return False
|
||
|
||
|
||
class Config:
|
||
# Project settings
|
||
PROJECT_NAME = "StockGNN_R9700"
|
||
VERSION = "1.0.0"
|
||
|
||
# Data directories
|
||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
DATA_DIR = os.path.join(BASE_DIR, "data")
|
||
RAW_DATA_DIR = os.path.join(DATA_DIR, "raw")
|
||
PROCESSED_DATA_DIR = os.path.join(DATA_DIR, "processed")
|
||
EXTERNAL_DATA_DIR = os.path.join(DATA_DIR, "external")
|
||
MODEL_DIR = os.path.join(BASE_DIR, "models")
|
||
|
||
# Stock universe settings
|
||
INITIAL_TICKERS = [
|
||
"AAPL",
|
||
"MSFT",
|
||
"GOOGL",
|
||
"AMZN",
|
||
"META",
|
||
"TSLA",
|
||
"NVDA",
|
||
"JPM",
|
||
"V",
|
||
"WMT",
|
||
"PG",
|
||
"DIS",
|
||
"NFLX",
|
||
"ADBE",
|
||
"PYPL",
|
||
"INTC",
|
||
"CSCO",
|
||
"PEP",
|
||
"KO",
|
||
"XOM",
|
||
"BAC",
|
||
"VZ",
|
||
"T",
|
||
"CRM",
|
||
"CMCSA",
|
||
"PFE",
|
||
"NKE",
|
||
"MRK",
|
||
"CVX",
|
||
"HD",
|
||
]
|
||
INDEX_TICKER = "^GSPC" # S&P 500
|
||
DELISTED_TICKERS_FILE = os.path.join(EXTERNAL_DATA_DIR, "delisted_stocks.csv")
|
||
|
||
# Date settings
|
||
START_DATE = "2015-01-01"
|
||
END_DATE = datetime.now().strftime("%Y-%m-%d")
|
||
TRAIN_END_DATE = "2022-12-31"
|
||
VAL_END_DATE = "2023-06-30"
|
||
TEST_END_DATE = END_DATE
|
||
|
||
# AMD GPU settings (Radeon R9700 AI Pro - 32GB)
|
||
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
||
AMD_GPU = _check_amd_gpu()
|
||
GPU_MEMORY_LIMIT = 0.9 # Use 90% of 32GB = 28.8GB
|
||
ROCM_OPT_LEVEL = "O2" # Optimization level for ROCm ('O0', 'O1', 'O2')
|
||
PIN_MEMORY = True # Enable pinned memory for faster data transfer
|
||
MIXED_PRECISION = True # Enable mixed precision training
|
||
PRECISION = "bf16" # 'fp16' or 'bf16' for mixed precision
|
||
|
||
# Model settings
|
||
MODEL_NAME = "stock_gnn_r9700"
|
||
# 64 hidden / 4 heads gives head_dim=16; appropriate for price-only 30-stock universe.
|
||
# Increase once alternative data processors are implemented (see USE_ALTERNATIVE_DATA).
|
||
HIDDEN_CHANNELS = 64
|
||
NUM_HEADS = 4
|
||
DROPOUT = 0.3
|
||
LEARNING_RATE = 0.0005
|
||
EPOCHS = 200
|
||
BATCH_SIZE = 128
|
||
SEQUENCE_LENGTH = 60
|
||
PREDICTION_HORIZON = 10
|
||
|
||
# Intraday trading settings
|
||
TRADING_FREQUENCY = "5min" # '1min', '5min', '15min', '30min', '1h'
|
||
TRADING_HOURS = {
|
||
"start": "09:30", # Market open (ET)
|
||
"end": "16:00", # Market close (ET)
|
||
}
|
||
PRE_MARKET_HOURS = {
|
||
"start": "04:00", # Pre-market start
|
||
"end": "09:30", # Pre-market end
|
||
}
|
||
AFTER_HOURS = {
|
||
"start": "16:00", # After-hours start
|
||
"end": "20:00", # After-hours end
|
||
}
|
||
MAX_POSITION_HOLD_TIME = "4h" # Maximum time to hold a position
|
||
MIN_POSITION_HOLD_TIME = "10min" # Minimum time to hold a position
|
||
MAX_DAILY_POSITIONS = 50 # Maximum number of positions per day
|
||
MAX_POSITION_SIZE = 0.03 # Maximum % of portfolio per position (3%)
|
||
|
||
# Data pipeline settings
|
||
LOOKBACK_WINDOW = 60 # Days for feature calculation
|
||
REALTIME_FEATURE_WINDOW = 30 # Number of data points for real-time features
|
||
DATA_BUFFER_SIZE = 5000 # Number of data points to keep in memory
|
||
DATA_FLUSH_INTERVAL = 300 # seconds - how often to flush data to database
|
||
|
||
# Alternative data settings (set via .env or environment variables)
|
||
NEWS_API_KEY = os.environ.get("NEWS_API_KEY", "")
|
||
TWITTER_BEARER_TOKEN = os.environ.get("TWITTER_BEARER_TOKEN", "")
|
||
REDDIT_CLIENT_ID = os.environ.get("REDDIT_CLIENT_ID", "")
|
||
REDDIT_CLIENT_SECRET = os.environ.get("REDDIT_CLIENT_SECRET", "")
|
||
NEWS_LOOKBACK_DAYS = 7 # Number of days to look back for news
|
||
SOCIAL_MEDIA_LOOKBACK_DAYS = 3 # Number of days to look back for social media
|
||
|
||
# Alternative data flag — set to True once NewsProcessor / SocialMediaProcessor
|
||
# are implemented; False skips those branches so the model trains on real data
|
||
# rather than zeros, which would mislead the attention layers.
|
||
USE_ALTERNATIVE_DATA = False
|
||
|
||
# Feature definitions (referenced by models)
|
||
NEWS_FEATURES = [
|
||
"sentiment",
|
||
"volume",
|
||
"recency",
|
||
"source_reliability",
|
||
"topic_relevance",
|
||
]
|
||
SOCIAL_FEATURES = [
|
||
"twitter_sentiment",
|
||
"twitter_volume",
|
||
"reddit_sentiment",
|
||
"reddit_volume",
|
||
"social_momentum",
|
||
]
|
||
INTRADAY_FEATURES = [
|
||
"return",
|
||
"volatility",
|
||
"momentum",
|
||
"volume_momentum",
|
||
"bid_ask_spread",
|
||
"bid_ask_spread_pct",
|
||
"volume_imbalance",
|
||
"order_flow",
|
||
"vwap_deviation",
|
||
]
|
||
|
||
# Stateful prediction
|
||
STATEFUL_PREDICTION = False
|
||
REALTIME_UPDATE_INTERVAL = 60 # seconds
|
||
|
||
# Backtesting settings
|
||
INITIAL_CAPITAL = 100000
|
||
TRANSACTION_COST = 0.0005 # 0.05% per trade
|
||
SLIPPAGE_MODEL = "volume_curve" # 'volume_curve', 'constant', or 'none'
|
||
SLIPPAGE_RATE = 0.0002 # 0.02% slippage
|
||
|
||
# Training loss: weight for (1 − Pearson IC) vs MSE.
|
||
# 0 = pure MSE, 1 = pure IC loss. 0.7 works well empirically for daily returns.
|
||
IC_LOSS_WEIGHT = 0.7
|
||
|
||
# Walk-forward cross-validation
|
||
WALK_FORWARD_FOLDS = 5 # number of folds
|
||
WALK_FORWARD_TEST_YEARS = 1 # out-of-sample window per fold (years)
|
||
|
||
# Live trading settings
|
||
LIVE_DATA_ENABLED = True
|
||
DATA_PROVIDER = "polygon" # 'polygon', 'alphavantage', 'ib', 'tdameritrade'
|
||
POLYGON_API_KEY = os.environ.get("POLYGON_API_KEY", "")
|
||
ALPHA_VANTAGE_API_KEY = os.environ.get("ALPHA_VANTAGE_API_KEY", "")
|
||
IB_HOST = "127.0.0.1"
|
||
IB_PORT = 7497
|
||
IB_CLIENT_ID = 1
|
||
|
||
# WebSocket settings
|
||
WEBSOCKET_RECONNECT_DELAY = 5 # seconds
|
||
WEBSOCKET_MAX_RETRIES = 20
|
||
WEBSOCKET_PING_INTERVAL = 30 # seconds
|
||
|
||
# Data loading settings
|
||
NUM_WORKERS = 8 # Number of data loading workers
|
||
PREFETCH_FACTOR = 4 # Number of batches to prefetch
|
||
|
||
# Online learning settings
|
||
ONLINE_LEARNING = True # Enable online learning
|
||
ONLINE_LEARNING_RATE = 0.0001 # Learning rate for online updates
|
||
ONLINE_LEARNING_INTERVAL = 3600 # seconds - how often to perform online learning
|
||
|
||
# Risk management settings
|
||
MAX_DAILY_LOSS = 0.01 # 1% max daily loss
|
||
MAX_DRAWDOWN = 0.05 # 5% max drawdown
|
||
VOLATILITY_TARGET = 0.15 # Annualized volatility target
|
||
POSITION_SIZING = (
|
||
"volatility_target" # 'volatility_target', 'equal_weight', 'kelly'
|
||
)
|
||
|
||
# Execution settings
|
||
EXECUTION_ALGORITHM = "vwap" # 'vwap', 'twap', 'pov', 'implementation_shortfall'
|
||
EXECUTION_TIME_HORIZON = "5min" # Time to complete execution
|
||
MARKET_IMPACT_MODEL = "kyle" # 'kyle', 'almgren_chriss', or 'none'
|
||
|
||
def __init__(self):
|
||
os.makedirs(self.RAW_DATA_DIR, exist_ok=True)
|
||
os.makedirs(self.PROCESSED_DATA_DIR, exist_ok=True)
|
||
os.makedirs(self.EXTERNAL_DATA_DIR, exist_ok=True)
|
||
os.makedirs(self.MODEL_DIR, exist_ok=True)
|
||
|
||
|
||
config = Config()
|