Mormoapp commited on
Commit
2b7cbba
Β·
verified Β·
1 Parent(s): 0ce6c1e

Update api.py

Browse files
Files changed (1) hide show
  1. api.py +101 -60
api.py CHANGED
@@ -44,6 +44,8 @@ from ta.trend import ADXIndicator, MACD
44
  from ta.volatility import AverageTrueRange
45
  from chronos import Chronos2Pipeline
46
  from dotenv import load_dotenv
 
 
47
 
48
  load_dotenv()
49
 
@@ -625,62 +627,105 @@ INSTRUMENT_CATALOG = [
625
  ]
626
 
627
 
628
- def _get_api_key():
629
- key = os.getenv("TWELVEDATA_API_KEY", "").strip()
630
- if not key or key == "your_api_key_here":
631
- return None
632
- return key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
633
 
634
 
635
  def fetch_live_data(symbol: str, exchange: str = "", interval: str = "5min",
636
  outputsize: int = 5000) -> pd.DataFrame:
637
  """
638
- Fetch OHLCV time-series from Twelve Data.
639
  Returns a pandas DataFrame with columns: date, open, high, low, close, volume.
640
- Raises ValueError on API errors.
641
  """
642
- api_key = _get_api_key()
643
- if not api_key:
644
- raise ValueError(
645
- "TWELVEDATA_API_KEY not set. Add your free API key to .env file. "
646
- "Sign up at https://twelvedata.com (free, 30 seconds)."
647
- )
648
-
649
- params = {
650
- "symbol": symbol,
651
- "interval": interval,
652
- "outputsize": outputsize,
653
- "apikey": api_key,
654
- "format": "JSON",
655
- "order": "ASC",
656
  }
657
- if exchange:
658
- params["exchange"] = exchange
659
-
660
- resp = http_requests.get(f"{TWELVEDATA_BASE}/time_series", params=params, timeout=30)
661
- data = resp.json()
662
-
663
- if data.get("status") == "error" or "code" in data:
664
- msg = data.get("message", str(data))
665
- raise ValueError(f"Twelve Data API error: {msg}")
666
-
667
- values = data.get("values", [])
668
- if not values:
669
- raise ValueError(f"No data returned for symbol '{symbol}'. Check the symbol is valid.")
670
-
671
- df = pd.DataFrame(values)
672
- df = df.rename(columns={"datetime": "date"})
673
-
 
 
 
 
 
 
 
 
 
 
 
 
674
  for col in ["open", "high", "low", "close", "volume"]:
675
  if col in df.columns:
676
  df[col] = pd.to_numeric(df[col], errors="coerce")
677
 
678
- if "date" in df.columns:
679
- df["date"] = pd.to_datetime(df["date"])
680
- df = df.sort_values("date").reset_index(drop=True)
681
-
682
  df = df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True)
683
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684
  # Remove flat candles
685
  flat = ((df["open"]==df["high"]) & (df["high"]==df["low"]) & (df["low"]==df["close"]))
686
  df = df[~flat].copy().reset_index(drop=True)
@@ -689,20 +734,17 @@ def fetch_live_data(symbol: str, exchange: str = "", interval: str = "5min",
689
 
690
 
691
  def fetch_current_price(symbol: str, exchange: str = ""):
692
- """Quick price lookup β€” 1 API credit."""
693
- api_key = _get_api_key()
694
- if not api_key:
695
- return None
696
-
697
- params = {"symbol": symbol, "apikey": api_key}
698
- if exchange:
699
- params["exchange"] = exchange
700
-
701
  try:
702
- resp = http_requests.get(f"{TWELVEDATA_BASE}/price", params=params, timeout=10)
703
- data = resp.json()
704
- if "price" in data:
705
- return float(data["price"])
 
 
 
 
706
  except Exception:
707
  pass
708
  return None
@@ -713,9 +755,8 @@ def fetch_current_price(symbol: str, exchange: str = ""):
713
  @app.get("/instruments")
714
  def list_instruments():
715
  """Return the catalog of supported instruments with categories."""
716
- api_ready = _get_api_key() is not None
717
  return {
718
- "api_ready": api_ready,
719
  "instruments": INSTRUMENT_CATALOG,
720
  "categories": sorted(set(i["category"] for i in INSTRUMENT_CATALOG)),
721
  "timeframes": ["1min","5min","15min","1h","4h","1day"],
@@ -727,7 +768,7 @@ def get_price(symbol: str, exchange: str = Query(default="")):
727
  """Quick current-price lookup for a symbol."""
728
  price = fetch_current_price(symbol, exchange)
729
  if price is None:
730
- return {"error": "Could not fetch price. Check API key and symbol."}
731
  return {"symbol": symbol, "price": price}
732
 
733
 
@@ -736,7 +777,7 @@ def live_analyse(symbol: str, exchange: str = Query(default=""),
736
  name: str = Query(default=""),
737
  interval: str = Query(default="5min")):
738
  """
739
- Fetch live OHLCV data from Twelve Data and run the full analysis pipeline:
740
  indicators β†’ Chronos forecast β†’ 3 XGBoost models β†’ signal + backtest.
741
  Returns the same JSON structure as POST /analyse.
742
  Supports intervals: 1min, 5min, 15min, 1h, 4h, 1day.
@@ -782,7 +823,7 @@ def live_analyse(symbol: str, exchange: str = Query(default=""),
782
  "current": round(float(df["close"].iloc[-1]), 4),
783
  },
784
  "avg_atr_pct": round(float((df["ATR"]/df["close"]*100).median()), 4),
785
- "source": "Twelve Data API (live)",
786
  }
787
 
788
  return {
 
44
  from ta.volatility import AverageTrueRange
45
  from chronos import Chronos2Pipeline
46
  from dotenv import load_dotenv
47
+ from yahooquery import Ticker
48
+
49
 
50
  load_dotenv()
51
 
 
627
  ]
628
 
629
 
630
+ # Symbol mappings from Frontend symbols to Yahoo Finance symbols
631
+ SYMBOL_MAP = {
632
+ "NIFTY50": "^NSEI",
633
+ "SENSEX": "^BSESN",
634
+ "RELIANCE": "RELIANCE.NS",
635
+ "TCS": "TCS.NS",
636
+ "HDFCBANK": "HDFCBANK.NS",
637
+ "INFY": "INFY.NS",
638
+ "ICICIBANK": "ICICIBANK.NS",
639
+ "SBIN": "SBIN.NS",
640
+ "AAPL": "AAPL",
641
+ "TSLA": "TSLA",
642
+ "MSFT": "MSFT",
643
+ "GOOGL": "GOOGL",
644
+ "AMZN": "AMZN",
645
+ "NVDA": "NVDA",
646
+ "META": "META",
647
+ "BTC/USD": "BTC-USD",
648
+ "ETH/USD": "ETH-USD",
649
+ "SOL/USD": "SOL-USD",
650
+ "XRP/USD": "XRP-USD",
651
+ "EUR/USD": "EURUSD=X",
652
+ "GBP/USD": "GBPUSD=X",
653
+ "USD/JPY": "USDJPY=X",
654
+ "USD/INR": "USDINR=X",
655
+ "XAU/USD": "GC=F",
656
+ "XAG/USD": "SI=F",
657
+ }
658
 
659
 
660
  def fetch_live_data(symbol: str, exchange: str = "", interval: str = "5min",
661
  outputsize: int = 5000) -> pd.DataFrame:
662
  """
663
+ Fetch OHLCV time-series from Yahoo Finance via yahooquery.
664
  Returns a pandas DataFrame with columns: date, open, high, low, close, volume.
 
665
  """
666
+ yf_symbol = SYMBOL_MAP.get(symbol, symbol)
667
+ if not yf_symbol:
668
+ yf_symbol = symbol
669
+
670
+ # Map frontend intervals to yahooquery intervals
671
+ intervals_map = {
672
+ "1min": "1m",
673
+ "5min": "5m",
674
+ "15min": "15m",
675
+ "1h": "1h",
676
+ "4h": "1h", # We download 1h and resample to 4h
677
+ "1day": "1d",
 
 
678
  }
679
+ yq_interval = intervals_map.get(interval, "5m")
680
+
681
+ # Select history period based on interval
682
+ if yq_interval == "1m":
683
+ period = "5d"
684
+ elif yq_interval in ["5m", "15m"]:
685
+ period = "1mo"
686
+ elif yq_interval == "1h":
687
+ period = "3mo" if interval == "4h" else "1mo"
688
+ else:
689
+ period = "1y"
690
+
691
+ t = Ticker(yf_symbol)
692
+ df = t.history(period=period, interval=yq_interval)
693
+
694
+ if isinstance(df, dict):
695
+ error_msg = df.get(yf_symbol, {}).get("description", str(df))
696
+ raise ValueError(f"Yahoo Finance error: {error_msg}")
697
+
698
+ if df is None or df.empty:
699
+ raise ValueError(f"No data returned for symbol '{symbol}' (Yahoo Symbol: '{yf_symbol}')")
700
+
701
+ # Reset MultiIndex to convert index level 1 to column 'date'
702
+ df = df.reset_index()
703
+ if 'symbol' in df.columns:
704
+ df = df.drop(columns=['symbol'])
705
+
706
+ # Standardize columns
707
+ df = df.rename(columns={"date": "date"})
708
  for col in ["open", "high", "low", "close", "volume"]:
709
  if col in df.columns:
710
  df[col] = pd.to_numeric(df[col], errors="coerce")
711
 
 
 
 
 
712
  df = df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True)
713
 
714
+ # Sort by date
715
+ df["date"] = pd.to_datetime(df["date"])
716
+ df = df.sort_values("date").reset_index(drop=True)
717
+
718
+ # Resample 1h data to 4h if requested
719
+ if interval == "4h":
720
+ df.set_index("date", inplace=True)
721
+ df = df.resample("4h").agg({
722
+ "open": "first",
723
+ "high": "max",
724
+ "low": "min",
725
+ "close": "last",
726
+ "volume": "sum"
727
+ }).dropna().reset_index()
728
+
729
  # Remove flat candles
730
  flat = ((df["open"]==df["high"]) & (df["high"]==df["low"]) & (df["low"]==df["close"]))
731
  df = df[~flat].copy().reset_index(drop=True)
 
734
 
735
 
736
  def fetch_current_price(symbol: str, exchange: str = ""):
737
+ """Quick price lookup via yahooquery β€” 1 call, no API keys."""
738
+ yf_symbol = SYMBOL_MAP.get(symbol, symbol)
 
 
 
 
 
 
 
739
  try:
740
+ t = Ticker(yf_symbol)
741
+ price_dict = t.price
742
+ if isinstance(price_dict, dict) and yf_symbol in price_dict:
743
+ details = price_dict[yf_symbol]
744
+ if isinstance(details, dict):
745
+ price = details.get("regularMarketPrice")
746
+ if price is not None:
747
+ return float(price)
748
  except Exception:
749
  pass
750
  return None
 
755
  @app.get("/instruments")
756
  def list_instruments():
757
  """Return the catalog of supported instruments with categories."""
 
758
  return {
759
+ "api_ready": True,
760
  "instruments": INSTRUMENT_CATALOG,
761
  "categories": sorted(set(i["category"] for i in INSTRUMENT_CATALOG)),
762
  "timeframes": ["1min","5min","15min","1h","4h","1day"],
 
768
  """Quick current-price lookup for a symbol."""
769
  price = fetch_current_price(symbol, exchange)
770
  if price is None:
771
+ return {"error": "Could not fetch price. Check symbol."}
772
  return {"symbol": symbol, "price": price}
773
 
774
 
 
777
  name: str = Query(default=""),
778
  interval: str = Query(default="5min")):
779
  """
780
+ Fetch live OHLCV data from Yahoo Finance and run the full analysis pipeline:
781
  indicators β†’ Chronos forecast β†’ 3 XGBoost models β†’ signal + backtest.
782
  Returns the same JSON structure as POST /analyse.
783
  Supports intervals: 1min, 5min, 15min, 1h, 4h, 1day.
 
823
  "current": round(float(df["close"].iloc[-1]), 4),
824
  },
825
  "avg_atr_pct": round(float((df["ATR"]/df["close"]*100).median()), 4),
826
+ "source": "Yahoo Finance (live)",
827
  }
828
 
829
  return {