286 lines
10 KiB
Python
286 lines
10 KiB
Python
import pandas as pd
|
||
import numpy as np
|
||
import json
|
||
import configparser
|
||
from datetime import datetime, timedelta
|
||
|
||
conf = configparser.ConfigParser()
|
||
# 读取配置文件
|
||
conf.read('conf.ini', encoding='utf-8')
|
||
|
||
class LotteryPredictorV14Profit:
|
||
"""
|
||
V28 "Iron-Shield" (铁盾终极版)
|
||
核心逻辑: V27 动态流水增强 (3-8注) + 绝佳 30% (6000元) 日内止损熔断。
|
||
目标: 100元/注重仓下,最高收益/回撤比。
|
||
"""
|
||
|
||
# --- 核心参数 ---
|
||
ODDS = float(conf["init"]["ODDS"])
|
||
REBATE_RATE = float(conf["dynamic"]["REBATE_RATE"])
|
||
BASE_BET_UNIT = int(conf["init"]["BASE_BET_UNIT"])
|
||
_bet_this_round = BASE_BET_UNIT
|
||
SAFE_BET_LEVEL_1 = 0.05
|
||
SAFE_BET_LEVEL_2 = 0.10
|
||
SAFE_BET_LEVEL_3 = 0.15
|
||
SAFE_BET_LEVEL_4 = 0.20
|
||
SAFE_BET_LEVEL_5 = 0.25
|
||
RADICAL_BET_LEVEL_0 = 0.05
|
||
RADICAL_BET_LEVEL_1 = 0.1
|
||
RADICAL_BET_LEVEL_2 = 0.2
|
||
INITIAL_CAPITAL = BASE_BET_UNIT * 200
|
||
DAILY_STOP_LOSS_RATE = float(conf["init"]["DAILY_STOP_LOSS_RATE"]) # 绝佳熔断点:6000元熔断
|
||
POSITION_LOOKBACK_PERIODS = int(conf["dynamic"]["POSITION_LOOKBACK_PERIODS"])
|
||
OMISSIONS_LEVEL_1 = int(conf["dynamic"]["OMISSIONS_LEVEL_1"])
|
||
OMISSIONS_LEVEL_2 = int(conf["dynamic"]["OMISSIONS_LEVEL_2"])
|
||
OMISSIONS_LEVEL_3 = int(conf["dynamic"]["OMISSIONS_LEVEL_3"])
|
||
MOMENTUM_LEVEL_1 = int(conf["dynamic"]["MOMENTUM_LEVEL_1"])
|
||
MOMENTUM_LEVEL_2 = int(conf["dynamic"]["MOMENTUM_LEVEL_2"])
|
||
MOMENTUM_LEVEL_3 = int(conf["dynamic"]["MOMENTUM_LEVEL_3"])
|
||
RECENT_OMISSIONS = int(conf["dynamic"]["RECENT_OMISSIONS"])
|
||
RECENT_MOMENTUM = int(conf["dynamic"]["RECENT_MOMENTUM"])
|
||
# V27 分级投注参数
|
||
# S级: 遗漏>=55, 动能>=45 -> 8注
|
||
# A级: 遗漏>=50, 动能>=40 -> 6注
|
||
# B级: 遗漏>=45, 动能>=35 -> 4注
|
||
|
||
def __init__(self):
|
||
# 用于追踪当日盈亏,确保日内止损
|
||
# {'pnl': 0, 'bets': 0, 'miss_count': 0}
|
||
self.daily_pnl_tracker = {}
|
||
|
||
def _calculate_omission_and_momentum(self, history_data):
|
||
"""计算遗漏和动能"""
|
||
results = [record for record in history_data["result"]]
|
||
results_array = np.array(results)
|
||
|
||
if len(results_array) < 500:
|
||
return None, None
|
||
|
||
recent_400 = results_array[-400:]
|
||
recent_500 = results_array[-500:]
|
||
|
||
omissions = np.full((10, 10), 400)
|
||
|
||
# 计算遗漏
|
||
for pos in range(10):
|
||
for num in range(1, 11):
|
||
found = np.where(recent_400[:, pos] == num)[0]
|
||
if len(found) > 0:
|
||
omissions[pos, num - 1] = len(recent_400) - 1 - found[-1]
|
||
else:
|
||
omissions[pos, num - 1] = 400
|
||
|
||
# 计算动能 (最近 500 期出现次数)
|
||
momentum = np.zeros((10, 10))
|
||
for pos in range(10):
|
||
for num in range(1, 11):
|
||
momentum[pos, num - 1] = np.sum(recent_500[:, pos] == num)
|
||
|
||
return omissions, momentum
|
||
|
||
def _get_dynamic_bet_nums(self, pos, omissions, momentum):
|
||
"""根据信号强度动态决定投注注数和号码"""
|
||
|
||
scores = []
|
||
for n in range(1, 11):
|
||
o = omissions[pos, n - 1]
|
||
m = momentum[pos, n - 1]
|
||
|
||
if o >= self.OMISSIONS_LEVEL_1 and m >= self.MOMENTUM_LEVEL_1:
|
||
level = 3 # S
|
||
elif o >= self.OMISSIONS_LEVEL_2 and m >= self.MOMENTUM_LEVEL_2:
|
||
level = 2 # A
|
||
elif o >= self.OMISSIONS_LEVEL_3 and m >= self.MOMENTUM_LEVEL_3:
|
||
level = 1 # B
|
||
else:
|
||
level = 0
|
||
|
||
if level > 0:
|
||
scores.append({'num': n, 'level': level, 'o': o, 'm': m})
|
||
|
||
if not scores:
|
||
return 0, []
|
||
|
||
max_level = max(s['level'] for s in scores)
|
||
if max_level == 3:
|
||
max_bets = 8
|
||
elif max_level == 2:
|
||
max_bets = 6
|
||
else:
|
||
max_bets = 4
|
||
|
||
scores.sort(key=lambda x: (x['level'], x['o'], x['m']), reverse=True)
|
||
top_nums = [s['num'] for s in scores[:max_bets]]
|
||
|
||
return len(top_nums), top_nums
|
||
|
||
def _check_position_elite(self, pos, history_data):
|
||
"""检查位置是否为精英位置 (在 predict 中简化为 True)"""
|
||
return True
|
||
|
||
def check_init_daily_pnl_tracker(self, current_date):
|
||
|
||
_date_range = None
|
||
for date_range in self.daily_pnl_tracker:
|
||
start_time, end_time = date_range
|
||
if start_time <= current_date <= end_time:
|
||
_date_range = (start_time, end_time)
|
||
break
|
||
|
||
if not _date_range:
|
||
|
||
if current_date.hour <= 6:
|
||
current_date_start_time = pd.to_datetime(current_date.date()) + timedelta(days=-1, hours=7, minutes=5)
|
||
current_date_end_time = current_date_start_time + timedelta(hours=22, minutes=55)
|
||
else:
|
||
current_date_start_time = pd.to_datetime(current_date.date()) + timedelta(hours=7, minutes=5)
|
||
current_date_end_time = current_date_start_time + timedelta(hours=22, minutes=55)
|
||
|
||
_date_range = (current_date_start_time, current_date_end_time)
|
||
|
||
self.daily_pnl_tracker[_date_range] = {'pnl': 0, 'bets': 0, 'miss_count': 0}
|
||
|
||
return _date_range
|
||
|
||
def _check_daily_stop_loss(self, current_date):
|
||
"""检查是否触发日内止损"""
|
||
|
||
stats = self.daily_pnl_tracker[self.check_init_daily_pnl_tracker(current_date)]
|
||
|
||
if not stats:
|
||
raise Exception(f"{current_date} not in {list(self.daily_pnl_tracker.keys())}")
|
||
|
||
# 先不算水钱
|
||
net_pnl = stats['pnl']
|
||
# net_pnl = stats['pnl'] + stats['bets'] * self.REBATE_RATE
|
||
|
||
if stats['miss_count'] >= 2:
|
||
# 连续 miss 取 小值
|
||
# print(f"行情不好, 少投点: {stats['miss_count']}")
|
||
self._bet_this_round = self._bet_this_round if self._bet_this_round < (self.BASE_BET_UNIT * 0.1) else self.BASE_BET_UNIT * 0.1
|
||
|
||
if net_pnl < -(self.INITIAL_CAPITAL * self.DAILY_STOP_LOSS_RATE):
|
||
# print("止损 =========")
|
||
return True
|
||
return False
|
||
|
||
def update_result(self, period_record, bets_made):
|
||
"""
|
||
在实际开奖后调用,用于更新日内盈亏追踪器
|
||
:param period_record: 包含 'result' 和 'time' 的记录
|
||
:param bets_made: 实际投注建议 {position: [num1, num2, ...]}
|
||
"""
|
||
if not bets_made:
|
||
return
|
||
|
||
current_date = period_record["time"].iloc[0]
|
||
# current_date = period_record["time"].dt.strftime("%Y-%m-%d").iloc[0]
|
||
# current_date = datetime.strptime(period_record['time'], '%Y-%m-%d %H:%M:%S').date()
|
||
|
||
pnl_this_period = 0
|
||
bets_this_period = 0
|
||
|
||
for pos, nums in bets_made["result"].items():
|
||
predict_nums = []
|
||
bets = 0
|
||
for predict_num, bet in nums.items():
|
||
if bet:
|
||
predict_nums.append(predict_num)
|
||
bets += bet
|
||
|
||
hit = list(period_record['result'])[0][pos] in predict_nums
|
||
|
||
pnl = (self._bet_this_round * self.ODDS if hit else 0) - bets
|
||
|
||
pnl_this_period += pnl
|
||
bets_this_period += bets
|
||
|
||
date_range = self.check_init_daily_pnl_tracker(current_date)
|
||
|
||
self.daily_pnl_tracker[date_range]['pnl'] += pnl_this_period
|
||
self.daily_pnl_tracker[date_range]['bets'] += bets_this_period
|
||
|
||
if pnl_this_period < bets_this_period:
|
||
self.daily_pnl_tracker[date_range]['miss_count'] += 1
|
||
else:
|
||
self.daily_pnl_tracker[date_range]['miss_count'] = 0
|
||
|
||
def _get_current_actual_pnl(self, current_date):
|
||
day_data = self.daily_pnl_tracker[self.check_init_daily_pnl_tracker(current_date)]
|
||
return day_data['pnl']
|
||
# return day_data['pnl'] + (day_data['bets'] * self.REBATE_RATE)
|
||
|
||
def _set_bet_level(self, actual_pnl):
|
||
if actual_pnl == 0:
|
||
return self.BASE_BET_UNIT * 0.1
|
||
|
||
if actual_pnl < 0:
|
||
if actual_pnl < -(self.SAFE_BET_LEVEL_4 * self.INITIAL_CAPITAL):
|
||
return self.BASE_BET_UNIT * 0.6
|
||
|
||
if actual_pnl < -(self.SAFE_BET_LEVEL_3 * self.INITIAL_CAPITAL):
|
||
return self.BASE_BET_UNIT * 0.4
|
||
|
||
if actual_pnl < -(self.SAFE_BET_LEVEL_2 * self.INITIAL_CAPITAL):
|
||
return self.BASE_BET_UNIT * 0.3
|
||
|
||
if actual_pnl < -(self.SAFE_BET_LEVEL_1 * self.INITIAL_CAPITAL):
|
||
return self.BASE_BET_UNIT * 0.2
|
||
|
||
return self.BASE_BET_UNIT
|
||
|
||
else:
|
||
if actual_pnl > self.RADICAL_BET_LEVEL_2 * self.INITIAL_CAPITAL:
|
||
return self.BASE_BET_UNIT * 1.2
|
||
|
||
if actual_pnl > self.RADICAL_BET_LEVEL_1 * self.INITIAL_CAPITAL:
|
||
return self.BASE_BET_UNIT * 1.5
|
||
|
||
if actual_pnl > self.RADICAL_BET_LEVEL_0 * self.INITIAL_CAPITAL:
|
||
return self.BASE_BET_UNIT * 2
|
||
|
||
return self.BASE_BET_UNIT
|
||
|
||
def predict(self, current_date, history_data):
|
||
"""
|
||
核心预测方法
|
||
:param current_period_record: 当前期的记录 (用于获取时间)
|
||
:param history_data: 历史数据列表
|
||
:return: 投注建议字典 {position: [num1, num2, ...]}
|
||
"""
|
||
|
||
# 1. 动态金额调节
|
||
actual_pnl = self._get_current_actual_pnl(current_date)
|
||
self._bet_this_round = self._set_bet_level(actual_pnl)
|
||
|
||
# 2. 日内止损检查
|
||
if self._check_daily_stop_loss(current_date):
|
||
return {}
|
||
|
||
omissions, momentum = self._calculate_omission_and_momentum(history_data)
|
||
if omissions is None:
|
||
return {}
|
||
|
||
bet_suggestions = {}
|
||
|
||
for pos in range(10):
|
||
# 2. 动态位置筛选
|
||
# elite_pos = self._get_elite_positions(history_df)
|
||
# if not elite_pos:
|
||
# continue
|
||
|
||
# 3. 动态注数投注
|
||
num_bets, top_nums = self._get_dynamic_bet_nums(pos, omissions, momentum)
|
||
|
||
if num_bets > 0:
|
||
bet_suggestions[pos] = {num: self._bet_this_round for num in top_nums}
|
||
|
||
# if self._check_daily_stop_loss(current_date):
|
||
# for pos, num_bets in bet_suggestions.items():
|
||
# bet_suggestions[pos] = {
|
||
# num: self.BASE_BET_UNIT * self.RADICAL_BET_LEVEL_0 for num, _ in num_bets.items()
|
||
# }
|
||
|
||
return bet_suggestions
|