Merge pull request #15 from p369349074/main
make manual positions can add same stock and symbal to difference gro…
This commit is contained in:
@@ -140,10 +140,10 @@ class BaseDataSource(ABC):
|
||||
if klines:
|
||||
latest_time = datetime.fromtimestamp(klines[-1]['time'])
|
||||
time_diff = (datetime.now() - latest_time).total_seconds()
|
||||
logger.info(
|
||||
f"{self.name}: {symbol} 获取 {len(klines)} 条数据, "
|
||||
f"最新时间: {latest_time}, 延迟: {time_diff:.0f}秒"
|
||||
)
|
||||
# logger.info(
|
||||
# f"{self.name}: {symbol} 获取 {len(klines)} 条数据, "
|
||||
# f"最新时间: {latest_time}, 延迟: {time_diff:.0f}秒"
|
||||
# )
|
||||
|
||||
# 检查数据是否过旧
|
||||
max_diff = TIMEFRAME_SECONDS.get(timeframe, 3600) * 2
|
||||
|
||||
@@ -255,7 +255,7 @@ def add_position():
|
||||
INSERT INTO qd_manual_positions
|
||||
(user_id, market, symbol, name, side, quantity, entry_price, entry_time, notes, tags, group_name, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
ON CONFLICT(user_id, market, symbol, side) DO UPDATE SET
|
||||
ON CONFLICT(user_id, market, symbol, side, group_name) DO UPDATE SET
|
||||
name = excluded.name,
|
||||
quantity = excluded.quantity,
|
||||
entry_price = excluded.entry_price,
|
||||
|
||||
@@ -91,7 +91,7 @@ def _get_positions_for_monitor(position_ids: List[int] = None, user_id: int = No
|
||||
placeholders = ','.join(['?' for _ in position_ids])
|
||||
cur.execute(
|
||||
f"""
|
||||
SELECT id, market, symbol, name, side, quantity, entry_price
|
||||
SELECT id, market, symbol, name, side, quantity, entry_price, group_name
|
||||
FROM qd_manual_positions
|
||||
WHERE user_id = ? AND id IN ({placeholders})
|
||||
""",
|
||||
@@ -100,7 +100,7 @@ def _get_positions_for_monitor(position_ids: List[int] = None, user_id: int = No
|
||||
else:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id, market, symbol, name, side, quantity, entry_price
|
||||
SELECT id, market, symbol, name, side, quantity, entry_price, group_name
|
||||
FROM qd_manual_positions
|
||||
WHERE user_id = ?
|
||||
""",
|
||||
@@ -116,6 +116,7 @@ def _get_positions_for_monitor(position_ids: List[int] = None, user_id: int = No
|
||||
entry_price = float(row.get('entry_price') or 0)
|
||||
quantity = float(row.get('quantity') or 0)
|
||||
side = row.get('side') or 'long'
|
||||
group_name = row.get('group_name')
|
||||
|
||||
# Get current price (use realtime price API)
|
||||
current_price = 0
|
||||
@@ -143,7 +144,8 @@ def _get_positions_for_monitor(position_ids: List[int] = None, user_id: int = No
|
||||
'entry_price': entry_price,
|
||||
'current_price': current_price,
|
||||
'pnl': round(pnl, 2),
|
||||
'pnl_percent': pnl_percent
|
||||
'pnl_percent': pnl_percent,
|
||||
'group_name': group_name
|
||||
})
|
||||
|
||||
return positions
|
||||
@@ -168,6 +170,7 @@ def _run_ai_analysis(positions: List[Dict[str, Any]], config: Dict[str, Any]) ->
|
||||
market = pos.get('market')
|
||||
symbol = pos.get('symbol')
|
||||
name = pos.get('name') or symbol
|
||||
group_name = pos.get('group_name')
|
||||
|
||||
if not market or not symbol:
|
||||
continue
|
||||
@@ -193,6 +196,7 @@ def _run_ai_analysis(positions: List[Dict[str, Any]], config: Dict[str, Any]) ->
|
||||
'market': market,
|
||||
'symbol': symbol,
|
||||
'name': name,
|
||||
'group_name': group_name,
|
||||
'entry_price': pos.get('entry_price'),
|
||||
'current_price': pos.get('current_price'),
|
||||
'pnl': pos.get('pnl'),
|
||||
@@ -454,6 +458,7 @@ def _build_html_report(
|
||||
symbol = pa.get('symbol', '')
|
||||
name = pa.get('name', symbol)
|
||||
market = pa.get('market', '')
|
||||
group_name = pa.get('group_name', '')
|
||||
|
||||
if pa.get('error'):
|
||||
html += f'''
|
||||
@@ -538,7 +543,7 @@ def _build_html_report(
|
||||
'''
|
||||
|
||||
# Generate unique ID for collapsible sections (use symbol hash to avoid special chars)
|
||||
section_id_base = hashlib.md5(f"{symbol}_{market}".encode()).hexdigest()[:8]
|
||||
section_id_base = hashlib.md5(f"{symbol}_{market}_{group_name}".encode()).hexdigest()[:8]
|
||||
|
||||
# Collapsible: Trader Analysis
|
||||
if trader_reasoning:
|
||||
|
||||
@@ -24,6 +24,7 @@ import json
|
||||
import os
|
||||
import smtplib
|
||||
import time
|
||||
import traceback
|
||||
from datetime import datetime, timezone
|
||||
from email.message import EmailMessage
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
@@ -482,6 +483,7 @@ class SignalNotifier:
|
||||
return True, ""
|
||||
except Exception as e:
|
||||
logger.warning(f"browser notify persist failed: {e}")
|
||||
logger.error('browser.error', traceback=traceback.format_exc())
|
||||
return False, str(e)
|
||||
|
||||
def _notify_webhook(
|
||||
@@ -571,6 +573,7 @@ class SignalNotifier:
|
||||
return False, f"http_{resp2.status_code}:{(resp2.text or '')[:300]}"
|
||||
return False, f"http_{resp.status_code}:{(resp.text or '')[:300]}"
|
||||
except Exception as e:
|
||||
logger.error('webhook.error', traceback=traceback.format_exc())
|
||||
return False, str(e)
|
||||
|
||||
def _notify_discord(self, *, url: str, payload: Dict[str, Any], fallback_text: str) -> Tuple[bool, str]:
|
||||
@@ -646,6 +649,7 @@ class SignalNotifier:
|
||||
pass
|
||||
return False, f"http_{resp.status_code}:{(resp.text or '')[:300]}"
|
||||
except Exception as e:
|
||||
logger.error('discord.error', traceback=traceback.format_exc())
|
||||
return False, str(e)
|
||||
|
||||
def _notify_telegram(
|
||||
@@ -680,6 +684,7 @@ class SignalNotifier:
|
||||
return True, ""
|
||||
return False, f"http_{resp.status_code}:{(resp.text or '')[:300]}"
|
||||
except Exception as e:
|
||||
logger.error('telegram.error', traceback=traceback.format_exc())
|
||||
return False, str(e)
|
||||
|
||||
def _notify_email(self, *, to_email: str, subject: str, body_text: str, body_html: str = "") -> Tuple[bool, str]:
|
||||
@@ -718,6 +723,7 @@ class SignalNotifier:
|
||||
server.send_message(msg)
|
||||
return True, ""
|
||||
except Exception as e:
|
||||
logger.error('email.error', traceback=traceback.format_exc())
|
||||
return False, str(e)
|
||||
|
||||
def _notify_phone(self, *, to_phone: str, body: str) -> Tuple[bool, str]:
|
||||
@@ -734,6 +740,7 @@ class SignalNotifier:
|
||||
return True, ""
|
||||
return False, f"http_{resp.status_code}:{(resp.text or '')[:300]}"
|
||||
except Exception as e:
|
||||
logger.error('phone.error', traceback=traceback.format_exc())
|
||||
return False, str(e)
|
||||
|
||||
|
||||
|
||||
@@ -439,7 +439,7 @@ CREATE TABLE IF NOT EXISTS qd_manual_positions (
|
||||
group_name VARCHAR(100) DEFAULT '',
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
UNIQUE(user_id, market, symbol, side)
|
||||
UNIQUE(user_id, market, symbol, side, group_name)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_positions_user_id ON qd_manual_positions(user_id);
|
||||
|
||||
Reference in New Issue
Block a user