Spaces:
Running
Running
Sync from GitHub
Browse files- app/inference.py +32 -5
- app/main.py +12 -6
- app/settings.py +4 -0
app/inference.py
CHANGED
|
@@ -40,22 +40,49 @@ def get_current_price(session: Session, symbol: str) -> Optional[float]:
|
|
| 40 |
"""
|
| 41 |
Get the current price for a symbol.
|
| 42 |
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
| 44 |
"""
|
|
|
|
| 45 |
import yfinance as yf
|
|
|
|
| 46 |
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
try:
|
| 49 |
ticker = yf.Ticker(symbol)
|
| 50 |
info = ticker.info
|
| 51 |
live_price = info.get('regularMarketPrice') or info.get('currentPrice')
|
| 52 |
if live_price is not None:
|
| 53 |
-
logger.info(f"Using
|
| 54 |
return float(live_price)
|
| 55 |
except Exception as e:
|
| 56 |
-
logger.debug(f"
|
| 57 |
|
| 58 |
-
#
|
| 59 |
latest = session.query(PriceBar).filter(
|
| 60 |
PriceBar.symbol == symbol
|
| 61 |
).order_by(PriceBar.date.desc()).first()
|
|
|
|
| 40 |
"""
|
| 41 |
Get the current price for a symbol.
|
| 42 |
|
| 43 |
+
Priority:
|
| 44 |
+
1. Twelve Data API (most reliable, no rate limit issues)
|
| 45 |
+
2. yfinance live data (15-min delayed)
|
| 46 |
+
3. Database fallback
|
| 47 |
"""
|
| 48 |
+
import httpx
|
| 49 |
import yfinance as yf
|
| 50 |
+
from app.settings import get_settings
|
| 51 |
|
| 52 |
+
settings = get_settings()
|
| 53 |
+
|
| 54 |
+
# Try Twelve Data first (for XCU/USD copper)
|
| 55 |
+
if settings.twelvedata_api_key:
|
| 56 |
+
try:
|
| 57 |
+
with httpx.Client(timeout=10.0) as client:
|
| 58 |
+
response = client.get(
|
| 59 |
+
"https://api.twelvedata.com/price",
|
| 60 |
+
params={
|
| 61 |
+
"symbol": "XCU/USD",
|
| 62 |
+
"apikey": settings.twelvedata_api_key,
|
| 63 |
+
}
|
| 64 |
+
)
|
| 65 |
+
if response.status_code == 200:
|
| 66 |
+
data = response.json()
|
| 67 |
+
price = data.get("price")
|
| 68 |
+
if price:
|
| 69 |
+
logger.info(f"Using Twelve Data price for copper: ${float(price):.4f}")
|
| 70 |
+
return float(price)
|
| 71 |
+
except Exception as e:
|
| 72 |
+
logger.debug(f"Twelve Data price fetch failed: {e}")
|
| 73 |
+
|
| 74 |
+
# Try yfinance as fallback
|
| 75 |
try:
|
| 76 |
ticker = yf.Ticker(symbol)
|
| 77 |
info = ticker.info
|
| 78 |
live_price = info.get('regularMarketPrice') or info.get('currentPrice')
|
| 79 |
if live_price is not None:
|
| 80 |
+
logger.info(f"Using yfinance price for {symbol}: ${live_price:.4f}")
|
| 81 |
return float(live_price)
|
| 82 |
except Exception as e:
|
| 83 |
+
logger.debug(f"yfinance price fetch failed for {symbol}: {e}")
|
| 84 |
|
| 85 |
+
# Final fallback to database
|
| 86 |
latest = session.query(PriceBar).filter(
|
| 87 |
PriceBar.symbol == symbol
|
| 88 |
).order_by(PriceBar.date.desc()).first()
|
app/main.py
CHANGED
|
@@ -175,19 +175,25 @@ async def get_analysis(
|
|
| 175 |
predicted_return = float(model.predict(dmatrix)[0])
|
| 176 |
|
| 177 |
# Update with live prediction
|
| 178 |
-
#
|
|
|
|
|
|
|
|
|
|
| 179 |
cached['predicted_return'] = round(predicted_return, 6)
|
| 180 |
cached['predicted_price'] = round(
|
| 181 |
-
|
| 182 |
4
|
| 183 |
)
|
| 184 |
|
| 185 |
-
#
|
|
|
|
|
|
|
|
|
|
| 186 |
std_mult = 1.0 # 1 standard deviation
|
| 187 |
-
cached['confidence_lower'] = round(
|
| 188 |
-
cached['confidence_upper'] = round(
|
| 189 |
|
| 190 |
-
logger.info(f"LIVE prediction:
|
| 191 |
|
| 192 |
except Exception as e:
|
| 193 |
logger.error(f"Live prediction failed, using cached: {e}")
|
|
|
|
| 175 |
predicted_return = float(model.predict(dmatrix)[0])
|
| 176 |
|
| 177 |
# Update with live prediction
|
| 178 |
+
# Apply futures-spot adjustment (HG=F is ~1.5% higher than XCU/USD)
|
| 179 |
+
adjustment = settings.futures_spot_adjustment
|
| 180 |
+
adjusted_base = float(prediction_base) * adjustment
|
| 181 |
+
|
| 182 |
cached['predicted_return'] = round(predicted_return, 6)
|
| 183 |
cached['predicted_price'] = round(
|
| 184 |
+
adjusted_base * (1 + predicted_return),
|
| 185 |
4
|
| 186 |
)
|
| 187 |
|
| 188 |
+
# Also adjust current_price for consistency
|
| 189 |
+
cached['current_price'] = round(adjusted_base, 4)
|
| 190 |
+
|
| 191 |
+
# Update confidence bounds (based on adjusted base)
|
| 192 |
std_mult = 1.0 # 1 standard deviation
|
| 193 |
+
cached['confidence_lower'] = round(adjusted_base * (1 - std_mult * abs(predicted_return)), 4)
|
| 194 |
+
cached['confidence_upper'] = round(adjusted_base * (1 + std_mult * abs(predicted_return) * 2), 4)
|
| 195 |
|
| 196 |
+
logger.info(f"LIVE prediction: HG=F=${prediction_base:.4f} -> XCU/USD≈${adjusted_base:.4f}, predicted=${cached['predicted_price']:.4f} ({predicted_return*100:.2f}%)")
|
| 197 |
|
| 198 |
except Exception as e:
|
| 199 |
logger.error(f"Live prediction failed, using cached: {e}")
|
app/settings.py
CHANGED
|
@@ -51,6 +51,10 @@ class Settings(BaseSettings):
|
|
| 51 |
analysis_ttl_minutes: int = 30
|
| 52 |
log_level: str = "INFO"
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
# Scheduler
|
| 55 |
schedule_time: str = "02:00"
|
| 56 |
tz: str = "Europe/Istanbul"
|
|
|
|
| 51 |
analysis_ttl_minutes: int = 30
|
| 52 |
log_level: str = "INFO"
|
| 53 |
|
| 54 |
+
# Futures vs Spot adjustment factor
|
| 55 |
+
# HG=F (futures) is ~1.5% higher than XCU/USD (spot)
|
| 56 |
+
futures_spot_adjustment: float = 0.985 # Multiply HG=F by this to get XCU/USD
|
| 57 |
+
|
| 58 |
# Scheduler
|
| 59 |
schedule_time: str = "02:00"
|
| 60 |
tz: str = "Europe/Istanbul"
|