max2201111

finviz quite good

May 24th, 2026
77
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 16.06 KB | None | 0 0
  1. import pandas as pd
  2. import numpy as np
  3. import warnings
  4. from finvizfinance.screener.technical import Technical   # Technical view!
  5. from finvizfinance.screener.overview  import Overview
  6.  
  7. warnings.filterwarnings("ignore")
  8.  
  9. # ── Pomocné mapovací tabulky pro Finviz filter_dict ──────────────────────────
  10. # (hodnoty jsou přesné řetězce, které Finviz akceptuje)
  11.  
  12. RSI_BINS = [
  13.     (10,  "Oversold (< 20)"),
  14.     (20,  "Oversold (< 30)"),
  15.     (30,  "Under 30"),
  16.     (40,  "Under 40"),
  17.     (50,  "Under 50"),
  18.     (60,  "Under 60"),
  19.     (70,  "Under 70"),
  20.     (80,  "Overbought (> 70)"),
  21.     (90,  "Overbought (> 80)"),
  22.     (100, "Overbought (> 90)"),
  23. ]
  24.  
  25. BETA_BINS = [
  26.     (0,   "Under 0"),
  27.     (0.5, "Under 0.5"),
  28.     (1.0, "Under 1"),
  29.     (1.5, "Under 1.5"),
  30.     (2.0, "Under 2"),
  31.     (3.0, "Under 3"),
  32.     (99,  "Over 3"),
  33. ]
  34.  
  35. ATR_BINS = [
  36.     (0.25, "Over 0.25"),
  37.     (0.5,  "Over 0.5"),
  38.     (1.0,  "Over 1"),
  39.     (1.5,  "Over 1.5"),
  40.     (2.0,  "Over 2"),
  41.     (2.5,  "Over 2.5"),
  42.     (3.0,  "Over 3"),
  43.     (4.0,  "Over 4"),
  44.     (5.0,  "Over 5"),
  45. ]
  46.  
  47. AVG_VOL_BINS = [
  48.     (100e3,  "Over 100K"),
  49.     (200e3,  "Over 200K"),
  50.     (500e3,  "Over 500K"),
  51.     (750e3,  "Over 750K"),
  52.     (1e6,    "Over 1M"),
  53.     (2e6,    "Over 2M"),
  54.     (5e6,    "Over 5M"),
  55.     (10e6,   "Over 10M"),
  56.     (20e6,   "Over 20M"),
  57.     (50e6,   "Over 50M"),
  58. ]
  59.  
  60. REL_VOL_BINS = [
  61.     (0.5,  "Over 0.5"),
  62.     (1.0,  "Over 1"),
  63.     (1.5,  "Over 1.5"),
  64.     (2.0,  "Over 2"),
  65.     (3.0,  "Over 3"),
  66.     (5.0,  "Over 5"),
  67.     (10.0, "Over 10"),
  68. ]
  69.  
  70. CHANGE_BINS_POS = [
  71.     (1,  "Up"),
  72.     (2,  "Up 2%"),
  73.     (3,  "Up 3%"),
  74.     (4,  "Up 4%"),
  75.     (5,  "Up 5%"),
  76.     (10, "Up 10%"),
  77.     (15, "Up 15%"),
  78.     (20, "Up 20%"),
  79. ]
  80.  
  81. CHANGE_BINS_NEG = [
  82.     (-1,  "Down"),
  83.     (-2,  "Down 2%"),
  84.     (-3,  "Down 3%"),
  85.     (-4,  "Down 4%"),
  86.     (-5,  "Down 5%"),
  87.     (-10, "Down 10%"),
  88.     (-15, "Down 15%"),
  89.     (-20, "Down 20%"),
  90. ]
  91.  
  92. PERF_BINS_POS = [
  93.     (2,  "Up 2%"),
  94.     (5,  "Up 5%"),
  95.     (10, "Up 10%"),
  96.     (15, "Up 15%"),
  97.     (20, "Up 20%"),
  98.     (25, "Up 25%"),
  99.     (30, "Up 30%"),
  100.     (50, "Up 50%"),
  101.     (100,"Up 100%"),
  102. ]
  103.  
  104. PERF_BINS_NEG = [
  105.     (-2,  "Down 2%"),
  106.     (-5,  "Down 5%"),
  107.     (-10, "Down 10%"),
  108.     (-15, "Down 15%"),
  109.     (-20, "Down 20%"),
  110.     (-25, "Down 25%"),
  111.     (-30, "Down 30%"),
  112.     (-50, "Down 50%"),
  113. ]
  114.  
  115. VOLATILITY_BINS = [
  116.     (0.5,  "Over 0.5%"),
  117.     (1.0,  "Over 1%"),
  118.     (1.5,  "Over 1.5%"),
  119.     (2.0,  "Over 2%"),
  120.     (2.5,  "Over 2.5%"),
  121.     (3.0,  "Over 3%"),
  122.     (4.0,  "Over 4%"),
  123.     (5.0,  "Over 5%"),
  124.     (6.0,  "Over 6%"),
  125.     (7.0,  "Over 7%"),
  126.     (8.0,  "Over 8%"),
  127.     (9.0,  "Over 9%"),
  128.     (10.0, "Over 10%"),
  129.     (15.0, "Over 15%"),
  130. ]
  131.  
  132. SMA20_BINS_POS = [
  133.     (0,   "Price above SMA20"),
  134.     (5,   "5% above SMA20"),
  135.     (10,  "10% above SMA20"),
  136.     (15,  "15% above SMA20"),
  137.     (20,  "20% above SMA20"),
  138.     (30,  "30% above SMA20"),
  139.     (40,  "40% above SMA20"),
  140.     (50,  "50% above SMA20"),
  141. ]
  142.  
  143. SMA50_BINS_POS = [
  144.     (0,   "Price above SMA50"),
  145.     (5,   "5% above SMA50"),
  146.     (10,  "10% above SMA50"),
  147.     (15,  "15% above SMA50"),
  148.     (20,  "20% above SMA50"),
  149.     (30,  "30% above SMA50"),
  150.     (40,  "40% above SMA50"),
  151.     (50,  "50% above SMA50"),
  152. ]
  153.  
  154. SMA200_BINS_POS = [
  155.     (0,   "Price above SMA200"),
  156.     (5,   "5% above SMA200"),
  157.     (10,  "10% above SMA200"),
  158.     (20,  "20% above SMA200"),
  159.     (30,  "30% above SMA200"),
  160.     (40,  "40% above SMA200"),
  161.     (50,  "50% above SMA200"),
  162. ]
  163.  
  164. HIGH52W_BINS = [
  165.     (0,  "New High"),
  166.     (3,  "0-3% below High"),
  167.     (5,  "0-5% below High"),
  168.     (10, "0-10% below High"),
  169.     (15, "0-15% below High"),
  170.     (20, "0-20% below High"),
  171.     (30, "0-30% below High"),
  172.     (50, "0-50% below High"),
  173. ]
  174.  
  175. # ── Pomocné funkce ────────────────────────────────────────────────────────────
  176.  
  177. def to_num(s):
  178.     """Bezpečná konverze řetězce na float."""
  179.     try:
  180.         return float(str(s).replace("%","\").replace(",","\").strip())
  181.    except:
  182.        return np.nan
  183.  
  184. def nearest_bin(value, bins, default=None):
  185.    """Najde nejbližší bin k hodnotě (vrátí label)."""
  186.    if pd.isna(value):
  187.        return default
  188.    best_label = default
  189.    best_dist  = float("inf")
  190.    for threshold, label in bins:
  191.        d = abs(value - threshold)
  192.        if d < best_dist:
  193.            best_dist  = d
  194.            best_label = label
  195.    return best_label
  196.  
  197. def above_bin(value, bins, default=None):
  198.    """Vrátí největší bin, jehož threshold je ≤ value."""
  199.    if pd.isna(value):
  200.        return default
  201.    chosen = default
  202.    for threshold, label in bins:
  203.        if value >= threshold:
  204.            chosen = label
  205.    return chosen
  206.  
  207. def normalize_col(df, col):
  208.    if col in df.columns:
  209.        df[col] = df[col].apply(to_num)
  210.    return df
  211.  
  212. # ── 1. Stažení dat – Technical view ──────────────────────────────────────────
  213.  
  214. TECH_COLS = [
  215.    "Ticker", "Beta", "ATR", "SMA20", "SMA50", "SMA200",
  216.    "52W High", "52W Low", "RSI", "Price", "Change",
  217.    "from Open", "Gap", "Volume", "Avg Volume",
  218.    "Rel Volume", "Volatility W", "Volatility M",
  219.    "Perf Week", "Perf Month", "Perf Quarter",
  220.    "Perf Half Y", "Perf Year", "Perf YTD",
  221. ]
  222.  
  223. def fetch_technical(signal: str, max_rows: int = 50) -> pd.DataFrame:
  224.    ft = Technical()
  225.    ft.set_filter(signal=signal)
  226.    try:
  227.        df = ft.screener_view(verbose=0)
  228.    except Exception as e:
  229.        print(f"  ⚠ Technical view selhal ({e}), zkouším Overview …")
  230.        ft2 = Overview()
  231.        ft2.set_filter(signal=signal)
  232.        df = ft2.screener_view(verbose=0)
  233.    if df is None or df.empty:
  234.        return pd.DataFrame()
  235.    df = df.head(max_rows).copy()
  236.    for col in df.columns:
  237.        df[col] = df[col].apply(to_num) if col != "Ticker" else df[col]
  238.    return df
  239.  
  240. print("📡 Stahuji Top Gainers (Technical view) …")
  241. gainers = fetch_technical("Top Gainers")
  242. print(f"   → {len(gainers)} tickerů | sloupce: {list(gainers.columns)}")
  243.  
  244. print("📡 Stahuji Top Losers  (Technical view) …")
  245. losers  = fetch_technical("Top Losers")
  246. print(f"   → {len(losers)} tickerů | sloupce: {list(losers.columns)}")
  247.  
  248. # ── 2. Statistický profil ─────────────────────────────────────────────────────
  249.  
  250. NUM_COLS = [
  251.    "Beta", "ATR", "SMA20", "SMA50", "SMA200",
  252.    "52W High", "52W Low", "RSI", "Price", "Change",
  253.    "from Open", "Gap", "Volume", "Avg Volume",
  254.    "Rel Volume", "Volatility W", "Volatility M",
  255.    "Perf Week", "Perf Month",
  256. ]
  257.  
  258. def compute_profile(df: pd.DataFrame) -> dict:
  259.    p = {}
  260.    for col in NUM_COLS:
  261.        if col in df.columns:
  262.            s = df[col].dropna()
  263.            if len(s):
  264.                p[col] = {"mean": s.mean(), "med": s.median(), "std": s.std(),
  265.                           "min": s.min(), "max": s.max()}
  266.    return p
  267.  
  268. def print_profile(prof: dict, label: str):
  269.    print(f"\n{'='*65}")
  270.    print(f"  Profil: {label}")
  271.    print(f"{'='*65}")
  272.    for col, s in prof.items():
  273.        print(f"  {col:<14} min={s['min']:>8.2f}  max={s['max']:>8.2f}  "
  274.              f"mean={s['mean']:>8.2f}  med={s['med']:>8.2f}")
  275.  
  276. prof_g = compute_profile(gainers)
  277. prof_l = compute_profile(losers)
  278. print_profile(prof_g, "Top Gainers (dnešní)")
  279. print_profile(prof_l, "Top Losers  (dnešní)")
  280.  
  281. # ── 3. Převod profilu na Finviz filter_dict ───────────────────────────────────
  282.  
  283. def build_filter_dict(prof: dict, direction: str = "up") -> dict:
  284.    """
  285.     direction = 'up'   → hledáme kandidáty na gainers
  286.     direction = 'down' → hledáme kandidáty na losers
  287.     """
  288.    fd = {}
  289.  
  290.    # RSI
  291.    # if "RSI" in prof:
  292.    #     rsi = prof["RSI"]["med"]
  293.    #     lbl = nearest_bin(rsi, RSI_BINS)
  294.    #     if lbl:
  295.    #         fd["RSI (14)"] = lbl
  296.  
  297.    # Beta
  298.    # if "Beta" in prof:
  299.    #     beta = prof["Beta"]["med"]
  300.    #     lbl  = nearest_bin(beta, BETA_BINS)
  301.    #     if lbl:
  302.    #         fd["Beta"] = lbl
  303.  
  304.    # ATR (používáme dolní kvartil jako minimum)
  305.    # if "ATR" in prof:
  306.    #     atr = max(0.1, prof["ATR"]["med"] * 0.5)
  307.    #     lbl = above_bin(atr, ATR_BINS)
  308.    #     if lbl:
  309.    #         fd["Average True Range"] = lbl
  310.  
  311.    # Average Volume (less strict)
  312.    if "Avg Volume" in prof:
  313.        # Set a very low minimum to be extremely permissive
  314.        avg_v = max(100e3, prof["Avg Volume"]["med"] * 0.05)
  315.        lbl   = above_bin(avg_v, AVG_VOL_BINS)
  316.        if lbl:
  317.            fd["Average Volume"] = lbl
  318.  
  319.    # Relative Volume (less strict)
  320.    if "Rel Volume" in prof:
  321.        # Set a very low minimum to be extremely permissive
  322.        rv  = max(0.1, prof["Rel Volume"]["med"] * 0.05)
  323.        lbl = above_bin(rv, REL_VOL_BINS)
  324.        if lbl:
  325.            fd["Relative Volume"] = lbl
  326.  
  327.    # Add 'Change' filter based on user's request: "did not rise/fall so much yesterday"
  328.    if direction == "up":
  329.        fd["Change"] = "Upto 10%" # For gainers, ensure today's change was not already too high
  330.        fd["Performance (Week)"] = "Upto 5%" # Not risen much last week
  331.        fd["52-Week High/Low"] = "0-3% above Low" # Near 52-week low
  332.    elif direction == "down":
  333.        fd["Change"] = "Flat (0%)" # For losers, ensure today's change was not already too negative
  334.        fd["Performance (Week)"] = "Down 5%" # Not fallen much last week
  335.        fd["52-Week High/Low"] = "0-3% below High" # Near 52-week high
  336.  
  337.    # Change (dnešní %) - original commented out block, now superseded by explicit filter above
  338.    # if "Change" in prof:
  339.    #     chg = prof["Change"]["med"]
  340.    #     bins = CHANGE_BINS_POS if direction == "up" else CHANGE_BINS_NEG
  341.    #     lbl  = above_bin(abs(chg) * (1 if direction == "up" else -1),
  342.    #                      CHANGE_BINS_POS) if direction == "up" else \
  343.    #            above_bin(chg, CHANGE_BINS_NEG)
  344.    #     if lbl:
  345.    #         fd["Change"] = lbl
  346.  
  347.    # Performance – Week
  348.    # if "Perf Week" in prof:
  349.    #     pw  = prof["Perf Week"]["med"]
  350.    #     bins = PERF_BINS_POS if direction == "up" else PERF_BINS_NEG
  351.    #     lbl  = above_bin(abs(pw) if direction == "up" else pw, bins)
  352.    #     if lbl:
  353.    #         fd["Performance (Week)"] = lbl
  354.  
  355.    # Volatility (Week) – chceme min
  356.    # if "Volatility W" in prof:
  357.    #     vw   = prof["Volatility W"]["med"] * 0.5
  358.    #     lbl = above_bin(vw, VOLATILITY_BINS)
  359.    #     if lbl:
  360.    #         fd["Volatility"] = lbl
  361.  
  362.    # SMA20 – relativní vzdálenost ceny od SMA20
  363.    # if "SMA20" in prof and direction == "up":
  364.    #     s = prof["SMA20"]["med"]
  365.    #     lbl = above_bin(s * 0.3, SMA20_BINS_POS) if s > 0 else None
  366.    #     if lbl:
  367.    #         fd["20-Day Simple Moving Average"] = lbl
  368.  
  369.    # if "SMA50" in prof and direction == "up":
  370.    #     s = prof["SMA50"]["med"]
  371.    #     lbl = above_bin(s * 0.3, SMA50_BINS_POS) if s > 0 else None
  372.    #     if lbl:
  373.    #         fd["50-Day Simple Moving Average"] = lbl
  374.  
  375.    # 52W High/Low relativní vzdálenost
  376.    # if "52W High" in prof:
  377.    #     h  = prof["52W High"]["med"]   # záporné číslo = % pod 52W high
  378.    #     lbl = above_bin(abs(h) if h < 0 else 0, [
  379.    #         (0,  "0-3% below High"),
  380.    #         (5,  "0-5% below High"),
  381.    #         (10, "0-10% below High"),
  382.    #         (20, "0-20% below High"),
  383.    #     ])
  384.    #     if lbl:
  385.    #         fd["52-Week High/Low"] = lbl
  386.  
  387.    return fd
  388.  
  389. fd_gainers = build_filter_dict(prof_g, direction="up")
  390. fd_losers  = build_filter_dict(prof_l, direction="down")
  391.  
  392. print("\n\n🔧 Filter dict pro Gainers kandidáty:")
  393. for k, v in fd_gainers.items():
  394.    print(f"   {k:<35} = {v}")
  395.  
  396. print("\n🔧 Filter dict pro Losers kandidáty:")
  397. for k, v in fd_losers.items():
  398.    print(f"   {k:<35} = {v}")
  399.  
  400. # ── 4. Screenovací dotaz ──────────────────────────────────────────────────────
  401.  
  402. def screen_candidates(
  403.    filters: dict,
  404.    signal: str,
  405.    label: str,
  406.    prof: dict,
  407.    top_n: int = 10,
  408. ) -> pd.DataFrame:
  409.    ft = Technical()
  410.    # Odstraníme filtry, které by mohly způsobit prázdný výsledek (fallback)
  411.    applied = {}
  412.    for k, v in filters.items():
  413.        test_ft = Technical()
  414.        try:
  415.            test_ft.set_filter(signal=signal, filters_dict={**applied, k: v})
  416.            df_test = test_ft.screener_view(verbose=0)
  417.            if df_test is not None and not df_test.empty:
  418.                applied[k] = v
  419.                print(f"   ✓ filtr přidán: {k} = {v}  ({len(df_test)} výsl.)")
  420.            else:
  421.                print(f"   ✗ filtr přeskočen (0 výsl.): {k} = {v}")
  422.        except Exception as e:
  423.            print(f"   ✗ filtr chyba ({k}): {e}")
  424.  
  425.    ft.set_filter(signal=signal, filters_dict=applied)
  426.    try:
  427.        df = ft.screener_view(verbose=0)
  428.    except Exception as e:
  429.        print(f"  ⚠ Finální dotaz selhal: {e}")
  430.        return pd.DataFrame()
  431.  
  432.    if df is None or df.empty:
  433.        return pd.DataFrame()
  434.  
  435.    for col in df.columns:
  436.        if col != "Ticker":
  437.            df[col] = df[col].apply(to_num)
  438.  
  439.    # Seřadit: highest |Change|
  440.    if "Change" in df.columns:
  441.        df = df.reindex(df["Change"].abs().sort_values(ascending=False).index)
  442.  
  443.    df["Tomorrow_Prediction"] = label
  444.    return df.head(top_n)
  445.  
  446. print("\n\n🔍 Screening zítřejších Gainers kandidátů …")
  447. cand_gainers = screen_candidates(
  448.    fd_gainers, signal="Top Gainers",
  449.    label="🚀 Gainer candidate", prof=prof_g, top_n=20
  450. )
  451.  
  452. print("\n🔍 Screening zítřejších Losers kandidátů …")
  453. cand_losers = screen_candidates(
  454.    fd_losers, signal="Top Losers",
  455.    label="📉 Loser candidate", prof=prof_l, top_n=20
  456. )
  457.  
  458. # ── 5. Výstup ─────────────────────────────────────────────────────────────────
  459.  
  460. SHOW_COLS = [
  461.    "Ticker", "Price", "Change", "RSI", "Beta", "ATR",
  462.    "Rel Volume", "Avg Volume", "Volatility W",
  463.    "SMA20", "SMA50", "52W High", "Perf Week",
  464.    "Tomorrow_Prediction",
  465. ]
  466.  
  467. def show(df: pd.DataFrame, title: str):
  468.    cols = [c for c in SHOW_COLS if c in df.columns]
  469.    print(f"\n{'='*75}")
  470.    print(f"  {title}")
  471.    print(f"{'='*75}")
  472.    if df.empty:
  473.        print("  (žádné výsledky – zkuste uvolnit filtry)")
  474.        return
  475.    print(df[cols].to_string(index=False))
  476.  
  477. show(cand_gainers, "🚀 Predikce: zítřejší TOP GAINERS")
  478. show(cand_losers,  "📉 Predikce: zítřejší TOP LOSERS")
  479.  
  480. # ── 6. Export ─────────────────────────────────────────────────────────────────
  481. if not cand_gainers.empty or not cand_losers.empty:
  482.    out = pd.concat([cand_gainers, cand_losers], ignore_index=True)
  483.    out.to_csv("tomorrow_candidates.csv", index=False)
  484.    print("\n✓ Uloženo do: tomorrow_candidates.csv")
  485.  
  486. # ── 7. Dnešní data pro referenci ──────────────────────────────────────────────
  487. ref_cols = [c for c in ["Ticker","Price","Change","RSI","Beta","ATR",
  488.                         "Rel Volume","Volatility W","Perf Week"] if c in gainers.columns]
  489. print("\n\n── Dnešní Top Gainers – Technical (vstup) ──")
  490. print(gainers[ref_cols].head(10).to_string(index=False) if not gainers.empty else "(prázdné)")
  491.  
  492. ref_cols2 = [c for c in ref_cols if c in losers.columns]
  493. print("\n── Dnešní Top Losers – Technical (vstup) ──")
  494. print(losers[ref_cols2].head(10).to_string(index=False) if not losers.empty else "(prázdné)")
Add Comment
Please, Sign In to add comment