-
Notifications
You must be signed in to change notification settings - Fork 131
Description
-- coding: utf-8 --
برنامج ممدوح — TradeSbayki (نسخة محدثة كاملة في ملف واحد)
import time
from datetime import datetime
import numpy as np
import pandas as pd
import streamlit as st
import plotly.graph_objects as go
تحليلات
import ccxt
from ta.trend import EMAIndicator, MACD
from ta.momentum import RSIIndicator
from ta.volatility import BollingerBands, AverageTrueRange
أخبار
import feedparser
(اختياري) تحديث تلقائي
try:
from streamlit_autorefresh import st_autorefresh
HAS_AR = True
except Exception:
HAS_AR = False
================= إعداد الصفحة العامة =================
st.set_page_config(page_title="برنامج ممدوح — TradeSbayki (كامل محدث)", layout="wide")
st.title("👋 برنامج ممدوح — TradeSbayki (كامل محدث)")
st.caption("كل الأدوات داخل خانات (Tabs) — التحليل المتقدم، شبكة الأوامر، المحفظة، أفضل الساعات (متقدمة)، الأخبار")
st.markdown("""
سياسة الحفظ:
- يتم حفظ مستويات القرار فقط (المقاومة/الدعوم) داخل الجلسة.
- لا يتم حفظ أي مدخلات تداول (رأس المال، عدد الأوامر، الرسوم…).
""")
تهيئة حالة الجلسة
if "levels_saved" not in st.session_state:
st.session_state["levels_saved"] = None
if "last_price" not in st.session_state:
st.session_state["last_price"] = None
================= توابع وذاكرة مؤقتة =================
@st.cache_resource(show_spinner=False)
def get_okx():
ex = ccxt.okx({"enableRateLimit": True})
ex.load_markets()
return ex
@st.cache_data(ttl=30, show_spinner=False, max_entries=64)
def fetch_ohlcv_okx(sym: str, tf: str, lim: int) -> pd.DataFrame:
ex = get_okx()
ohlcv = ex.fetch_ohlcv(sym, timeframe=tf, limit=lim)
df = pd.DataFrame(ohlcv, columns=["ts", "Open", "High", "Low", "Close", "Volume"])
df["ts"] = pd.to_datetime(df["ts"], unit="ms")
for c in ["Open","High","Low","Close","Volume"]:
df[c] = pd.to_numeric(df[c], downcast="float")
return df
def add_indicators(df: pd.DataFrame) -> pd.DataFrame:
dfa = df.copy()
# EMAs
dfa["EMA5"] = EMAIndicator(dfa["Close"], 5).ema_indicator()
dfa["EMA10"] = EMAIndicator(dfa["Close"], 10).ema_indicator()
dfa["EMA20"] = EMAIndicator(dfa["Close"], 20).ema_indicator()
# Bollinger
bb = BollingerBands(dfa["Close"], 20, 2)
dfa["BOLL_mid"] = bb.bollinger_mavg()
dfa["BOLL_ub"] = bb.bollinger_hband()
dfa["BOLL_lb"] = bb.bollinger_lband()
dfa["BOLL_bw%"] = (dfa["BOLL_ub"] - dfa["BOLL_lb"]) / dfa["BOLL_mid"] * 100.0
dfa["BOLL_%B"] = (dfa["Close"] - dfa["BOLL_lb"]) / (dfa["BOLL_ub"] - dfa["BOLL_lb"])
# MACD
macd = MACD(dfa["Close"], 26, 12, 9)
dfa["MACD_diff"] = macd.macd()
dfa["MACD_signal"] = macd.macd_signal()
dfa["MACD_hist"] = macd.macd_diff()
# RSI + ATR
dfa["RSI6"] = RSIIndicator(dfa["Close"], 6).rsi()
dfa["RSI14"] = RSIIndicator(dfa["Close"], 14).rsi()
atr = AverageTrueRange(dfa["High"], dfa["Low"], dfa["Close"], 14)
dfa["ATR14"] = atr.average_true_range()
return dfa
def last_swings(dfa: pd.DataFrame, lookback: int = 48):
sub = dfa.tail(lookback)
return float(sub["High"].max()), float(sub["Low"].min())
def compute_live_levels(dfa: pd.DataFrame):
row = dfa.iloc[-1]
swingH, swingL = last_swings(dfa, 48)
atr = float(row["ATR14"]) if not np.isnan(row["ATR14"]) else 0.0
resistance = swingH
support1 = swingL
support2 = max(support1 - 0.5 * atr, 0.0)
return float(resistance), float(support1), float(support2)
def build_analysis_table(dfa: pd.DataFrame, resistance: float, support1: float, support2: float) -> pd.DataFrame:
row = dfa.iloc[-1]
price = float(row["Close"])
prev = float(dfa["Close"].iloc[-2])
change_pct = (price - prev) / prev * 100.0
swingH, swingL = last_swings(dfa, 48)
ema_state = "صاعد" if row["EMA5"] > row["EMA10"] > row["EMA20"] else \
("تعافٍ" if row["EMA5"] > row["EMA10"] and price > row["EMA20"] else "هابط")
macd_state = "إيجابي" if row["MACD_hist"] > 0 else "سلبي"
rsi_state = "قوي" if row["RSI14"] >= 60 else ("متوازن" if row["RSI14"] >= 45 else "ضعيف")
vol_state = "↑" if (row["Volume"] >= (row["Volume"].rolling(5).mean().iloc[-1])) else "↓"
data = {
"البند": [
"السعر الحالي","التغير %","EMA5","EMA10","EMA20","حالة المتوسطات",
"BOLL وسط","BOLL علوي","BOLL سفلي","عرض الباند %","%B",
"ATR14","MACD (diff)","Signal","Histogram","حالة MACD",
"RSI6","RSI14","حالة RSI",
"حجم",
"قمة (48)","قاع (48)",
"📌 مقاومة القرار (محفوظة/حيّة)","📌 دعم 1 (محفوظ)","📌 دعم 2 (محفوظ)",
"قرار التداول"
],
"القيمة": [
f"{price:,.2f}", f"{change_pct:+.2f}%",
f"{row['EMA5']:.2f}", f"{row['EMA10']:.2f}", f"{row['EMA20']:.2f}", ema_state,
f"{row['BOLL_mid']:.2f}", f"{row['BOLL_ub']:.2f}", f"{row['BOLL_lb']:.2f}", f"{row['BOLL_bw%']:.2f}%", f"{row['BOLL_%B']:.2f}",
f"{row['ATR14']:.2f}", f"{row['MACD_diff']:.2f}", f"{row['MACD_signal']:.2f}", f"{row['MACD_hist']:.2f}", macd_state,
f"{row['RSI6']:.2f}", f"{row['RSI14']:.2f}", rsi_state,
f"{row['Volume']:.2f}",
f"{swingH:,.2f}", f"{swingL:,.2f}",
f"{resistance:,.2f}", f"{support1:,.2f}", f"{support2:,.2f}",
"" # يملأ أدناه
]
}
df_tbl = pd.DataFrame(data)
# قرار التداول
if price > resistance and row["RSI14"] > 50 and row["MACD_hist"] > 0:
decision = "✅ اختراق مقاومة → شراء بسيط (تعزيز)"
elif price <= support1:
decision = "✅ عند دعم 1 → تعزيز كمي"
elif price <= support2:
decision = "✅ عند دعم 2 → شراء إضافي (بحذر)"
elif row["EMA5"] < row["EMA10"] and row["MACD_hist"] < 0 and row["RSI14"] < 45:
decision = "❌ لا تشتري الآن — زخم هابط"
else:
decision = "⏳ ترقّب إغلاق شمعة/تأكيد MACD"
df_tbl.loc[df_tbl["البند"]=="قرار التداول","القيمة"] = decision
return df_tbl
def plot_candles(dfa: pd.DataFrame, title: str = ""):
"""رسم شموع + EMA + Bollinger"""
fig = go.Figure()
fig.add_trace(go.Candlestick(
x=dfa["ts"], open=dfa["Open"], high=dfa["High"], low=dfa["Low"], close=dfa["Close"],
name="OHLC"
))
for col, nm in [("EMA5","EMA5"),("EMA10","EMA10"),("EMA20","EMA20")]:
fig.add_trace(go.Scatter(x=dfa["ts"], y=dfa[col], mode="lines", name=nm))
fig.add_trace(go.Scatter(x=dfa["ts"], y=dfa["BOLL_ub"], mode="lines", name="BOLL Upper"))
fig.add_trace(go.Scatter(x=dfa["ts"], y=dfa["BOLL_mid"], mode="lines", name="BOLL Mid"))
fig.add_trace(go.Scatter(x=dfa["ts"], y=dfa["BOLL_lb"], mode="lines", name="BOLL Lower"))
fig.update_layout(height=520, title=title, xaxis_rangeslider_visible=False, margin=dict(l=10,r=10,t=40,b=10))
return fig
================= الخانات (Tabs) =================
tab1, tab2, tab3, tab4, tab5 = st.tabs([
"📈 لايف + التحليل (كامل)",
"🧩 شبكة الأوامر",
"💼 المحفظة",
"⏰ أفضل الساعات (متقدمة)",
"📰 أخبار اليوم",
])
-------------------------------------------------
📈 لايف + التحليل (كامل) — يحفظ المستويات فقط
-------------------------------------------------
with tab1:
st.subheader("📈 لايف + التحليل — نسخة محدثة كاملة")
c1, c2, c3, c4 = st.columns(4)
with c1:
symbol = st.text_input("الرمز", value="ETH/USDT", key="live_symbol")
with c2:
timeframe = st.selectbox("الفريم", ["1m","3m","5m","15m","30m","1h","2h","4h","6h","12h","1d"], index=6, key="live_tf")
with c3:
limit = st.slider("عدد الشموع", 150, 2000, 800, step=50, key="live_lim")
with c4:
refresh_sec = st.number_input("تحديث كل (ثوانٍ)", min_value=0, value=30, step=5, key="live_ref",
help="ضع 0 لإيقاف التحديث التلقائي")
if refresh_sec > 0 and HAS_AR:
st_autorefresh(interval=refresh_sec*1000, limit=None, key="auto_refresh_live_full")
try:
df = fetch_ohlcv_okx(symbol, timeframe, limit)
if df.empty:
st.warning("لا توجد بيانات متاحة لهذا الرمز/الفريم.")
else:
dfa = add_indicators(df)
price_now = float(dfa["Close"].iloc[-1])
st.session_state["last_price"] = price_now # إرساله لشبكة الأوامر
# حساب/حفظ المستويات
recalc = st.button("🔄 إعادة حساب المستويات", key="btn_recalc_levels_full")
if (st.session_state.get("levels_saved") is None) or recalc:
R, S1, S2 = compute_live_levels(dfa)
st.session_state["levels_saved"] = {"R": R, "S1": S1, "S2": S2}
levels = st.session_state["levels_saved"]
resistance, support1, support2 = levels["R"], levels["S1"], levels["S2"]
# Metrics
m1, m2, m3, m4 = st.columns(4)
with m1: st.metric("💎 السعر الحالي", f"{price_now:,.2f}")
with m2: st.metric("📌 مقاومة القرار", f"{resistance:,.2f}")
with m3: st.metric("📌 دعم 1", f"{support1:,.2f}")
with m4: st.metric("📌 دعم 2", f"{support2:,.2f}")
st.caption("📌 يتم حفظ المقاومة/الدعوم فقط داخل الجلسة.")
# رسم الشموع كامل
st.plotly_chart(plot_candles(dfa, f"{symbol} — {timeframe}"), use_container_width=True)
# جدول التحليل
st.markdown("#### 🔎 جدول التحليل الكامل")
table = build_analysis_table(dfa, resistance, support1, support2)
st.dataframe(table, use_container_width=True)
# ملاحظات وتنبيهات فنية
row = dfa.iloc[-1]
notes = []
if row["EMA5"] > row["EMA10"] > row["EMA20"]:
notes.append("ترتيب EMAs صاعد")
if row["MACD_hist"] > 0 and row["MACD_diff"] > row["MACD_signal"]:
notes.append("MACD صاعد")
if row["RSI14"] >= 70:
notes.append("تنبيه: RSI قرب تشبع شرائي")
if row["RSI14"] <= 30:
notes.append("تنبيه: RSI قرب تشبع بيعي")
if row["BOLL_%B"] <= 0.05:
notes.append("قرب الحد السفلي للبولنجر")
if row["BOLL_%B"] >= 0.95:
notes.append("قرب الحد العلوي للبولنجر")
st.markdown("**ملاحظات سريعة:** " + ("، ".join(notes) if notes else "—"))
# قرار نهائي مختصر
if price_now > resistance and row["RSI14"] > 50 and row["MACD_hist"] > 0:
st.success("✅ اختراق مقاومة → شراء بسيط (تعزيز)")
elif price_now <= support1:
st.warning("✅ عند دعم 1 → تعزيز كمي")
elif price_now <= support2:
st.error("✅ عند دعم 2 → شراء بحذر")
else:
st.info("⏳ ترقّب إغلاق شمعة/تأكيد MACD")
except Exception as e:
st.error(f"خطأ: {e}")
-------------------------------------------------
🧩 شبكة الأوامر (كاملة)
-------------------------------------------------
with tab2:
st.subheader("🧩 شبكة الأوامر — كاملة")
# السعر من التبويب الأول إن وُجد
initial_price = st.session_state.get("last_price") or 3500.0
price_now = st.number_input("السعر الحالي (يمكن تعبئته تلقائيًا من خانة اللايف)", min_value=0.0, value=float(initial_price), step=0.1, key="grid_price")
g1, g2, g3, g4 = st.columns(4)
with g1:
capital = st.number_input("💰 رأس المال (USDT)", min_value=10.0, value=600.0, step=10.0, key="cap")
with g2:
leverage = st.number_input("⚖️ الرافعة ×", min_value=1.0, max_value=50.0, value=5.0, step=0.5, key="lev")
with g3:
n_orders = st.slider("عدد الأوامر", 1, 30, 10, key="n_orders")
with g4:
step_pct = st.number_input("📐 خطوة التدرّج %", min_value=0.1, value=1.25, step=0.05, key="step_pct")
h1, h2, h3, h4 = st.columns(4)
with h1:
tp_pct = st.number_input("🎯 هدف الربح % لكل أمر", min_value=0.1, value=0.80, step=0.05, key="tp_pct")
with h2:
fee_pct = st.number_input("💸 الرسوم لكل جهة %", min_value=0.01, max_value=0.50, value=0.10, step=0.01, key="fee_pct")
with h3:
alloc_mode = st.selectbox("توزيع رأس المال", ["متساوٍ", "يزداد كلما نزل"], index=1, key="alloc_mode")
with h4:
qty_round = st.number_input("تقريب الكمية لـ", min_value=1e-8, value=1e-4, step=1e-4, format="%.8f", key="qty_round")
def build_grid_table(price: float) -> pd.DataFrame:
rows = []
effective_cap = capital * leverage
weights = (np.ones(n_orders) if alloc_mode == "متساوٍ" else np.linspace(1, 2.0, n_orders))
weights = weights / weights.sum()
for i in range(1, n_orders + 1):
buy_price = price * (1 - step_pct/100.0 * i)
alloc_usdt = effective_cap * weights[i-1]
qty = max(alloc_usdt / buy_price, 0.0)
qty = np.floor(qty / qty_round) * qty_round
tp_price = buy_price * (1 + tp_pct/100.0)
gross_pnl = (tp_price - buy_price) * qty
fees = (fee_pct/100.0) * buy_price * qty + (fee_pct/100.0) * tp_price * qty
net_pnl = gross_pnl - fees
rows.append({
"مستوى": i,
"سعر الشراء": buy_price,
"الكمية": qty,
"قيمة الأمر (USDT)": buy_price * qty,
"هدف جني الربح": tp_price,
"بعد عن السعر الحالي %": (buy_price/price - 1)*100.0,
"رسوم تقديرية (USDT)": fees,
"ربح صافٍ (USDT)": net_pnl,
"ربح % على مبلغ الأمر": (net_pnl / (buy_price*qty) * 100.0) if buy_price*qty>0 else 0.0,
})
df_grid = pd.DataFrame(rows)
totals = {
"مستوى": "الإجمالي",
"سعر الشراء": "",
"الكمية": df_grid["الكمية"].sum(),
"قيمة الأمر (USDT)": df_grid["قيمة الأمر (USDT)"].sum(),
"هدف جني الربح": "",
"بعد عن السعر الحالي %": "",
"رسوم تقديرية (USDT)": df_grid["رسوم تقديرية (USDT)"].sum(),
"ربح صافٍ (USDT)": df_grid["ربح صافٍ (USDT)"].sum(),
"ربح % على مبلغ الأمر": ""
}
df_grid = pd.concat([df_grid, pd.DataFrame([totals])], ignore_index=True)
return df_grid
grid_df = build_grid_table(price_now)
# تنسيق الأرقام لعرض أجمل
fmt_cols = {
"سعر الشراء": "{:,.4f}",
"الكمية": "{:,.6f}",
"قيمة الأمر (USDT)": "{:,.2f}",
"هدف جني الربح": "{:,.4f}",
"بعد عن السعر الحالي %": "{:+.2f}%",
"رسوم تقديرية (USDT)": "{:,.4f}",
"ربح صافٍ (USDT)": "{:,.4f}",
"ربح % على مبلغ الأمر": "{:+.2f}%"
}
for c, f in fmt_cols.items():
if c in grid_df.columns and pd.api.types.is_numeric_dtype(grid_df[c]):
grid_df[c] = grid_df[c].apply(lambda v: f.format(v))
st.markdown("#### 📋 جدول أوامر الشراء")
st.dataframe(grid_df, use_container_width=True)
-------------------------------------------------
💼 المحفظة (بدون حفظ)
-------------------------------------------------
with tab3:
st.subheader("💼 محفظة ممدوح — إدخال يدوي (بدون حفظ)")
st.caption("أدخل مراكزك يدويًا — لن تُحفظ بعد انتهاء الجلسة.")
colA, colB, colC = st.columns(3)
with colA:
symbols = st.text_area("العملات (سطر لكل رمز)", "ETH/USDT\nBTC/USDT\nLINK/USDT", key="pf_syms")
with colB:
qtys = st.text_area("الكميات", "0.25\n0.01\n10", key="pf_qtys")
with colC:
avgp = st.text_area("متوسطات الشراء", "3500\n60000\n12", key="pf_avgp")
try:
syms = [s.strip() for s in symbols.splitlines() if s.strip()]
qs = [float(x) for x in qtys.splitlines()]
aps = [float(x) for x in avgp.splitlines()]
assert len(syms) == len(qs) == len(aps), "عدد الأسطر غير متطابق."
dfp = pd.DataFrame({"الرمز": syms, "الكمية": qs, "متوسط الشراء": aps})
dfp["قيمة التكلفة (USDT)"] = dfp["الكمية"] * dfp["متوسط الشراء"]
st.dataframe(dfp, use_container_width=True)
except Exception as e:
st.error(f"تأكد من صحة الإدخال: {e}")
-------------------------------------------------
⏰ أفضل الساعات (متقدمة)
-------------------------------------------------
with tab4:
st.subheader("⏰ أفضل الساعات — نسخة متقدمة")
c1, c2, c3 = st.columns(3)
with c1:
bh_symbol = st.text_input("الرمز", "ETH/USDT", key="bh_sym")
with c2:
bh_timeframe = st.selectbox("الفريم", ["5m","15m","30m","1h","4h","1d"], index=3, key="bh_tf")
with c3:
bh_limit = st.slider("عدد الشموع", 120, 3000, 1200, step=60, key="bh_lim")
@st.cache_data(ttl=90, show_spinner=False)
def fetch_bh(sym, tf, lim):
ex = get_okx()
ohlcv = ex.fetch_ohlcv(sym, timeframe=tf, limit=lim)
df = pd.DataFrame(ohlcv, columns=["ts","Open","High","Low","Close","Volume"])
df["ts"] = pd.to_datetime(df["ts"], unit="ms")
return df
try:
df_bh = fetch_bh(bh_symbol, bh_timeframe, bh_limit)
if df_bh.empty:
st.warning("لا توجد بيانات.")
else:
# تغيّر النسبي لكل شمعة
df_bh["chg%"] = df_bh["Close"].pct_change() * 100.0
df_bh["hour"] = df_bh["ts"].dt.hour
df_bh["day"] = df_bh["ts"].dt.day_name()
# إحصاءات حسب الساعة
agg = df_bh.groupby("hour").agg(
متوسط_التغير_pct=("chg%","mean"),
تذبذب_std=("chg%","std"),
أعلى_إغلاق=("Close","max"),
أدنى_إغلاق=("Close","min"),
عدد_الشموع=("Close","count")
).reset_index().sort_values("متوسط_التغير_pct", ascending=False)
# تصنيف الحالة لكل ساعة
def state(row):
if row["متوسط_التغير_pct"] >= 0.15 and row["تذبذب_std"] <= 0.6:
return "⬆️ صعود مستقر"
if row["متوسط_التغير_pct"] >= 0.00 and row["تذبذب_std"] > 0.6:
return "↗️ صعود متذبذب"
if row["متوسط_التغير_pct"] < 0 and row["تذبذب_std"] > 0.6:
return "↘️ هبوط متذبذب"
if row["متوسط_التغير_pct"] < 0 and row["تذبذب_std"] <= 0.6:
return "⬇️ هبوط مستقر"
return "➖ حيادي"
agg["الحالة"] = agg.apply(state, axis=1)
# تنبيهات مبكرة (تقاطع تغيّر من سالب لموجب في آخر 24 ساعة)
last_day = df_bh.tail(24 if bh_timeframe=="1h" else 96) # تقريبي للفريمات الأصغر
flip = (last_day["chg%"].iloc[-2] < 0) and (last_day["chg%"].iloc[-1] > 0)
if flip:
st.success("🔔 تنبيه: تحوّل الزخم مؤخراً من سلبي إلى إيجابي (آخر نافذة).")
st.markdown("#### 🕒 جدول «أفضل الساعات» (متقدم)")
# تلوين بسيط عبر تنسيق أرقام كنص
show = agg.copy()
for c, fmt in [("متوسط_التغير_pct", "{:+.2f}%"), ("تذبذب_std", "{:.2f}"),
("أعلى_إغلاق", "{:,.2f}"), ("أدنى_إغلاق", "{:,.2f}")]:
show[c] = show[c].apply(lambda v: fmt.format(v))
st.dataframe(show, use_container_width=True)
# ملخّص نصي
st.markdown("#### 📝 ملخّص")
counts = agg["الحالة"].value_counts().to_dict()
total_h = len(agg)
def pct(x):
return f"{(x/total_h*100):.0f}%" if total_h else "0%"
lines = [f"- عدد الساعات المقيمة: {total_h}"]
for key in ["⬆️ صعود مستقر","↗️ صعود متذبذب","⬇️ هبوط مستقر","↘️ هبوط متذبذب","➖ حيادي"]:
if key in counts: lines.append(f"- {key}: {counts[key]} ({pct(counts[key])})")
st.write("\n".join(lines))
# أفضل 3 ساعات صعود/هبوط
top_up = agg.sort_values("متوسط_التغير_pct", ascending=False).head(3)["hour"].tolist()
top_dn = agg.sort_values("متوسط_التغير_pct", ascending=True ).head(3)["hour"].tolist()
st.info(f"🟢 أقوى ساعات صعود: {top_up} — 🔴 أقوى ساعات هبوط: {top_dn}")
except Exception as e:
st.error(f"خطأ: {e}")
-------------------------------------------------
📰 أخبار اليوم (RSS)
-------------------------------------------------
with tab5:
st.subheader("📰 أخبار اليوم — نصيًا (RSS)")
feeds = {
"CoinDesk": "https://www.coindesk.com/arc/outboundfeeds/rss/",
"CoinTelegraph": "https://cointelegraph.com/rss",
"Investing Crypto": "https://www.investing.com/rss/news_301.rss",
}
cols = st.columns(len(feeds))
for i, (name, url) in enumerate(feeds.items()):
with cols[i]:
st.markdown(f"### {name}")
try:
d = feedparser.parse(url)
if not d.entries:
st.caption("لا توجد عناصر.")
continue
for e in d.entries[:8]:
title = e.get("title", "بدون عنوان")
link = e.get("link", "")
date = e.get("published", "")
try:
if hasattr(e, "published_parsed") and e.published_parsed:
date = datetime(*e.published_parsed[:6]).strftime("%Y-%m-%d %H:%M")
except Exception:
pass
st.write(f"- [{title}]({link}) — {date}")
except Exception as ex:
st.error(f"تعذّر جلب {name}: {ex}")