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
|
IndicatorsData instance ready to save to database, or None if insufficient data
|
||||||
or if any calculated values are NaN
|
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
|
# Fetch historical OHLCV data from database
|
||||||
start_date: datetime = target_date - timedelta(
|
start_date: datetime = target_date - timedelta(
|
||||||
days=lookback_days * 2
|
days=lookback_days * 2
|
||||||
@ -60,8 +71,7 @@ class IndicatorService:
|
|||||||
ticker=ticker, start_date=start_date, end_date=target_date
|
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) < min_required:
|
||||||
if len(ohlcv_records) < 40:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Convert to numpy arrays for TA-Lib
|
# Convert to numpy arrays for TA-Lib
|
||||||
@ -89,7 +99,6 @@ class IndicatorService:
|
|||||||
):
|
):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Build and return strongly-typed IndicatorsData
|
|
||||||
return IndicatorsData(
|
return IndicatorsData(
|
||||||
ticker=ticker,
|
ticker=ticker,
|
||||||
date=target_date,
|
date=target_date,
|
||||||
@ -130,26 +139,21 @@ class IndicatorService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def calculate_and_save_indicators(
|
def calculate_and_save_indicators(
|
||||||
self, ticker: str, target_date: datetime
|
self, ticker: str, target_date: datetime, force_update: bool = False
|
||||||
) -> IndicatorsData | None:
|
) -> IndicatorsData | None:
|
||||||
"""
|
if not force_update:
|
||||||
Calculate indicators and immediately save them to the database.
|
existing = self._crud.get_indicators(ticker=ticker, date=target_date)
|
||||||
|
|
||||||
Args:
|
if existing is not None:
|
||||||
ticker: Stock ticker symbol
|
return existing
|
||||||
target_date: Date to calculate indicators for
|
|
||||||
|
|
||||||
Returns:
|
indicators = self.calculate_indicators_for_date(
|
||||||
Saved IndicatorsData instance or None if calculation failed
|
|
||||||
"""
|
|
||||||
indicators: IndicatorsData | None = self.calculate_indicators_for_date(
|
|
||||||
ticker=ticker, target_date=target_date
|
ticker=ticker, target_date=target_date
|
||||||
)
|
)
|
||||||
|
|
||||||
if indicators is None:
|
if indicators is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Upsert (insert or update if exists)
|
|
||||||
return self._crud.upsert_indicators(indicators)
|
return self._crud.upsert_indicators(indicators)
|
||||||
|
|
||||||
def bulk_calculate_indicators(
|
def bulk_calculate_indicators(
|
||||||
@ -177,34 +181,30 @@ class IndicatorService:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
def bulk_calculate_and_save_indicators(
|
def bulk_calculate_and_save_indicators(
|
||||||
self, ticker: str, dates: List[datetime]
|
self, ticker: str, dates: List[datetime], force_update: bool = False
|
||||||
) -> int:
|
) -> int:
|
||||||
"""
|
# Batch check existing records
|
||||||
Calculate and save indicators for multiple dates efficiently.
|
if not force_update:
|
||||||
Uses upsert logic to handle existing records.
|
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
|
if not dates:
|
||||||
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:
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
# Use upsert for each indicator to avoid UNIQUE constraint violations
|
if indicators:
|
||||||
saved_count = 0
|
results.append(indicators)
|
||||||
for indicators in indicators_list:
|
|
||||||
result = self._crud.upsert_indicators(indicators)
|
# Batch insert
|
||||||
if result:
|
return self._crud.bulk_upsert_indicators(results)
|
||||||
saved_count += 1
|
|
||||||
|
|
||||||
return saved_count
|
|
||||||
|
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
# PRIVATE: Data Conversion
|
# PRIVATE: Data Conversion
|
||||||
|
Loading…
x
Reference in New Issue
Block a user