File size: 20,043 Bytes
43fd6f4
7d7d0d5
43fd6f4
7d7d0d5
43fd6f4
7d7d0d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43fd6f4
7d7d0d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e45f4fb
7d7d0d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43fd6f4
7d7d0d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43fd6f4
7d7d0d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43fd6f4
7d7d0d5
 
 
 
 
 
 
 
 
 
43fd6f4
7d7d0d5
 
 
 
 
 
fa6bb84
7d7d0d5
 
832f45c
fa6bb84
9b55bc1
7d7d0d5
 
fa6bb84
7d7d0d5
 
 
 
fa6bb84
 
 
 
 
 
 
 
 
 
 
 
 
 
43fd6f4
7d7d0d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43fd6f4
 
7d7d0d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43fd6f4
7d7d0d5
43fd6f4
7d7d0d5
 
 
43fd6f4
7d7d0d5
 
 
43fd6f4
7d7d0d5
 
fa6bb84
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
import json
import time
import requests
from flask import Flask, jsonify
from apscheduler.schedulers.background import BackgroundScheduler
from fetch_news import get_recent_news_items, build_news_summary
from get_price import get_live_rates_for_pair
from ai_api import call_o1_ai_api
from db_news import (
    fetch_json_from_github as fetch_news_json_from_github,
    fetch_authenticity_token_and_commit_oid as fetch_news_auth_token_and_oid,
    update_user_json_file as update_news_json_file
)

# NEW: import twitter db helpers
from db_twiter import (
    fetch_json_from_github as fetch_twitter_json_from_github,
    fetch_authenticity_token_and_commit_oid as fetch_twiter_auth_token_and_oid,
    update_user_json_file as update_twiter_json_file
)

# Create Flask app
app = Flask(__name__)

# Global variable to store the last update result
last_update_result = {
    "success": False,
    "timestamp": None,
    "message": "No updates yet"
}

# Simple logger helper
def log(step, msg):
    print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] [{step}] {msg}", flush=True)

# =========================
# Screenshot helper
# =========================
SCREENSHOT_API = "https://corvo-ai-xx-sc.hf.space/capture"

def capture_screenshots(urls, width=1080, height=1920, full_page=True, timeout=400):
    """
    Calls the screenshot API with one or more URLs and returns a dict:
    {
      'success': True/False,
      'images': [ { 'srcUrl': <original>, 'imageUrl': <png> }, ... ],
      'errors': [ ...raw errors... ]
    }
    """
    step = "SCREENSHOT"
    payload = {
        "urls": urls,
        "width": width,
        "height": height,
        "fullPage": full_page
    }
    try:
        log(step, f"Requesting screenshots for {len(urls)} URL(s): {urls} w={width} h={height} fullPage={full_page}")
        resp = requests.post(SCREENSHOT_API, json=payload, timeout=timeout)
        log(step, f"API status code: {resp.status_code}")
        resp.raise_for_status()
        data = resp.json()
        results = data.get("results", [])
        errors = data.get("errors", [])
        log(step, f"API results count: {len(results)}, errors count: {len(errors)}")
        images = []
        for item in results:
            out = item.get("output", {})
            img_url = out.get("imageUrl")
            src = item.get("url")
            log(step, f"Parsed result for {src}: imageUrl={'OK' if img_url else 'MISSING'}")
            if img_url:
                images.append({
                    "srcUrl": src,
                    "imageUrl": img_url
                })
        success = len(images) > 0
        log(step, f"Collected {len(images)} image(s). Success={success}")
        if errors:
            log(step, f"Errors: {errors}")
        return {"success": success, "images": images, "errors": errors}
    except Exception as e:
        log(step, f"Exception: {e}")
        return {"success": False, "images": [], "errors": [str(e)]}

# =========================
# Chat history builder
# =========================
def build_formatted_chat_history(
    old_ai_response=None,
    news_summary="",
    twitter_summary="",
    gold_price_data=None,
    chart_images=None
):
    step = "CHAT_BUILD"
    log(step, "Start building chat history")

    chat_history = []

    # 1) role=system
    log(step, "Append system role prompt")
    chat_history.append({"role": "system", "content": """(أنت نظام ذكاء اصطناعي احترافي متخصص في بناء استراتيجية تداول ذكية ومباشرة على الذهب XAUUSD.

هدفك:
- إنتاج استراتيجية تداول احترافية قابلة للتنفيذ، معتمدة على مزيج من التحليل الإخباري والقراءات الفنية المتقدمة وإدارة المخاطر الديناميكية، ومهيكلة بتنسيق XML المحدد.
- اختيار زوج واحد فقط لعلاقة الارتباط لاكتشاف الفخاخ، دون شرح داخل <Relation>.

مسؤولياتك الأساسية:
1) الأخبار والسياق والسعر:
   - جمع آخر النقاط الجوهرية من الأخبار المؤثرة على الذهب (الفيدرالي، التضخم، PMI، التوترات الجيوسياسية، النمو، الدولار، العوائد، سيولة السوق).
   - الاستدلال على دفة المخاطر Risk-on/Risk-off.
   - تحديد الاتجاه المرجّح قصير/متوسط المدى إن أمكن.
   - ملاحظة السعر الحالي أو نطاق سعري حديث إن توفّر. إذا لم يتوفر سعر لحظي، استخدم نطاقاً تقديرياً مع توضيح عدم اليقين.
   - إضافة ملخص تويتر مختصر مع ذكر الحسابات ذات التأثير على الذهب/الفوركس.

2) بناء الاستراتيجية:
   - استراتيجية تداول احترافية متعددة السيناريوهات، تتضمن:
     • شروط دخول/خروج محددة قابلة للاختبار (مستويات، مؤشرات، سرعات شموع، أحجام، وقت).
     • شروط متقدمة مثل: Velocity Candle Detection، Signal Score (درجة إشارة رقمية)، فلترة الأخبار القوية (توقف/خفض حجم قبل وبعد الأخبار).
     • إدارة صفقات ديناميكية Dynamic Trade Management: وقف خسارة متحرك، جني أرباح جزئي، إعادة توازن الحجم، إلغاء أو تعديل الأوامر المعلّقة.
     • قواعد التعامل مع التقلبات: توسيع/تقليص الوقف، تجميد الدخول أثناء سبريد مرتفع أو عزوف سيولة.
     • جداول احترافية للسيناريوهات والفخاخ المحتملة مرتبطة بالزوج المختار في <Relation>.

3) علاقة الارتباط والفخاخ:
   - اختر زوجاً واحداً فقط له ارتباط مهم مع الذهب للمقارنة والكشف عن الفخاخ (مثل قوة/ضعف الدولار أو العوائد).
   - استخدم هذا الزوج في الجداول لتمييز السيناريوهات المتضادة (Divergence/Confluence).
   - في وسم <Relation> اكتب اسم الزوج فقط دون شرح.

4) جودة المخرجات وتنسيق XML:
   - استخدم العربية الفصيحة الموجزة والعملية.
   - لا تضف نصاً خارج عناصر XML المطلوبة.
   - التزم بالبنية التالية حصراً:
     <news>…</news>
     <twiter>…</twiter>
     <Strategy>…</Strategy>
     <ُExpired>..<ُ/Expired>
     <BestOpportunities>
         <Buy>…</Buy>
         <Sell>…</Sell>
     </BestOpportunities>
     <Relation>زوج-واحد-فقط</Relation>

تفاصيل العناصر:

• <news>
  -  أساسية: توجه الفيدرالي، بيانات تضخم/وظائف حديثة، تحركات عوائد وسندات، شهية المخاطرة، أحداث جيوسياسية، وضع الدولار.
  - اختتم باستنتاج اتجاهي مرجّح وتأثيره المتوقع على XAUUSD.
  - إذا كانت البيانات غير محدثة، اذكر ذلك بوضوح وقدّم إطار احتمالات بدل الجزم.

• <twiter>
  - قدم ملخصاً مختصراً م لأبرز ما يتداوله على تويتر.
  - اذكر حسابات قامت بنشر اشياء تهمك مع ذكر المنشور @username
  - ركّز على المزاج العام، التحذيرات من التقلب، أي إشارات توافق/اختلاف مع السرد الإخباري.

• <Strategy>
  - ضع هنا استراتيجيه قويه تقلل خسائر و تصنع ربح حوالي 100 نقطه بناء عل الاخبار و التوجه العام لارسالها الي بوت صانع صفقات

• <ُExpired>
  - ضع هنا تاريخ ووقت و سعر تعتقد ان استرااتيجيتك تنتهي اذا وصلت

• <BestOpportunities>
  - قدّم وصفاً عملياً قصيراً لأفضل حالات الشراء وأفضل حالات البيع وفق شروط مثالية متكاملة (مستوى، زخم، Score، فلترة أخبار، إدارة).

• <Relation>
  - اسم زوج واحد فقط، مثل: DXY أو USDJPY أو US10Y أو EURUSD. اكتب الرمز/الاسم فقط بلا أي شرح.

معايير الدقة والشفافية:
- لا تخترع أسعاراً مؤكدة إن لم تكن متاحة؛ استخدم نطاقات تقريبية مع توضيح عدم اليقين.
- اذكر الافتراضات بوضوح.
- احرص أن تكون القواعد قابلة للتنفيذ وليست عامة أو إنشائية.
- لا تكرر المحتوى بلا داع.
- أعد المخرجات دائماً بصيغة XML بالترتيب المحدد أعلاه دون إضافات.)"""})

    # 2) role=user -> chart images as multipart entries
    if chart_images:
        log(step, f"Appending {len(chart_images)} chart image(s) to chat")
        for img in chart_images:
            timeframe = img.get("timeframe", "").strip()
            image_url = img.get("imageUrl")
            if image_url:
                chat_history.append({
                    "role": "user",
                    "type": "multipart",
                    "content": [
                        {"type": "image", "url": image_url},
                        {"type": "text", "text": f"هذه لقطة شاشة لزوج XAUUSD إطار زمني {timeframe}"}
                    ]
                })
                log(step, f"Added image message for timeframe={timeframe}, url={image_url}")
            else:
                log(step, f"Skipped image missing URL for timeframe={timeframe}")
    else:
        log(step, "No chart images available to append")

    # 3) role=user -> News + Twitter + Price
    log(step, "Appending news + twitter + price text block")
    user_content = "News from Tridingview News :"
    if news_summary:
        user_content += f"{news_summary}\n\n"
    else:
        user_content += "No fresh news summary available.\n\n"

    if twitter_summary:
        user_content += "Twitter Feed Summary:\n"
        user_content += f"{twitter_summary}\n\n"
    else:
        user_content += "Twitter Feed Summary: No recent Twitter data available.\n\n"

    if gold_price_data is not None:
        user_content += (
            f"Gold Price [XAUUSD]:\n"
            f"  Bid: {gold_price_data.get('bid')}\n"
            f"  Ask: {gold_price_data.get('ask')}\n"
            f"  Spread: {gold_price_data.get('difference')}\n"
        )
    chat_history.append({"role": "user", "content": user_content})
    log(step, "Appended news/price block")

    # 4) role=user -> OLD ANALYSIS if exists
    if old_ai_response:
        chat_history.append({"role": "user", "content": "OLD ANALYSIS FROM YOU:" + old_ai_response})
        log(step, "Appended old AI analysis")
    else:
        log(step, "No old AI analysis available")

    # 5) role=user -> update question
    update_prompt = "هل تريد تحديث تحليلك واستراتيجيتك بناءً على هذه المعطيات؟"
    chat_history.append({"role": "user", "content": update_prompt})
    log(step, "Appended update question")

    log(step, "Chat history build complete")
    return chat_history


def update_strategy_job():
    """Function to be scheduled, runs the main logic"""
    global last_update_result

    step = "JOB"
    log(step, "Scheduled update started")

    try:
        # 1) Fetch old data from news.json in GitHub
        log(step, "Fetching old data from GitHub (news.json)")
        old_data_response = fetch_news_json_from_github()
        old_ai_response = None

        if old_data_response.get("success") and old_data_response.get("data"):
            log(step, "Old data fetch success")
            old_data = old_data_response["data"]
            old_ai_response = json.dumps(old_data, ensure_ascii=False, separators=(',', ':'))
        else:
            log(step, f"Old data fetch failed or empty: {old_data_response}")
            old_ai_response = None

        # 2) Fetch news + gold price
        log(step, "Fetching recent news items")
        news_items = get_recent_news_items(hours=24)
        log(step, f"Fetched {len(news_items) if news_items else 0} news item(s)")
        news_summary = build_news_summary(news_items)
        log(step, "Built news summary")

        log(step, "Fetching live gold price XAUUSD")
        gold_price_data = get_live_rates_for_pair("XAUUSD")
        log(step, f"Price data: {gold_price_data}")

        # 2b) Fetch Twitter summary from twiter.json
        log(step, "Fetching twitter summary from GitHub (twiter.json)")
        twitter_summary = ""
        try:
            tw_resp = fetch_twitter_json_from_github()
            if tw_resp.get("success") and tw_resp.get("data"):
                data = tw_resp["data"]
                if isinstance(data, dict) and "twiter" in data:
                    twitter_summary = data.get("twiter", "")
                elif isinstance(data, str):
                    twitter_summary = data
                else:
                    twitter_summary = json.dumps(data, ensure_ascii=False)
                log(step, "Twitter summary fetched successfully")
            else:
                log(step, f"Twitter fetch returned no data: {tw_resp}")
                twitter_summary = ""
        except Exception as te:
            log(step, f"Twitter fetch error: {te}")
            twitter_summary = ""

        # 2c) Capture chart screenshots for 15m and 1h
        charts = [
            {
                "url": "https://corvo-ai-charts.static.hf.space/index.html?symbol=XAUUSD&interval=15&exchange=OANDA",
                "timeframe": "15 دقيقة"
            },
            {
                "url": "https://corvo-ai-charts.static.hf.space/index.html?symbol=XAUUSD&interval=60&exchange=OANDA",
                "timeframe": "1 ساعة"
            }
        ]
        chart_images = []
        try:
            log(step, "Capturing chart screenshots (15m, 1h)")
            capture_result = capture_screenshots([c["url"] for c in charts], width=1920, height=1080, full_page=False)
            if capture_result.get("success"):
                log(step, "Screenshot capture success")
                src_to_tf = {c["url"]: c["timeframe"] for c in charts}
                for img in capture_result.get("images", []):
                    chart_images.append({
                        "timeframe": src_to_tf.get(img.get("srcUrl"), ""),
                        "imageUrl": img.get("imageUrl")
                    })
                log(step, f"Prepared {len(chart_images)} chart image entries for chat")
            else:
                errs = capture_result.get("errors")
                log(step, f"Screenshot capture failed. Errors: {errs}")
        except Exception as e:
            log(step, f"Screenshot exception: {e}")

        # 3) Build chat_history including images
        log(step, "Building chat history")
        chat_history = build_formatted_chat_history(
            old_ai_response=old_ai_response,
            news_summary=news_summary,
            twitter_summary=twitter_summary,
            gold_price_data=gold_price_data,
            chart_images=chart_images
        )
        log(step, f"Chat history ready. Messages count: {len(chat_history)}")

        # 4) Call AI
        log(step, "Calling AI API with chat history")
        ai_response, updated_chat_history = call_o1_ai_api(chat_history)
        log(step, "AI API call completed")

        # 5) Save AI response to GitHub in news.json
        log(step, "Serializing AI response for GitHub")
        result_json_object = {
            "response": ai_response,
            "timestamp": time.time()
        }
        new_content_one_line = json.dumps([result_json_object], ensure_ascii=False, separators=(',', ':'))
        log(step, f"Serialized content length: {len(new_content_one_line)}")

        # 6) Get authenticity_token and commit_oid
        log(step, "Fetching authenticity token and commit oid for news.json")
        token, commit_oid = fetch_news_auth_token_and_oid()
        if not token or not commit_oid:
            last_update_result = {
                "success": False,
                "timestamp": time.time(),
                "message": "Could not fetch authenticity token or commit oid for news.json."
            }
            log(step, last_update_result['message'])
            return
        log(step, "Fetched token and commit oid successfully")

        # 7) Update GitHub file
        log(step, "Updating news.json on GitHub")
        update_result = update_news_json_file(token, commit_oid, new_content_one_line)

        if update_result.get("success"):
            last_update_result = {
                "success": True,
                "timestamp": time.time(),
                "message": "Updated news.json successfully!",
                "response": ai_response
            }
            log(step, last_update_result['message'])
        else:
            last_update_result = {
                "success": False,
                "timestamp": time.time(),
                "message": f"Failed to update news.json: {update_result.get('message')}"
            }
            log(step, last_update_result['message'])

    except Exception as e:
        last_update_result = {
            "success": False,
            "timestamp": time.time(),
            "message": f"Error during update: {str(e)}"
        }
        log(step, last_update_result['message'])

# API routes
@app.route('/status', methods=['GET'])
def get_status():
    """Returns the status of the last update"""
    log("API", "GET /status called")
    return jsonify({
        "success": last_update_result.get("success", False),
        "timestamp": last_update_result.get("timestamp"),
        "message": last_update_result.get("message"),
        "last_updated": time.strftime('%Y-%m-%d %H:%M:%S',
                                      time.localtime(last_update_result.get("timestamp")))
                                      if last_update_result.get("timestamp") else None
    })

@app.route('/force-update', methods=['GET'])
def force_update():
    """Force an immediate update"""
    log("API", "GET /force-update called")
    update_strategy_job()
    return jsonify({
        "success": last_update_result.get("success", False),
        "message": "Update job triggered",
        "result": last_update_result
    })

@app.route('/latest', methods=['GET'])
def get_latest():
    """Get the latest strategy response"""
    log("API", "GET /latest called")
    if "response" in last_update_result:
        return jsonify({
            "success": True,
            "timestamp": last_update_result.get("timestamp"),
            "response": last_update_result.get("response"),
            "last_updated": time.strftime('%Y-%m-%d %H:%M:%S',
                                         time.localtime(last_update_result.get("timestamp")))
        })
    else:
        return jsonify({
            "success": False,
            "message": "No strategy data available yet"
        })

# Setup the scheduler
def init_scheduler():
    """Initialize and start the scheduler"""
    scheduler = BackgroundScheduler()
    scheduler.add_job(func=update_strategy_job, trigger="interval", hours=1)
    scheduler.start()
    log("SCHED", "Scheduler started: will run every hour")

    # Run once immediately on startup
    log("SCHED", "Running initial update job on startup")
    update_strategy_job()

if __name__ == "__main__":
    # Initialize the scheduler before starting the Flask app
    init_scheduler()

    # Start the Flask app
    log("APP", "Starting Flask server on port 7860")
    app.run(host='0.0.0.0', port=7860)