Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions _doc/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@

- LimitSeller 逻辑优化

## [ In Progress ]
## [ 4.3.0 ] 2026-01-29

### 添加
- 临跌停卖点模块,用以预防跌停被锁死的极端风险
- 竞价结束callback接口

### 修改
- 系统日志优化
- 部分文案优化
- 负成本清算显示变动为0
- Subscriber代码拆分重构

### 删除
- 旧版schedule移除
- 旧版scheduler移除
- 第三方库日志内容精简

## [ 4.2.0 ] 2025-11-18

Expand Down
10 changes: 6 additions & 4 deletions delegate/base_subscriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,11 @@ def check_before_finished(self):
# 盘后报告总结
# -----------------------
def daily_summary(self):
if not check_is_open_day(datetime.datetime.now().strftime('%Y-%m-%d')):
now = datetime.datetime.now()
if not check_is_open_day(now.strftime('%Y-%m-%d')):
return

curr_date = datetime.datetime.now().strftime('%Y-%m-%d')
curr_date = now.strftime('%Y-%m-%d')

if self.open_today_deal_report:
try:
Expand All @@ -229,7 +230,7 @@ def daily_summary(self):
try:
if self.delegate is not None:
asset = self.delegate.check_asset()
self.daily_reporter.check_asset(today=curr_date, asset=asset)
self.daily_reporter.check_asset(today=curr_date, asset=asset, is_afternoon=(now.hour > 12))
except Exception as e:
print('Report asset failed: ', e)
traceback.print_exc()
Expand Down Expand Up @@ -337,7 +338,8 @@ def prev_check_open_day(self):
curr_time = now.strftime('%H:%M')
print(f'[{curr_time}]', end='')
is_open_day = check_is_open_day(curr_date)
self.delegate.is_open_day = is_open_day
if self.delegate is not None:
self.delegate.is_open_day = is_open_day


class HistorySubscriber(BaseSubscriber):
Expand Down
31 changes: 17 additions & 14 deletions delegate/daily_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from tools.constants import MSG_INNER_SEPARATOR, MSG_OUTER_SEPARATOR
from tools.utils_basic import code_to_symbol
from tools.utils_cache import StockNames
from tools.utils_cache import StockNames, get_prev_trading_date_str
from tools.utils_ding import BaseMessager


Expand Down Expand Up @@ -113,9 +113,10 @@ def today_hold_report(self, today: str, positions):
if open_price == 0.0 or curr_price is None:
continue

# 负成本不显示变化
total_change = curr_price - open_price if open_price > 0 else 0
ratio_change = curr_price / open_price - 1 if open_price > 0 else 0
vol = position.volume
total_change = curr_price - open_price
ratio_change = curr_price / open_price - 1
hold_count += 1
display_list.append([code, curr_price, open_price, vol, ratio_change, total_change])

Expand Down Expand Up @@ -147,12 +148,14 @@ def today_hold_report(self, today: str, positions):
if self.messager is not None:
self.messager.send_text_as_md(text)

def check_asset(self, today: str, asset):
title = f'[{self.account_id}]{self.strategy_name} 盘后清点'
text = title + MSG_OUTER_SEPARATOR
def check_asset(self, today: str, asset, is_afternoon: bool = True):
title = f'[{self.account_id}]{self.strategy_name} {"午盘" if is_afternoon else "早盘"}清点'
text = title

increase = get_total_asset_increase(self.path_assets, today, asset.total_asset)
if increase is not None:
text += MSG_OUTER_SEPARATOR

total_change = colour_text(
f'{"+" if increase > 0 else ""}{round(increase, 2)}',
increase > 0,
Expand All @@ -164,38 +167,38 @@ def check_asset(self, today: str, asset):
increase > 0,
increase < 0,
)
text += f'当日变动: {total_change}元({ratio_change})'
text += f'{"当日" if is_afternoon else "盘中"}变动: {total_change}元({ratio_change})'

if self.today_report_show_bank \
and hasattr(self.delegate, 'xt_trader') \
and hasattr(self.delegate.xt_trader, 'query_bank_info'):

cash_change = 0.0
today_xt = today.replace('-', '')
yesterday_xt = get_prev_trading_date_str(today, 1)
bank_info = self.delegate.xt_trader.query_bank_info(self.delegate.account) # 银行信息查询
for bank in bank_info:
if bank.success:
if bank.success or bank.error_msg == '':
# 银行卡流水记录查询
transfers = self.delegate.xt_trader.query_bank_transfer_stream(
self.delegate.account, today_xt, today_xt, bank.bank_no, bank.bank_account)
self.delegate.account, yesterday_xt, today_xt, bank.bank_no, bank.bank_account)
total_change = sum(
-t.balance
if t.transfer_direction == '2' else t.balance
for t in transfers if t.success
for t in transfers if (t.success or t.error_msg == '') and t.date == today_xt and t.transfer_status == '成功'
)
cash_change += total_change

if abs(cash_change) > 0.0001:
cash_change = colour_text(
cash_change_str = colour_text(
f'{"+" if cash_change > 0 else ""}{round(cash_change, 2)}',
cash_change > 0,
cash_change < 0,
)
text += MSG_INNER_SEPARATOR
text += f'银证转账: {cash_change}元'
text += f'银证转账: {cash_change_str}元'

text += MSG_INNER_SEPARATOR
text += f'持仓市值: {round(asset.market_value, 2)}元'
text += f'{"持仓" if is_afternoon else "浮动"}市值: {round(asset.market_value, 2)}元'

text += MSG_INNER_SEPARATOR
text += f'剩余现金: {round(asset.cash, 2)}元'
Expand Down
12 changes: 6 additions & 6 deletions delegate/xt_delegate.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def __init__(

def connect(self, callback: object) -> (XtQuantTrader, bool):
session_id = int(time.time()) # 生成session id 整数类型 同时运行的策略不能重复
print("生成临时 session_id: ", session_id)
print("[交易通道] 连接开始,临时 session_id: ", session_id)
self.xt_trader = XtQuantTrader(self.path, session_id)

if callback is None:
Expand All @@ -73,7 +73,7 @@ def connect(self, callback: object) -> (XtQuantTrader, bool):
self.xt_trader.start() # 启动交易线程

# 建立交易连接,返回0表示连接成功
print('正在建立交易连接...', end='')
print('[交易通道] 正在建立交易连接...', end='')
connect_result = self.xt_trader.connect()
print(f'返回值:{connect_result}...', end='')
if connect_result != 0:
Expand All @@ -83,7 +83,7 @@ def connect(self, callback: object) -> (XtQuantTrader, bool):
print('成功!')

# 对交易回调进行订阅,订阅后可以收到交易主推,返回0表示订阅成功
print('正在订阅主推回调...', end='')
print('[交易通道] 正在订阅主推回调...', end='')
subscribe_result = self.xt_trader.subscribe(self.account)
print(f'返回值:{subscribe_result}...', end='')
if subscribe_result != 0:
Expand All @@ -92,15 +92,15 @@ def connect(self, callback: object) -> (XtQuantTrader, bool):
return None, False
print('成功!')

print('连接完毕')
print('[交易通道] 连接完毕')
return self.xt_trader, True

def reconnect(self) -> None:
if self.xt_trader is None and self.is_open_day: # 仅在交易日重连
print('开始重连交易接口')
print('[交易通道] 开始重连交易接口')
_, success = self.connect(self.callback)
if success:
print('交易接口重连成功')
print('[交易通道] 交易接口重连成功')
if self.subscriber is not None:
self.subscriber.resubscribe_tick(True)
# else:
Expand Down
3 changes: 2 additions & 1 deletion delegate/xt_subscriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ def callback_sub_whole(self, quotes: Dict) -> None:
if self.cache_limits['prev_seconds'] != curr_seconds:
self.cache_limits['prev_seconds'] = curr_seconds

print_mark = '.' if len(self.cache_quotes) > 0 else 'x'
print_mark = "'" if int(curr_seconds) % 10 == 9 else "."
print_mark = print_mark if len(self.cache_quotes) > 0 else "x"

if int(curr_seconds) % self.execute_interval == 0:
# 更全(默认:先记录再执行)
Expand Down
10 changes: 8 additions & 2 deletions run_shield.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
IS_PROD = False # 生产环境标志:False 表示使用掘金模拟盘 True 表示使用QMT账户下单交易
IS_DEBUG = True # 日志输出标记:控制台是否打印debug方法的输出

CAN_BUY = False # 双重保险,改为 True 才会下单买
CAN_SELL = False # 双重保险,改为 True 才会下单卖

PATH_BASE = CACHE_PROD_PATH if IS_PROD else CACHE_TEST_PATH

PATH_ASSETS = PATH_BASE + '/assets.csv' # 记录历史净值
Expand Down Expand Up @@ -68,7 +71,10 @@ class SellConf:

def before_trade_day() -> None:
# held_increase() -> None:
update_position_held(disk_lock, my_delegate, PATH_HELD)

# 持仓自动发现,对于连续做波段的票,会因为券商不同可能有成本价的问题,所以暂时禁用
# update_position_held(disk_lock, my_delegate, PATH_HELD)

if all_held_inc(disk_lock, PATH_HELD):
logging.warning('===== 所有持仓计数 +1 =====')
print(f'[持仓计数] All stocks held day +1')
Expand Down Expand Up @@ -100,7 +106,7 @@ def execute_strategy(curr_date: str, curr_time: str, curr_seconds: str, curr_quo
for time_range in SellConf.time_ranges:
if time_range[0] <= curr_time <= time_range[1]:
if int(curr_seconds) % SellConf.interval == 0:
scan_sell(curr_quotes, curr_date, curr_time, positions)
CAN_SELL & bool(scan_sell(curr_quotes, curr_date, curr_time, positions))

return False

Expand Down
7 changes: 3 additions & 4 deletions run_swords_tdx.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
PATH_LOGS = PATH_BASE + '/logs.txt' # 记录策略的历史日志
disk_lock = threading.Lock() # 操作磁盘文件缓存的锁
cache_selected: Dict[str, Set] = {} # 记录选股历史,去重
cache_history: Dict[str, pd.DataFrame] = {} # 记录历史日线行情的信息 { code: DataFrame }


class PoolConf:
Expand Down Expand Up @@ -71,7 +70,7 @@ def before_trade_day() -> None:
update_position_held(disk_lock, my_delegate, PATH_HELD)
if all_held_inc(disk_lock, PATH_HELD):
logging.warning('===== 所有持仓计数 +1 =====')
print(f'All held stock day +1!')
print(f'[持仓计数] All stocks held day +1')

# refresh_code_list() -> None:
my_pool.refresh()
Expand Down Expand Up @@ -199,8 +198,8 @@ def execute_strategy(curr_date: str, curr_time: str, curr_seconds: str, curr_quo

if __name__ == '__main__':
logging_init(path=PATH_LOGS, level=logging.INFO)
STRATEGY_NAME = STRATEGY_NAME if IS_PROD else STRATEGY_NAME + "[测]"
print(f'正在启动 {STRATEGY_NAME}...')
STRATEGY_NAME = STRATEGY_NAME if IS_PROD else STRATEGY_NAME + '[测]'
print(f'[正在启动] {STRATEGY_NAME}')
if IS_PROD:
from delegate.xt_callback import XtCustomCallback
from delegate.xt_delegate import XtDelegate
Expand Down
12 changes: 6 additions & 6 deletions selector/select_wencai.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@
default_prompt = "中证500成分股,非ST,非科创,MACD金叉,按价格从小到大" # 这里自定义问财选股的问句prompt


def get_prompt(prompt_number: any = 0) -> str:
def get_prompt(prompt_index: any = 0) -> str:
ans = default_prompt

if type(prompts) == list and prompt_number < len(prompts):
ans = prompts[prompt_number]
if type(prompts) == list and prompt_index < len(prompts):
ans = prompts[prompt_index]

if type(prompts) == dict and prompt_number in prompts:
ans = prompts[prompt_number]
if type(prompts) == dict and prompt_index in prompts:
ans = prompts[prompt_index]

print('选股问句:', ans, '\n')
print(f'[{prompt_index}]: {ans}')
return ans


Expand Down
32 changes: 30 additions & 2 deletions tools/utils_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ def debug(*args, **kwargs):

# pandas dataframe 显示配置优化
def pd_show_all() -> None:
pd.set_option('display.width', None)
pd.set_option('display.expand_frame_repr', False)
pd.set_option('display.width', 2000)
pd.set_option('display.min_rows', 9999)
pd.set_option('display.max_rows', 9999)
pd.set_option('display.max_columns', 200)
Expand All @@ -24,16 +25,43 @@ def pd_show_all() -> None:


# logging 模块的初始化配置
def logging_init(path=None, level=logging.DEBUG, file_line=False):
def logging_init(path=None, level=logging.DEBUG, file_line=False, silence_libs=True):
"""
初始化日志配置
:param path: 日志文件路径,None则输出到控制台
:param level: 日志级别
:param file_line: 是否显示文件名和行号
:param silence_libs: 是否静默第三方库日志
"""
file_line_fmt = ""
if file_line:
file_line_fmt = "%(filename)s[line:%(lineno)d] - %(levelname)s: "

logging.basicConfig(
level=level,
format=file_line_fmt + "%(asctime)s|%(message)s",
filename=path
)

# 静默常见第三方库的日志(完全忽略)
if silence_libs:
noisy_libs = [
'gmtrade', 'gmtradelogger', 'tushare', 'urllib3', 'requests',
'httpx', 'httpcore', 'asyncio', 'websockets',
'apscheduler', 'schedule', 'chardet',
]
# 既设置顶层 logger,也把已创建的同前缀子 logger 一并静默(例如 gmtrade.xxx)
for lib in noisy_libs:
logging.getLogger(lib).setLevel(logging.CRITICAL)
logging.getLogger(lib).propagate = False

for name, obj in logging.root.manager.loggerDict.items():
if not isinstance(obj, logging.Logger):
continue
if name == lib or name.startswith(lib + '.'):
obj.setLevel(logging.CRITICAL) # 只输出崩溃级日志
obj.propagate = False # 阻止传播到 root logger


# 多文件 logger的配置
def logger_init(path=None, name='default') -> logging.Logger:
Expand Down
2 changes: 1 addition & 1 deletion tools/utils_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ def all_held_inc(lock: threading.Lock, path: str) -> bool:
else:
return False
except Exception as e:
print('held days +1 failed! ', e)
print('[持仓计数] Held days +1 failed! ', e)
return False


Expand Down
2 changes: 1 addition & 1 deletion tools/utils_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ def get_bao_daily_history(
[symbol, exchange] = code.split('.')
rs = bs.query_history_k_data_plus(
f'{exchange.lower()}.{symbol}',
"date,code,open,high,low,close,volume,amount",
"date,code,open,high,low,close,volume,amount,peTTM",
start_date=start,
end_date=end,
frequency='d',
Expand Down
Loading
Loading