feat: Introduce dynamic position sizing based on market conditions and enhance trading report with grouped positions and ROI.

This commit is contained in:
2569718930@qq.com
2026-02-06 20:11:01 +08:00
parent 81b3736ea1
commit df2117afe8
4 changed files with 125 additions and 20 deletions
+42 -16
View File
@@ -110,20 +110,43 @@ def start_bot():
) )
return return
msg_lines = ["📊 <b>模拟交易报告 (北京时间)</b>\n" + "" * 15] msg_lines = ["📊 <b>模拟交易报告</b>\n" + "" * 20]
# 1. 活跃持仓 # 1. 活跃持仓 - 按目标日期分组
if positions: if positions:
msg_lines.append("📌 <b>当前持仓:</b>") # 按目标日期分组
total_pnl = 0 positions_by_date = {}
for pid, pos in positions.items(): for pid, pos in positions.items():
pnl_usd = pos.get("pnl_usd", 0) target_date = pos.get("target_date") or "未知日期"
total_pnl += pnl_usd if target_date not in positions_by_date:
icon = "🟢" if pnl_usd >= 0 else "🔴" positions_by_date[target_date] = []
msg_lines.append( positions_by_date[target_date].append(pos)
f"{icon} {pos['city']} {pos['option']} ({pos['side']}): {pnl_usd:+.2f}$"
) # 按日期排序显示
msg_lines.append(f"<b>持仓小计: {total_pnl:+.2f}$</b>\n") 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 笔) # 2. 最近交易记录 (最新 5 笔)
trades = data.get("trades", []) trades = data.get("trades", [])
@@ -132,8 +155,8 @@ def start_bot():
# 取末尾 5 笔交易并展示 # 取末尾 5 笔交易并展示
recent_trades = trades[-5:] recent_trades = trades[-5:]
for t in reversed(recent_trades): for t in reversed(recent_trades):
t_type = "🛒 买入" if t["type"] == "BUY" else "💰 卖出" t_type = "🛒" if t["type"] == "BUY" else "💰"
t_time = t.get("time", "").split(" ")[1] # 仅显示时间 t_time = t.get("time", "").split(" ")[1] if " " in t.get("time", "") else t.get("time", "")
msg_lines.append( msg_lines.append(
f"{t_time} {t_type} {t['city']} {t['option']} ({t['price']}¢)" f"{t_time} {t_type} {t['city']} {t['option']} ({t['price']}¢)"
) )
@@ -142,12 +165,15 @@ def start_bot():
if history: if history:
total_trades = len(history) total_trades = len(history)
wins = sum(1 for p in history if p.get("pnl_usd", 0) > 0) 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 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("\n📈 <b>历史战绩:</b>")
msg_lines.append(f"累计成交: {total_trades} ") msg_lines.append(f"累计成交: {total_trades} | 胜率: <b>{win_rate:.1f}%</b>")
msg_lines.append(f"综合胜率: <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) msg_lines.append(footer)
bot.reply_to(message, "\n".join(msg_lines), parse_mode="HTML") bot.reply_to(message, "\n".join(msg_lines), parse_mode="HTML")
+73 -2
View File
@@ -2,6 +2,7 @@ import sys
import time import time
import os import os
import json import json
import re
from datetime import datetime, timedelta from datetime import datetime, timedelta
from loguru import logger from loguru import logger
@@ -311,6 +312,67 @@ def main():
else int(buy_no_price * 100) 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" side = "YES" if trigger_side == "Buy Yes" else "NO"
success = paper_trader.open_position( success = paper_trader.open_position(
@@ -319,15 +381,24 @@ def main():
option=question, option=question,
price=trigger_price, price=trigger_price,
side=side, 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( city_alerts.append(
{ {
"type": "price", "type": "price",
"market": f"{question} ({target_date or '今日'})", "market": f"{question} ({target_date or '今日'})",
"msg": f"{trigger_side}进入锁定区间 {trigger_price}¢", "msg": f"{trigger_side} {trigger_price}¢ | {forecast_text}",
"bought": success, "bought": success,
"amount": amount_usd,
"confidence": confidence_tag,
} }
) )
pushed_signals[alert_key] = time.time() pushed_signals[alert_key] = time.time()
+3 -1
View File
@@ -43,7 +43,7 @@ class PaperTrader:
indent=2, 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_usd": 0.0,
"pnl_pct": 0.0, "pnl_pct": 0.0,
"status": "OPEN", "status": "OPEN",
"target_date": target_date,
"predicted_temp": predicted_temp,
"opened_at": (datetime.utcnow() + timedelta(hours=8)).strftime("%Y-%m-%d %H:%M:%S") "opened_at": (datetime.utcnow() + timedelta(hours=8)).strftime("%Y-%m-%d %H:%M:%S")
} }
+7 -1
View File
@@ -132,7 +132,13 @@ class TelegramNotifier:
items_text = "" items_text = ""
for a in alerts: for a in alerts:
type_icon = "" if a["type"] == "price" else "🐋" 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" items_text += f"{type_icon} <b>{a['market']}</b>: {a['msg']}{buy_tag}\n"
text = ( text = (