Files
bocai/PyModel/predict_model_v14_profit_maximizer.py
2026-01-28 09:44:21 +08:00

279 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = int(conf["init"]["BET_THIS_ROUND"])
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 = int(conf["init"]["INITIAL_CAPITAL"])
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)
if not _date_range:
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