โ† Back
โ˜†
"""
AlphaPulse Bot โ€” Card Templates & Visual Helpers

All post formatting lives here. Every post goes through build_card()
for consistent branding.
"""

import random

from config import BASE_HASHTAGS, AFFILIATE_LINKS, PREMIUM_TEASERS

# โ”€โ”€โ”€ Visual constants โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
HEADER_LINE = "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"
FOOTER_LINE = "โ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆ"
BRAND = "โšก <b>AlphaPulse</b> ยท @alphapulsexp"

# Post counter for rotation logic
_post_seq = {'count': 0}

# Spark bar characters (index 0 = lowest, 7 = highest)
SPARK_CHARS = "โ–โ–‚โ–ƒโ–„โ–…โ–†โ–‡โ–ˆ"


def spark_bar(values: list[float]) -> str:
    """Convert a list of floats into a Unicode sparkline bar.
    E.g. [1, 3, 5, 2, 4] -> 'โ–‚โ–„โ–ˆโ–ƒโ–…'
    """
    if not values or len(values) < 2:
        return ""
    mn, mx = min(values), max(values)
    spread = mx - mn
    if spread == 0:
        return SPARK_CHARS[4] * len(values)
    return "".join(
        SPARK_CHARS[min(int((v - mn) / spread * 7), 7)]
        for v in values
    )


def progress_bar(value: int, max_val: int = 100, width: int = 10) -> str:
    """Render a progress bar: [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘] 78/100
    """
    filled = round(value / max_val * width)
    filled = max(0, min(width, filled))
    empty = width - filled
    return f"[{'โ–ˆ' * filled}{'โ–‘' * empty}] {value}/{max_val}"


def format_price_line(emoji: str, symbol: str, price: float,
                      change_24h: float, sparkline: str = '') -> str:
    """Format a single price line with arrow and optional sparkline.
    E.g.: โ‚ฟ BTC  $87,432  โ–ฒ +2.1%  โ–โ–‚โ–ƒโ–…โ–‡
    """
    arrow = '๐ŸŸข' if change_24h > 0 else '๐Ÿ”ด' if change_24h < 0 else 'โšช'
    # Format price: no decimals for BTC, 0 decimals for ETH, 2 for SOL
    if price >= 1000:
        price_str = f"${price:,.0f}"
    elif price >= 10:
        price_str = f"${price:,.1f}"
    else:
        price_str = f"${price:,.2f}"

    line = f"{emoji} {symbol}  <b>{price_str}</b>  {arrow} {change_24h:+.1f}%"
    if sparkline:
        line += f"  {sparkline}"
    return line


def pick_affiliate() -> dict:
    """Weighted random affiliate link selection."""
    weights = [a['weight'] for a in AFFILIATE_LINKS]
    return random.choices(AFFILIATE_LINKS, weights=weights, k=1)[0]


def pick_premium_teaser() -> str:
    """Random premium teaser line."""
    return random.choice(PREMIUM_TEASERS)


def build_card(header_emoji: str, title: str, body_lines: list[str],
               hashtags: str = '', show_brand: bool = True,
               show_affiliate: bool = False,
               show_premium: bool = False) -> str:
    """Universal card wrapper.

    โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
    ๐Ÿง  MARKET ANALYSIS
    โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”

    body lines here...

    #hashtags

    ๐Ÿ’ผ Trade on Binance  (optional affiliate)
    ๐Ÿ”’ Premium teaser    (optional)

    โ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆ
    โšก AlphaPulse ยท @alphapulsexp
    """
    _post_seq['count'] += 1

    parts = [
        HEADER_LINE,
        f"{header_emoji} <b>{title}</b>",
        HEADER_LINE,
        "",
    ]

    parts.extend(body_lines)

    if hashtags:
        parts += ["", hashtags]

    # Affiliate link (weighted rotation)
    if show_affiliate:
        aff = pick_affiliate()
        parts += ["", f"<a href='{aff['url']}'>{aff['cta']}</a>"]

    # Premium teaser
    if show_premium:
        parts += ["", f"<i>{pick_premium_teaser()}</i>"]

    if show_brand:
        parts += ["", FOOTER_LINE, BRAND]

    return "\n".join(parts)


# โ”€โ”€โ”€ Specific card builders โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

def card_news(emoji: str, commentary: str, source: str, url: str,
              hashtags: str, add_affiliate: bool = False) -> str:
    """News post card with AI commentary."""
    body = [commentary]
    body.append("")
    body.append(f"๐Ÿ“ฐ <i>{source}</i>")
    if url:
        body.append(f"๐Ÿ”— <a href='{url}'>Read full article</a>")

    return build_card(emoji, "CRYPTO NEWS", body, hashtags,
                      show_affiliate=add_affiliate)


def card_price_snapshot(prices: dict, fg: dict = None,
                        dominance: dict = None,
                        sparklines: dict = None) -> str:
    """Daily price snapshot card."""
    sp = sparklines or {}

    body = [
        format_price_line("โ‚ฟ", "BTC", prices['btc_price'],
                          prices['btc_change'], sp.get('btc', '')),
        format_price_line("โŸ ", "ETH", prices['eth_price'],
                          prices['eth_change'], sp.get('eth', '')),
        format_price_line("โ—Ž", "SOL", prices['sol_price'],
                          prices.get('sol_change', 0), sp.get('sol', '')),
    ]

    if dominance:
        mcap_arrow = 'โ–ฒ' if dominance.get('mcap_change', 0) > 0 else 'โ–ผ'
        body += [
            "",
            f"๐Ÿ”ต BTC.D: <b>{dominance['btc_dominance']}%</b>  โ”‚  "
            f"ETH.D: {dominance['eth_dominance']}%",
            f"๐Ÿ“ฆ MCap: <b>${dominance['total_mcap'] / 1e12:.2f}T</b>  "
            f"{mcap_arrow} {dominance['mcap_change']:+.1f}%",
        ]

    if fg:
        bar = progress_bar(fg['value'])
        body += [
            "",
            f"๐Ÿ˜ฑ Fear & Greed: <b>{fg['value']}</b> โ€” {fg['classification']}",
            f"<code>{bar}</code>",
        ]

    # Show premium teaser on ~20% of price snapshots
    show_prem = random.random() < 0.2
    return build_card("๐Ÿ’น", "PRICE SNAPSHOT", body, BASE_HASHTAGS,
                      show_premium=show_prem)


def card_trending(coins: list, date_str: str) -> str:
    """Trending coins card."""
    medals = ['๐Ÿฅ‡', '๐Ÿฅˆ', '๐Ÿฅ‰', '4๏ธโƒฃ', '5๏ธโƒฃ', '6๏ธโƒฃ', '7๏ธโƒฃ']
    body = [f"<i>{date_str} ยท CoinGecko</i>", ""]

    for i, c in enumerate(coins[:7]):
        rank = f"<i>#{c['rank']}</i>" if c['rank'] != '?' else ''
        body.append(f"{medals[i]}  <b>{c['symbol']}</b> {c['name']}  {rank}")

    return build_card("๐Ÿ”ฅ", "TRENDING NOW", body, "#crypto #trending #altcoins")


def card_top_movers(gainers: list, losers: list) -> str:
    """Top movers card (gainers + losers)."""
    body = []

    if gainers:
        body.append("๐ŸŸข <b>Top Gainers</b>")
        for c in gainers[:3]:
            sym   = c['symbol'].upper()
            pct   = c['price_change_percentage_24h'] or 0
            price = c['current_price']
            if price >= 1000:
                p = f"${price:,.0f}"
            elif price >= 1:
                p = f"${price:,.2f}"
            else:
                p = f"${price:,.4f}"
            body.append(f"  โ–ฒ {sym}  <b>{p}</b>  <b>+{pct:.1f}%</b>")

    if gainers and losers:
        body.append("")

    if losers:
        body.append("๐Ÿ”ด <b>Top Losers</b>")
        for c in losers[:3]:
            sym   = c['symbol'].upper()
            pct   = c['price_change_percentage_24h'] or 0
            price = c['current_price']
            if price >= 1000:
                p = f"${price:,.0f}"
            elif price >= 1:
                p = f"${price:,.2f}"
            else:
                p = f"${price:,.4f}"
            body.append(f"  โ–ผ {sym}  <b>{p}</b>  <b>{pct:.1f}%</b>")

    return build_card("๐Ÿ†", "24H TOP MOVERS", body, BASE_HASHTAGS)


def card_weekly_digest(prices: dict, fg: dict = None,
                       gainers: list = None,
                       week_range: str = '') -> str:
    """Sunday weekly digest card."""
    btc_arrow = 'โ–ฒ' if (prices.get('btc_7d') or 0) > 0 else 'โ–ผ'
    eth_arrow = 'โ–ฒ' if (prices.get('eth_7d') or 0) > 0 else 'โ–ผ'
    sol_arrow = 'โ–ฒ' if (prices.get('sol_7d') or 0) > 0 else 'โ–ผ'

    body = [
        f"<i>{week_range}</i>",
        "",
        "๐Ÿ“ˆ <b>7-Day Performance</b>",
        f"โ‚ฟ BTC  <b>${prices['btc_price']:,.0f}</b>  {btc_arrow} {prices.get('btc_7d', 0):+.1f}%",
        f"โŸ  ETH  <b>${prices['eth_price']:,.0f}</b>  {eth_arrow} {prices.get('eth_7d', 0):+.1f}%",
        f"โ—Ž SOL  <b>${prices['sol_price']:,.2f}</b>  {sol_arrow} {prices.get('sol_7d', 0):+.1f}%",
    ]

    if fg:
        bar = progress_bar(fg['value'])
        body += [
            "",
            f"๐Ÿ˜ฑ Sentiment: <b>{fg['classification']}</b> ({fg['value']})",
            f"<code>{bar}</code>",
        ]

    if gainers and len(gainers) >= 3:
        body += ["", "๐Ÿ† <b>Week's Top Gainers (24h)</b>"]
        for c in gainers[:3]:
            pct = c['price_change_percentage_24h'] or 0
            body.append(f"  โ–ฒ {c['symbol'].upper()}  <b>+{pct:.1f}%</b>")

    body += [
        "",
        "๐Ÿ’ฌ How was your week? Drop it below ๐Ÿ‘‡",
    ]

    return build_card("๐Ÿ“…", "WEEKLY DIGEST", body, BASE_HASHTAGS)


def card_fear_greed(value: int, classification: str, comment: str) -> str:
    """Standalone Fear & Greed card."""
    bar = progress_bar(value)

    # Emoji based on level
    if value <= 25:
        level_emoji = "๐Ÿ˜ฑ"
    elif value <= 45:
        level_emoji = "๐Ÿ˜ฐ"
    elif value <= 55:
        level_emoji = "๐Ÿ˜"
    elif value <= 75:
        level_emoji = "๐Ÿค‘"
    else:
        level_emoji = "๐Ÿš€"

    body = [
        f"{level_emoji} <b>{classification}</b>",
        "",
        f"<code>{bar}</code>",
        "",
        comment,
    ]

    return build_card("๐ŸŽš๏ธ", "FEAR & GREED INDEX", body, BASE_HASHTAGS)


def card_funding_rates(rates: list) -> str:
    """Funding rates card โ€” shows market long/short bias."""
    body = [
        "<i>Binance Futures ยท 8h Rate</i>",
        "",
    ]

    # Split into positive (longs pay) and negative (shorts pay)
    longs_pay = [r for r in rates if r['rate'] > 0][:5]
    shorts_pay = [r for r in rates if r['rate'] < 0][:5]

    if longs_pay:
        body.append("๐ŸŸข <b>Longs Pay (bullish crowd)</b>")
        for r in longs_pay:
            body.append(f"  {r['symbol']}  <b>{r['rate_pct']:+.4f}%</b>")

    if longs_pay and shorts_pay:
        body.append("")

    if shorts_pay:
        body.append("๐Ÿ”ด <b>Shorts Pay (bearish crowd)</b>")
        for r in shorts_pay:
            body.append(f"  {r['symbol']}  <b>{r['rate_pct']:+.4f}%</b>")

    # Summary line
    total_positive = len([r for r in rates if r['rate'] > 0])
    total_negative = len([r for r in rates if r['rate'] < 0])
    if total_positive + total_negative > 0:
        bias = "Longs dominant ๐Ÿ‚" if total_positive > total_negative else "Shorts dominant ๐Ÿป"
        body += ["", f"๐Ÿ“Š Market bias: <b>{bias}</b>"]

    return build_card("๐Ÿ“Š", "FUNDING RATES", body,
                      "#crypto #futures #trading #BTC #funding",
                      show_affiliate=True)


def card_watchlist(coins: list, ai_commentary: str,
                   date_str: str) -> str:
    """Monday watchlist card โ€” coins to watch this week."""
    body = [
        f"<i>Week of {date_str}</i>",
        "",
    ]

    for i, coin in enumerate(coins[:5], 1):
        emoji = ['1๏ธโƒฃ', '2๏ธโƒฃ', '3๏ธโƒฃ', '4๏ธโƒฃ', '5๏ธโƒฃ'][i - 1]
        sym = coin.get('symbol', '').upper()
        reason = coin.get('reason', '')
        body.append(f"{emoji} <b>{sym}</b> โ€” {reason}")

    if ai_commentary:
        body += ["", f"๐Ÿ’ก <i>{ai_commentary}</i>"]

    return build_card("๐ŸŽฏ", "WEEKLY WATCHLIST", body,
                      "#crypto #watchlist #trading #altcoins")


def card_history(events: list, date_str: str, ai_reflection: str = '') -> str:
    """'This Day in Crypto' historical card."""
    body = [
        f"<i>{date_str}</i>",
        "",
    ]

    for e in events:
        year = e['year']
        icon = e.get('icon', '๐Ÿ“…')
        text = e['event']
        nearby = e.get('_nearby', False)
        prefix = "~" if nearby else ""
        body.append(f"{icon} <b>{prefix}{year}</b> โ€” {text}")

    if ai_reflection:
        body += ["", f"๐Ÿ’ญ <i>{ai_reflection}</i>"]

    body += ["", "โ™ป๏ธ History doesn't repeat, but it rhymes"]

    return build_card("๐Ÿ•ฐ๏ธ", "THIS DAY IN CRYPTO", body,
                      "#crypto #history #bitcoin #OnThisDay")


def card_long_short(positions: list) -> str:
    """Market positioning card โ€” Long/Short ratios for top coins."""
    # Count overall bias
    bulls = sum(1 for p in positions if p['ratio'] > 1.2)
    bears = sum(1 for p in positions if p['ratio'] < 0.8)

    if bulls > bears + 2:
        verdict = "๐Ÿ‚ Market heavily long โ€” watch for squeeze"
    elif bears > bulls + 2:
        verdict = "๐Ÿป Market heavily short โ€” squeeze risk high"
    else:
        verdict = "โš–๏ธ Mixed positioning"

    body = [
        "<i>Binance Futures ยท Account Ratios</i>",
        "",
    ]

    for p in positions:
        long_pct = p['long_pct'] * 100
        short_pct = p['short_pct'] * 100

        # Visual bar: 10 chars total
        long_blocks = round(long_pct / 10)
        long_blocks = max(0, min(10, long_blocks))
        short_blocks = 10 - long_blocks
        bar = f"๐ŸŸข{'โ–ˆ' * long_blocks}{'โ–‘' * short_blocks}๐Ÿ”ด"

        sym = f"{p['symbol']:>5}"
        body.append(f"<code>{sym}</code> {bar} L:{long_pct:.0f}% S:{short_pct:.0f}%")

    body += ["", f"๐Ÿ“Š <i>{verdict}</i>"]

    return build_card("๐Ÿ“", "MARKET POSITIONING", body,
                      "#crypto #futures #longshort #trading",
                      show_affiliate=True, show_premium=True)


def card_whale_alerts(alerts: list) -> str:
    """Whale alert card โ€” large BTC transactions."""
    body = [
        "<i>Blockchair ยท Largest BTC Transactions (24h)</i>",
        "",
    ]

    for a in alerts:
        btc = a['amount_btc']
        usd_approx = ""  # would need BTC price
        body.append(f"๐Ÿ‹ <b>{btc:,.1f} BTC</b>  tx: <code>{a['hash']}</code>")

    body += [
        "",
        "๐Ÿ” <i>Large moves often precede volatility</i>",
    ]

    return build_card("๐Ÿณ", "WHALE WATCH", body,
                      "#crypto #bitcoin #whales #onchain")

๐Ÿ“œ Git History

a09f02fchore: initial commit โ€” version control setup5 weeks ago
Show last diff
Loading...