Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -214,12 +214,17 @@ def count_xml_tags(ai_response):
|
|
| 214 |
return counts
|
| 215 |
|
| 216 |
def save_latest_final_analysis(final_text):
|
|
|
|
|
|
|
|
|
|
| 217 |
try:
|
| 218 |
record = {
|
| 219 |
"timestamp_utc": datetime.now(timezone.utc).isoformat(),
|
| 220 |
"response": final_text
|
| 221 |
}
|
| 222 |
-
|
|
|
|
|
|
|
| 223 |
auth_token, commit_oid = db_analysis.fetch_authenticity_token_and_commit_oid()
|
| 224 |
if auth_token and commit_oid:
|
| 225 |
result = db_analysis.update_user_json_file(auth_token, commit_oid, payload_text)
|
|
@@ -248,10 +253,41 @@ def get_chart_screenshot(symbol="XAUUSD", exchange="OANDA", interval="15m", indi
|
|
| 248 |
resp.raise_for_status()
|
| 249 |
return resp.json()
|
| 250 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
def fetch_signals_raw():
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
out = {
|
| 256 |
"has_active_signal": False,
|
| 257 |
"active_signal": None,
|
|
@@ -264,28 +300,32 @@ def fetch_signals_raw():
|
|
| 264 |
if res["success"] and res["data"]:
|
| 265 |
raw = res["data"][0]
|
| 266 |
out["raw"] = raw
|
| 267 |
-
|
|
|
|
| 268 |
if isinstance(raw, list) and raw and isinstance(raw[0], dict) and "pair" in raw[0] and "type" in raw[0]:
|
| 269 |
out["has_active_signal"] = True
|
| 270 |
out["active_signal"] = raw
|
| 271 |
-
|
|
|
|
| 272 |
elif isinstance(raw, dict) and "scenario" in raw:
|
| 273 |
out["has_scenario"] = True
|
| 274 |
out["scenario"] = raw["scenario"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
except Exception as e:
|
| 276 |
logger.error(f"Error fetching signals/scenario: {e}")
|
| 277 |
return out
|
| 278 |
|
| 279 |
def save_scenario_object(scenario_obj):
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
# "Sell": {"at": "...", "SL": "...", "TP": "..."},
|
| 284 |
-
# "timestamps": {...}
|
| 285 |
-
# }
|
| 286 |
try:
|
| 287 |
-
|
| 288 |
-
payload_text = json.dumps(
|
| 289 |
auth_token, commit_oid = db_signals.fetch_authenticity_token_and_commit_oid()
|
| 290 |
if auth_token and commit_oid:
|
| 291 |
result = db_signals.update_user_json_file(auth_token, commit_oid, payload_text)
|
|
@@ -308,252 +348,126 @@ def post_scenario_to_tracker(buy_at, sell_at):
|
|
| 308 |
def build_initial_chat_history(alert_message=None):
|
| 309 |
chat_history = []
|
| 310 |
|
| 311 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
try:
|
| 313 |
system_data = db_system.fetch_json_from_github()
|
| 314 |
-
system_content = ""
|
| 315 |
if system_data["success"] and system_data["data"]:
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
"لطلب صورة استخدم:\n"
|
| 321 |
-
"<img><XAUUSD><QQE>//يمكن إضافة مؤشرات مثل <Zerp_Lag>...</QQE><15m></img>\n"
|
| 322 |
-
"لا تُنشئ <signal> بعد الآن. بدلاً من ذلك، عندما تُنهي التحليل أرسل:\n"
|
| 323 |
-
"<final>\n"
|
| 324 |
-
"<Analysis>ملخص</Analysis>\n"
|
| 325 |
-
"<wait>سبب</wait>\n"
|
| 326 |
-
"<scenario>\n"
|
| 327 |
-
"<Buy><@>شرط الدخول/المستوى</@><SL>...</SL><TP>...</TP></Buy>\n"
|
| 328 |
-
"<Sell><@>شرط الدخول/المستوى</@><SL>...</SL><TP>...</TP></Sell>\n"
|
| 329 |
-
"</scenario>\n"
|
| 330 |
-
"<Edit>للتحديث (SL/TP) إذا وُجدت صفقة مفتوحة</Edit>\n"
|
| 331 |
-
"<send_group>رسالة قصيرة</send_group>\n"
|
| 332 |
-
"<Alert>إن رغبت بمنبه الأسعار بصيغته الجديدة</Alert>\n"
|
| 333 |
-
"</final>\n"
|
| 334 |
-
"إن كان هناك صفقة مفتوحة بالفعل فلا تُنشئ سيناريو جديد؛ فقط حلّل وتتبع الصفقة وقدّم خطة.\n"
|
| 335 |
-
"بعد كل صورة سيتم تزويدك بسعر السوق الحالي ليساعدك على فهم الحركة أثناء المحادثة."
|
| 336 |
-
)
|
| 337 |
|
|
|
|
|
|
|
| 338 |
times = get_time_zones()
|
| 339 |
time_info = "\n".join([f"{city}: {time}" for city, time in times.items()])
|
| 340 |
-
|
|
|
|
|
|
|
|
|
|
| 341 |
chat_history.append({
|
| 342 |
"role": "system",
|
| 343 |
-
"content":
|
| 344 |
-
|
| 345 |
-
time :
|
| 346 |
-
{time_info}
|
| 347 |
-
You are AI trading system working in tele group for trade on XAUUSD
|
| 348 |
-
|
| 349 |
-
GOAL :
|
| 350 |
-
|
| 351 |
-
Your goal is get is day about 500 pip which is 50$ in GOLD system
|
| 352 |
-
not 500 pip in one trade but in alot of trades with
|
| 353 |
-
|
| 354 |
-
TP about 50 - 70 - 100 pip
|
| 355 |
-
SL about 10 - 30 - 50 pip
|
| 356 |
-
|
| 357 |
-
NOTE : in gold every 10 pip 1 $
|
| 358 |
-
|
| 359 |
-
you can manage it based on your mind
|
| 360 |
-
|
| 361 |
-
you can not start trade directly NO , your task is analysis and create scenario BUY / SELL each other we need
|
| 362 |
-
|
| 363 |
-
now i will start conversation with you in this conversation you will request images for the chart with Indicators , timeframes You need .
|
| 364 |
-
|
| 365 |
-
you can request 1 image chart in message with 1 timeframe and MAX 3 indicators (for make the chart more clear)
|
| 366 |
-
|
| 367 |
-
Also you can make the chat like you want , request images and i will fast pring it to you .
|
| 368 |
-
|
| 369 |
-
How you gonna request the image you want ?
|
| 370 |
-
|
| 371 |
-
it is easy just use this :
|
| 372 |
-
<img><XAUUSD><id_for_IND><id_for_IND><id_for_IND>// you can use maxumum 3 indecators in same image<timeframe></img>
|
| 373 |
-
Here All Timeframe :
|
| 374 |
-
|
| 375 |
-
"1m"
|
| 376 |
-
"3m"
|
| 377 |
-
"5m"
|
| 378 |
-
"15m"
|
| 379 |
-
"30m"
|
| 380 |
-
"1h"
|
| 381 |
-
"2h"
|
| 382 |
-
"4h"
|
| 383 |
-
"1D"
|
| 384 |
-
"1W"
|
| 385 |
-
"1M"
|
| 386 |
-
|
| 387 |
-
Here All indicators id :
|
| 388 |
-
|
| 389 |
-
1 . ADX - <ADX>
|
| 390 |
-
2 . VWAP - <VWAP>
|
| 391 |
-
3 . Fibonacci - <FIBO>
|
| 392 |
-
4 . RSI with Divergence - <RSI>
|
| 393 |
-
5 . EMA 20/50/100/200 - <EMA>
|
| 394 |
-
6 . Breakout channle - <BRCH>
|
| 395 |
-
maybe num 6 you don't know it here :
|
| 396 |
-
This tool draws areas called "Smart Money Breakout Channels" based on the analysis of adjusted price volatility. These channels are identified when price volatility reaches a local low and then rises, indicating the potential presence of smart money activity in that area. A channel is drawn between the highest and lowest prices during that period and continues until the price breaks through the defined boundaries, either by closing a strong candle or by touching the boundaries.
|
| 397 |
-
|
| 398 |
-
Okay what if you think that is enough images then you will response with <final></final> Action
|
| 399 |
-
|
| 400 |
-
what is <final> Action ?
|
| 401 |
-
you will use the final xml response when you get enough images from the chart and then you will response with final and into it the settings
|
| 402 |
-
<final>
|
| 403 |
-
<Analysis>here put your summrasion of the chat analysis</Analysis>
|
| 404 |
-
<wait>this is just using when you are Acually added a scenario and you don't want to change it, put here the reason about why you don't want to change the scenario</wait>
|
| 405 |
-
Now the important one (optional):
|
| 406 |
-
|
| 407 |
-
<scenario>
|
| 408 |
-
<Buy><@>...</@><SL>...</SL><TP>...</TP></Buy> // make sure just put price number no text
|
| 409 |
-
<Sell><@>...</@><SL>...</SL><TP>...</TP></Sell> // make sure just put price number no text
|
| 410 |
-
</scenario>
|
| 411 |
-
we need scalping plans
|
| 412 |
-
|
| 413 |
-
TP about 50 - 70 - 100 pip
|
| 414 |
-
SL about 10 - 30 - 50 pip
|
| 415 |
-
|
| 416 |
-
NOTE : if one of scenarios running mean there trade active don't create new scnario just response without it
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
after analysis complete from the chat you will decide finally the scenario and we will save it until one of scenario HIT and then gonna tell you what heppen to start new chat with you for maybe we need to
|
| 421 |
-
|
| 422 |
-
📊 Dynamic Trade Editing Feature
|
| 423 |
-
<Edit><stop_lose>...</stop_lose><take_profit>...</take_profit></Edit>
|
| 424 |
-
|
| 425 |
-
Here Another tool optional using just if you acully need it
|
| 426 |
-
that will chage the SL TP for the active trade not the (SCENARIO)
|
| 427 |
-
Also you just can change one them like :
|
| 428 |
-
|
| 429 |
-
<Edit><take_profit>...</take_profit></Edit>
|
| 430 |
-
that will make it just change for TP only (you can use it also for SL like you want)
|
| 431 |
-
|
| 432 |
-
Okay to here everything clear but i don't want to manual create message for the tele group and send it , i want you to send message to them using :
|
| 433 |
-
|
| 434 |
-
<send_group>what you put here gonna send to the tele group and it is not optional you need to use it every time you use final , to make the group in clear(need to be in arabic becasue all of members Arabic)</send_group>
|
| 435 |
-
|
| 436 |
-
okay now the important question How many chats gonna chat with you or how and when you want me back to you to create new chat ?
|
| 437 |
-
|
| 438 |
-
Good Q and i have the Good answer we gonna use Alert by time and price also saved messages in the price here we go :
|
| 439 |
-
|
| 440 |
-
<Alert>
|
| 441 |
-
<price>3...<message>here.......</message></price> //put more than 1 price as you want for next analysis , for messages put the saved messages in the prices for Auto send to user just if the price hit as + 50 pips ....etc of messages (all in arabic)
|
| 442 |
-
..... may more than one price
|
| 443 |
-
<duration_min>10(EXP)</duration_min> // that using for if the price still in same place and don't moving alot so the duration if end gonna back to you
|
| 444 |
-
</Alert>
|
| 445 |
-
|
| 446 |
-
I think it’s very clear. Let me explain only the structure:
|
| 447 |
-
|
| 448 |
-
<price>3...<message>here.......</message></price>
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
price → the price level at which you want the next analysis to be triggered.
|
| 452 |
-
|
| 453 |
-
message → the auto message that will be sent to the group when that price is hit.
|
| 454 |
-
|
| 455 |
-
If the group already has an active trade, you should use the alert as your main tool to track what’s happening with the trade. You can set it at 10, 30, or 50 pips (or however you prefer), but keep it close enough so the group can clearly see how the trade is moving.
|
| 456 |
-
|
| 457 |
-
This is your main engine that reactivates you after finishing your current analysis. You use it to create an alert for your next analysis, where you set the upper and lower target prices at which you want to analyze again. This applies regardless of whether you’re reviewing an existing trade or creating a new one — it will notify the group when your requested price is reached.
|
| 458 |
-
|
| 459 |
-
You can also give it a time limit (in minutes) in case neither target is reached and the price keeps fluctuating in between.
|
| 460 |
-
|
| 461 |
-
⚠️ Keep in mind: the price fields are extremely sensitive. You must enter the price as if you’re entering it in a numeric-only field.
|
| 462 |
-
</final>
|
| 463 |
-
|
| 464 |
-
NOTE : All XML need to be in the final to work
|
| 465 |
-
|
| 466 |
-
Now for the how to request image you will find it in the chat with full IDs for indicators
|
| 467 |
-
GOOD LUCK
|
| 468 |
-
|
| 469 |
-
i created a summary of news maybe you will find it helpful
|
| 470 |
-
|
| 471 |
-
{system_content}
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
'''
|
| 477 |
})
|
| 478 |
except Exception as e:
|
| 479 |
-
logger.error(f"Error
|
| 480 |
chat_history.append({
|
| 481 |
"role": "system",
|
| 482 |
-
"content":
|
| 483 |
})
|
| 484 |
|
| 485 |
multipart_content = []
|
| 486 |
|
| 487 |
-
# Previous analysis (optional)
|
| 488 |
try:
|
| 489 |
analysis_data = db_analysis.fetch_json_from_github()
|
| 490 |
prev_text = ""
|
| 491 |
if analysis_data["success"] and analysis_data["data"]:
|
| 492 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 493 |
prev_text = str(raw_text)[:1500]
|
| 494 |
if prev_text:
|
| 495 |
-
multipart_content.append({"type": "text", "text": f"
|
| 496 |
except Exception as e:
|
| 497 |
logger.error(f"Error fetching previous analysis: {e}")
|
| 498 |
|
| 499 |
# Alert + current context (active signal or scenario or none)
|
| 500 |
try:
|
| 501 |
-
state = fetch_signals_raw()
|
| 502 |
times = get_time_zones()
|
| 503 |
time_info = "\n".join([f"{city}: {time}" for city, time in times.items()])
|
|
|
|
| 504 |
|
| 505 |
message_content = ""
|
| 506 |
if alert_message:
|
| 507 |
-
message_content += f"
|
| 508 |
else:
|
| 509 |
-
message_content += "
|
| 510 |
-
|
| 511 |
-
# Compose context for AI based on state
|
| 512 |
-
prices_text = format_live_prices_text(get_live_prices_for_pairs())
|
| 513 |
|
| 514 |
-
if
|
| 515 |
sig = state["active_signal"][0]
|
| 516 |
message_content += (
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
elif state["has_scenario"]:
|
| 532 |
sc = state["scenario"]
|
| 533 |
buy = sc.get("Buy", {})
|
| 534 |
sell = sc.get("Sell", {})
|
| 535 |
message_content += (
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
else:
|
| 548 |
message_content += (
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
|
| 558 |
multipart_content.append({"type": "text", "text": message_content})
|
| 559 |
except Exception as e:
|
|
@@ -687,8 +601,8 @@ def build_image_reply_user_turn(png_url):
|
|
| 687 |
{"type": "text", "text": (
|
| 688 |
"Your image is ready for analysis.\n"
|
| 689 |
"- Request **only ONE <img>** at a time for each chart.\n <img><XAUUSD><id_for_IND><id_for_IND><id_for_IND>// you can use maxumum 3 indecators in same image<timeframe></img>\n"
|
| 690 |
-
"- Use <final> **only** if you think the conversation already has enough information to conclude
|
| 691 |
-
"Do not send multiple images at once. \n\n look i want you to be smart analysis
|
| 692 |
)},
|
| 693 |
{"type": "text", "text": f"Current Time:\n{time_info}"},
|
| 694 |
{"type": "text", "text": f"Live Prices:\n{prices_text}"}
|
|
@@ -868,15 +782,24 @@ def parse_and_execute_final(final_xml_full, final_inner):
|
|
| 868 |
return actions_performed
|
| 869 |
|
| 870 |
def edit_existing_signal(edit_data):
|
|
|
|
|
|
|
|
|
|
| 871 |
try:
|
| 872 |
signals_data = db_signals.fetch_json_from_github()
|
| 873 |
if not (signals_data["success"] and signals_data["data"]):
|
| 874 |
return {"success": False, "error": "No active signal found to edit"}
|
| 875 |
|
| 876 |
-
current_signal = None
|
| 877 |
raw = signals_data["data"][0]
|
|
|
|
| 878 |
if isinstance(raw, list) and raw and isinstance(raw[0], dict):
|
| 879 |
current_signal = raw[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 880 |
else:
|
| 881 |
return {"success": False, "error": "No active signal found to edit"}
|
| 882 |
|
|
@@ -897,9 +820,11 @@ def edit_existing_signal(edit_data):
|
|
| 897 |
|
| 898 |
auth_token, commit_oid = db_signals.fetch_authenticity_token_and_commit_oid()
|
| 899 |
if auth_token and commit_oid:
|
| 900 |
-
|
| 901 |
-
|
| 902 |
-
|
|
|
|
|
|
|
| 903 |
return {"success": True, "updates": updates_made}
|
| 904 |
else:
|
| 905 |
return {"success": False, "error": "Failed to update signal"}
|
|
|
|
| 214 |
return counts
|
| 215 |
|
| 216 |
def save_latest_final_analysis(final_text):
|
| 217 |
+
"""
|
| 218 |
+
Save ONLY the latest final analysis to db_analysis as an array: [ { ... } ]
|
| 219 |
+
"""
|
| 220 |
try:
|
| 221 |
record = {
|
| 222 |
"timestamp_utc": datetime.now(timezone.utc).isoformat(),
|
| 223 |
"response": final_text
|
| 224 |
}
|
| 225 |
+
# Wrap as array for saving
|
| 226 |
+
payload_list = [record]
|
| 227 |
+
payload_text = json.dumps(payload_list, ensure_ascii=False)
|
| 228 |
auth_token, commit_oid = db_analysis.fetch_authenticity_token_and_commit_oid()
|
| 229 |
if auth_token and commit_oid:
|
| 230 |
result = db_analysis.update_user_json_file(auth_token, commit_oid, payload_text)
|
|
|
|
| 253 |
resp.raise_for_status()
|
| 254 |
return resp.json()
|
| 255 |
|
| 256 |
+
def load_system_prompt_from_files(has_active_signal: bool, has_scenario: bool):
|
| 257 |
+
"""
|
| 258 |
+
Returns system prompt string based on current state:
|
| 259 |
+
- If has_active_signal: use prompt_signal.txt
|
| 260 |
+
- Else (no active signal): use prompt_scenario.txt
|
| 261 |
+
If files are missing, fall back to a minimal default in Arabic.
|
| 262 |
+
"""
|
| 263 |
+
prompt_file = "prompt_signal.txt" if has_active_signal else "prompt_scenario.txt"
|
| 264 |
+
try:
|
| 265 |
+
with open(prompt_file, "r", encoding="utf-8") as f:
|
| 266 |
+
text = f.read().strip()
|
| 267 |
+
if text:
|
| 268 |
+
return text
|
| 269 |
+
except Exception as e:
|
| 270 |
+
logger.warning(f"Failed to load system prompt from {prompt_file}: {e}")
|
| 271 |
+
|
| 272 |
+
# Fallbacks
|
| 273 |
+
if has_active_signal:
|
| 274 |
+
return "وضع متابعة الصفقة: لا تنشئ سيناريو جديد. حلّل الصفقة الحالية فقط ويمكنك استخدام <Edit> و<send_group> و<Alert>."
|
| 275 |
+
else:
|
| 276 |
+
return "وضع بناء السيناريو: حلّل وأنشئ سيناريو داخل <final> يتضمن <scenario> مع Buy/Sell و(@/SL/TP)."
|
| 277 |
+
|
| 278 |
+
|
| 279 |
def fetch_signals_raw():
|
| 280 |
+
"""
|
| 281 |
+
Returns:
|
| 282 |
+
{
|
| 283 |
+
"has_active_signal": bool,
|
| 284 |
+
"active_signal": list or None, # when active, it's a list with 1 object (normalized)
|
| 285 |
+
"has_scenario": bool,
|
| 286 |
+
"scenario": dict or None,
|
| 287 |
+
"raw": original
|
| 288 |
+
}
|
| 289 |
+
Accepts both legacy object and new array shapes, but normalizes in-memory to arrays when needed.
|
| 290 |
+
"""
|
| 291 |
out = {
|
| 292 |
"has_active_signal": False,
|
| 293 |
"active_signal": None,
|
|
|
|
| 300 |
if res["success"] and res["data"]:
|
| 301 |
raw = res["data"][0]
|
| 302 |
out["raw"] = raw
|
| 303 |
+
|
| 304 |
+
# If array and first element has pair/type => active signal
|
| 305 |
if isinstance(raw, list) and raw and isinstance(raw[0], dict) and "pair" in raw[0] and "type" in raw[0]:
|
| 306 |
out["has_active_signal"] = True
|
| 307 |
out["active_signal"] = raw
|
| 308 |
+
|
| 309 |
+
# If object with "scenario" => scenario mode
|
| 310 |
elif isinstance(raw, dict) and "scenario" in raw:
|
| 311 |
out["has_scenario"] = True
|
| 312 |
out["scenario"] = raw["scenario"]
|
| 313 |
+
|
| 314 |
+
# Legacy: single signal object (not array) => treat as active signal
|
| 315 |
+
elif isinstance(raw, dict) and "pair" in raw and "type" in raw:
|
| 316 |
+
out["has_active_signal"] = True
|
| 317 |
+
out["active_signal"] = [raw]
|
| 318 |
except Exception as e:
|
| 319 |
logger.error(f"Error fetching signals/scenario: {e}")
|
| 320 |
return out
|
| 321 |
|
| 322 |
def save_scenario_object(scenario_obj):
|
| 323 |
+
"""
|
| 324 |
+
Save scenario to db_signals as an array: [ { "scenario": {...} } ]
|
| 325 |
+
"""
|
|
|
|
|
|
|
|
|
|
| 326 |
try:
|
| 327 |
+
payload_list = [{"scenario": scenario_obj}]
|
| 328 |
+
payload_text = json.dumps(payload_list, ensure_ascii=False)
|
| 329 |
auth_token, commit_oid = db_signals.fetch_authenticity_token_and_commit_oid()
|
| 330 |
if auth_token and commit_oid:
|
| 331 |
result = db_signals.update_user_json_file(auth_token, commit_oid, payload_text)
|
|
|
|
| 348 |
def build_initial_chat_history(alert_message=None):
|
| 349 |
chat_history = []
|
| 350 |
|
| 351 |
+
# Determine current state (active signal vs scenario/no state)
|
| 352 |
+
try:
|
| 353 |
+
state = fetch_signals_raw()
|
| 354 |
+
except Exception as e:
|
| 355 |
+
logger.error(f"Error determining state for system prompt: {e}")
|
| 356 |
+
state = {"has_active_signal": False, "has_scenario": False}
|
| 357 |
+
|
| 358 |
+
has_active = state.get("has_active_signal", False)
|
| 359 |
+
has_scen = state.get("has_scenario", False)
|
| 360 |
+
|
| 361 |
+
# Load system prompt from files based on state
|
| 362 |
+
try:
|
| 363 |
+
system_base_prompt = load_system_prompt_from_files(has_active, has_scen)
|
| 364 |
+
except Exception as e:
|
| 365 |
+
logger.error(f"Error loading system prompt: {e}")
|
| 366 |
+
system_base_prompt = "ابدأ التحليل وفق حالتك (صفقة نشطة أو سيناريو)."
|
| 367 |
+
|
| 368 |
+
# Fetch news summary from db_system and name it 'news'
|
| 369 |
+
news = ""
|
| 370 |
try:
|
| 371 |
system_data = db_system.fetch_json_from_github()
|
|
|
|
| 372 |
if system_data["success"] and system_data["data"]:
|
| 373 |
+
news = system_data["data"][0].get("response", "") or ""
|
| 374 |
+
except Exception as e:
|
| 375 |
+
logger.error(f"Error fetching news from db_system: {e}")
|
| 376 |
+
news = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
|
| 378 |
+
# Build system turn (system prompt + time zones + news)
|
| 379 |
+
try:
|
| 380 |
times = get_time_zones()
|
| 381 |
time_info = "\n".join([f"{city}: {time}" for city, time in times.items()])
|
| 382 |
+
parts = [system_base_prompt, f"[Time Zones]\n{time_info}"]
|
| 383 |
+
if news.strip():
|
| 384 |
+
parts.append(f"[News]\n{news.strip()}")
|
| 385 |
+
system_full = "\n\n".join(parts)
|
| 386 |
chat_history.append({
|
| 387 |
"role": "system",
|
| 388 |
+
"content": system_full
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
})
|
| 390 |
except Exception as e:
|
| 391 |
+
logger.error(f"Error building system turn: {e}")
|
| 392 |
chat_history.append({
|
| 393 |
"role": "system",
|
| 394 |
+
"content": system_base_prompt
|
| 395 |
})
|
| 396 |
|
| 397 |
multipart_content = []
|
| 398 |
|
| 399 |
+
# Previous analysis (optional) - Read from db_analysis; supports array and legacy object
|
| 400 |
try:
|
| 401 |
analysis_data = db_analysis.fetch_json_from_github()
|
| 402 |
prev_text = ""
|
| 403 |
if analysis_data["success"] and analysis_data["data"]:
|
| 404 |
+
raw_obj = analysis_data["data"][0]
|
| 405 |
+
if isinstance(raw_obj, list) and raw_obj:
|
| 406 |
+
raw_text = raw_obj[-1].get("response", "")
|
| 407 |
+
elif isinstance(raw_obj, dict):
|
| 408 |
+
raw_text = raw_obj.get("response", "")
|
| 409 |
+
else:
|
| 410 |
+
raw_text = ""
|
| 411 |
prev_text = str(raw_text)[:1500]
|
| 412 |
if prev_text:
|
| 413 |
+
multipart_content.append({"type": "text", "text": f"LAST ANALYSIS HAPPEN :\n{prev_text}"})
|
| 414 |
except Exception as e:
|
| 415 |
logger.error(f"Error fetching previous analysis: {e}")
|
| 416 |
|
| 417 |
# Alert + current context (active signal or scenario or none)
|
| 418 |
try:
|
|
|
|
| 419 |
times = get_time_zones()
|
| 420 |
time_info = "\n".join([f"{city}: {time}" for city, time in times.items()])
|
| 421 |
+
prices_text = format_live_prices_text(get_live_prices_for_pairs())
|
| 422 |
|
| 423 |
message_content = ""
|
| 424 |
if alert_message:
|
| 425 |
+
message_content += f" ALERT MESSAGE: {alert_message}\n\n"
|
| 426 |
else:
|
| 427 |
+
message_content += "NO Any Message from ALERT\n\n"
|
|
|
|
|
|
|
|
|
|
| 428 |
|
| 429 |
+
if has_active:
|
| 430 |
sig = state["active_signal"][0]
|
| 431 |
message_content += (
|
| 432 |
+
"The user is currently in an active trade (one of the scenarios has been triggered):\n"
|
| 433 |
+
f"- Pair: {sig.get('pair','N/A')}\n"
|
| 434 |
+
f"- Type: {sig.get('type','N/A')}\n"
|
| 435 |
+
f"- Entry: {sig.get('entry','N/A')}\n"
|
| 436 |
+
f"- Stop Loss: {sig.get('stop_loss','N/A')}\n"
|
| 437 |
+
f"- Take Profit: {sig.get('take_profit','N/A')}\n"
|
| 438 |
+
"Important Instructions:\n"
|
| 439 |
+
"- Provide only ONE <img> at a time in the format:\n"
|
| 440 |
+
" <img><XAUUSD><id_for_IND><id_for_IND><id_for_IND>// you can use maxumum 3 indecators in same image<timeframe></img>\n"
|
| 441 |
+
"- Use <final> only if you think the conversation already has enough information to conclude.\n\n"
|
| 442 |
+
f"Current Time:\n{time_info}\n\n"
|
| 443 |
+
f"Live Prices:\n{prices_text}"
|
| 444 |
+
)
|
| 445 |
+
elif has_scen:
|
|
|
|
| 446 |
sc = state["scenario"]
|
| 447 |
buy = sc.get("Buy", {})
|
| 448 |
sell = sc.get("Sell", {})
|
| 449 |
message_content += (
|
| 450 |
+
"There is a previously saved scenario that hasn’t been triggered yet. Creating a new scenario will replace the old one:\n"
|
| 451 |
+
f"- Buy: @={buy.get('at','N/A')}, SL={buy.get('SL','N/A')}, TP={buy.get('TP','N/A')}\n"
|
| 452 |
+
f"- Sell: @={sell.get('at','N/A')}, SL={sell.get('SL','N/A')}, TP={sell.get('TP','N/A')}\n\n"
|
| 453 |
+
"AI Guidance: Continue analyzing. If you want to update the scenario, send a <final> with a new <scenario> to replace it. If no new scenario is created, we will wait for one of the scenarios to be triggered.\n\n"
|
| 454 |
+
"Important Instructions:\n"
|
| 455 |
+
"- Provide only ONE <img> at a time:\n"
|
| 456 |
+
" <img><XAUUSD><id_for_IND><id_for_IND><id_for_IND>// you can use maxumum 3 indecators in same image<timeframe></img>\n"
|
| 457 |
+
"- Use <final> only if you believe there is enough information in the conversation.\n\n"
|
| 458 |
+
f"Current Time:\n{time_info}\n\n"
|
| 459 |
+
f"Live Prices:\n{prices_text}"
|
| 460 |
+
)
|
| 461 |
else:
|
| 462 |
message_content += (
|
| 463 |
+
"No scenario or active trade exists (first run). Please analyze and create the first scenario within <final> when done.\n\n"
|
| 464 |
+
"Important Instructions:\n"
|
| 465 |
+
"- Provide only ONE <img> at a time:\n"
|
| 466 |
+
" <img><XAUUSD><id_for_IND><id_for_IND><id_for_IND>// you can use maxumum 3 indecators in same image<timeframe></img>\n"
|
| 467 |
+
"- Use <final> only if you believe there is enough information in the conversation to conclude.\n\n"
|
| 468 |
+
f"Current Time:\n{time_info}\n\n"
|
| 469 |
+
f"Live Prices:\n{prices_text}"
|
| 470 |
+
)
|
| 471 |
|
| 472 |
multipart_content.append({"type": "text", "text": message_content})
|
| 473 |
except Exception as e:
|
|
|
|
| 601 |
{"type": "text", "text": (
|
| 602 |
"Your image is ready for analysis.\n"
|
| 603 |
"- Request **only ONE <img>** at a time for each chart.\n <img><XAUUSD><id_for_IND><id_for_IND><id_for_IND>// you can use maxumum 3 indecators in same image<timeframe></img>\n"
|
| 604 |
+
"- Use <final> **only** if you think the conversation already has enough information to conclude. ELSE request new image\n"
|
| 605 |
+
"Do not send multiple images at once. \n\n look i want you to be smart analysis and choose image and indicators like pro not idiot okay ? go ahead and tell me what next"
|
| 606 |
)},
|
| 607 |
{"type": "text", "text": f"Current Time:\n{time_info}"},
|
| 608 |
{"type": "text", "text": f"Live Prices:\n{prices_text}"}
|
|
|
|
| 782 |
return actions_performed
|
| 783 |
|
| 784 |
def edit_existing_signal(edit_data):
|
| 785 |
+
"""
|
| 786 |
+
Edit the active signal (assumed stored as an array with a single signal object) and save back as an array.
|
| 787 |
+
"""
|
| 788 |
try:
|
| 789 |
signals_data = db_signals.fetch_json_from_github()
|
| 790 |
if not (signals_data["success"] and signals_data["data"]):
|
| 791 |
return {"success": False, "error": "No active signal found to edit"}
|
| 792 |
|
|
|
|
| 793 |
raw = signals_data["data"][0]
|
| 794 |
+
# Expecting current storage shape to be an array; ensure we handle both array and object safely
|
| 795 |
if isinstance(raw, list) and raw and isinstance(raw[0], dict):
|
| 796 |
current_signal = raw[0]
|
| 797 |
+
container_is_list = True
|
| 798 |
+
elif isinstance(raw, dict):
|
| 799 |
+
# Legacy/object format, normalize to a single-element list
|
| 800 |
+
current_signal = raw
|
| 801 |
+
container_is_list = False
|
| 802 |
+
logger.warning("Signals DB returned an object; normalizing to array on save.")
|
| 803 |
else:
|
| 804 |
return {"success": False, "error": "No active signal found to edit"}
|
| 805 |
|
|
|
|
| 820 |
|
| 821 |
auth_token, commit_oid = db_signals.fetch_authenticity_token_and_commit_oid()
|
| 822 |
if auth_token and commit_oid:
|
| 823 |
+
# Always save as array
|
| 824 |
+
updated_signal_list = [current_signal]
|
| 825 |
+
updated_json = json.dumps(updated_signal_list, ensure_ascii=False)
|
| 826 |
+
result = db_signals.update_user_json_file(auth_token, commit_oid, updated_json)
|
| 827 |
+
if result.get("success"):
|
| 828 |
return {"success": True, "updates": updates_made}
|
| 829 |
else:
|
| 830 |
return {"success": False, "error": "Failed to update signal"}
|