feat(indicators): add validation and force_update to indicator calculations
This commit is contained in:
parent
30ed373ac5
commit
d87733b80e
@ -52,6 +52,17 @@ class IndicatorService:
|
||||
IndicatorsData instance ready to save to database, or None if insufficient data
|
||||
or if any calculated values are NaN
|
||||
"""
|
||||
# Add indicator-specific validation
|
||||
required_periods = {
|
||||
"MACD": 26 + 9, # slowperiod + signalperiod
|
||||
"ADX": 14 + 14, # Additional warmup needed
|
||||
"RSI": 14 + 1, # Plus warmup
|
||||
}
|
||||
min_required = max(required_periods.values()) + 10 # Safety buffer
|
||||
|
||||
if lookback_days < min_required:
|
||||
return None
|
||||
|
||||
# Fetch historical OHLCV data from database
|
||||
start_date: datetime = target_date - timedelta(
|
||||
days=lookback_days * 2
|
||||
@ -60,8 +71,7 @@ class IndicatorService:
|
||||
ticker=ticker, start_date=start_date, end_date=target_date
|
||||
)
|
||||
|
||||
# Verify we have enough data (minimum 40 trading days for MACD + ADX)
|
||||
if len(ohlcv_records) < 40:
|
||||
if len(ohlcv_records) < min_required:
|
||||
return None
|
||||
|
||||
# Convert to numpy arrays for TA-Lib
|
||||
@ -89,7 +99,6 @@ class IndicatorService:
|
||||
):
|
||||
return None
|
||||
|
||||
# Build and return strongly-typed IndicatorsData
|
||||
return IndicatorsData(
|
||||
ticker=ticker,
|
||||
date=target_date,
|
||||
@ -130,26 +139,21 @@ class IndicatorService:
|
||||
)
|
||||
|
||||
def calculate_and_save_indicators(
|
||||
self, ticker: str, target_date: datetime
|
||||
self, ticker: str, target_date: datetime, force_update: bool = False
|
||||
) -> IndicatorsData | None:
|
||||
"""
|
||||
Calculate indicators and immediately save them to the database.
|
||||
if not force_update:
|
||||
existing = self._crud.get_indicators(ticker=ticker, date=target_date)
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker symbol
|
||||
target_date: Date to calculate indicators for
|
||||
if existing is not None:
|
||||
return existing
|
||||
|
||||
Returns:
|
||||
Saved IndicatorsData instance or None if calculation failed
|
||||
"""
|
||||
indicators: IndicatorsData | None = self.calculate_indicators_for_date(
|
||||
indicators = self.calculate_indicators_for_date(
|
||||
ticker=ticker, target_date=target_date
|
||||
)
|
||||
|
||||
if indicators is None:
|
||||
return None
|
||||
|
||||
# Upsert (insert or update if exists)
|
||||
return self._crud.upsert_indicators(indicators)
|
||||
|
||||
def bulk_calculate_indicators(
|
||||
@ -177,34 +181,30 @@ class IndicatorService:
|
||||
return results
|
||||
|
||||
def bulk_calculate_and_save_indicators(
|
||||
self, ticker: str, dates: List[datetime]
|
||||
) -> int:
|
||||
"""
|
||||
Calculate and save indicators for multiple dates efficiently.
|
||||
Uses upsert logic to handle existing records.
|
||||
self, ticker: str, dates: List[datetime], force_update: bool = False
|
||||
) -> int:
|
||||
# Batch check existing records
|
||||
if not force_update:
|
||||
existing_dates = self._crud.get_existing_indicator_dates(ticker, dates)
|
||||
dates = [d for d in dates if d not in existing_dates]
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker symbol
|
||||
dates: List of dates to calculate indicators for
|
||||
|
||||
Returns:
|
||||
Number of indicator records successfully saved/updated
|
||||
"""
|
||||
indicators_list: List[IndicatorsData] = self.bulk_calculate_indicators(
|
||||
ticker=ticker, dates=dates
|
||||
)
|
||||
|
||||
if not indicators_list:
|
||||
if not dates:
|
||||
return 0
|
||||
|
||||
# Use upsert for each indicator to avoid UNIQUE constraint violations
|
||||
saved_count = 0
|
||||
for indicators in indicators_list:
|
||||
result = self._crud.upsert_indicators(indicators)
|
||||
if result:
|
||||
saved_count += 1
|
||||
start_date = min(dates) - timedelta(days=150)
|
||||
end_date = max(dates)
|
||||
all_ohlcv = self._crud.get_ohlcv_range(ticker, start_date, end_date)
|
||||
|
||||
results = []
|
||||
for date in dates:
|
||||
indicators = self._calculate_for_date_from_data(all_ohlcv, date)
|
||||
|
||||
if indicators:
|
||||
results.append(indicators)
|
||||
|
||||
# Batch insert
|
||||
return self._crud.bulk_upsert_indicators(results)
|
||||
|
||||
return saved_count
|
||||
|
||||
# ========================================================================
|
||||
# PRIVATE: Data Conversion
|
||||
|
Loading…
x
Reference in New Issue
Block a user