dromerosm commited on
Commit
5827878
Β·
1 Parent(s): b892954

Refactor code structure for improved readability and maintainability

Browse files
Files changed (4) hide show
  1. api/index.py +132 -62
  2. logs/api.log +774 -0
  3. tests/run_tests.sh +1 -1
  4. tests/test_api.py +80 -6
api/index.py CHANGED
@@ -64,6 +64,9 @@ class TickerData(Base):
64
  low: Mapped[float] = mapped_column(Float, nullable=False)
65
  close: Mapped[float] = mapped_column(Float, nullable=False)
66
  volume: Mapped[int] = mapped_column(Integer, nullable=False)
 
 
 
67
  created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
68
 
69
  __table_args__ = (
@@ -114,12 +117,16 @@ class TickerDataResponse(BaseModel):
114
  low: float
115
  close: float
116
  volume: int
 
 
 
117
  created_at: datetime
118
 
119
 
120
  class DownloadDataRequest(BaseModel):
121
  tickers: Optional[List[str]] = Field(default=None, description="Specific tickers to download. If not provided, downloads all available tickers")
122
  force_refresh: bool = Field(default=False, description="Force refresh even if data exists")
 
123
 
124
 
125
  class DownloadDataResponse(BaseModel):
@@ -170,7 +177,8 @@ class Config:
170
  with open(config_path, 'r') as f:
171
  return yaml.safe_load(f)
172
  except FileNotFoundError:
173
- logging.warning(f"Config file '{config_path}' not found. Using defaults.")
 
174
  return self._get_default_config()
175
 
176
  def _get_default_config(self):
@@ -208,6 +216,8 @@ class Config:
208
  handlers=handlers,
209
  datefmt='%Y-%m-%d %H:%M:%S'
210
  )
 
 
211
 
212
  @property
213
  def database_url(self) -> str:
@@ -253,11 +263,11 @@ class TickerService:
253
 
254
  df = next((table for table in tables if all(col in table.columns for col in columns_needed)), None)
255
  if df is None:
256
- self.logger.error(f"Could not find columns {columns_needed} on {url}")
257
  return []
258
 
259
  entries = df[columns_needed].dropna(subset=[ticker_column])
260
- self.logger.info(f"Fetched {len(entries)} rows from {url}")
261
 
262
  results: List[tuple[str, str, Optional[str], Optional[str]]] = []
263
  for _, row in entries.iterrows():
@@ -268,7 +278,7 @@ class TickerService:
268
  results.append((ticker, name, sector, subindustry))
269
  return results
270
  except Exception as e:
271
- self.logger.error(f"Failed to fetch tickers and names from {url}: {e}")
272
  return []
273
 
274
 
@@ -307,7 +317,7 @@ class TickerService:
307
  last = pytz.UTC.localize(last)
308
  delta = now - last
309
  if delta.total_seconds() < 86400:
310
- self.logger.info(f"Tickers not updated: last update {last.isoformat()} < 1 day ago.")
311
  from sqlalchemy import func
312
  total_tickers = await session.scalar(select(func.count()).select_from(Ticker))
313
  sp500_count = await session.scalar(select(func.count()).select_from(Ticker).where(Ticker.is_sp500 == 1))
@@ -360,17 +370,11 @@ class TickerService:
360
  "nasdaq100_count": len(nasdaq_list),
361
  "updated_at": current_time
362
  }
363
- self.logger.info(
364
- "Tickers table updated: total=%d, sp500=%d, nasdaq100=%d at %s",
365
- result["total_tickers"],
366
- result["sp500_count"],
367
- result["nasdaq100_count"],
368
- result["updated_at"].isoformat()
369
- )
370
  return result
371
  except Exception as e:
372
  await session.rollback()
373
- self.logger.error(f"Failed to update tickers: {e}")
374
  raise
375
 
376
 
@@ -379,6 +383,31 @@ class YFinanceService:
379
  self.config = config
380
  self.logger = logging.getLogger(__name__)
381
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
  async def check_tickers_freshness(self, session: AsyncSession) -> bool:
383
  """
384
  Check if tickers were updated within the last week (7 days).
@@ -392,7 +421,7 @@ class YFinanceService:
392
  last_update = result.scalar()
393
 
394
  if not last_update:
395
- self.logger.info("No tickers found in database")
396
  return False
397
 
398
  # Ensure timezone awareness
@@ -402,11 +431,11 @@ class YFinanceService:
402
  delta = now - last_update
403
  is_fresh = delta.total_seconds() < (7 * 24 * 3600) # 7 days
404
 
405
- self.logger.info(f"Tickers last updated: {last_update.isoformat()}, Fresh: {is_fresh}")
406
  return is_fresh
407
 
408
  except Exception as e:
409
- self.logger.error(f"Error checking ticker freshness: {e}")
410
  return False
411
 
412
  async def check_ticker_data_freshness(self, session: AsyncSession) -> bool:
@@ -422,7 +451,7 @@ class YFinanceService:
422
  last_update = result.scalar()
423
 
424
  if not last_update:
425
- self.logger.info("No ticker data found in database")
426
  return False
427
 
428
  # Ensure timezone awareness
@@ -432,11 +461,11 @@ class YFinanceService:
432
  delta = now - last_update
433
  is_fresh = delta.total_seconds() < (24 * 3600) # 24 hours
434
 
435
- self.logger.info(f"Ticker data last updated: {last_update.isoformat()}, Fresh: {is_fresh}")
436
  return is_fresh
437
 
438
  except Exception as e:
439
- self.logger.error(f"Error checking ticker data freshness: {e}")
440
  return False
441
 
442
  async def clear_and_bulk_insert_ticker_data(self, session: AsyncSession, ticker_list: List[str]) -> Dict[str, Any]:
@@ -448,20 +477,20 @@ class YFinanceService:
448
  # Start timing for total end-to-end process
449
  total_start_time = time.perf_counter()
450
 
451
- self.logger.info(f"Starting bulk data refresh for {len(ticker_list)} tickers (clear and insert)")
452
 
453
  # Start timing for data download
454
  download_start_time = time.perf_counter()
455
 
456
  # Download data for all tickers at once using period
457
- data = yf.download(ticker_list, period='1mo', group_by='ticker', progress=True, auto_adjust=True)
458
 
459
  download_end_time = time.perf_counter()
460
  download_duration = download_end_time - download_start_time
461
- self.logger.info(f"DEBUG: Data download completed in {download_duration:.2f} seconds for {len(ticker_list)} tickers")
462
 
463
  if data.empty:
464
- self.logger.warning("No data found for any tickers")
465
  return {
466
  "created": 0,
467
  "updated": 0,
@@ -472,11 +501,11 @@ class YFinanceService:
472
  db_start_time = time.perf_counter()
473
 
474
  # Clear all existing ticker data
475
- self.logger.info("Clearing all existing ticker data...")
476
  clear_start = time.perf_counter()
477
  await session.execute(delete(TickerData))
478
  clear_end = time.perf_counter()
479
- self.logger.info(f"DEBUG: Data cleared in {clear_end - clear_start:.2f} seconds")
480
 
481
  # Prepare data for bulk insert
482
  current_time = datetime.now(pytz.UTC)
@@ -491,7 +520,11 @@ class YFinanceService:
491
  if len(ticker_list) == 1:
492
  # Single ticker case - data is not grouped
493
  ticker = ticker_list[0]
494
- for date_idx, row in data.iterrows():
 
 
 
 
495
  if pd.isna(row['Close']):
496
  continue
497
 
@@ -504,6 +537,9 @@ class YFinanceService:
504
  'low': float(row['Low']),
505
  'close': float(row['Close']),
506
  'volume': int(row['Volume']),
 
 
 
507
  'created_at': current_time
508
  }
509
  all_records.append(record)
@@ -511,14 +547,17 @@ class YFinanceService:
511
  # Multiple tickers case - data is grouped by ticker
512
  for ticker in ticker_list:
513
  if ticker not in data.columns.get_level_values(0):
514
- self.logger.warning(f"No data found for ticker {ticker}")
515
  continue
516
 
517
  ticker_data = data[ticker]
518
  if ticker_data.empty:
519
  continue
 
 
 
520
 
521
- for date_idx, row in ticker_data.iterrows():
522
  if pd.isna(row['Close']):
523
  continue
524
 
@@ -531,6 +570,9 @@ class YFinanceService:
531
  'low': float(row['Low']),
532
  'close': float(row['Close']),
533
  'volume': int(row['Volume']),
 
 
 
534
  'created_at': current_time
535
  }
536
  all_records.append(record)
@@ -540,7 +582,7 @@ class YFinanceService:
540
  total_records = len(all_records)
541
  inserted_count = 0
542
 
543
- self.logger.info(f"Inserting {total_records} records in chunks of {chunk_size}")
544
 
545
  for i in range(0, total_records, chunk_size):
546
  chunk = all_records[i:i + chunk_size]
@@ -552,24 +594,24 @@ class YFinanceService:
552
 
553
  chunk_end = time.perf_counter()
554
  inserted_count += len(chunk)
555
- self.logger.info(f"DEBUG: Inserted chunk {i//chunk_size + 1}/{(total_records + chunk_size - 1)//chunk_size} ({len(chunk)} records) in {chunk_end - chunk_start:.2f} seconds")
556
 
557
  # Commit all changes
558
  commit_start = time.perf_counter()
559
  await session.commit()
560
  commit_end = time.perf_counter()
561
- self.logger.info(f"DEBUG: Database commit completed in {commit_end - commit_start:.2f} seconds")
562
 
563
  db_end_time = time.perf_counter()
564
  db_duration = db_end_time - db_start_time
565
- self.logger.info(f"DEBUG: Database operations completed in {db_duration:.2f} seconds for {inserted_count} records")
566
 
567
  # Calculate total end-to-end duration
568
  total_end_time = time.perf_counter()
569
  total_duration = total_end_time - total_start_time
570
- self.logger.info(f"DEBUG: Total bulk refresh completed in {total_duration:.2f} seconds (download: {download_duration:.2f}s, database: {db_duration:.2f}s)")
571
 
572
- self.logger.info(f"Bulk refresh: inserted {inserted_count} records")
573
  return {
574
  "created": inserted_count,
575
  "updated": 0,
@@ -581,18 +623,19 @@ class YFinanceService:
581
 
582
  except Exception as e:
583
  await session.rollback()
584
- self.logger.error(f"Error in bulk refresh: {e}")
585
  raise
586
 
587
- async def download_all_tickers_data(self, session: AsyncSession, ticker_list: Optional[List[str]] = None) -> Dict[str, Any]:
588
  """
589
- Download data for all or specified tickers for the last month.
590
  Uses smart strategy: checks data freshness, if > 24h, clears DB and bulk inserts new data.
 
591
  """
592
  try:
593
  # Check ticker freshness and update if needed
594
  if not await self.check_tickers_freshness(session):
595
- self.logger.info("Tickers are stale, updating...")
596
  ticker_service = TickerService(self.config)
597
  await ticker_service.update_tickers_in_db(session, force_refresh=True)
598
 
@@ -605,7 +648,7 @@ class YFinanceService:
605
  valid_tickers = [row[0] for row in result.fetchall()]
606
  invalid_tickers = set(ticker_list) - set(valid_tickers)
607
  if invalid_tickers:
608
- self.logger.warning(f"Invalid tickers ignored: {invalid_tickers}")
609
  tickers_to_process = valid_tickers
610
  else:
611
  # Get all tickers from database
@@ -621,19 +664,31 @@ class YFinanceService:
621
  "message": "No valid tickers found to process"
622
  }
623
 
624
- # Check if ticker data is fresh (less than 24h old)
625
- if await self.check_ticker_data_freshness(session):
626
- self.logger.info("Ticker data is fresh (less than 24h old), skipping update")
627
- return {
628
- "tickers_processed": len(tickers_to_process),
629
- "records_created": 0,
630
- "records_updated": 0,
631
- "date_range": {"start_date": "", "end_date": ""},
632
- "message": f"Data is fresh, no update needed for {len(tickers_to_process)} tickers"
633
- }
 
 
 
 
 
 
 
 
 
 
 
 
634
 
635
  # Data is stale - use bulk refresh strategy
636
- self.logger.info("Data is stale (>24h old), using bulk refresh strategy")
637
  result = await self.clear_and_bulk_insert_ticker_data(session, tickers_to_process)
638
  total_created = result["created"]
639
  total_updated = result["updated"]
@@ -648,7 +703,7 @@ class YFinanceService:
648
  }
649
 
650
  except Exception as e:
651
- self.logger.error(f"Error in download_all_tickers_data: {e}")
652
  raise
653
 
654
 
@@ -795,11 +850,12 @@ async def lifespan(app: FastAPI):
795
  # Startup
796
  await database.create_tables()
797
  await task_manager.create_table_if_not_exists()
798
- logging.info("Database tables created/verified")
 
799
  yield
800
  # Shutdown
801
  await database.engine.dispose()
802
- logging.info("Database connections closed")
803
 
804
  # Create FastAPI app
805
  app = FastAPI(
@@ -953,7 +1009,8 @@ async def get_tickers(
953
  for t in tickers
954
  ]
955
  except Exception as e:
956
- logging.error(f"Error fetching tickers: {e}")
 
957
  raise HTTPException(status_code=500, detail="Failed to fetch tickers")
958
 
959
 
@@ -1011,7 +1068,8 @@ async def update_tickers(
1011
  **result
1012
  )
1013
  except Exception as e:
1014
- logging.error(f"Error updating tickers: {e}")
 
1015
  raise HTTPException(status_code=500, detail=f"Failed to update tickers: {str(e)}")
1016
 
1017
 
@@ -1157,28 +1215,33 @@ async def delete_old_tasks(api_key: str = Depends(verify_api_key)):
1157
 
1158
  @app.post("/data/download-all", response_model=DownloadDataResponse)
1159
  async def download_all_tickers_data(
 
1160
  session: AsyncSession = Depends(get_db_session),
1161
  api_key: str = Depends(verify_api_key)
1162
  ):
1163
  """
1164
- Download daily ticker data for the last month for ALL tickers in database.
1165
 
1166
  **Logic**:
1167
  - Automatically downloads data for all tickers stored in the tickers table
1168
  - Checks if tickers were updated within the last week, updates if needed
1169
- - Only downloads if ticker data is older than 24 hours
1170
- - Downloads daily data for the last 30 days for all available tickers
 
1171
  - Uses bulk delete and insert strategy for optimal performance
1172
  - Returns summary with counts and date range
1173
 
1174
  **Args**:
 
1175
  - **session**: AsyncSession (DB session, injected)
1176
  - **api_key**: str (API key for authentication, injected)
1177
 
1178
  **Example request:**
1179
  ```bash
1180
  curl -X POST "http://localhost:${PORT}/data/download-all" \
1181
- -H "Authorization: Bearer your_api_key"
 
 
1182
  ```
1183
 
1184
  **Example response:**
@@ -1201,7 +1264,9 @@ async def download_all_tickers_data(
1201
  # Use existing service without specifying ticker list (downloads all)
1202
  result = await yfinance_service.download_all_tickers_data(
1203
  session,
1204
- ticker_list=None # None means download all tickers
 
 
1205
  )
1206
 
1207
  return DownloadDataResponse(
@@ -1215,7 +1280,8 @@ async def download_all_tickers_data(
1215
  )
1216
 
1217
  except Exception as e:
1218
- logging.error(f"Error downloading all ticker data: {e}")
 
1219
  raise HTTPException(status_code=500, detail=f"Failed to download all ticker data: {str(e)}")
1220
 
1221
 
@@ -1285,6 +1351,9 @@ async def get_ticker_data(
1285
  low=data.low,
1286
  close=data.close,
1287
  volume=data.volume,
 
 
 
1288
  created_at=data.created_at
1289
  )
1290
  for data in ticker_data
@@ -1293,7 +1362,8 @@ async def get_ticker_data(
1293
  except HTTPException:
1294
  raise
1295
  except Exception as e:
1296
- logging.error(f"Error fetching ticker data for {ticker}: {e}")
 
1297
  raise HTTPException(status_code=500, detail="Failed to fetch ticker data")
1298
 
1299
 
 
64
  low: Mapped[float] = mapped_column(Float, nullable=False)
65
  close: Mapped[float] = mapped_column(Float, nullable=False)
66
  volume: Mapped[int] = mapped_column(Integer, nullable=False)
67
+ sma_fast: Mapped[Optional[float]] = mapped_column(Float, nullable=True) # SMA 10
68
+ sma_med: Mapped[Optional[float]] = mapped_column(Float, nullable=True) # SMA 20
69
+ sma_slow: Mapped[Optional[float]] = mapped_column(Float, nullable=True) # SMA 50
70
  created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
71
 
72
  __table_args__ = (
 
117
  low: float
118
  close: float
119
  volume: int
120
+ sma_fast: Optional[float] = None # SMA 10
121
+ sma_med: Optional[float] = None # SMA 20
122
+ sma_slow: Optional[float] = None # SMA 50
123
  created_at: datetime
124
 
125
 
126
  class DownloadDataRequest(BaseModel):
127
  tickers: Optional[List[str]] = Field(default=None, description="Specific tickers to download. If not provided, downloads all available tickers")
128
  force_refresh: bool = Field(default=False, description="Force refresh even if data exists")
129
+ force_indicators: bool = Field(default=False, description="Force calculation of technical indicators even if data is fresh")
130
 
131
 
132
  class DownloadDataResponse(BaseModel):
 
177
  with open(config_path, 'r') as f:
178
  return yaml.safe_load(f)
179
  except FileNotFoundError:
180
+ logger = logging.getLogger(__name__)
181
+ logger.warning(f"config_file_not_found path={config_path} using_defaults=true")
182
  return self._get_default_config()
183
 
184
  def _get_default_config(self):
 
216
  handlers=handlers,
217
  datefmt='%Y-%m-%d %H:%M:%S'
218
  )
219
+ self.logger = logging.getLogger(__name__)
220
+ self.logger.info(f"logging_configured level={log_config.get('level', 'INFO')} hf_spaces={os.getenv('SPACE_ID') is not None}")
221
 
222
  @property
223
  def database_url(self) -> str:
 
263
 
264
  df = next((table for table in tables if all(col in table.columns for col in columns_needed)), None)
265
  if df is None:
266
+ self.logger.error(f"wikipedia_parsing_failed url={url} required_columns={columns_needed}")
267
  return []
268
 
269
  entries = df[columns_needed].dropna(subset=[ticker_column])
270
+ self.logger.info(f"wikipedia_data_fetched url={url} rows={len(entries)}")
271
 
272
  results: List[tuple[str, str, Optional[str], Optional[str]]] = []
273
  for _, row in entries.iterrows():
 
278
  results.append((ticker, name, sector, subindustry))
279
  return results
280
  except Exception as e:
281
+ self.logger.error(f"wikipedia_fetch_failed url={url} error={str(e)}")
282
  return []
283
 
284
 
 
317
  last = pytz.UTC.localize(last)
318
  delta = now - last
319
  if delta.total_seconds() < 86400:
320
+ self.logger.info(f"tickers_update_skipped last_update={last.isoformat()} reason=fresh_data")
321
  from sqlalchemy import func
322
  total_tickers = await session.scalar(select(func.count()).select_from(Ticker))
323
  sp500_count = await session.scalar(select(func.count()).select_from(Ticker).where(Ticker.is_sp500 == 1))
 
370
  "nasdaq100_count": len(nasdaq_list),
371
  "updated_at": current_time
372
  }
373
+ self.logger.info(f"tickers_updated total={result['total_tickers']} sp500={result['sp500_count']} nasdaq100={result['nasdaq100_count']} timestamp={result['updated_at'].isoformat()}")
 
 
 
 
 
 
374
  return result
375
  except Exception as e:
376
  await session.rollback()
377
+ self.logger.error(f"tickers_update_failed error={str(e)}")
378
  raise
379
 
380
 
 
383
  self.config = config
384
  self.logger = logging.getLogger(__name__)
385
 
386
+ def calculate_technical_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
387
+ """
388
+ Calculate technical indicators for a ticker's data.
389
+ Adds SMA columns: sma_fast (10), sma_med (20), sma_slow (50)
390
+ """
391
+ if df.empty or 'Close' not in df.columns:
392
+ return df
393
+
394
+ start_time = time.perf_counter()
395
+ records_count = len(df)
396
+
397
+ # Sort by date to ensure proper calculation
398
+ df = df.sort_index()
399
+
400
+ # Calculate Simple Moving Averages
401
+ df['sma_fast'] = df['Close'].rolling(window=10, min_periods=10).mean()
402
+ df['sma_med'] = df['Close'].rolling(window=20, min_periods=20).mean()
403
+ df['sma_slow'] = df['Close'].rolling(window=50, min_periods=50).mean()
404
+
405
+ end_time = time.perf_counter()
406
+ duration = end_time - start_time
407
+ self.logger.info(f"technical_indicators_calculated records={records_count} duration_ms={duration*1000:.2f}")
408
+
409
+ return df
410
+
411
  async def check_tickers_freshness(self, session: AsyncSession) -> bool:
412
  """
413
  Check if tickers were updated within the last week (7 days).
 
421
  last_update = result.scalar()
422
 
423
  if not last_update:
424
+ self.logger.info("ticker_freshness_check result=no_tickers_found")
425
  return False
426
 
427
  # Ensure timezone awareness
 
431
  delta = now - last_update
432
  is_fresh = delta.total_seconds() < (7 * 24 * 3600) # 7 days
433
 
434
+ self.logger.info(f"ticker_freshness_check last_update={last_update.isoformat()} is_fresh={is_fresh}")
435
  return is_fresh
436
 
437
  except Exception as e:
438
+ self.logger.error(f"ticker_freshness_check_failed error={str(e)}")
439
  return False
440
 
441
  async def check_ticker_data_freshness(self, session: AsyncSession) -> bool:
 
451
  last_update = result.scalar()
452
 
453
  if not last_update:
454
+ self.logger.info("ticker_data_freshness_check result=no_data_found")
455
  return False
456
 
457
  # Ensure timezone awareness
 
461
  delta = now - last_update
462
  is_fresh = delta.total_seconds() < (24 * 3600) # 24 hours
463
 
464
+ self.logger.info(f"ticker_data_freshness_check last_update={last_update.isoformat()} is_fresh={is_fresh}")
465
  return is_fresh
466
 
467
  except Exception as e:
468
+ self.logger.error(f"ticker_data_freshness_check_failed error={str(e)}")
469
  return False
470
 
471
  async def clear_and_bulk_insert_ticker_data(self, session: AsyncSession, ticker_list: List[str]) -> Dict[str, Any]:
 
477
  # Start timing for total end-to-end process
478
  total_start_time = time.perf_counter()
479
 
480
+ self.logger.info(f"bulk_refresh_started tickers_count={len(ticker_list)} operation=clear_and_insert")
481
 
482
  # Start timing for data download
483
  download_start_time = time.perf_counter()
484
 
485
  # Download data for all tickers at once using period
486
+ data = yf.download(ticker_list, period='3mo', group_by='ticker', progress=True, auto_adjust=True)
487
 
488
  download_end_time = time.perf_counter()
489
  download_duration = download_end_time - download_start_time
490
+ self.logger.info(f"data_download_completed tickers_count={len(ticker_list)} duration_ms={download_duration*1000:.2f}")
491
 
492
  if data.empty:
493
+ self.logger.warning("data_download_empty reason=no_data_for_any_tickers")
494
  return {
495
  "created": 0,
496
  "updated": 0,
 
501
  db_start_time = time.perf_counter()
502
 
503
  # Clear all existing ticker data
504
+ self.logger.info("database_clear_started operation=delete_all_ticker_data")
505
  clear_start = time.perf_counter()
506
  await session.execute(delete(TickerData))
507
  clear_end = time.perf_counter()
508
+ self.logger.info(f"database_clear_completed duration_ms={(clear_end - clear_start)*1000:.2f}")
509
 
510
  # Prepare data for bulk insert
511
  current_time = datetime.now(pytz.UTC)
 
520
  if len(ticker_list) == 1:
521
  # Single ticker case - data is not grouped
522
  ticker = ticker_list[0]
523
+
524
+ # Calculate technical indicators
525
+ data_with_indicators = self.calculate_technical_indicators(data)
526
+
527
+ for date_idx, row in data_with_indicators.iterrows():
528
  if pd.isna(row['Close']):
529
  continue
530
 
 
537
  'low': float(row['Low']),
538
  'close': float(row['Close']),
539
  'volume': int(row['Volume']),
540
+ 'sma_fast': float(row['sma_fast']) if pd.notna(row['sma_fast']) else None,
541
+ 'sma_med': float(row['sma_med']) if pd.notna(row['sma_med']) else None,
542
+ 'sma_slow': float(row['sma_slow']) if pd.notna(row['sma_slow']) else None,
543
  'created_at': current_time
544
  }
545
  all_records.append(record)
 
547
  # Multiple tickers case - data is grouped by ticker
548
  for ticker in ticker_list:
549
  if ticker not in data.columns.get_level_values(0):
550
+ self.logger.warning(f"ticker_data_missing ticker={ticker} reason=not_in_downloaded_data")
551
  continue
552
 
553
  ticker_data = data[ticker]
554
  if ticker_data.empty:
555
  continue
556
+
557
+ # Calculate technical indicators for this ticker
558
+ ticker_data_with_indicators = self.calculate_technical_indicators(ticker_data)
559
 
560
+ for date_idx, row in ticker_data_with_indicators.iterrows():
561
  if pd.isna(row['Close']):
562
  continue
563
 
 
570
  'low': float(row['Low']),
571
  'close': float(row['Close']),
572
  'volume': int(row['Volume']),
573
+ 'sma_fast': float(row['sma_fast']) if pd.notna(row['sma_fast']) else None,
574
+ 'sma_med': float(row['sma_med']) if pd.notna(row['sma_med']) else None,
575
+ 'sma_slow': float(row['sma_slow']) if pd.notna(row['sma_slow']) else None,
576
  'created_at': current_time
577
  }
578
  all_records.append(record)
 
582
  total_records = len(all_records)
583
  inserted_count = 0
584
 
585
+ self.logger.info(f"database_insert_started total_records={total_records} chunk_size={chunk_size}")
586
 
587
  for i in range(0, total_records, chunk_size):
588
  chunk = all_records[i:i + chunk_size]
 
594
 
595
  chunk_end = time.perf_counter()
596
  inserted_count += len(chunk)
597
+ self.logger.info(f"database_chunk_inserted chunk={i//chunk_size + 1}/{(total_records + chunk_size - 1)//chunk_size} records={len(chunk)} duration_ms={(chunk_end - chunk_start)*1000:.2f}")
598
 
599
  # Commit all changes
600
  commit_start = time.perf_counter()
601
  await session.commit()
602
  commit_end = time.perf_counter()
603
+ self.logger.info(f"database_commit_completed duration_ms={(commit_end - commit_start)*1000:.2f}")
604
 
605
  db_end_time = time.perf_counter()
606
  db_duration = db_end_time - db_start_time
607
+ self.logger.info(f"database_operations_completed records_inserted={inserted_count} duration_ms={db_duration*1000:.2f}")
608
 
609
  # Calculate total end-to-end duration
610
  total_end_time = time.perf_counter()
611
  total_duration = total_end_time - total_start_time
612
+ self.logger.info(f"bulk_refresh_completed total_duration_ms={total_duration*1000:.2f} download_ms={download_duration*1000:.2f} database_ms={db_duration*1000:.2f}")
613
 
614
+ self.logger.info(f"bulk_refresh_summary records_inserted={inserted_count} operation=completed")
615
  return {
616
  "created": inserted_count,
617
  "updated": 0,
 
623
 
624
  except Exception as e:
625
  await session.rollback()
626
+ self.logger.error(f"bulk_refresh_failed error={str(e)}")
627
  raise
628
 
629
+ async def download_all_tickers_data(self, session: AsyncSession, ticker_list: Optional[List[str]] = None, force_refresh: bool = False, force_indicators: bool = False) -> Dict[str, Any]:
630
  """
631
+ Download data for all or specified tickers for the last 3 months.
632
  Uses smart strategy: checks data freshness, if > 24h, clears DB and bulk inserts new data.
633
+ Calculates technical indicators (SMA 10, 20, 50) for all data.
634
  """
635
  try:
636
  # Check ticker freshness and update if needed
637
  if not await self.check_tickers_freshness(session):
638
+ self.logger.info("tickers_update_required reason=stale_data")
639
  ticker_service = TickerService(self.config)
640
  await ticker_service.update_tickers_in_db(session, force_refresh=True)
641
 
 
648
  valid_tickers = [row[0] for row in result.fetchall()]
649
  invalid_tickers = set(ticker_list) - set(valid_tickers)
650
  if invalid_tickers:
651
+ self.logger.warning(f"invalid_tickers_ignored count={len(invalid_tickers)} tickers={list(invalid_tickers)}")
652
  tickers_to_process = valid_tickers
653
  else:
654
  # Get all tickers from database
 
664
  "message": "No valid tickers found to process"
665
  }
666
 
667
+ # Check if ticker data is fresh (less than 24h old) unless force_refresh
668
+ if not force_refresh and await self.check_ticker_data_freshness(session):
669
+ if not force_indicators:
670
+ self.logger.info("data_download_skipped reason=fresh_data age_limit=24h")
671
+ return {
672
+ "tickers_processed": len(tickers_to_process),
673
+ "records_created": 0,
674
+ "records_updated": 0,
675
+ "date_range": {"start_date": "", "end_date": ""},
676
+ "message": f"Data is fresh, no update needed for {len(tickers_to_process)} tickers"
677
+ }
678
+ else:
679
+ # Data is fresh but force_indicators is True - only recalculate indicators
680
+ self.logger.info("indicators_recalculation_requested reason=force_indicators_flag data_age=fresh")
681
+ # TODO: Implement indicators-only recalculation
682
+ return {
683
+ "tickers_processed": len(tickers_to_process),
684
+ "records_created": 0,
685
+ "records_updated": 0,
686
+ "date_range": {"start_date": "", "end_date": ""},
687
+ "message": f"Indicators recalculation for {len(tickers_to_process)} tickers (not implemented yet)"
688
+ }
689
 
690
  # Data is stale - use bulk refresh strategy
691
+ self.logger.info("bulk_refresh_strategy_selected reason=stale_data age_limit=24h")
692
  result = await self.clear_and_bulk_insert_ticker_data(session, tickers_to_process)
693
  total_created = result["created"]
694
  total_updated = result["updated"]
 
703
  }
704
 
705
  except Exception as e:
706
+ self.logger.error(f"download_all_tickers_failed error={str(e)}")
707
  raise
708
 
709
 
 
850
  # Startup
851
  await database.create_tables()
852
  await task_manager.create_table_if_not_exists()
853
+ logger = logging.getLogger(__name__)
854
+ logger.info("database_lifecycle event=tables_created_verified")
855
  yield
856
  # Shutdown
857
  await database.engine.dispose()
858
+ logger.info("database_lifecycle event=connections_closed")
859
 
860
  # Create FastAPI app
861
  app = FastAPI(
 
1009
  for t in tickers
1010
  ]
1011
  except Exception as e:
1012
+ logger = logging.getLogger(__name__)
1013
+ logger.error(f"endpoint_error endpoint=get_tickers error={str(e)}")
1014
  raise HTTPException(status_code=500, detail="Failed to fetch tickers")
1015
 
1016
 
 
1068
  **result
1069
  )
1070
  except Exception as e:
1071
+ logger = logging.getLogger(__name__)
1072
+ logger.error(f"endpoint_error endpoint=update_tickers error={str(e)}")
1073
  raise HTTPException(status_code=500, detail=f"Failed to update tickers: {str(e)}")
1074
 
1075
 
 
1215
 
1216
  @app.post("/data/download-all", response_model=DownloadDataResponse)
1217
  async def download_all_tickers_data(
1218
+ request: DownloadDataRequest = DownloadDataRequest(),
1219
  session: AsyncSession = Depends(get_db_session),
1220
  api_key: str = Depends(verify_api_key)
1221
  ):
1222
  """
1223
+ Download daily ticker data for the last 3 months for ALL tickers in database.
1224
 
1225
  **Logic**:
1226
  - Automatically downloads data for all tickers stored in the tickers table
1227
  - Checks if tickers were updated within the last week, updates if needed
1228
+ - Only downloads if ticker data is older than 24 hours (unless force_refresh=true)
1229
+ - Downloads daily data for the last 3 months for all available tickers
1230
+ - Calculates technical indicators: SMA 10 (fast), SMA 20 (med), SMA 50 (slow)
1231
  - Uses bulk delete and insert strategy for optimal performance
1232
  - Returns summary with counts and date range
1233
 
1234
  **Args**:
1235
+ - **request**: DownloadDataRequest (force_refresh, force_indicators flags)
1236
  - **session**: AsyncSession (DB session, injected)
1237
  - **api_key**: str (API key for authentication, injected)
1238
 
1239
  **Example request:**
1240
  ```bash
1241
  curl -X POST "http://localhost:${PORT}/data/download-all" \
1242
+ -H "Authorization: Bearer your_api_key" \
1243
+ -H "Content-Type: application/json" \
1244
+ -d '{"force_refresh": false, "force_indicators": true}'
1245
  ```
1246
 
1247
  **Example response:**
 
1264
  # Use existing service without specifying ticker list (downloads all)
1265
  result = await yfinance_service.download_all_tickers_data(
1266
  session,
1267
+ ticker_list=request.tickers, # None means download all tickers
1268
+ force_refresh=request.force_refresh,
1269
+ force_indicators=request.force_indicators
1270
  )
1271
 
1272
  return DownloadDataResponse(
 
1280
  )
1281
 
1282
  except Exception as e:
1283
+ logger = logging.getLogger(__name__)
1284
+ logger.error(f"endpoint_error endpoint=download_all_tickers error={str(e)}")
1285
  raise HTTPException(status_code=500, detail=f"Failed to download all ticker data: {str(e)}")
1286
 
1287
 
 
1351
  low=data.low,
1352
  close=data.close,
1353
  volume=data.volume,
1354
+ sma_fast=data.sma_fast,
1355
+ sma_med=data.sma_med,
1356
+ sma_slow=data.sma_slow,
1357
  created_at=data.created_at
1358
  )
1359
  for data in ticker_data
 
1362
  except HTTPException:
1363
  raise
1364
  except Exception as e:
1365
+ logger = logging.getLogger(__name__)
1366
+ logger.error(f"endpoint_error endpoint=get_ticker_data ticker={ticker} error={str(e)}")
1367
  raise HTTPException(status_code=500, detail="Failed to fetch ticker data")
1368
 
1369
 
logs/api.log ADDED
@@ -0,0 +1,774 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 2025-07-30 20:35:48 [INFO] __main__: logging_configured level=INFO hf_spaces=False
2
+ 2025-07-30 20:35:48 [INFO] __mp_main__: logging_configured level=INFO hf_spaces=False
3
+ 2025-07-30 20:35:48 [INFO] watchfiles.main: 1 change detected
4
+ 2025-07-30 20:35:49 [INFO] index: logging_configured level=INFO hf_spaces=False
5
+ 2025-07-30 20:35:49 [INFO] watchfiles.main: 3 changes detected
6
+ 2025-07-30 20:35:50 [INFO] index: database_lifecycle event=tables_created_verified
7
+ 2025-07-30 20:35:51 [INFO] watchfiles.main: 2 changes detected
8
+ 2025-07-30 20:35:52 [INFO] watchfiles.main: 4 changes detected
9
+ 2025-07-30 20:35:57 [INFO] watchfiles.main: 2 changes detected
10
+ 2025-07-30 20:36:02 [INFO] watchfiles.main: 2 changes detected
11
+ 2025-07-30 20:36:04 [INFO] watchfiles.main: 1 change detected
12
+ 2025-07-30 20:36:05 [INFO] watchfiles.main: 1 change detected
13
+ 2025-07-30 20:36:07 [INFO] watchfiles.main: 2 changes detected
14
+ 2025-07-30 20:36:09 [INFO] watchfiles.main: 1 change detected
15
+ 2025-07-30 20:36:12 [INFO] watchfiles.main: 2 changes detected
16
+ 2025-07-30 20:36:17 [INFO] watchfiles.main: 2 changes detected
17
+ 2025-07-30 20:36:20 [INFO] watchfiles.main: 1 change detected
18
+ 2025-07-30 20:36:20 [INFO] watchfiles.main: 1 change detected
19
+ 2025-07-30 20:36:22 [INFO] watchfiles.main: 2 changes detected
20
+ 2025-07-30 20:36:25 [INFO] watchfiles.main: 1 change detected
21
+ 2025-07-30 20:36:27 [INFO] watchfiles.main: 2 changes detected
22
+ 2025-07-30 20:36:32 [INFO] watchfiles.main: 2 changes detected
23
+ 2025-07-30 20:36:35 [INFO] watchfiles.main: 1 change detected
24
+ 2025-07-30 20:36:35 [INFO] watchfiles.main: 1 change detected
25
+ 2025-07-30 20:36:36 [INFO] watchfiles.main: 1 change detected
26
+ 2025-07-30 20:36:37 [INFO] watchfiles.main: 2 changes detected
27
+ 2025-07-30 20:36:41 [INFO] watchfiles.main: 1 change detected
28
+ 2025-07-30 20:36:43 [INFO] watchfiles.main: 2 changes detected
29
+ 2025-07-30 20:36:48 [INFO] watchfiles.main: 2 changes detected
30
+ 2025-07-30 20:36:51 [INFO] watchfiles.main: 1 change detected
31
+ 2025-07-30 20:36:51 [INFO] watchfiles.main: 1 change detected
32
+ 2025-07-30 20:36:53 [INFO] watchfiles.main: 2 changes detected
33
+ 2025-07-30 20:36:56 [INFO] watchfiles.main: 1 change detected
34
+ 2025-07-30 20:36:58 [INFO] watchfiles.main: 2 changes detected
35
+ 2025-07-30 20:37:03 [INFO] watchfiles.main: 2 changes detected
36
+ 2025-07-30 20:37:06 [INFO] watchfiles.main: 1 change detected
37
+ 2025-07-30 20:37:06 [INFO] watchfiles.main: 1 change detected
38
+ 2025-07-30 20:37:08 [INFO] watchfiles.main: 2 changes detected
39
+ 2025-07-30 20:37:11 [INFO] watchfiles.main: 1 change detected
40
+ 2025-07-30 20:37:11 [INFO] watchfiles.main: 1 change detected
41
+ 2025-07-30 20:37:13 [INFO] watchfiles.main: 2 changes detected
42
+ 2025-07-30 20:37:18 [INFO] watchfiles.main: 2 changes detected
43
+ 2025-07-30 20:37:21 [INFO] watchfiles.main: 1 change detected
44
+ 2025-07-30 20:37:22 [INFO] watchfiles.main: 1 change detected
45
+ 2025-07-30 20:37:24 [INFO] watchfiles.main: 2 changes detected
46
+ 2025-07-30 20:37:26 [INFO] watchfiles.main: 1 change detected
47
+ 2025-07-30 20:37:27 [INFO] watchfiles.main: 1 change detected
48
+ 2025-07-30 20:37:29 [INFO] watchfiles.main: 2 changes detected
49
+ 2025-07-30 20:37:34 [INFO] watchfiles.main: 2 changes detected
50
+ 2025-07-30 20:37:37 [INFO] watchfiles.main: 1 change detected
51
+ 2025-07-30 20:37:37 [INFO] watchfiles.main: 1 change detected
52
+ 2025-07-30 20:37:39 [INFO] watchfiles.main: 2 changes detected
53
+ 2025-07-30 20:37:42 [INFO] watchfiles.main: 1 change detected
54
+ 2025-07-30 20:37:43 [INFO] watchfiles.main: 4 changes detected
55
+ 2025-07-30 20:37:43 [INFO] index: database_lifecycle event=connections_closed
56
+ 2025-07-30 20:37:43 [INFO] watchfiles.main: 1 change detected
57
+ 2025-07-30 20:37:44 [INFO] __mp_main__: logging_configured level=INFO hf_spaces=False
58
+ 2025-07-30 20:37:44 [INFO] index: logging_configured level=INFO hf_spaces=False
59
+ 2025-07-30 20:37:44 [INFO] watchfiles.main: 1 change detected
60
+ 2025-07-30 20:37:44 [INFO] watchfiles.main: 1 change detected
61
+ 2025-07-30 20:37:45 [INFO] watchfiles.main: 2 changes detected
62
+ 2025-07-30 20:37:45 [INFO] index: database_lifecycle event=tables_created_verified
63
+ 2025-07-30 20:37:46 [INFO] watchfiles.main: 3 changes detected
64
+ 2025-07-30 20:37:46 [INFO] index: database_lifecycle event=connections_closed
65
+ 2025-07-30 20:37:47 [INFO] watchfiles.main: 1 change detected
66
+ 2025-07-30 20:37:47 [INFO] __mp_main__: logging_configured level=INFO hf_spaces=False
67
+ 2025-07-30 20:37:47 [INFO] index: logging_configured level=INFO hf_spaces=False
68
+ 2025-07-30 20:37:47 [INFO] watchfiles.main: 1 change detected
69
+ 2025-07-30 20:37:48 [INFO] index: database_lifecycle event=tables_created_verified
70
+ 2025-07-30 20:37:49 [INFO] watchfiles.main: 2 changes detected
71
+ 2025-07-30 20:37:53 [INFO] watchfiles.main: 1 change detected
72
+ 2025-07-30 20:37:55 [INFO] watchfiles.main: 3 changes detected
73
+ 2025-07-30 20:37:58 [INFO] watchfiles.main: 2 changes detected
74
+ 2025-07-30 20:38:00 [INFO] watchfiles.main: 1 change detected
75
+ 2025-07-30 20:38:01 [INFO] watchfiles.main: 1 change detected
76
+ 2025-07-30 20:38:03 [INFO] watchfiles.main: 2 changes detected
77
+ 2025-07-30 20:38:06 [INFO] watchfiles.main: 1 change detected
78
+ 2025-07-30 20:38:06 [INFO] watchfiles.main: 1 change detected
79
+ 2025-07-30 20:38:08 [INFO] watchfiles.main: 2 changes detected
80
+ 2025-07-30 20:38:13 [INFO] watchfiles.main: 2 changes detected
81
+ 2025-07-30 20:38:16 [INFO] watchfiles.main: 1 change detected
82
+ 2025-07-30 20:38:17 [INFO] watchfiles.main: 1 change detected
83
+ 2025-07-30 20:38:19 [INFO] watchfiles.main: 2 changes detected
84
+ 2025-07-30 20:38:23 [INFO] watchfiles.main: 2 changes detected
85
+ 2025-07-30 20:38:27 [INFO] watchfiles.main: 3 changes detected
86
+ 2025-07-30 20:38:27 [INFO] index: database_lifecycle event=connections_closed
87
+ 2025-07-30 20:38:28 [INFO] watchfiles.main: 1 change detected
88
+ 2025-07-30 20:38:28 [INFO] __mp_main__: logging_configured level=INFO hf_spaces=False
89
+ 2025-07-30 20:38:28 [INFO] index: logging_configured level=INFO hf_spaces=False
90
+ 2025-07-30 20:38:28 [INFO] watchfiles.main: 1 change detected
91
+ 2025-07-30 20:38:29 [INFO] watchfiles.main: 2 changes detected
92
+ 2025-07-30 20:38:29 [INFO] index: database_lifecycle event=tables_created_verified
93
+ 2025-07-30 20:38:29 [INFO] watchfiles.main: 3 changes detected
94
+ 2025-07-30 20:38:29 [INFO] index: database_lifecycle event=connections_closed
95
+ 2025-07-30 20:38:30 [INFO] watchfiles.main: 1 change detected
96
+ 2025-07-30 20:38:30 [INFO] __mp_main__: logging_configured level=INFO hf_spaces=False
97
+ 2025-07-30 20:38:30 [INFO] index: logging_configured level=INFO hf_spaces=False
98
+ 2025-07-30 20:38:30 [INFO] watchfiles.main: 1 change detected
99
+ 2025-07-30 20:38:31 [INFO] index: database_lifecycle event=tables_created_verified
100
+ 2025-07-30 20:38:32 [INFO] watchfiles.main: 1 change detected
101
+ 2025-07-30 20:38:32 [INFO] watchfiles.main: 1 change detected
102
+ 2025-07-30 20:38:34 [INFO] watchfiles.main: 2 changes detected
103
+ 2025-07-30 20:38:37 [INFO] watchfiles.main: 1 change detected
104
+ 2025-07-30 20:38:39 [INFO] watchfiles.main: 2 changes detected
105
+ 2025-07-30 20:38:44 [INFO] watchfiles.main: 2 changes detected
106
+ 2025-07-30 20:38:47 [INFO] watchfiles.main: 1 change detected
107
+ 2025-07-30 20:38:47 [INFO] watchfiles.main: 1 change detected
108
+ 2025-07-30 20:38:49 [INFO] watchfiles.main: 2 changes detected
109
+ 2025-07-30 20:38:55 [INFO] watchfiles.main: 2 changes detected
110
+ 2025-07-30 20:39:00 [INFO] watchfiles.main: 2 changes detected
111
+ 2025-07-30 20:39:02 [INFO] watchfiles.main: 1 change detected
112
+ 2025-07-30 20:39:03 [INFO] watchfiles.main: 1 change detected
113
+ 2025-07-30 20:39:04 [INFO] watchfiles.main: 2 changes detected
114
+ 2025-07-30 20:39:08 [INFO] watchfiles.main: 1 change detected
115
+ 2025-07-30 20:39:10 [INFO] watchfiles.main: 2 changes detected
116
+ 2025-07-30 20:39:15 [INFO] watchfiles.main: 2 changes detected
117
+ 2025-07-30 20:39:18 [INFO] watchfiles.main: 1 change detected
118
+ 2025-07-30 20:39:19 [INFO] watchfiles.main: 1 change detected
119
+ 2025-07-30 20:39:21 [INFO] watchfiles.main: 2 changes detected
120
+ 2025-07-30 20:39:23 [INFO] watchfiles.main: 1 change detected
121
+ 2025-07-30 20:39:24 [INFO] watchfiles.main: 1 change detected
122
+ 2025-07-30 20:39:25 [INFO] index: database_lifecycle event=connections_closed
123
+ 2025-07-30 20:42:55 [INFO] __main__: logging_configured level=INFO hf_spaces=False
124
+ 2025-07-30 20:42:55 [INFO] __mp_main__: logging_configured level=INFO hf_spaces=False
125
+ 2025-07-30 20:42:55 [INFO] index: logging_configured level=INFO hf_spaces=False
126
+ 2025-07-30 20:42:56 [INFO] watchfiles.main: 1 change detected
127
+ 2025-07-30 20:42:57 [INFO] index: database_lifecycle event=tables_created_verified
128
+ 2025-07-30 20:42:57 [INFO] watchfiles.main: 2 changes detected
129
+ 2025-07-30 20:43:03 [INFO] watchfiles.main: 2 changes detected
130
+ 2025-07-30 20:43:08 [INFO] watchfiles.main: 2 changes detected
131
+ 2025-07-30 20:43:11 [INFO] watchfiles.main: 1 change detected
132
+ 2025-07-30 20:43:11 [INFO] watchfiles.main: 1 change detected
133
+ 2025-07-30 20:43:13 [INFO] watchfiles.main: 2 changes detected
134
+ 2025-07-30 20:43:16 [INFO] watchfiles.main: 1 change detected
135
+ 2025-07-30 20:43:19 [INFO] watchfiles.main: 2 changes detected
136
+ 2025-07-30 20:43:23 [INFO] index: tickers_update_skipped last_update=2025-07-30T15:35:47+00:00 reason=fresh_data
137
+ 2025-07-30 20:43:24 [INFO] watchfiles.main: 2 changes detected
138
+ 2025-07-30 20:43:24 [INFO] index: ticker_freshness_check last_update=2025-07-30T15:35:47+00:00 is_fresh=True
139
+ 2025-07-30 20:43:24 [INFO] index: ticker_data_freshness_check result=no_data_found
140
+ 2025-07-30 20:43:24 [INFO] index: bulk_refresh_strategy_selected reason=stale_data age_limit=24h
141
+ 2025-07-30 20:43:24 [INFO] index: bulk_refresh_started tickers_count=517 operation=clear_and_insert
142
+ 2025-07-30 20:43:27 [INFO] watchfiles.main: 1 change detected
143
+ 2025-07-30 20:43:27 [INFO] watchfiles.main: 1 change detected
144
+ 2025-07-30 20:43:29 [INFO] watchfiles.main: 2 changes detected
145
+ 2025-07-30 20:43:32 [INFO] watchfiles.main: 1 change detected
146
+ 2025-07-30 20:43:34 [INFO] watchfiles.main: 2 changes detected
147
+ 2025-07-30 20:43:34 [ERROR] yfinance:
148
+ 2 Failed downloads:
149
+ 2025-07-30 20:43:34 [ERROR] yfinance: ['BRK.B']: YFPricesMissingError('possibly delisted; no price data found (period=3mo) (Yahoo error = "No data found, symbol may be delisted")')
150
+ 2025-07-30 20:43:34 [ERROR] yfinance: ['BF.B']: YFPricesMissingError('possibly delisted; no price data found (period=3mo)')
151
+ 2025-07-30 20:43:34 [INFO] index: data_download_completed tickers_count=517 duration_ms=10186.18
152
+ 2025-07-30 20:43:34 [INFO] index: database_clear_started operation=delete_all_ticker_data
153
+ 2025-07-30 20:43:34 [INFO] index: database_clear_completed duration_ms=44.55
154
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.46
155
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
156
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
157
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
158
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.49
159
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.32
160
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
161
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
162
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
163
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
164
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
165
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
166
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
167
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
168
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
169
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
170
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
171
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
172
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
173
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
174
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
175
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
176
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
177
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
178
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
179
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
180
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
181
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.33
182
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
183
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
184
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
185
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
186
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
187
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
188
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
189
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
190
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
191
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
192
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
193
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
194
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.39
195
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.29
196
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.48
197
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
198
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.31
199
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
200
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
201
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
202
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
203
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.29
204
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
205
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
206
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
207
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
208
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
209
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
210
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
211
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
212
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
213
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
214
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
215
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
216
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.43
217
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
218
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
219
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
220
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
221
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.29
222
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
223
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
224
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
225
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
226
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
227
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
228
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
229
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
230
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.38
231
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
232
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
233
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.33
234
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.35
235
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
236
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
237
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
238
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=1.35
239
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.32
240
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
241
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.74
242
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
243
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
244
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
245
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.39
246
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
247
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
248
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
249
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
250
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.56
251
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.31
252
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.52
253
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
254
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
255
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
256
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
257
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
258
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.56
259
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.76
260
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.56
261
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
262
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
263
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
264
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
265
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.36
266
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
267
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
268
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
269
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.36
270
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
271
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.31
272
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
273
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.61
274
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.59
275
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.33
276
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
277
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.32
278
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
279
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
280
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
281
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
282
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
283
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
284
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
285
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
286
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
287
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
288
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
289
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.35
290
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
291
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
292
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
293
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
294
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
295
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
296
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
297
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
298
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.29
299
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
300
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
301
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
302
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.29
303
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
304
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.29
305
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
306
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
307
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
308
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
309
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
310
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
311
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
312
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.32
313
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
314
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
315
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
316
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
317
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
318
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
319
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
320
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.36
321
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
322
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
323
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
324
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
325
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
326
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
327
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
328
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
329
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
330
+ 2025-07-30 20:43:34 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
331
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
332
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
333
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
334
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
335
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
336
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
337
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.34
338
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
339
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
340
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
341
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
342
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
343
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
344
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
345
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
346
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.38
347
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.29
348
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
349
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
350
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
351
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
352
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
353
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
354
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
355
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
356
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.31
357
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
358
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
359
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
360
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
361
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.34
362
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
363
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
364
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
365
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
366
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
367
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
368
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
369
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
370
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.29
371
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
372
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
373
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.29
374
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.36
375
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
376
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
377
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
378
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
379
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
380
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
381
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
382
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
383
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
384
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.29
385
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.36
386
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
387
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
388
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.35
389
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
390
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
391
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
392
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
393
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
394
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
395
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
396
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
397
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.55
398
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
399
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
400
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
401
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
402
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
403
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
404
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
405
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
406
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
407
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
408
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
409
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.44
410
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
411
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
412
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
413
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
414
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
415
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
416
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
417
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
418
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
419
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
420
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.70
421
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.79
422
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
423
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
424
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
425
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
426
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
427
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
428
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.31
429
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
430
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
431
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
432
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
433
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.34
434
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
435
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
436
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
437
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
438
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
439
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
440
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
441
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
442
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
443
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
444
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
445
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
446
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
447
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
448
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
449
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
450
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
451
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
452
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
453
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
454
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.38
455
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
456
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
457
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
458
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.33
459
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
460
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
461
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
462
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
463
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
464
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
465
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
466
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
467
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.38
468
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
469
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
470
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
471
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
472
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
473
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
474
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
475
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
476
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
477
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
478
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
479
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
480
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
481
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
482
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
483
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.40
484
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
485
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
486
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
487
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
488
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
489
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
490
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.32
491
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
492
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
493
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
494
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
495
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
496
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
497
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
498
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.33
499
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
500
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
501
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
502
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
503
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
504
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
505
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.41
506
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.31
507
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
508
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
509
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
510
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
511
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.31
512
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
513
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
514
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
515
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.41
516
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
517
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.42
518
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
519
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
520
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
521
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
522
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
523
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
524
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
525
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
526
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
527
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
528
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
529
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
530
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
531
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
532
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
533
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
534
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.32
535
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
536
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
537
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
538
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.32
539
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
540
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
541
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
542
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
543
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
544
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
545
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
546
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
547
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
548
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
549
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
550
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
551
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.31
552
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
553
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
554
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
555
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
556
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
557
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
558
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
559
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
560
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.29
561
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
562
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
563
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
564
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.38
565
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.29
566
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
567
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
568
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.31
569
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
570
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
571
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
572
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
573
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
574
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
575
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
576
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
577
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.46
578
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.35
579
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
580
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
581
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
582
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
583
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
584
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
585
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
586
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
587
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
588
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
589
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.56
590
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.52
591
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
592
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
593
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
594
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.51
595
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.63
596
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.56
597
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.65
598
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
599
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.55
600
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.51
601
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
602
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.33
603
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
604
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
605
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
606
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
607
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
608
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
609
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
610
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
611
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.40
612
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
613
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
614
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
615
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
616
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.42
617
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.29
618
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
619
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
620
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.32
621
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
622
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
623
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
624
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
625
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.32
626
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
627
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
628
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.59
629
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.38
630
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.26
631
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
632
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
633
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
634
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
635
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
636
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.28
637
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
638
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.62
639
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.29
640
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
641
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.36
642
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
643
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
644
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
645
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
646
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.77
647
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.27
648
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.32
649
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
650
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
651
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
652
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
653
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
654
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
655
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
656
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
657
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.25
658
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.30
659
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
660
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
661
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
662
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
663
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.24
664
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
665
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
666
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
667
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
668
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
669
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.23
670
+ 2025-07-30 20:43:35 [INFO] index: technical_indicators_calculated records=63 duration_ms=0.22
671
+ 2025-07-30 20:43:35 [INFO] index: database_insert_started total_records=32445 chunk_size=1000
672
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=1/33 records=1000 duration_ms=7.77
673
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=2/33 records=1000 duration_ms=7.73
674
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=3/33 records=1000 duration_ms=7.91
675
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=4/33 records=1000 duration_ms=7.71
676
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=5/33 records=1000 duration_ms=7.79
677
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=6/33 records=1000 duration_ms=32.24
678
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=7/33 records=1000 duration_ms=7.71
679
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=8/33 records=1000 duration_ms=7.78
680
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=9/33 records=1000 duration_ms=7.58
681
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=10/33 records=1000 duration_ms=7.62
682
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=11/33 records=1000 duration_ms=7.93
683
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=12/33 records=1000 duration_ms=7.68
684
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=13/33 records=1000 duration_ms=7.82
685
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=14/33 records=1000 duration_ms=7.69
686
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=15/33 records=1000 duration_ms=7.82
687
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=16/33 records=1000 duration_ms=7.34
688
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=17/33 records=1000 duration_ms=7.88
689
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=18/33 records=1000 duration_ms=38.16
690
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=19/33 records=1000 duration_ms=7.70
691
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=20/33 records=1000 duration_ms=7.70
692
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=21/33 records=1000 duration_ms=7.60
693
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=22/33 records=1000 duration_ms=7.99
694
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=23/33 records=1000 duration_ms=7.71
695
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=24/33 records=1000 duration_ms=7.76
696
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=25/33 records=1000 duration_ms=7.62
697
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=26/33 records=1000 duration_ms=7.79
698
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=27/33 records=1000 duration_ms=7.64
699
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=28/33 records=1000 duration_ms=7.69
700
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=29/33 records=1000 duration_ms=43.56
701
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=30/33 records=1000 duration_ms=7.76
702
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=31/33 records=1000 duration_ms=7.67
703
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=32/33 records=1000 duration_ms=7.81
704
+ 2025-07-30 20:43:35 [INFO] index: database_chunk_inserted chunk=33/33 records=445 duration_ms=3.38
705
+ 2025-07-30 20:43:39 [INFO] watchfiles.main: 2 changes detected
706
+ 2025-07-30 20:43:40 [INFO] index: database_commit_completed duration_ms=4540.73
707
+ 2025-07-30 20:43:40 [INFO] index: database_operations_completed records_inserted=32445 duration_ms=5686.00
708
+ 2025-07-30 20:43:40 [INFO] index: bulk_refresh_completed total_duration_ms=15872.39 download_ms=10186.18 database_ms=5686.00
709
+ 2025-07-30 20:43:40 [INFO] index: bulk_refresh_summary records_inserted=32445 operation=completed
710
+ 2025-07-30 20:43:41 [INFO] index: ticker_freshness_check last_update=2025-07-30T15:35:47+00:00 is_fresh=True
711
+ 2025-07-30 20:43:41 [INFO] index: ticker_data_freshness_check last_update=2025-07-30T18:43:34+00:00 is_fresh=True
712
+ 2025-07-30 20:43:41 [INFO] index: indicators_recalculation_requested reason=force_indicators_flag data_age=fresh
713
+ 2025-07-30 20:43:42 [INFO] watchfiles.main: 1 change detected
714
+ 2025-07-30 20:43:42 [INFO] watchfiles.main: 1 change detected
715
+ 2025-07-30 20:43:45 [INFO] watchfiles.main: 2 changes detected
716
+ 2025-07-30 20:43:47 [INFO] watchfiles.main: 1 change detected
717
+ 2025-07-30 20:43:48 [INFO] watchfiles.main: 1 change detected
718
+ 2025-07-30 20:43:50 [INFO] watchfiles.main: 2 changes detected
719
+ 2025-07-30 20:43:56 [INFO] watchfiles.main: 2 changes detected
720
+ 2025-07-30 20:43:58 [INFO] watchfiles.main: 1 change detected
721
+ 2025-07-30 20:43:58 [INFO] watchfiles.main: 1 change detected
722
+ 2025-07-30 20:44:00 [INFO] watchfiles.main: 2 changes detected
723
+ 2025-07-30 20:44:03 [INFO] watchfiles.main: 1 change detected
724
+ 2025-07-30 20:44:06 [INFO] watchfiles.main: 2 changes detected
725
+ 2025-07-30 20:44:11 [INFO] watchfiles.main: 2 changes detected
726
+ 2025-07-30 20:44:13 [INFO] watchfiles.main: 1 change detected
727
+ 2025-07-30 20:44:13 [INFO] watchfiles.main: 1 change detected
728
+ 2025-07-30 20:44:17 [INFO] watchfiles.main: 2 changes detected
729
+ 2025-07-30 20:44:18 [INFO] watchfiles.main: 1 change detected
730
+ 2025-07-30 20:44:18 [INFO] watchfiles.main: 1 change detected
731
+ 2025-07-30 20:44:22 [INFO] watchfiles.main: 2 changes detected
732
+ 2025-07-30 20:44:28 [INFO] watchfiles.main: 2 changes detected
733
+ 2025-07-30 20:44:29 [INFO] watchfiles.main: 1 change detected
734
+ 2025-07-30 20:44:29 [INFO] watchfiles.main: 1 change detected
735
+ 2025-07-30 20:44:33 [INFO] watchfiles.main: 2 changes detected
736
+ 2025-07-30 20:44:34 [INFO] watchfiles.main: 1 change detected
737
+ 2025-07-30 20:44:34 [INFO] watchfiles.main: 1 change detected
738
+ 2025-07-30 20:44:37 [INFO] watchfiles.main: 2 changes detected
739
+ 2025-07-30 20:44:43 [INFO] watchfiles.main: 2 changes detected
740
+ 2025-07-30 20:44:45 [INFO] watchfiles.main: 1 change detected
741
+ 2025-07-30 20:44:45 [INFO] watchfiles.main: 1 change detected
742
+ 2025-07-30 20:44:47 [INFO] watchfiles.main: 2 changes detected
743
+ 2025-07-30 20:44:52 [INFO] watchfiles.main: 2 changes detected
744
+ 2025-07-30 20:44:57 [INFO] watchfiles.main: 2 changes detected
745
+ 2025-07-30 20:44:59 [INFO] watchfiles.main: 1 change detected
746
+ 2025-07-30 20:45:00 [INFO] watchfiles.main: 1 change detected
747
+ 2025-07-30 20:45:02 [INFO] watchfiles.main: 2 changes detected
748
+ 2025-07-30 20:45:05 [INFO] watchfiles.main: 1 change detected
749
+ 2025-07-30 20:45:05 [INFO] watchfiles.main: 1 change detected
750
+ 2025-07-30 20:45:08 [INFO] watchfiles.main: 2 changes detected
751
+ 2025-07-30 20:45:13 [INFO] watchfiles.main: 2 changes detected
752
+ 2025-07-30 20:45:15 [INFO] watchfiles.main: 1 change detected
753
+ 2025-07-30 20:45:15 [INFO] watchfiles.main: 1 change detected
754
+ 2025-07-30 20:45:18 [INFO] watchfiles.main: 2 changes detected
755
+ 2025-07-30 20:45:23 [INFO] watchfiles.main: 2 changes detected
756
+ 2025-07-30 20:45:27 [INFO] watchfiles.main: 2 changes detected
757
+ 2025-07-30 20:45:29 [INFO] watchfiles.main: 1 change detected
758
+ 2025-07-30 20:45:30 [INFO] watchfiles.main: 1 change detected
759
+ 2025-07-30 20:45:32 [INFO] watchfiles.main: 2 changes detected
760
+ 2025-07-30 20:45:34 [INFO] watchfiles.main: 1 change detected
761
+ 2025-07-30 20:45:35 [INFO] watchfiles.main: 1 change detected
762
+ 2025-07-30 20:45:37 [INFO] watchfiles.main: 2 changes detected
763
+ 2025-07-30 20:45:42 [INFO] watchfiles.main: 2 changes detected
764
+ 2025-07-30 20:45:44 [INFO] watchfiles.main: 1 change detected
765
+ 2025-07-30 20:45:44 [INFO] watchfiles.main: 1 change detected
766
+ 2025-07-30 20:45:46 [INFO] watchfiles.main: 2 changes detected
767
+ 2025-07-30 20:45:51 [INFO] watchfiles.main: 2 changes detected
768
+ 2025-07-30 20:45:57 [INFO] watchfiles.main: 2 changes detected
769
+ 2025-07-30 20:45:59 [INFO] watchfiles.main: 1 change detected
770
+ 2025-07-30 20:46:00 [INFO] watchfiles.main: 1 change detected
771
+ 2025-07-30 20:46:02 [INFO] watchfiles.main: 2 changes detected
772
+ 2025-07-30 20:46:05 [INFO] watchfiles.main: 1 change detected
773
+ 2025-07-30 20:46:05 [INFO] watchfiles.main: 1 change detected
774
+ 2025-07-30 20:46:06 [INFO] index: database_lifecycle event=connections_closed
tests/run_tests.sh CHANGED
@@ -46,7 +46,7 @@ fi
46
 
47
  # Install test dependencies if needed
48
  echo "πŸ“¦ Installing test dependencies..."
49
- pip install requests python-dotenv > /dev/null 2>&1
50
 
51
  # Run the tests
52
  echo "πŸ§ͺ Running API tests..."
 
46
 
47
  # Install test dependencies if needed
48
  echo "πŸ“¦ Installing test dependencies..."
49
+ pip install requests python-dotenv pandas > /dev/null 2>&1
50
 
51
  # Run the tests
52
  echo "πŸ§ͺ Running API tests..."
tests/test_api.py CHANGED
@@ -5,8 +5,10 @@ Tests authentication, endpoints, and security features.
5
 
6
  Updated for new API architecture:
7
  - Removed /data/download endpoint (now uses only /data/download-all)
8
- - Removed force_refresh parameter (uses automatic 24h freshness check)
9
- - Updated bulk download strategy testing
 
 
10
  """
11
 
12
  import requests
@@ -102,7 +104,7 @@ def test_protected_endpoints_no_auth():
102
  protected_endpoints = [
103
  ("POST", "/tickers/update", {"force_refresh": False}),
104
  ("POST", "/tickers/update-async", {"force_refresh": False}),
105
- ("POST", "/data/download-all", None),
106
  ("GET", "/tasks", None),
107
  ("DELETE", "/tasks/old", None)
108
  ]
@@ -134,7 +136,7 @@ def test_protected_endpoints_invalid_auth():
134
 
135
  protected_endpoints = [
136
  ("POST", "/tickers/update", {"force_refresh": False}),
137
- ("POST", "/data/download-all", None),
138
  ("GET", "/tasks", None),
139
  ]
140
 
@@ -229,11 +231,13 @@ def test_data_endpoints():
229
  # Test POST /data/download-all (bulk download with automatic freshness check)
230
  # Note: This endpoint now automatically checks if data is <24h old and skips update if fresh
231
  # First run will download all data, subsequent runs may return "data is fresh" message
 
232
  try:
233
  response = requests.post(
234
  f"{BASE_URL}/data/download-all",
235
- headers=HEADERS_VALID_AUTH,
236
- timeout=90 # Bulk download might take longer, especially first time
 
237
  )
238
  passed = response.status_code == 200
239
  all_passed &= print_result("/data/download-all", "POST", 200, response.status_code, passed)
@@ -261,6 +265,16 @@ def test_data_endpoints():
261
  if data:
262
  latest = data[0]
263
  print(f" πŸ’° Latest close: ${latest.get('close', 0):.2f}")
 
 
 
 
 
 
 
 
 
 
264
  except Exception as e:
265
  print(f"❌ GET /data/tickers/AAPL failed: {e}")
266
  all_passed = False
@@ -318,6 +332,7 @@ def main():
318
  all_tests_passed &= test_protected_endpoints_invalid_auth()
319
  all_tests_passed &= test_protected_endpoints_valid_auth()
320
  all_tests_passed &= test_data_endpoints()
 
321
  all_tests_passed &= test_sql_injection_safety()
322
 
323
  # Final results
@@ -329,6 +344,8 @@ def main():
329
  print("βœ… SQL injection protection is active")
330
  print("βœ… Public endpoints are accessible")
331
  print("βœ… Bulk data download with freshness check is working")
 
 
332
  print("βœ… New optimized API architecture is functional")
333
  else:
334
  print("❌ SOME TESTS FAILED!")
@@ -337,5 +354,62 @@ def main():
337
 
338
  return 0 if all_tests_passed else 1
339
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  if __name__ == "__main__":
341
  exit(main())
 
5
 
6
  Updated for new API architecture:
7
  - Removed /data/download endpoint (now uses only /data/download-all)
8
+ - Added force_refresh and force_indicators parameters
9
+ - Updated bulk download strategy testing with 3-month period
10
+ - Added technical indicators validation (SMA 10, 20, 50)
11
+ - Updated response validation for new SMA fields
12
  """
13
 
14
  import requests
 
104
  protected_endpoints = [
105
  ("POST", "/tickers/update", {"force_refresh": False}),
106
  ("POST", "/tickers/update-async", {"force_refresh": False}),
107
+ ("POST", "/data/download-all", {"force_refresh": False, "force_indicators": False}),
108
  ("GET", "/tasks", None),
109
  ("DELETE", "/tasks/old", None)
110
  ]
 
136
 
137
  protected_endpoints = [
138
  ("POST", "/tickers/update", {"force_refresh": False}),
139
+ ("POST", "/data/download-all", {"force_refresh": False, "force_indicators": False}),
140
  ("GET", "/tasks", None),
141
  ]
142
 
 
231
  # Test POST /data/download-all (bulk download with automatic freshness check)
232
  # Note: This endpoint now automatically checks if data is <24h old and skips update if fresh
233
  # First run will download all data, subsequent runs may return "data is fresh" message
234
+ # Now supports force_refresh and force_indicators parameters
235
  try:
236
  response = requests.post(
237
  f"{BASE_URL}/data/download-all",
238
+ headers=HEADERS_VALID_AUTH,
239
+ json={"force_refresh": False, "force_indicators": False},
240
+ timeout=120 # Bulk download might take longer with 3mo data and technical indicators
241
  )
242
  passed = response.status_code == 200
243
  all_passed &= print_result("/data/download-all", "POST", 200, response.status_code, passed)
 
265
  if data:
266
  latest = data[0]
267
  print(f" πŸ’° Latest close: ${latest.get('close', 0):.2f}")
268
+ # Check for technical indicators
269
+ sma_fast = latest.get('sma_fast')
270
+ sma_med = latest.get('sma_med')
271
+ sma_slow = latest.get('sma_slow')
272
+ if sma_fast is not None:
273
+ print(f" πŸ“Š SMA Fast (10): ${sma_fast:.2f}")
274
+ if sma_med is not None:
275
+ print(f" πŸ“Š SMA Med (20): ${sma_med:.2f}")
276
+ if sma_slow is not None:
277
+ print(f" πŸ“Š SMA Slow (50): ${sma_slow:.2f}")
278
  except Exception as e:
279
  print(f"❌ GET /data/tickers/AAPL failed: {e}")
280
  all_passed = False
 
332
  all_tests_passed &= test_protected_endpoints_invalid_auth()
333
  all_tests_passed &= test_protected_endpoints_valid_auth()
334
  all_tests_passed &= test_data_endpoints()
335
+ all_tests_passed &= test_technical_indicators()
336
  all_tests_passed &= test_sql_injection_safety()
337
 
338
  # Final results
 
344
  print("βœ… SQL injection protection is active")
345
  print("βœ… Public endpoints are accessible")
346
  print("βœ… Bulk data download with freshness check is working")
347
+ print("βœ… Technical indicators (SMA 10, 20, 50) are working")
348
+ print("βœ… 3-month data period and force_indicators flag functional")
349
  print("βœ… New optimized API architecture is functional")
350
  else:
351
  print("❌ SOME TESTS FAILED!")
 
354
 
355
  return 0 if all_tests_passed else 1
356
 
357
+ def test_technical_indicators():
358
+ """Test technical indicators functionality."""
359
+ print_test_header("Technical Indicators Tests")
360
+
361
+ all_passed = True
362
+
363
+ # Test POST /data/download-all with force_indicators=True
364
+ try:
365
+ response = requests.post(
366
+ f"{BASE_URL}/data/download-all",
367
+ headers=HEADERS_VALID_AUTH,
368
+ json={"force_refresh": False, "force_indicators": True},
369
+ timeout=120
370
+ )
371
+ passed = response.status_code == 200
372
+ all_passed &= print_result("/data/download-all (force_indicators)", "POST", 200, response.status_code, passed)
373
+
374
+ if passed:
375
+ data = response.json()
376
+ print(f" πŸ“Š Processed {data.get('tickers_processed', 0)} tickers")
377
+ print(f" πŸ’¬ Message: {data.get('message', 'N/A')}")
378
+ except Exception as e:
379
+ print(f"❌ POST /data/download-all (force_indicators) failed: {e}")
380
+ all_passed = False
381
+
382
+ # Test that ticker data now includes technical indicators
383
+ try:
384
+ response = requests.get(f"{BASE_URL}/data/tickers/AAPL?days=60", headers=HEADERS_NO_AUTH, timeout=10)
385
+ passed = response.status_code == 200
386
+ all_passed &= print_result("/data/tickers/AAPL (indicators validation)", "GET", 200, response.status_code, passed)
387
+
388
+ if passed:
389
+ data = response.json()
390
+ print(f" πŸ“Š Retrieved {len(data)} days of AAPL data for indicators test")
391
+
392
+ # Check that we have enough data and some records have indicators
393
+ indicators_found = 0
394
+ for record in data:
395
+ if (record.get('sma_fast') is not None or
396
+ record.get('sma_med') is not None or
397
+ record.get('sma_slow') is not None):
398
+ indicators_found += 1
399
+
400
+ print(f" πŸ“ˆ Records with indicators: {indicators_found}/{len(data)}")
401
+
402
+ # Validate that recent records have indicators (should have SMA after 50+ days)
403
+ if len(data) >= 50:
404
+ recent_records = data[:10] # Check most recent 10 records
405
+ sma_slow_count = sum(1 for r in recent_records if r.get('sma_slow') is not None)
406
+ print(f" πŸ“Š Recent records with SMA Slow (50): {sma_slow_count}/10")
407
+
408
+ except Exception as e:
409
+ print(f"❌ Technical indicators validation failed: {e}")
410
+ all_passed = False
411
+
412
+ return all_passed
413
+
414
  if __name__ == "__main__":
415
  exit(main())