182 lines
7.3 KiB
Python
182 lines
7.3 KiB
Python
import json
|
|
import os
|
|
import sqlite3
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
|
if PROJECT_DIR not in sys.path:
|
|
sys.path.insert(0, PROJECT_DIR)
|
|
|
|
import altcoin_db
|
|
|
|
|
|
class RecommendationHistoryBase(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmpdir = tempfile.TemporaryDirectory()
|
|
self.db_path = os.path.join(self.tmpdir.name, 'test_altcoin.db')
|
|
self.db_patch = patch.object(altcoin_db, 'DB_PATH', self.db_path)
|
|
self.db_patch.start()
|
|
altcoin_db.init_db()
|
|
|
|
def tearDown(self):
|
|
self.db_patch.stop()
|
|
self.tmpdir.cleanup()
|
|
|
|
def _insert_rec(self, **kwargs):
|
|
defaults = dict(
|
|
symbol='AAA/USDT',
|
|
rec_time='2026-04-29T10:00:00',
|
|
rec_state='加速',
|
|
rec_score=8,
|
|
entry_price=100.0,
|
|
stop_loss=95.0,
|
|
tp1=110.0,
|
|
tp2=118.0,
|
|
sector='AI',
|
|
signals='[]',
|
|
is_meme=0,
|
|
status='active',
|
|
current_price=100.0,
|
|
max_price=104.0,
|
|
min_price=98.0,
|
|
pnl_pct=0.0,
|
|
max_pnl_pct=4.0,
|
|
max_drawdown_pct=-1.0,
|
|
hit_tp1_time='',
|
|
hit_tp2_time='',
|
|
stopped_out_time='',
|
|
expired_time='',
|
|
last_track_time='2026-04-29T10:05:00',
|
|
entry_plan_json=json.dumps({
|
|
'entry_price': 100.0,
|
|
'entry_action': '可即刻买入',
|
|
'risk_reward_ok': True,
|
|
'stop_loss': 95.0,
|
|
'stop_pct': -5.0,
|
|
'tp1': 110.0,
|
|
'tp2': 118.0,
|
|
'rr1': 2.0,
|
|
'rr2': 3.6,
|
|
}, ensure_ascii=False),
|
|
action_status='可即刻买入',
|
|
direction='多头启动',
|
|
)
|
|
defaults.update(kwargs)
|
|
conn = sqlite3.connect(self.db_path)
|
|
conn.execute(
|
|
'''
|
|
INSERT INTO recommendation (
|
|
symbol, rec_time, rec_state, rec_score, entry_price, stop_loss, tp1, tp2,
|
|
sector, signals, is_meme, status, current_price, max_price, min_price,
|
|
pnl_pct, max_pnl_pct, max_drawdown_pct, hit_tp1_time, hit_tp2_time,
|
|
stopped_out_time, expired_time, last_track_time, entry_plan_json,
|
|
action_status, direction
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
''',
|
|
(
|
|
defaults['symbol'], defaults['rec_time'], defaults['rec_state'], defaults['rec_score'],
|
|
defaults['entry_price'], defaults['stop_loss'], defaults['tp1'], defaults['tp2'],
|
|
defaults['sector'], defaults['signals'], defaults['is_meme'], defaults['status'],
|
|
defaults['current_price'], defaults['max_price'], defaults['min_price'], defaults['pnl_pct'],
|
|
defaults['max_pnl_pct'], defaults['max_drawdown_pct'], defaults['hit_tp1_time'], defaults['hit_tp2_time'],
|
|
defaults['stopped_out_time'], defaults['expired_time'], defaults['last_track_time'], defaults['entry_plan_json'],
|
|
defaults['action_status'], defaults['direction'],
|
|
),
|
|
)
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
|
|
class RecommendationHistoryGroupingTests(RecommendationHistoryBase):
|
|
def test_get_all_recommendations_exposes_each_history_group(self):
|
|
self._insert_rec(symbol='AAA/USDT', action_status='可即刻买入', status='active')
|
|
self._insert_rec(
|
|
symbol='BBB/USDT',
|
|
action_status='等回踩',
|
|
status='active',
|
|
entry_plan_json=json.dumps({'entry_action': '等回踩', 'entry_price': 100.0}, ensure_ascii=False),
|
|
)
|
|
self._insert_rec(
|
|
symbol='CCC/USDT',
|
|
action_status='持有',
|
|
status='active',
|
|
entry_plan_json=json.dumps({'entry_action': '继续观察', 'entry_price': 100.0}, ensure_ascii=False),
|
|
)
|
|
self._insert_rec(symbol='DDD/USDT', action_status='衰减', status='active')
|
|
self._insert_rec(symbol='EEE/USDT', action_status='止盈1', status='hit_tp1')
|
|
|
|
rows = altcoin_db.get_all_recommendations(limit=20)
|
|
mapping = {row['symbol']: row['execution_status'] for row in rows}
|
|
|
|
self.assertEqual(mapping['AAA/USDT'], 'buy_now')
|
|
self.assertEqual(mapping['BBB/USDT'], 'wait_pullback')
|
|
self.assertEqual(mapping['CCC/USDT'], 'observe')
|
|
self.assertEqual(mapping['DDD/USDT'], 'invalid')
|
|
self.assertEqual(mapping['EEE/USDT'], 'completed')
|
|
|
|
def test_get_all_recommendations_can_drive_history_summary_counts(self):
|
|
self._insert_rec(symbol='AAA/USDT', action_status='可即刻买入', status='active')
|
|
self._insert_rec(symbol='BBB/USDT', action_status='可即刻买入', status='active')
|
|
self._insert_rec(
|
|
symbol='CCC/USDT',
|
|
action_status='等回踩',
|
|
status='active',
|
|
entry_plan_json=json.dumps({'entry_action': '等回踩', 'entry_price': 100.0}, ensure_ascii=False),
|
|
)
|
|
self._insert_rec(
|
|
symbol='DDD/USDT',
|
|
action_status='持有',
|
|
status='active',
|
|
entry_plan_json=json.dumps({'entry_action': '继续观察', 'entry_price': 100.0}, ensure_ascii=False),
|
|
)
|
|
self._insert_rec(symbol='EEE/USDT', action_status='衰减', status='active')
|
|
self._insert_rec(symbol='FFF/USDT', action_status='止盈2', status='hit_tp2')
|
|
|
|
rows = altcoin_db.get_all_recommendations(limit=20)
|
|
counts = {}
|
|
for row in rows:
|
|
key = row['execution_status']
|
|
counts[key] = counts.get(key, 0) + 1
|
|
|
|
self.assertEqual(counts, {
|
|
'buy_now': 2,
|
|
'wait_pullback': 1,
|
|
'observe': 1,
|
|
'invalid': 1,
|
|
'completed': 1,
|
|
})
|
|
|
|
|
|
class DecisionModeHistoryTests(RecommendationHistoryBase):
|
|
def test_get_all_recommendations_supports_decision_only_history_mode(self):
|
|
self._insert_rec(symbol='BUY/USDT', action_status='可即刻买入', status='active')
|
|
self._insert_rec(
|
|
symbol='WAIT/USDT',
|
|
action_status='等回踩',
|
|
status='active',
|
|
entry_plan_json=json.dumps({'entry_action': '等回踩', 'entry_price': 100.0}, ensure_ascii=False),
|
|
)
|
|
self._insert_rec(
|
|
symbol='OBS/USDT',
|
|
action_status='持有',
|
|
status='active',
|
|
entry_plan_json=json.dumps({'entry_action': '继续观察', 'entry_price': 100.0}, ensure_ascii=False),
|
|
)
|
|
self._insert_rec(symbol='INV/USDT', action_status='衰减', status='active')
|
|
self._insert_rec(symbol='TP/USDT', action_status='止盈1', status='hit_tp1')
|
|
self._insert_rec(symbol='STOP/USDT', action_status='止损', status='stopped_out')
|
|
|
|
rows = altcoin_db.get_all_recommendations(limit=20, decision_only=True)
|
|
mapping = {row['symbol']: row['execution_status'] for row in rows}
|
|
|
|
self.assertEqual(set(mapping.keys()), {'TP/USDT', 'STOP/USDT'})
|
|
self.assertEqual(mapping['TP/USDT'], 'completed')
|
|
self.assertEqual(mapping['STOP/USDT'], 'invalid')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|