Update app.py
Browse files
app.py
CHANGED
|
@@ -16,7 +16,7 @@ SYMBOL = os.getenv("SYMBOL", "VN30F1M")
|
|
| 16 |
RESOLUTION = os.getenv("RESOLUTION", "1")
|
| 17 |
COUNTBACK = int(os.getenv("COUNTBACK", "330"))
|
| 18 |
UPDATE_SECONDS = int(os.getenv("UPDATE_SECONDS", "10"))
|
| 19 |
-
MIN_BARS =
|
| 20 |
|
| 21 |
|
| 22 |
@dataclass
|
|
@@ -83,7 +83,7 @@ def build_features(data: Dict[str, List[float]]) -> pd.DataFrame:
|
|
| 83 |
|
| 84 |
|
| 85 |
# ===== DATA FETCH =====
|
| 86 |
-
def fetch_data(retries: int = 4) -> Optional[Dict[str, Any]]:
|
| 87 |
now = int(time.time())
|
| 88 |
for i in range(retries):
|
| 89 |
try:
|
|
@@ -103,8 +103,8 @@ def fetch_data(retries: int = 4) -> Optional[Dict[str, Any]]:
|
|
| 103 |
raise ValueError("Response missing OHLCV keys")
|
| 104 |
|
| 105 |
n = len(data["c"])
|
| 106 |
-
if n <
|
| 107 |
-
raise ValueError(f"Not enough candles ({n}/{
|
| 108 |
|
| 109 |
return data
|
| 110 |
except Exception as exc:
|
|
@@ -120,9 +120,26 @@ def compute_signal(data: Dict[str, List[float]]) -> Dict[str, Any]:
|
|
| 120 |
df = build_features(data)
|
| 121 |
last = df.iloc[-1]
|
| 122 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
cols = ["ma20", "ma50", "zscore20", "rsi14", "mom10", "atr14", "volatility"]
|
| 124 |
if last[cols].isna().any():
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
trend_score = 1 if last["close"] > last["ma20"] else -1
|
| 128 |
structure_score = 1 if last["ma20"] > last["ma50"] else -1
|
|
@@ -179,7 +196,7 @@ def compute_signal(data: Dict[str, List[float]]) -> Dict[str, Any]:
|
|
| 179 |
def update_loop() -> None:
|
| 180 |
while True:
|
| 181 |
try:
|
| 182 |
-
data = fetch_data()
|
| 183 |
if data is None:
|
| 184 |
raise RuntimeError("Cannot fetch data from feed")
|
| 185 |
|
|
@@ -252,7 +269,7 @@ def signal() -> Dict[str, Any]:
|
|
| 252 |
|
| 253 |
@app.get("/forecast")
|
| 254 |
def forecast() -> Dict[str, Any]:
|
| 255 |
-
data = fetch_data()
|
| 256 |
if data is None:
|
| 257 |
raise HTTPException(status_code=503, detail="Data feed unavailable")
|
| 258 |
return compute_signal(data)
|
|
@@ -260,7 +277,7 @@ def forecast() -> Dict[str, Any]:
|
|
| 260 |
|
| 261 |
@app.get("/ohlc")
|
| 262 |
def ohlc(limit: int = 200) -> List[Dict[str, Any]]:
|
| 263 |
-
data = fetch_data()
|
| 264 |
if data is None:
|
| 265 |
raise HTTPException(status_code=503, detail="Data feed unavailable")
|
| 266 |
|
|
|
|
| 16 |
RESOLUTION = os.getenv("RESOLUTION", "1")
|
| 17 |
COUNTBACK = int(os.getenv("COUNTBACK", "330"))
|
| 18 |
UPDATE_SECONDS = int(os.getenv("UPDATE_SECONDS", "10"))
|
| 19 |
+
MIN_BARS = int(os.getenv("MIN_BARS", "50"))
|
| 20 |
|
| 21 |
|
| 22 |
@dataclass
|
|
|
|
| 83 |
|
| 84 |
|
| 85 |
# ===== DATA FETCH =====
|
| 86 |
+
def fetch_data(retries: int = 4, min_bars: int = MIN_BARS) -> Optional[Dict[str, Any]]:
|
| 87 |
now = int(time.time())
|
| 88 |
for i in range(retries):
|
| 89 |
try:
|
|
|
|
| 103 |
raise ValueError("Response missing OHLCV keys")
|
| 104 |
|
| 105 |
n = len(data["c"])
|
| 106 |
+
if n < min_bars:
|
| 107 |
+
raise ValueError(f"Not enough candles ({n}/{min_bars})")
|
| 108 |
|
| 109 |
return data
|
| 110 |
except Exception as exc:
|
|
|
|
| 120 |
df = build_features(data)
|
| 121 |
last = df.iloc[-1]
|
| 122 |
|
| 123 |
+
if len(df) < 50:
|
| 124 |
+
return {
|
| 125 |
+
"signal": "WAIT",
|
| 126 |
+
"price": float(last["close"]),
|
| 127 |
+
"confidence": 0.0,
|
| 128 |
+
"score": 0.0,
|
| 129 |
+
"reason": f"Warming up: need >=50 candles, got {len(df)}",
|
| 130 |
+
"risk": None,
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
cols = ["ma20", "ma50", "zscore20", "rsi14", "mom10", "atr14", "volatility"]
|
| 134 |
if last[cols].isna().any():
|
| 135 |
+
return {
|
| 136 |
+
"signal": "WAIT",
|
| 137 |
+
"price": float(last["close"]),
|
| 138 |
+
"confidence": 0.0,
|
| 139 |
+
"score": 0.0,
|
| 140 |
+
"reason": "Indicators not ready",
|
| 141 |
+
"risk": None,
|
| 142 |
+
}
|
| 143 |
|
| 144 |
trend_score = 1 if last["close"] > last["ma20"] else -1
|
| 145 |
structure_score = 1 if last["ma20"] > last["ma50"] else -1
|
|
|
|
| 196 |
def update_loop() -> None:
|
| 197 |
while True:
|
| 198 |
try:
|
| 199 |
+
data = fetch_data(min_bars=10)
|
| 200 |
if data is None:
|
| 201 |
raise RuntimeError("Cannot fetch data from feed")
|
| 202 |
|
|
|
|
| 269 |
|
| 270 |
@app.get("/forecast")
|
| 271 |
def forecast() -> Dict[str, Any]:
|
| 272 |
+
data = fetch_data(min_bars=10)
|
| 273 |
if data is None:
|
| 274 |
raise HTTPException(status_code=503, detail="Data feed unavailable")
|
| 275 |
return compute_signal(data)
|
|
|
|
| 277 |
|
| 278 |
@app.get("/ohlc")
|
| 279 |
def ohlc(limit: int = 200) -> List[Dict[str, Any]]:
|
| 280 |
+
data = fetch_data(min_bars=1)
|
| 281 |
if data is None:
|
| 282 |
raise HTTPException(status_code=503, detail="Data feed unavailable")
|
| 283 |
|