feat: Introduce dynamic position sizing based on market conditions and enhance trading report with grouped positions and ROI.
This commit is contained in:
+42
-16
@@ -110,20 +110,43 @@ def start_bot():
|
||||
)
|
||||
return
|
||||
|
||||
msg_lines = ["📊 <b>模拟交易报告 (北京时间)</b>\n" + "═" * 15]
|
||||
msg_lines = ["📊 <b>模拟交易报告</b>\n" + "═" * 20]
|
||||
|
||||
# 1. 活跃持仓
|
||||
# 1. 活跃持仓 - 按目标日期分组
|
||||
if positions:
|
||||
msg_lines.append("📌 <b>当前持仓:</b>")
|
||||
total_pnl = 0
|
||||
# 按目标日期分组
|
||||
positions_by_date = {}
|
||||
for pid, pos in positions.items():
|
||||
pnl_usd = pos.get("pnl_usd", 0)
|
||||
total_pnl += pnl_usd
|
||||
icon = "🟢" if pnl_usd >= 0 else "🔴"
|
||||
msg_lines.append(
|
||||
f"{icon} {pos['city']} {pos['option']} ({pos['side']}): {pnl_usd:+.2f}$"
|
||||
)
|
||||
msg_lines.append(f"<b>持仓小计: {total_pnl:+.2f}$</b>\n")
|
||||
target_date = pos.get("target_date") or "未知日期"
|
||||
if target_date not in positions_by_date:
|
||||
positions_by_date[target_date] = []
|
||||
positions_by_date[target_date].append(pos)
|
||||
|
||||
# 按日期排序显示
|
||||
for target_date in sorted(positions_by_date.keys()):
|
||||
date_positions = positions_by_date[target_date]
|
||||
date_pnl = sum(p.get("pnl_usd", 0) for p in date_positions)
|
||||
date_icon = "📈" if date_pnl >= 0 else "📉"
|
||||
|
||||
msg_lines.append(f"\n{date_icon} <b>【{target_date}】</b> 小计: {date_pnl:+.2f}$")
|
||||
msg_lines.append("─" * 18)
|
||||
|
||||
for pos in date_positions:
|
||||
pnl_usd = pos.get("pnl_usd", 0)
|
||||
icon = "🟢" if pnl_usd >= 0 else "🔴"
|
||||
entry_price = pos.get("entry_price", 0)
|
||||
current_price = pos.get("current_price", entry_price)
|
||||
predicted_temp = pos.get("predicted_temp")
|
||||
|
||||
# 格式:城市 选项 | 方向 入场→当前 | 预测温度 | 盈亏
|
||||
pred_text = f"预测:{predicted_temp}" if predicted_temp else ""
|
||||
msg_lines.append(
|
||||
f"{icon} {pos['city']} {pos['option']}\n"
|
||||
f" {pos['side']} {entry_price}¢→{current_price}¢ {pred_text} | {pnl_usd:+.2f}$"
|
||||
)
|
||||
|
||||
total_pnl = sum(p.get("pnl_usd", 0) for p in positions.values())
|
||||
msg_lines.append(f"\n💰 <b>持仓总计: {total_pnl:+.2f}$</b>")
|
||||
|
||||
# 2. 最近交易记录 (最新 5 笔)
|
||||
trades = data.get("trades", [])
|
||||
@@ -132,8 +155,8 @@ def start_bot():
|
||||
# 取末尾 5 笔交易并展示
|
||||
recent_trades = trades[-5:]
|
||||
for t in reversed(recent_trades):
|
||||
t_type = "🛒 买入" if t["type"] == "BUY" else "💰 卖出"
|
||||
t_time = t.get("time", "").split(" ")[1] # 仅显示时间
|
||||
t_type = "🛒" if t["type"] == "BUY" else "💰"
|
||||
t_time = t.get("time", "").split(" ")[1] if " " in t.get("time", "") else t.get("time", "")
|
||||
msg_lines.append(
|
||||
f"• {t_time} {t_type} {t['city']} {t['option']} ({t['price']}¢)"
|
||||
)
|
||||
@@ -142,12 +165,15 @@ def start_bot():
|
||||
if history:
|
||||
total_trades = len(history)
|
||||
wins = sum(1 for p in history if p.get("pnl_usd", 0) > 0)
|
||||
total_cost = sum(p.get("cost_usd", 0) for p in history)
|
||||
total_profit = sum(p.get("pnl_usd", 0) for p in history)
|
||||
win_rate = (wins / total_trades) * 100 if total_trades > 0 else 0
|
||||
roi = (total_profit / total_cost * 100) if total_cost > 0 else 0
|
||||
msg_lines.append("\n📈 <b>历史战绩:</b>")
|
||||
msg_lines.append(f"累计成交: {total_trades} 笔")
|
||||
msg_lines.append(f"综合胜率: <b>{win_rate:.1f}%</b>")
|
||||
msg_lines.append(f"累计成交: {total_trades}笔 | 胜率: <b>{win_rate:.1f}%</b>")
|
||||
msg_lines.append(f"已投入: ${total_cost:.2f} | 盈亏: <b>{total_profit:+.2f}$</b> ({roi:+.1f}%)")
|
||||
|
||||
footer = "\n" + "═" * 15 + "\n" + f"💳 虚拟账户余额: <b>${balance:.2f}</b>"
|
||||
footer = "\n" + "═" * 20 + "\n" + f"💳 账户余额: <b>${balance:.2f}</b>"
|
||||
msg_lines.append(footer)
|
||||
|
||||
bot.reply_to(message, "\n".join(msg_lines), parse_mode="HTML")
|
||||
|
||||
@@ -2,6 +2,7 @@ import sys
|
||||
import time
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from loguru import logger
|
||||
|
||||
@@ -311,6 +312,67 @@ def main():
|
||||
else int(buy_no_price * 100)
|
||||
)
|
||||
|
||||
# --- 智能动态仓位计算 ---
|
||||
# 1. 获取 Open-Meteo 对目标日期的最高温预测
|
||||
predicted_high = None
|
||||
weather_supports = False
|
||||
daily_data = weather_data.get("open-meteo", {}).get("daily", {})
|
||||
if daily_data and target_date:
|
||||
dates = daily_data.get("time", [])
|
||||
max_temps = daily_data.get("temperature_2m_max", [])
|
||||
for idx, d_str in enumerate(dates):
|
||||
if target_date == d_str and idx < len(max_temps):
|
||||
predicted_high = max_temps[idx]
|
||||
break
|
||||
|
||||
# 2. 判断天气预测是否支持当前方向
|
||||
if predicted_high is not None:
|
||||
# 解析选项的温度范围 (例如 "40-41°F" 或 "32°F or below")
|
||||
temp_match = re.search(r'(\d+)(?:-(\d+))?°[FC]', question)
|
||||
if temp_match:
|
||||
low_bound = int(temp_match.group(1))
|
||||
high_bound = int(temp_match.group(2)) if temp_match.group(2) else low_bound
|
||||
|
||||
# 如果买 NO,天气预测应该在这个区间之外
|
||||
if trigger_side == "Buy No":
|
||||
weather_supports = (predicted_high < low_bound - 2) or (predicted_high > high_bound + 2)
|
||||
else: # 买 YES
|
||||
weather_supports = (low_bound - 2 <= predicted_high <= high_bound + 2)
|
||||
|
||||
# 3. 获取成交量信息
|
||||
market_volume = market.get("volume", 0)
|
||||
if isinstance(market_volume, str):
|
||||
try:
|
||||
market_volume = float(market_volume.replace("$", "").replace(",", ""))
|
||||
except:
|
||||
market_volume = 0
|
||||
high_volume = market_volume >= 5000 # $5000+ 算高成交量
|
||||
|
||||
# 4. 动态仓位决策
|
||||
# 条件: 价格锁定程度 + 天气支持 + 成交量
|
||||
if trigger_price >= 90 and weather_supports and high_volume:
|
||||
# 三重确认:重注
|
||||
amount_usd = 10.0
|
||||
confidence_tag = "🔥高置信"
|
||||
elif trigger_price >= 90 and weather_supports:
|
||||
# 双重确认:中等仓位
|
||||
amount_usd = 7.0
|
||||
confidence_tag = "⭐中置信"
|
||||
elif trigger_price >= 92:
|
||||
# 价格接近锁定,即使其他条件不满足也小额参与
|
||||
amount_usd = 5.0
|
||||
confidence_tag = "📌价格锁定"
|
||||
else:
|
||||
# 普通信号:最小仓位
|
||||
amount_usd = 3.0
|
||||
confidence_tag = "💡试探"
|
||||
|
||||
logger.info(
|
||||
f"【仓位决策】{city} {question} | "
|
||||
f"价格:{trigger_price}¢ | 预测:{predicted_high} | 天气支持:{weather_supports} | "
|
||||
f"高量:{high_volume} | 仓位:${amount_usd} ({confidence_tag})"
|
||||
)
|
||||
|
||||
# --- 模拟交易触发逻辑 ---
|
||||
side = "YES" if trigger_side == "Buy Yes" else "NO"
|
||||
success = paper_trader.open_position(
|
||||
@@ -319,15 +381,24 @@ def main():
|
||||
option=question,
|
||||
price=trigger_price,
|
||||
side=side,
|
||||
amount_usd=5.0,
|
||||
amount_usd=amount_usd,
|
||||
target_date=target_date,
|
||||
predicted_temp=predicted_high,
|
||||
)
|
||||
|
||||
# 构建预测温度显示文本
|
||||
temp_unit = weather_data.get("open-meteo", {}).get("unit", "celsius")
|
||||
temp_symbol = "°F" if temp_unit == "fahrenheit" else "°C"
|
||||
forecast_text = f"预测:{predicted_high}{temp_symbol}" if predicted_high else "预测:N/A"
|
||||
|
||||
city_alerts.append(
|
||||
{
|
||||
"type": "price",
|
||||
"market": f"{question} ({target_date or '今日'})",
|
||||
"msg": f"{trigger_side}进入锁定区间 {trigger_price}¢",
|
||||
"msg": f"{trigger_side} {trigger_price}¢ | {forecast_text}",
|
||||
"bought": success,
|
||||
"amount": amount_usd,
|
||||
"confidence": confidence_tag,
|
||||
}
|
||||
)
|
||||
pushed_signals[alert_key] = time.time()
|
||||
|
||||
@@ -43,7 +43,7 @@ class PaperTrader:
|
||||
indent=2,
|
||||
)
|
||||
|
||||
def open_position(self, market_id: str, city: str, option: str, price: int, side: str, amount_usd: float = 5.0):
|
||||
def open_position(self, market_id: str, city: str, option: str, price: int, side: str, amount_usd: float = 5.0, target_date: str = None, predicted_temp: float = None):
|
||||
"""
|
||||
开仓进入模拟仓位
|
||||
"""
|
||||
@@ -76,6 +76,8 @@ class PaperTrader:
|
||||
"pnl_usd": 0.0,
|
||||
"pnl_pct": 0.0,
|
||||
"status": "OPEN",
|
||||
"target_date": target_date,
|
||||
"predicted_temp": predicted_temp,
|
||||
"opened_at": (datetime.utcnow() + timedelta(hours=8)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,13 @@ class TelegramNotifier:
|
||||
items_text = ""
|
||||
for a in alerts:
|
||||
type_icon = "⚡" if a["type"] == "price" else "🐋"
|
||||
buy_tag = " [🛒 模拟仓已买入]" if a.get("bought") else ""
|
||||
# 买入标签:显示金额
|
||||
if a.get("bought"):
|
||||
amount = a.get("amount", 5.0)
|
||||
confidence = a.get("confidence", "")
|
||||
buy_tag = f" [🛒 ${amount} {confidence}]"
|
||||
else:
|
||||
buy_tag = ""
|
||||
items_text += f"{type_icon} <b>{a['market']}</b>: {a['msg']}{buy_tag}\n"
|
||||
|
||||
text = (
|
||||
|
||||
Reference in New Issue
Block a user