Cai
2026-08-25 84b96f8551d2db9404fcdcf1628ac76f62c2a186
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
Ë
’O>jý1ãóŽ—dZddlmZddlZddlZddlZddlZddlZddlZddl    Z    ddl
Z
ddl m Z m Z ddl mZee«j!«Zej$dZeej(j+dd««Zdd    „Zddd
„Zdd „Zdd „Zdd „Zdd„Zdd„Zdd„Zd d„Zd!d„Z d"d„Z!d#d„Z"d$d%d„Z#e$dk(r e%e#««‚y)&aBScan local A-share K-line/profile data for market reverse-gap candidates.
 
The script uses local MySQL read-only tables:
 
- a_share_profile_snapshot
- a_share_daily_price
 
It writes market manifestation audit artifacts into the industry case layout.
Use the result as a gap-finding input, not as an investment conclusion.
é)Ú annotationsN)ÚdatetimeÚtimezone)ÚPathéÚTIANXIA_MYSQL_EXEzM:/mysql/server/bin/mysql.execó„—tjtj«j    «j d¬«S)NÚseconds)Útimespec)rÚnowrÚutcÚ
astimezoneÚ    isoformat©óúAE:\mb-ms-doc\project-info\ana-data\tools\market_gap_scan_mysql.pyÚnow_isors,€Ü <‰<œŸ ™ Ó %× 0Ñ 0Ó 2× <Ñ <ÀiÐ <Ó PÐPrcó€—tjdd|j««}|jd«}|xs|d|S)Nz[^A-Za-z0-9._-]+Ú_z._-)ÚreÚsubÚstrip)ÚvalueÚfallbackÚmax_lens   rÚ    safe_namer#s;€Ü F‰FÐ&¨¨U¯[©[«]Ó ;€EØ K‰K˜Ó €EØ Ò X˜x Ð (Ð(rcón—|€yt|«}d|jdd«jdd«zdzS)NÚNULLÚ'Ú\ú\\z'')ÚstrÚreplace)rÚtexts  rÚ sql_literalr%)s;€Ø €}ØÜ ˆu‹:€DØ —‘˜d FÓ+×3Ñ3°C¸Ó>Ñ >ÀÑ DÐDrcóf—tj|jd««j«S)Núutf-8)ÚhashlibÚsha256ÚencodeÚ    hexdigest)rs rÚ sha256_textr,0s#€Ü >‰>˜%Ÿ,™, wÓ/Ó 0× :Ñ :Ó <Ð<rcóܗ    |j«jtj««j«S#t$r!|j«j«cYSwxYw©N)ÚresolveÚ relative_toÚ PROJECT_ROOTÚas_posixÚ
ValueError)Úpaths rÚproject_relativer54sT€ð)؏|‰|‹~×)Ñ)¬,×*>Ñ*>Ó*@ÓA×JÑJÓLÐLøÜ ò)؏|‰|‹~×&Ñ&Ó(Ò(ð)ús‚>AÁ'A+Á*A+cóþ—g}|D]f}d|jdd«jdd«jdd«zdz}t|«}|jd|›d|›d    |›d
|›d |›d  «Œhd j|«S)NÚ%r r!z\%rz\_z(p.industry_l1 LIKE z# ESCAPE '\\' OR p.industry_l2 LIKE z$ ESCAPE '\\' OR p.concepts_raw LIKE z) ESCAPE '\\' OR p.main_business_raw LIKE z" ESCAPE '\\' OR p.stock_name LIKE z  ESCAPE '\\')z OR )r#r%ÚappendÚjoin)ÚkeywordsÚclausesÚkeywordÚpatternÚlits     rÚkeyword_predicater?;s«€Ø€GۈؘŸ™¨¨fÓ5×=Ñ=¸cÀ5ÓI×QÑQÐRUÐW\Ó]Ñ]Ð`cÑcˆÜ˜'Ó"ˆØ‰ð "Ø"% ð'"Ø"% ð'#Ø#& %ð((Ø(+ uð-!Ø!$ ð&ð  õ    
ðð ;‰;wÓ ÐrcóP—|js td«‚t|j«}t|j«}t|j
«}t |j«}t |j«}t |j«}djgd‘|›‘d‘|›‘d‘|›‘d‘|›‘d‘|›‘d‘|›‘d    ‘|›‘d
‘t|j«›‘d ‘t|j«›‘d ‘|›‘d ‘|›‘d‘|›‘d ‘|›‘d‘|›‘d‘|›‘d‘«j«S)Nzpass at least one --keywordÚz€
WITH latest_profile AS (
  SELECT MAX(snapshot_date) AS snapshot_date
  FROM a_share_profile_snapshot
  WHERE snapshot_date <= a
),
universe AS (
  SELECT
    p.symbol,
    p.stock_name,
    p.industry_l1,
    p.industry_l2,
    p.concepts_raw,
    p.main_business_raw
  FROM a_share_profile_snapshot p
  JOIN latest_profile lp ON lp.snapshot_date = p.snapshot_date
  WHERE p.status = 'OK'
    AND (a)
),
price_with_prev AS (
  SELECT
    d.symbol,
    d.trade_date,
    d.open_price,
    d.high_price,
    d.low_price,
    d.close_price,
    d.volume,
    d.amount,
    LAG(d.close_price) OVER (PARTITION BY d.symbol ORDER BY d.trade_date) AS prev_close,
    AVG(d.amount) OVER (
      PARTITION BY d.symbol
      ORDER BY d.trade_date
      ROWS BETWEEN 20 PRECEDING AND 1 PRECEDING
    ) AS avg_amount_20
  FROM a_share_daily_price d
  JOIN universe u ON u.symbol = d.symbol
  WHERE d.trade_date BETWEEN DATE_SUB(z, INTERVAL 30 DAY) AND a
),
window_rows AS (
  SELECT
    u.stock_name,
    u.industry_l1,
    u.industry_l2,
    u.concepts_raw,
    u.main_business_raw,
    p.*,
    CASE
      WHEN p.prev_close IS NULL OR p.prev_close = 0 THEN NULL
      ELSE (p.close_price / p.prev_close - 1) * 100
    END AS daily_return_pct,
    CASE
      WHEN p.avg_amount_20 IS NULL OR p.avg_amount_20 = 0 THEN NULL
      ELSE p.amount / p.avg_amount_20
    END AS amount_ratio_20
  FROM price_with_prev p
  JOIN universe u ON u.symbol = p.symbol
  WHERE p.trade_date BETWEEN z AND aØ
),
ranked AS (
  SELECT
    w.*,
    ROW_NUMBER() OVER (PARTITION BY w.symbol ORDER BY w.trade_date) AS rn_first,
    ROW_NUMBER() OVER (PARTITION BY w.symbol ORDER BY w.trade_date DESC) AS rn_last
  FROM window_rows w
),
agg AS (
  SELECT
    symbol,
    MAX(stock_name) AS company_name,
    MAX(industry_l1) AS industry_l1,
    MAX(industry_l2) AS industry_l2,
    MAX(concepts_raw) AS concepts_raw,
    MAX(main_business_raw) AS main_business_raw,
    MIN(trade_date) AS window_start,
    MAX(trade_date) AS window_end,
    MAX(CASE WHEN rn_first = 1 THEN close_price END) AS first_close,
    MAX(CASE WHEN rn_last = 1 THEN close_price END) AS last_close,
    COUNT(*) AS trading_days,
    SUM(CASE WHEN daily_return_pct >= a THEN 1 ELSE 0 END) AS strong_up_days,
    MAX(daily_return_pct) AS max_daily_return_pct,
    MAX(amount_ratio_20) AS max_amount_ratio_20,
    MAX(amount) AS max_amount
  FROM ranked
  GROUP BY symbol
),
scored AS (
  SELECT
    *,
    CASE
      WHEN first_close IS NULL OR first_close = 0 THEN NULL
      ELSE (last_close / first_close - 1) * 100
    END AS period_return_pct
  FROM agg
)
SELECT
  z AS scan_id,
  a¤ AS industry_case,
  symbol,
  company_name,
  industry_l1,
  industry_l2,
  window_start,
  window_end,
  trading_days,
  ROUND(first_close, 4) AS first_close,
  ROUND(last_close, 4) AS last_close,
  ROUND(period_return_pct, 4) AS period_return_pct,
  strong_up_days,
  ROUND(max_daily_return_pct, 4) AS max_daily_return_pct,
  ROUND(max_amount_ratio_20, 4) AS max_amount_ratio_20,
  CASE
    WHEN period_return_pct >= z2 OR strong_up_days >= 1 OR max_amount_ratio_20 >= zù
    THEN 'STRONG_MANIFESTATION'
    ELSE 'WEAK_OR_NORMAL'
  END AS manifestation_type,
  'REVIEW_REQUIRED' AS scope_type,
  'REVIEW_REQUIRED' AS suspected_missing_track,
  'MARKET_REVERSE_GAP_SCAN' AS gap_type,
  CASE
    WHEN period_return_pct >= aX
    THEN 1
    ELSE 0
  END AS supplement_required_flag,
  'Review core business against industry scheme before adding to evidence queue' AS supplement_question,
  'DRAFT_FOR_REVIEW' AS review_status,
  LEFT(concepts_raw, 500) AS concepts_excerpt,
  LEFT(main_business_raw, 500) AS main_business_excerpt
FROM scored
WHERE period_return_pct >= z8
   OR strong_up_days >= 1
   OR max_amount_ratio_20 >= zp
ORDER BY supplement_required_flag DESC, period_return_pct DESC, strong_up_days DESC, max_amount_ratio_20 DESC;
)r<Ú
SystemExitr?r%Ú
start_dateÚend_dateÚfloatÚmin_return_pctÚstrong_day_pctÚ amount_ratior9Úscan_idÚ industry_caser)ÚargsÚpredÚstartÚendÚ
min_returnrGrHs       rÚ    build_sqlrPLsX€Ø <Š<ÜÐ6Ó7Ð7Ü ˜TŸ\™\Ó *€DÜ ˜Ÿ™Ó (€EÜ
d—m‘mÓ
$€Cܐt×*Ñ*Ó+€Jܘ4×.Ñ.Ó/€Nܘ×*Ñ*Ó+€L÷E óE ððE ð˜ð    E ð 
ð    E ð" ˆð#E ð"'ð#E ðJ(- gðKE ðJ.EðKE ðJFIÀEðKE ðJJðKE ðr$˜WðsE ðr%*ðsE ðr+.¨ðsE ðr/'ðsE ð^(6Ð&6ð_E ð^7ð_E ô@ˆt|‰|ÓÐðAE ð@ðAE ôBˆt×!Ñ!Ó"Ð#ðCE ðB$ðCE ð` *˜lðaE ð`+]ðaE ð`^jÐ\jðaE ð`kðaE ðp *˜lðqE ðp+]ðqE ðp^jÐ\jðqE ðp    kðqE ðB'˜<ðCE ðB(ðCE ðF+˜^ðGE ðF,ôGE ÷J
EƒGðKE rc ó~—tjjd«}|s td«‚t    |j
«}|j «std|›«‚t|«dddddd    |j›d
|j›d |j›|jd |g }tjj«}||d <tj|dd|d¬«}|jdk7r?t|j j#«xs|j$j#««‚|j$S)NÚTIANXIA_MYSQL_PASSWORDz5TIANXIA_MYSQL_PASSWORD is required in the environmentzmysql executable not found: z--protocol=TCPz--batchz--rawz--quickz--default-character-set=utf8mb4z--host=z--port=z--user=z-eÚ    MYSQL_PWDTF)r$Úcapture_outputÚenvÚcheckr)ÚosÚenvironÚgetrBrÚ    mysql_exeÚexistsr"ÚhostÚportÚuserÚdatabaseÚcopyÚ
subprocessÚrunÚ
returncodeÚstderrrÚstdout)rKÚsqlÚpasswordrZÚcmdrUÚprocs       rÚ    run_mysqlrjÝs€Üz‰z~‰~Ð6Ó7€HÙ ÜÐPÓQÐQܐT—^‘^Ó$€IØ × Ñ Ô ÜÐ7¸    °{ÐCÓDÐDä ˆI‹ØØØØØ)Ø
$—)‘)ÐØ
$—)‘)ÐØ
$—)‘)ÐØ  ‰ Ø Ø ð €Cô *‰*/‰/Ó
€CØ€Cˆ ÑÜ >‰>˜# D¸À3ÈeÔ T€DØ ‡˜!ÒܘŸ™×*Ñ*Ó,ÒC°· ± ×0AÑ0AÓ0CÓDÐDØ ;‰;Ðrcó\—|jjdd¬«d}|jddd¬«5}tj|«}t |j ««D]0\}}|j|jd««|dkDsŒ,|d    z }Œ2    ddd«|S#1swY|SxYw)
NT©ÚparentsÚexist_okrÚwú    utf-8-sigrA©ÚencodingÚnewlineÚ    é)    ÚparentÚmkdirÚopenÚcsvÚwriterÚ    enumerateÚ
splitlinesÚwriterowÚsplit)ÚtsvÚout_pathÚ    row_countÚfhrzÚ
line_indexÚlines       rÚwrite_csv_from_tsvr…úsš€Ø ‡OO×Ñ $°ÐÔ6Ø€IØ    ‰s [¸"ˆÔ    =ÀÜ—‘˜B“ˆÜ )¨#¯.©.Ó*:Ö ;Ñ ˆJ˜Ø O‰O˜DŸJ™J tÓ,Ô -ؘA‹~ؘQ‘‘    ñ!<÷
>ð Ð÷
>ð Ðús´AB!ÂB!Â!B+cóR—|j«}|jjdd¬«|jddd¬«5}t    j
|t |j««¬«}|s|j«|j|«ddd«y#1swYyxYw)NTrlÚarprArq)Ú
fieldnames)
r[rvrwrxryÚ
DictWriterÚlistÚkeysÚ writeheaderr})r4Úrowr[r‚rzs     rÚappend_manifestrŽsz€Ø [‰[‹]€F؇KK×јd¨TÐÔ2Ø     ‰3 °bˆÔ    9¸RÜ—‘ ¬t°C·H±H³JÓ/?Ô@ˆÙØ × Ñ Ô  Ø‰˜Ô÷    
:×    9Ñ    9ús ÁABÂB&cód—|jst|jd«›d|_tdz dz |jz }|dz }|dz }|j dd¬«|j dd¬«t|jd«}t |«}t|«}|d    |›d
|dd ›d z }|d |›d
|dd ›dz }|d|›d
|dd ›dz }    |d|›d z }
t||«} t| |«} |    j|dzd¬«id|j“d|j“d|j“d|j“d|j“d|j“d|j“d|j“d|j “d|j"“d|j$“d | “d!t'|«“d"|“d#t)«“d$d%“} |jt+j,| d&d'¬(«d¬«t/|
|j|j|j|j|j|j|jd)j1|j«|t'|    «t'|«t'|«t3| «| d#d*d+œ«t5d,t'|«›d-| ›«t5d.t'|«›«y/)0NrbÚ_MARKET_GAP_SCANzana-dataÚcasesÚ
supplementÚmanifestTrlÚmarket_manifestation_gap_audit_ré z.csvÚ!market_manifestation_gap_summary_z.jsonÚmarket_manifestation_gap_query_z.sqlÚ"market_manifestation_gap_manifest_Ú
r')rrrIÚcase_idÚbatch_idÚrun_idrJrCrDr:rFrGrHrÚ
audit_pathÚ
sql_sha256Ú generated_atÚreadoutÚ'GAP_SCAN_ONLY_NOT_INVESTMENT_CONCLUSIONFr)Ú ensure_asciiÚindentÚ|ÚDRAFT_FOR_REVIEW)rIršr›rœrJrCrDr:ržÚsql_relative_pathÚaudit_relative_pathÚsummary_relative_pathrrŸÚ review_statuszaudit=z rows=zsummary=r)rIrrœr1rJrwrPr,rjr…Ú
write_textršr›rCrDr<rFrGrHr5rÚjsonÚdumpsrŽr9r"Úprint)rKÚ industry_rootÚsupplement_dirÚ manifest_dirÚrun_saferfÚsql_hashrÚ summary_pathÚsql_pathÚ manifest_pathrrÚsummarys              rÚscanr·s&€Ø <Š<Ü# D§K¡K°Ó7Ð8Ð8HÐIˆŒ Ü  :Ñ-°Ñ7¸$×:LÑ:LÑL€MØ" \Ñ1€NØ  :Ñ-€LØ×Ñ °ÐÔ5Ø×јt¨dÐÔ3䘟™ eÓ,€HÜ
D‹/€Cܘ3Ó€HØÐ$CÀHÀ:ÈQÈxÐX[ÐY[È}ÈoÐ]aÐ"bÑb€JØ!Ð&GÈÀzÐQRÐS[Ð\_Ð]_ÐS`ÐRaÐafÐ$gÑg€LØР?À¸zÈÈ8ÐTWÐUWÈ=È/ÐY]Ð^Ñ^€HØ Ð%GÈÀzÐQUÐ#VÑV€Mä
D˜#Ó
€CÜ" 3¨
Ó3€IØ ×ј˜d™
¨WÐÔ5ðؐ4—<‘<ðà4—<‘<ðð    D—M‘Mðð    $—+‘+ð    ð
    ˜×+Ñ+ð ð     d—o‘oð ð    D—M‘Mðð    D—L‘Lðð    ˜$×-Ñ-ðð    ˜$×-Ñ-ðð    ˜×)Ñ)ðð    Yðð    Ô& zÓ2ðð    hðð    œ›    ðð     Ð<ð!€Gð$×ÑœDŸJ™J w¸UÈ1ÔMÐX_ÐÔ`ÜØà—|‘|Ø—|‘|ØŸ ™ Ø—k‘kØ!×/Ñ/ØŸ/™/ØŸ ™ ØŸ™ §¡Ó.Ø"Ü!1°(Ó!;Ü#3°JÓ#?Ü%5°lÓ%CܘY›Ø# NÑ3Ø/ñ    
ôô(
ˆFÔ# JÓ/Ð0°°y°kÐ
BÔCÜ    ˆHÔ% lÓ3Ð4Ð
5Ô6Ø rcóÞ—tjt¬«}|jdd¬«|jdd¬«|jdd¬«|jdd¬«|jd«|jd    d¬«|jd
d¬«|jd d d¬ «|jdtd¬«|jdtd¬«|jdtd¬«|jdt t «¬«|jdtjjdd«¬«|jdtjjdd«¬«|jdtjjdd«¬«|jd tjjd!d"«¬«|j|«S)#N)Ú descriptionz--industry-caseT)Úrequiredz    --case-idz
--batch-idz--run-idz    --scan-idz --start-datez
--end-datez    --keywordr8)Úactionrºz--min-return-pctg4@)ÚtypeÚdefaultz--strong-day-pctg#@z--amount-ratiog@z --mysql-exe)r½z--hostÚTIANXIA_MYSQL_HOSTz    127.0.0.1z--portÚTIANXIA_MYSQL_PORTÚ3306z--userÚTIANXIA_MYSQL_USERÚrootz
--databaseÚTIANXIA_MYSQL_DBÚtianxia) ÚargparseÚArgumentParserÚ__doc__Ú add_argumentrEr"ÚDEFAULT_MYSQL_EXErWrXrYÚ
parse_args)ÚargvÚparsers  rrÊrÊPs¡€Ü × $Ñ $´Ô 9€FØ
×ÑÐ)°DÐÔ9Ø
×ј ¨dÐÔ3Ø
×ј ¨tÐÔ4Ø
×ј
¨TÐÔ2Ø
×ј Ô$Ø
×ј°ÐÔ6Ø
×ј ¨tÐÔ4Ø
×ј ¨H¸tÐÔDØ
×ÑÐ*´ÀÐÔEØ
×ÑÐ*´ÀÐÔDØ
×ÑÐ(¬u¸cÐÔBØ
×ј ¬sÔ3DÓ/EÐÔFØ
×ј¬"¯*©*¯.©.Ð9MÈ{Ó*[ÐÔ\Ø
×ј¬"¯*©*¯.©.Ð9MÈvÓ*VÐÔWØ
×ј¬"¯*©*¯.©.Ð9MÈvÓ*VÐÔWØ
×ј ¬b¯j©j¯n©nÐ=OÐQZÓ.[ÐÔ\Ø × Ñ ˜TÓ "Ð"rcóX—t|xstjdd«}t|«S)Nru)rÊÚsysrËr·)rËrKs  rÚmainrÏes%€Ü dÒ*œcŸh™h q r˜lÓ +€DÜ ‹:ÐrÚ__main__)Úreturnr")Úartifacté`)rr"rr"rÚintrÑr")rÚobjectrÑr")rr"rÑr")r4rrÑr")r:ú    list[str]rÑr")rKúargparse.NamespacerÑr")rKr×rfr"rÑr")rr"r€rrÑrÔ)r4rrzdict[str, str]rÑÚNone)rKr×rÑrÔ)rËrÖrÑr×r.)rËzlist[str] | NonerÑrÔ)&rÇÚ
__future__rrÅryr(r«rWrrarÎrrÚpathlibrÚ__file__r/Ú SCRIPT_PATHrmr1rXrYrÉrrr%r,r5r?rPrjr…rŽr·rÊrÏÚ__name__rBrrrÚ<module>rÞsÏðñ    õ#ãÛ
ÛÛ Û    Û    ÛÛ
ß'Ýñ8‹n×$Ñ$Ó&€ Ø×"Ñ" 1Ñ%€ Ù˜Ÿ™Ÿ™Ð(;Ð=\Ó]Ó^ÐóQô)ó Eó=ó)ó ó"N óbó:    óó= ó@#ô*ð
 ˆzÒÙ
‘T“VÓ
Ððr