Commit 6f20cad
Changed files (4)
src
src/bridge/chartimg.py
@@ -33,7 +33,7 @@ async def send_to_chartimg_bridge(client: Client, message: Message, symbol: str,
else:
target_mid = to_int(reply_msg_id)
metadata = {"target_cid": target_cid, "target_mid": target_mid, "src": f"{symbol} {interval}"}
- cache.set(f"bridge-{symbol} {interval}", metadata, ttl=15) # save metadata to cache
+ cache.set(f"bridge-{symbol} {interval}", metadata, ttl=60) # save metadata to cache
logger.warning(f"Trying chartimg bridge (@{CHART_BOT}): {symbol} {interval}")
await client.send_message(chat_id=f"@{CHART_BOT}", text=f"/chart {symbol} {interval}")
src/price/coinmarketcap.py
@@ -68,10 +68,7 @@ async def cmc_supported(coin: str, fiat: str = "USD") -> dict:
@cache.memoize(ttl=60)
async def get_cmc_price(coin: str, fiat: str = "USD") -> str:
- """Get the price of a crypto asset from CoinMarketCap.
-
- If the market cap of the coin is less than 10M, we skip it.
- """
+ """Get the price of a crypto asset from CoinMarketCap."""
params = await cmc_supported(coin, fiat)
if not params:
return ""
@@ -81,8 +78,6 @@ async def get_cmc_price(coin: str, fiat: str = "USD") -> str:
if not data:
return f"CoinMarketCap price failed: {coin}"
stats = data["quote"][fiat]
- if float(stats["market_cap"]) < 1000_0000: # 1000万
- return ""
precision = 2 if float(stats["price"]) > 1 else 6
emoji = lambda x: "🟢" if float(x) > 0 else "🔴"
msg = f"🪙**{data['symbol']}** ({data['name']})\n"
@@ -94,7 +89,7 @@ async def get_cmc_price(coin: str, fiat: str = "USD") -> str:
msg += f"{emoji(stats['percent_change_90d'])}90d涨跌: {stats['percent_change_90d']:+.2f}%\n"
date = datetime.fromisoformat(data["last_updated"]).astimezone(ZoneInfo(TZ))
msg += f"🕒{date:%Y-%m-%d %H:%M}\n"
- msg += "📡CoinMarketCap\n"
+ msg += f"📡[CoinMarketCap](https://coinmarketcap.com/currencies/{data['slug']})\n"
return msg.strip()
src/price/entrypoint.py
@@ -17,22 +17,26 @@ from price.tradingview import get_tradingview_price, tradingview_supported
HELP = f"""
💵**查询价格**
-使用说明: `{PREFIX.PRICE}` + Symbol + [@Interval]
+使用说明:
+- `{PREFIX.PRICE}` + Symbol + [@Interval]
+- 或`{PREFIX.CRYPTO}` 仅查询加密货币市场
+- 或`{PREFIX.STOCK}` 仅查询股票市场
+
其中symbol(大小写不限)支持如下类别:
1. 加密货币, 如 `BTC`
-2. 股票&指数, 如 `AAPL` / `SPX` (A股, 港股, 美股)
+2. 股票, 如 `AAPL` / `SPX` (A股, 港股, 美股)
3. 汇率, 如 `USD CNY` (中间有空格)
K线Interval (可选):
- 加密货币(默认30m)
1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1D,3D,1W,1M
-- 股票&指数(默认15m)
+- 股票(默认15m)
1m,3m,5m,15m,30m,45m,1h,2h,3h,4h,1D,1W,1M,3M,6M,1Y
说明:
- 加密货币支持币种代码(BTC), 币种名称(bitcoin), 或交易对(BTCUSDC).
- 此外在Symbol前添加数字可以计算对应数量的价值。当前仅支持对加密货币和法币汇率进行计算。
-
+- 有些代码同时被加密货币和股票市场使用而产生冲突。例如`SPX`是标普500指数, 同时也是一个山寨币。此时可以使用`{PREFIX.CRYPTO}`或`{PREFIX.STOCK}`来限定市场。
示例:
1. 查询加密货币价格
- 对于Binance和OKX支持的币种, 还会返回K线图(默认30m)
@@ -41,7 +45,7 @@ K线Interval (可选):
- `{PREFIX.PRICE} DOGEUSDT`
- `{PREFIX.PRICE} BTC @4h`
-2. 查询股票&指数价格:
+2. 查询股票价格:
- 默认返回Interval为15m的K线图
- `{PREFIX.PRICE}` AAPL 或 NASDAQ:AAPL
- `{PREFIX.PRICE}` SPX 或 SP:SPX
@@ -67,13 +71,16 @@ async def get_asset_price(client: Client, message: Message, **kwargs):
return
info = parse_msg(message)
# send docs if message == "/price"
- if equal_prefix(info["text"], prefix=[PREFIX.PRICE]):
+ if equal_prefix(info["text"], prefix=[PREFIX.PRICE, PREFIX.CRYPTO, PREFIX.STOCK]):
await send2tg(client, message, texts=HELP, **kwargs)
return
- if not startswith_prefix(info["text"], prefix=[PREFIX.PRICE]):
+ if not startswith_prefix(info["text"], prefix=[PREFIX.PRICE, PREFIX.CRYPTO, PREFIX.STOCK]):
return
- text = info["text"].removeprefix(PREFIX.PRICE).strip()
+ prefix = info["text"].split(" ")[0]
+ crypto_only = prefix == PREFIX.CRYPTO
+ stock_only = prefix == PREFIX.STOCK
+ text = info["text"].removeprefix(prefix).strip()
# these patterns should use CoinMarketCap API
# some coin has "$" in symbol, so we need to match it
pattern_1 = r"^([\d.]+)\s+([$\dA-Za-z]+)\s+([$\dA-Za-z]+)$" # match "1.5 BTC CNY"
@@ -92,10 +99,9 @@ async def get_asset_price(client: Client, message: Message, **kwargs):
amount = 1
base = matched.group(1)
quote = matched.group(2)
- if amount > 0 and base and quote and (msg := await cmc_convert_price(amount, base, quote)):
+ if not stock_only and amount > 0 and base and quote and (msg := await cmc_convert_price(amount, base, quote)):
await send2tg(client, message, texts=msg, **kwargs)
return
-
# match interval: "BTC @1m" or "000001 @15m"
if matched := re.search(r"^([$\dA-Za-z]+)\s+@(\d+[A-Za-z])$", text, re.IGNORECASE):
symbol = matched.group(1)
@@ -103,7 +109,7 @@ async def get_asset_price(client: Client, message: Message, **kwargs):
else: # match single symbol: "BTC" / "AAPL" / "SPX" / "000001"
symbol = text
interval = None
- categories = await match_symbol_category(symbol)
+ categories = await match_symbol_category(symbol, crypto_only=crypto_only, stock_only=stock_only)
if not categories:
await send2tg(client, message, texts=f"不支持此Symbol: {symbol.upper()}\n{HELP}", **kwargs)
return
@@ -113,10 +119,13 @@ async def get_asset_price(client: Client, message: Message, **kwargs):
else:
warn_msg = None
# Tradingview
- if categories.get("tradingview") and await get_tradingview_price(client, message, symbol, interval, **kwargs):
- await modify_progress(warn_msg, del_status=True, del_delay=5)
+ if not crypto_only and categories.get("tradingview") and await get_tradingview_price(client, message, symbol, interval, **kwargs):
+ await modify_progress(warn_msg, del_status=True, del_delay=10)
return
+ # Belows are only for crypto market
+ if stock_only:
+ return
# Binance & OKX will return klines chart
if (res := await get_binance_price(symbol, interval)) or (res := await get_okx_price(symbol, interval)):
await send2tg(client, message, **res, **kwargs)
@@ -124,27 +133,32 @@ async def get_asset_price(client: Client, message: Message, **kwargs):
# other crypto assets supported by CoinMarketCap
if res := await get_cmc_price(text):
await send2tg(client, message, texts=res, **kwargs)
- await modify_progress(warn_msg, del_status=True, del_delay=5)
+ await modify_progress(warn_msg, del_status=True, del_delay=10)
@cache.memoize(ttl=3600)
-async def match_symbol_category(symbol: str = "") -> dict[str, str]:
+async def match_symbol_category(symbol: str = "", *, crypto_only: bool = False, stock_only: bool = False) -> dict[str, str]:
if not ENABLE.PRICE:
return {}
category = {}
- if cmc := await cmc_supported(symbol): # {"symbol": "BTC"} or {"slug": "bitcoin"}
- category["cmc"] = cmc.get("symbol", cmc.get("slug"))
- category["crypto"] = cmc.get("symbol", cmc.get("slug"))
- # okx
- okx_symbol, _ = await okx_supported(symbol) # (symbol, instId)
- if okx_symbol:
- category["okx"] = okx_symbol
- category["crypto"] = okx_symbol
- binance_symbol, _ = await binance_supported(symbol) # (symbol, market)
- if binance_symbol:
- category["binance"] = binance_symbol
- category["crypto"] = binance_symbol
- if tradingview := await tradingview_supported(symbol): # ["SSE:000001", "SZSE:000001"]
+ # Crypto market
+ if not stock_only:
+ if cmc := await cmc_supported(symbol): # {"symbol": "BTC"} or {"slug": "bitcoin"}
+ category["cmc"] = cmc.get("symbol", cmc.get("slug"))
+ category["crypto"] = cmc.get("symbol", cmc.get("slug"))
+ # okx
+ okx_symbol, _ = await okx_supported(symbol) # (symbol, instId)
+ if okx_symbol:
+ category["okx"] = okx_symbol
+ category["crypto"] = okx_symbol
+ binance_symbol, _ = await binance_supported(symbol) # (symbol, market)
+ if binance_symbol:
+ category["binance"] = binance_symbol
+ category["crypto"] = binance_symbol
+
+ # Stock market
+ tradingview = []
+ if not crypto_only and (tradingview := await tradingview_supported(symbol)): # ["SSE:000001", "SZSE:000001"]
category["tradingview"] = ", ".join(tradingview)
# skip some crypto ETF (e.g. Grayscale Bitcoin Mini Trust use symbol "AMEX:BTC")
if category.get("crypto") and category.get("tradingview"):
@@ -156,7 +170,7 @@ async def match_symbol_category(symbol: str = "") -> dict[str, str]:
# if tradingview has multiles symbols
if len(tradingview) > 1:
msg = f"⚠️**{symbol.upper()}**代码重复:\n"
- msg += f"股票&指数: **{category['tradingview']}**\n"
+ msg += f"股票: **{category['tradingview']}**\n"
msg += f"本次查询: **{tradingview[0]}**\n"
msg += f"查询其他请使用完整代码:\n`{PREFIX.PRICE} {'/ '.join(tradingview[1:])}`"
category["warnings"] = msg
@@ -164,10 +178,10 @@ async def match_symbol_category(symbol: str = "") -> dict[str, str]:
# if symbol matches tradingview & crypto categories
if category.get("crypto") and category.get("tradingview"):
msg = f"⚠️**{symbol.upper()}**代码重复:\n"
- msg += f"股票&指数: **{category['tradingview']}**\n"
+ msg += f"股票: **{category['tradingview']}**\n"
msg += f"加密货币: **{category['crypto']}**\n"
- msg += f"默认查询股票&指数: **{tradingview[0]}**\n"
- msg += f"查询其他请使用完整代码:\n`{PREFIX.PRICE} {category['crypto']}`"
+ msg += f"默认查询股票: **{tradingview[0]}**\n"
+ msg += f"查询其他`{PREFIX.CRYPTO}` 或 `{PREFIX.STOCK}`限定市场"
category["warnings"] = msg
return category
src/config.py
@@ -59,7 +59,9 @@ class PREFIX:
SUBTITLE = os.getenv("PREFIX_SUBTITLE", "/subtitle").lower()
WGET = os.getenv("PREFIX_WGET", "/wget").lower()
OCR = os.getenv("PREFIX_OCR", "/ocr").lower()
- PRICE = os.getenv("PREFIX_PRICE", "/price").lower()
+ PRICE = os.getenv("PREFIX_PRICE", "/price").lower() # unify crypto, stock
+ CRYPTO = os.getenv("PREFIX_CRYPTO", "/crypto").lower() # crypto only
+ STOCK = os.getenv("PREFIX_STOCK", "/stock").lower() # stock only
COMBINATION = os.getenv("PREFIX_COMBINATION", "/combine").lower()