Update app.py
Browse files
app.py
CHANGED
|
@@ -146,14 +146,13 @@ def build_expired_message(symbol, hi_bid, lo_ask, target_up, target_down, durati
|
|
| 146 |
def monitor_price_dual(target_up, target_down, duration_minutes, symbol=SYMBOL_DEFAULT):
|
| 147 |
"""
|
| 148 |
Monitor both upside and downside targets. Synchronous call.
|
| 149 |
-
|
|
|
|
| 150 |
"""
|
| 151 |
if duration_minutes <= 0:
|
| 152 |
-
|
| 153 |
-
return {"status": "error", "message": msg}, 400
|
| 154 |
if target_up <= target_down:
|
| 155 |
-
|
| 156 |
-
return {"status": "error", "message": msg}, 400
|
| 157 |
|
| 158 |
# Initial fetch with retry
|
| 159 |
retries, snap = 0, None
|
|
@@ -166,7 +165,7 @@ def monitor_price_dual(target_up, target_down, duration_minutes, symbol=SYMBOL_D
|
|
| 166 |
time.sleep(delay)
|
| 167 |
else:
|
| 168 |
msg = "Unable to retrieve initial price snapshot."
|
| 169 |
-
return {"status": "error", "message": msg}
|
| 170 |
|
| 171 |
t_start = datetime.datetime.now()
|
| 172 |
t_end = t_start + datetime.timedelta(minutes=duration_minutes)
|
|
@@ -181,34 +180,14 @@ def monitor_price_dual(target_up, target_down, duration_minutes, symbol=SYMBOL_D
|
|
| 181 |
elapsed_min = (datetime.datetime.now() - t_start).total_seconds() / 60
|
| 182 |
message = build_upside_message(symbol, bid0, ask0, target_up, target_down, elapsed_min, hi_bid, lo_ask)
|
| 183 |
send_alert_message(message)
|
| 184 |
-
return {
|
| 185 |
-
"status": "hit",
|
| 186 |
-
"side": "upside",
|
| 187 |
-
"message": message,
|
| 188 |
-
"symbol": symbol,
|
| 189 |
-
"bid": bid0,
|
| 190 |
-
"ask": ask0,
|
| 191 |
-
"hi_bid": hi_bid,
|
| 192 |
-
"lo_ask": lo_ask,
|
| 193 |
-
"elapsed_min": round(elapsed_min, 4)
|
| 194 |
-
}, 200
|
| 195 |
|
| 196 |
if ask0 <= target_down:
|
| 197 |
play_alert_sound()
|
| 198 |
elapsed_min = (datetime.datetime.now() - t_start).total_seconds() / 60
|
| 199 |
message = build_downside_message(symbol, bid0, ask0, target_up, target_down, elapsed_min, hi_bid, lo_ask)
|
| 200 |
send_alert_message(message)
|
| 201 |
-
return {
|
| 202 |
-
"status": "hit",
|
| 203 |
-
"side": "downside",
|
| 204 |
-
"message": message,
|
| 205 |
-
"symbol": symbol,
|
| 206 |
-
"bid": bid0,
|
| 207 |
-
"ask": ask0,
|
| 208 |
-
"hi_bid": hi_bid,
|
| 209 |
-
"lo_ask": lo_ask,
|
| 210 |
-
"elapsed_min": round(elapsed_min, 4)
|
| 211 |
-
}, 200
|
| 212 |
|
| 213 |
ticks = 0
|
| 214 |
try:
|
|
@@ -228,34 +207,14 @@ def monitor_price_dual(target_up, target_down, duration_minutes, symbol=SYMBOL_D
|
|
| 228 |
elapsed_min = (datetime.datetime.now() - t_start).total_seconds() / 60
|
| 229 |
message = build_upside_message(symbol, bid, ask, target_up, target_down, elapsed_min, hi_bid, lo_ask)
|
| 230 |
send_alert_message(message)
|
| 231 |
-
return {
|
| 232 |
-
"status": "hit",
|
| 233 |
-
"side": "upside",
|
| 234 |
-
"message": message,
|
| 235 |
-
"symbol": symbol,
|
| 236 |
-
"bid": bid,
|
| 237 |
-
"ask": ask,
|
| 238 |
-
"hi_bid": hi_bid,
|
| 239 |
-
"lo_ask": lo_ask,
|
| 240 |
-
"elapsed_min": round(elapsed_min, 4)
|
| 241 |
-
}, 200
|
| 242 |
|
| 243 |
if ask <= target_down:
|
| 244 |
play_alert_sound()
|
| 245 |
elapsed_min = (datetime.datetime.now() - t_start).total_seconds() / 60
|
| 246 |
message = build_downside_message(symbol, bid, ask, target_up, target_down, elapsed_min, hi_bid, lo_ask)
|
| 247 |
send_alert_message(message)
|
| 248 |
-
return {
|
| 249 |
-
"status": "hit",
|
| 250 |
-
"side": "downside",
|
| 251 |
-
"message": message,
|
| 252 |
-
"symbol": symbol,
|
| 253 |
-
"bid": bid,
|
| 254 |
-
"ask": ask,
|
| 255 |
-
"hi_bid": hi_bid,
|
| 256 |
-
"lo_ask": lo_ask,
|
| 257 |
-
"elapsed_min": round(elapsed_min, 4)
|
| 258 |
-
}, 200
|
| 259 |
|
| 260 |
# Optional: light logging cadence
|
| 261 |
ticks += 1
|
|
@@ -268,26 +227,33 @@ def monitor_price_dual(target_up, target_down, duration_minutes, symbol=SYMBOL_D
|
|
| 268 |
# Expiry
|
| 269 |
message = build_expired_message(symbol, hi_bid, lo_ask, target_up, target_down, duration_minutes)
|
| 270 |
send_alert_message(message)
|
| 271 |
-
return {
|
| 272 |
-
"status": "expired",
|
| 273 |
-
"side": None,
|
| 274 |
-
"message": message,
|
| 275 |
-
"symbol": symbol,
|
| 276 |
-
"hi_bid": hi_bid,
|
| 277 |
-
"lo_ask": lo_ask,
|
| 278 |
-
"duration_minutes": duration_minutes
|
| 279 |
-
}, 200
|
| 280 |
|
| 281 |
except Exception as e:
|
| 282 |
message = f"Unexpected error: {e}"
|
| 283 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
|
| 285 |
# ========== API Endpoints ==========
|
| 286 |
|
| 287 |
@app.route("/monitor", methods=["POST"])
|
| 288 |
def start_monitor():
|
| 289 |
"""
|
| 290 |
-
Start monitoring and
|
| 291 |
Payload:
|
| 292 |
{
|
| 293 |
"symbol": "XAUUSD", // optional, default XAUUSD
|
|
@@ -295,8 +261,8 @@ def start_monitor():
|
|
| 295 |
"target_down": 2475.0, // required
|
| 296 |
"duration_minutes": 15 // required
|
| 297 |
}
|
| 298 |
-
Response:
|
| 299 |
-
|
| 300 |
"""
|
| 301 |
try:
|
| 302 |
data = request.get_json(force=True)
|
|
@@ -311,6 +277,7 @@ def start_monitor():
|
|
| 311 |
if target_up is None or target_down is None or duration_minutes is None:
|
| 312 |
return jsonify({"status": "error", "message": "target_up, target_down, duration_minutes are required"}), 400
|
| 313 |
|
|
|
|
| 314 |
try:
|
| 315 |
target_up = float(target_up)
|
| 316 |
target_down = float(target_down)
|
|
@@ -318,16 +285,26 @@ def start_monitor():
|
|
| 318 |
except Exception:
|
| 319 |
return jsonify({"status": "error", "message": "Invalid types: target_up/target_down must be float, duration_minutes must be int"}), 400
|
| 320 |
|
| 321 |
-
|
| 322 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
|
| 324 |
@app.route("/", methods=["GET"])
|
| 325 |
def root():
|
| 326 |
return jsonify({
|
| 327 |
-
"service": "Forex Trade Alert API (
|
| 328 |
"fixed_alert_webhook": ALERT_WEBHOOK_URL,
|
| 329 |
-
"endpoint": "POST /monitor"
|
|
|
|
| 330 |
}), 200
|
| 331 |
|
| 332 |
if __name__ == "__main__":
|
|
|
|
| 333 |
app.run(host="0.0.0.0", port=7860, debug=False, threaded=True)
|
|
|
|
| 146 |
def monitor_price_dual(target_up, target_down, duration_minutes, symbol=SYMBOL_DEFAULT):
|
| 147 |
"""
|
| 148 |
Monitor both upside and downside targets. Synchronous call.
|
| 149 |
+
On hit/expired, sends alert message to fixed webhook.
|
| 150 |
+
Returns a final dict, but in async usage we don't return it to client.
|
| 151 |
"""
|
| 152 |
if duration_minutes <= 0:
|
| 153 |
+
return {"status": "error", "message": "Configuration error: duration must be > 0."}
|
|
|
|
| 154 |
if target_up <= target_down:
|
| 155 |
+
return {"status": "error", "message": "Configuration error: target_up must be greater than target_down."}
|
|
|
|
| 156 |
|
| 157 |
# Initial fetch with retry
|
| 158 |
retries, snap = 0, None
|
|
|
|
| 165 |
time.sleep(delay)
|
| 166 |
else:
|
| 167 |
msg = "Unable to retrieve initial price snapshot."
|
| 168 |
+
return {"status": "error", "message": msg}
|
| 169 |
|
| 170 |
t_start = datetime.datetime.now()
|
| 171 |
t_end = t_start + datetime.timedelta(minutes=duration_minutes)
|
|
|
|
| 180 |
elapsed_min = (datetime.datetime.now() - t_start).total_seconds() / 60
|
| 181 |
message = build_upside_message(symbol, bid0, ask0, target_up, target_down, elapsed_min, hi_bid, lo_ask)
|
| 182 |
send_alert_message(message)
|
| 183 |
+
return {"status": "hit", "side": "upside", "message": message}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
|
| 185 |
if ask0 <= target_down:
|
| 186 |
play_alert_sound()
|
| 187 |
elapsed_min = (datetime.datetime.now() - t_start).total_seconds() / 60
|
| 188 |
message = build_downside_message(symbol, bid0, ask0, target_up, target_down, elapsed_min, hi_bid, lo_ask)
|
| 189 |
send_alert_message(message)
|
| 190 |
+
return {"status": "hit", "side": "downside", "message": message}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
|
| 192 |
ticks = 0
|
| 193 |
try:
|
|
|
|
| 207 |
elapsed_min = (datetime.datetime.now() - t_start).total_seconds() / 60
|
| 208 |
message = build_upside_message(symbol, bid, ask, target_up, target_down, elapsed_min, hi_bid, lo_ask)
|
| 209 |
send_alert_message(message)
|
| 210 |
+
return {"status": "hit", "side": "upside", "message": message}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
|
| 212 |
if ask <= target_down:
|
| 213 |
play_alert_sound()
|
| 214 |
elapsed_min = (datetime.datetime.now() - t_start).total_seconds() / 60
|
| 215 |
message = build_downside_message(symbol, bid, ask, target_up, target_down, elapsed_min, hi_bid, lo_ask)
|
| 216 |
send_alert_message(message)
|
| 217 |
+
return {"status": "hit", "side": "downside", "message": message}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
|
| 219 |
# Optional: light logging cadence
|
| 220 |
ticks += 1
|
|
|
|
| 227 |
# Expiry
|
| 228 |
message = build_expired_message(symbol, hi_bid, lo_ask, target_up, target_down, duration_minutes)
|
| 229 |
send_alert_message(message)
|
| 230 |
+
return {"status": "expired", "message": message}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
|
| 232 |
except Exception as e:
|
| 233 |
message = f"Unexpected error: {e}"
|
| 234 |
+
send_alert_message(f"[ERROR] {message}")
|
| 235 |
+
return {"status": "error", "message": message}
|
| 236 |
+
|
| 237 |
+
def start_monitor_async(symbol: str, target_up: float, target_down: float, duration_minutes: int):
|
| 238 |
+
"""
|
| 239 |
+
Start monitor_price_dual in a daemon thread (non-blocking).
|
| 240 |
+
"""
|
| 241 |
+
def _runner():
|
| 242 |
+
try:
|
| 243 |
+
result = monitor_price_dual(target_up, target_down, duration_minutes, symbol=symbol)
|
| 244 |
+
# Optional: log final result
|
| 245 |
+
print(f"[MONITOR-END] {symbol} result: {result}")
|
| 246 |
+
except Exception as e:
|
| 247 |
+
print(f"[MONITOR-THREAD] Error: {e}")
|
| 248 |
+
t = threading.Thread(target=_runner, daemon=True)
|
| 249 |
+
t.start()
|
| 250 |
|
| 251 |
# ========== API Endpoints ==========
|
| 252 |
|
| 253 |
@app.route("/monitor", methods=["POST"])
|
| 254 |
def start_monitor():
|
| 255 |
"""
|
| 256 |
+
Start monitoring asynchronously and immediately return 200.
|
| 257 |
Payload:
|
| 258 |
{
|
| 259 |
"symbol": "XAUUSD", // optional, default XAUUSD
|
|
|
|
| 261 |
"target_down": 2475.0, // required
|
| 262 |
"duration_minutes": 15 // required
|
| 263 |
}
|
| 264 |
+
Immediate Response:
|
| 265 |
+
{ "status": "accepted", "message": "Okay your alert added succes" }
|
| 266 |
"""
|
| 267 |
try:
|
| 268 |
data = request.get_json(force=True)
|
|
|
|
| 277 |
if target_up is None or target_down is None or duration_minutes is None:
|
| 278 |
return jsonify({"status": "error", "message": "target_up, target_down, duration_minutes are required"}), 400
|
| 279 |
|
| 280 |
+
# Basic type validation before starting async work
|
| 281 |
try:
|
| 282 |
target_up = float(target_up)
|
| 283 |
target_down = float(target_down)
|
|
|
|
| 285 |
except Exception:
|
| 286 |
return jsonify({"status": "error", "message": "Invalid types: target_up/target_down must be float, duration_minutes must be int"}), 400
|
| 287 |
|
| 288 |
+
if duration_minutes <= 0:
|
| 289 |
+
return jsonify({"status": "error", "message": "duration_minutes must be > 0"}), 400
|
| 290 |
+
if target_up <= target_down:
|
| 291 |
+
return jsonify({"status": "error", "message": "target_up must be greater than target_down"}), 400
|
| 292 |
+
|
| 293 |
+
# Start monitoring in background thread
|
| 294 |
+
start_monitor_async(symbol, target_up, target_down, duration_minutes)
|
| 295 |
+
|
| 296 |
+
# Immediate success response
|
| 297 |
+
return jsonify({"status": "accepted", "message": "Okay your alert added succes"}), 200
|
| 298 |
|
| 299 |
@app.route("/", methods=["GET"])
|
| 300 |
def root():
|
| 301 |
return jsonify({
|
| 302 |
+
"service": "Forex Trade Alert API (async monitor)",
|
| 303 |
"fixed_alert_webhook": ALERT_WEBHOOK_URL,
|
| 304 |
+
"endpoint": "POST /monitor",
|
| 305 |
+
"behavior": "returns immediately; monitoring runs in background"
|
| 306 |
}), 200
|
| 307 |
|
| 308 |
if __name__ == "__main__":
|
| 309 |
+
# threaded=True is fine; we also use daemon threads for monitors
|
| 310 |
app.run(host="0.0.0.0", port=7860, debug=False, threaded=True)
|