@@ -30,260 +30,289 @@
window . syncRollFormMode = syncRollFormMode ;
const form = document . getElementById ( "roll-form" ) ;
if ( ! form ) return ;
if ( form . dataset . rollJsInit === "1" ) return ;
form . dataset . rollJsInit = "1" ;
const symbolSel = document . getElementById ( "roll-symbol" ) ;
const dirInput = document . getElementById ( "roll-direction" ) ;
const modeSel = document . getElementById ( "roll-add-mode" ) ;
const riskBanner = document . getElementById ( "roll-risk-banner" ) ;
const previewBtn = document . getElementById ( "roll-preview-btn" ) ;
const submitBtn = document . getElementById ( "roll-submit-btn" ) ;
const previewBox = document . getElementById ( "roll-preview-box" ) ;
const previewText = document . getElementById ( "roll-preview-text" ) ;
const countdownEl = document . getElementById ( "roll-countdown" ) ;
const trendLocked = submitBtn && submitBtn . getAttribute ( "data-trend-locked" ) === "1" ;
let countdownTimer = null ;
let previewOk = false ;
let lastPreviewMode = "" ;
let monitorSubmitting = false ;
function isMarketMode ( ) {
return ( modeSel . value || "market" ) === "market" ;
function isEmbedShell ( ) {
return document . body && document . body . getAttribute ( "data-embed-shell" ) === "1" ;
}
function isMonitorMode ( ) {
const m = modeSel . value || "market" ;
return m === "fib_618" || m === "fib_786" || m === "breakout" ;
}
function selectedOption ( ) {
return symbolSel . options [ symbolSel . selectedIndex ] ;
}
function syncDirectionLock ( ) {
const opt = selectedOption ( ) ;
if ( ! opt || ! opt . value ) {
riskBanner . textContent = "当前风险:请选择持仓币种" ;
function submitRollForm ( form ) {
if ( isEmbedShell ( ) && window . InstanceEmbed && typeof window . InstanceEmbed . postFormAndReload === "function" ) {
window . InstanceEmbed . postFormAndReload ( form , "执行中…" ) ;
return ;
}
const dir = opt . getAttribute ( "data-direction" ) || "long" ;
const rp = opt . getAttribute ( "data-risk-percent" ) || "—" ;
dirInput . value = dir ;
riskBanner . textContent =
"当前风险:" + rp + "%(来自监控单 #" + ( opt . getAttribute ( "data-monitor-id" ) || "?" ) + ") " ;
}
function syncSubmitButton ( ) {
if ( ! submitBtn || trendLocked ) return ;
if ( isMonitorMode ( ) ) {
submitBtn . disabled = false ;
if ( window . FormSubmitGuard && typeof window . FormSubmitGuard . nativeSubmitOnce === "function" ) {
window . FormSubmitGuard . nativeSubmitOnce ( form , "执行中…" ) ;
return ;
}
submitBtn . disabled = ! previewOk || ! ! countdownTimer ;
form . submit ( ) ;
}
function clearMessageBox ( ) {
if ( ! previewBox ) return ;
previewBox . style . display = "none" ;
previewBox . classList . remove ( "is-error" , "is-preview ") ;
if ( previewText ) previewText . textContent = "" ;
if ( countdownEl ) countdownEl . style . display = "none" ;
}
function initStrategyRollForm ( ) {
const form = document . getElementById ( "roll-form" ) ;
if ( ! form ) return ;
if ( form . dataset . rollJsInit === "1 ") return ;
form . dataset . rollJsInit = "1 " ;
function showReject ( msg ) {
if ( ! previewBox || ! previewText ) return ;
previewBox . style . display = "block" ;
previewBox . classList . remove ( "is-preview ") ;
previewBox . classList . add ( "is-error ") ;
previewText . textContent = msg || "无法执行" ;
if ( countdownEl ) countdownEl . style . display = "none" ;
previewBox . scrollIntoView ( { behavior : "smooth" , block : "nearest" } ) ;
}
const symbolSel = document . getElementById ( "roll-symbol" ) ;
const dirInput = document . getElementById ( "roll-direction" ) ;
const modeSel = document . getElementById ( "roll-add-mode" ) ;
const riskBanner = document . getElementById ( "roll-risk-banner ") ;
const previewBtn = document . getElementById ( "roll-preview-btn ") ;
const submitBtn = document . getElementById ( "roll-submit-btn" ) ;
const previewBox = document . getElementById ( "roll-preview-box" ) ;
const previewText = document . getElementById ( "roll-preview-text" ) ;
const countdownEl = document . getElementById ( "roll-countdown" ) ;
const trendLocked = submitBtn && submitBtn . getAttribute ( "data-trend-locked" ) === "1" ;
function showPreviewResult ( p ) {
if ( ! previewBox || ! previewText ) return ;
previewBox . style . display = "block " ;
previewBox . classList . remove ( "is-error" ) ;
previewBox . classList . add ( "is-preview" ) ;
previewText . innerHTML =
"<strong>" +
( p . add _mode _label || "" ) +
"</strong> · 约 <strong>" +
( p . add _amount _display != null ? p . add _amount _display : p . add _amount _raw ) +
"</strong> 张<br>" +
"加仓参考价 " +
( p . add _price _display != null ? p . add _price _display : p . add _price ) +
" · 新止损 " +
( p . new _sl _display != null ? p . new _sl _display : p . new _stop _loss ) +
"<br>" +
"合并均价 " +
p . avg _entry _after +
" · 打到止损约 " +
p . loss _at _sl _usdt +
"U(风险预算 " +
( p . risk _budget _usdt != null ? p . risk _budget _usdt : "—" ) +
"U) " ;
}
let countdownTimer = null ;
let previewOk = false ;
let lastPreviewMode = "" ;
let monitorSubmitting = false ;
function syncFieldVisibility ( ) {
syncRollFormMode ( form , modeSel . value || "market" ) ;
resetPreview ( ) ;
}
function resetPreview ( ) {
previewOk = false ;
monitorSubmitting = false ;
clearMessageBox ( ) ;
if ( countdownTimer ) {
clearInterval ( countdownTimer ) ;
countdownTimer = null ;
function isMarketMode ( ) {
return ( modeSel . value || "market" ) === "market" ;
}
syncSubmitButton ( ) ;
}
function formPayload ( ) {
const fd = new FormData ( form ) ;
const obj = { } ;
fd . forEach ( function ( v , k ) {
if ( v !== "" ) obj [ k ] = v ;
} ) ;
return obj ;
}
function requestPreview ( ) {
return fetch ( "/strategy/roll/preview" , {
method : "POST" ,
headers : { "Content-Type" : "application/json" , Accept : "application/json" } ,
body : JSON . stringify ( formPayload ( ) ) ,
credentials : "same-origin" ,
} ) . then ( function ( r ) {
return r . json ( ) ;
} ) ;
}
function runPreview ( ) {
resetPreview ( ) ;
if ( ! symbolSel . value ) {
showReject ( "请先选择持仓币种" ) ;
return ;
function isMonitorMode ( ) {
const m = modeSel . value || "market" ;
return m === "fib_618" || m === "fib_786" || m === "breakout" ;
}
if ( previewBtn ) previewBtn . disabled = true ;
requestPreview ( )
. then ( function ( data ) {
if ( previewBtn ) previewBtn . disabled = false ;
if ( ! data . ok ) {
showReject ( data . msg || "预览失败" ) ;
return ;
}
const p = data . preview || { } ;
lastPreviewMode = p . add _mode || modeSel . value ;
showPreviewResult ( p ) ;
previewOk = true ;
if ( lastPreviewMode === "market" ) {
startCountdown ( 10 ) ;
} else {
syncSubmitButton ( ) ;
}
} )
. catch ( function ( ) {
if ( previewBtn ) previewBtn . disabled = false ;
showReject ( "预览请求失败,请稍后重试" ) ;
} ) ;
}
function runMonitorSubmit ( ) {
if ( monitorSubmitting ) return ;
if ( ! symbolSel . value ) {
showReject ( "请先选择持仓币种" ) ;
return ;
function selectedOption ( ) {
return symbolSel . options [ symbolSel . selectedIndex ] ;
}
monitorSubmitting = true ;
if ( submitBtn ) submitBtn . disabled = true ;
requestPreview ( )
. then ( function ( data ) {
monitorSubmitting = false ;
if ( submitBtn && ! trendLocked ) submitBtn . disabled = false ;
if ( ! data . ok ) {
showReject ( data . msg || "无法提交监控" ) ;
return ;
}
const p = data . preview || { } ;
const modeLabel = modeSel . options [ modeSel . selectedIndex ] . text ;
const summary =
"约 " +
( p . add _amount _display != null ? p . add _amount _display : p . add _amount _raw ) +
" 张 · 触发参考价 " +
( p . add _price _display != null ? p . add _price _display : p . add _price ) +
" · 新止损 " +
( p . new _sl _display != null ? p . new _sl _display : p . new _stop _loss ) ;
if ( ! confirm ( "确认提交「" + modeLabel + "」?\n" + summary ) ) {
return ;
}
form . submit ( ) ;
} )
. catch ( function ( ) {
monitorSubmitting = false ;
if ( submitBtn && ! trendLocked ) submitBtn . disabled = false ;
showReject ( "校验请求失败,请稍后重试" ) ;
} ) ;
}
function startCountdown ( sec ) {
let left = sec ;
if ( submitBtn ) submitBtn . disabled = true ;
if ( countdownEl ) {
countdownEl . style . display = "block" ;
countdownEl . textContent = "市价加仓:" + left + " 秒后可执行(修改表单将取消预览)" ;
}
countdownTimer = setInterval ( function ( ) {
left -= 1 ;
if ( left <= 0 ) {
clearInterval ( countdownTimer ) ;
countdownTimer = null ;
if ( countdownEl ) countdownEl . textContent = "可以执行市价加仓" ;
syncSubmitButton ( ) ;
function syncDirectionLock ( ) {
const opt = selectedOption ( ) ;
if ( ! opt || ! opt . value ) {
riskBanner . textContent = "当前风险:请选择持仓币种" ;
return ;
}
if ( countdownEl ) countdownEl . textContent = "市价加仓:" + left + " 秒后可执行 ";
} , 1000 ) ;
const dir = opt . getAttribute ( "data-direction" ) || "long ";
const rp = opt . getAttribute ( "data-risk-percent" ) || "—" ;
dirInput . value = dir ;
riskBanner . textContent =
"当前风险:" + rp + "%(来自监控单 #" + ( opt . getAttribute ( "data-monitor-id" ) || "?" ) + ") " ;
}
function syncSubmitButton ( ) {
if ( ! submitBtn || trendLocked ) return ;
if ( isMonitorMode ( ) ) {
submitBtn . disabled = false ;
submitBtn . removeAttribute ( "disabled" ) ;
return ;
}
const blocked = ! previewOk || ! ! countdownTimer ;
submitBtn . disabled = blocked ;
if ( ! blocked ) submitBtn . removeAttribute ( "disabled" ) ;
}
function clearMessageBox ( ) {
if ( ! previewBox ) return ;
previewBox . style . display = "none" ;
previewBox . classList . remove ( "is-error" , "is-preview" ) ;
if ( previewText ) previewText . textContent = "" ;
if ( countdownEl ) countdownEl . style . display = "none" ;
}
function showReject ( msg ) {
if ( ! previewBox || ! previewText ) return ;
previewBox . style . display = "block" ;
previewBox . classList . remove ( "is-preview" ) ;
previewBox . classList . add ( "is-error" ) ;
previewText . textContent = msg || "无法执行" ;
if ( countdownEl ) countdownEl . style . display = "none" ;
previewBox . scrollIntoView ( { behavior : "smooth" , block : "nearest" } ) ;
}
function showPreviewResult ( p ) {
if ( ! previewBox || ! previewText ) return ;
previewBox . style . display = "block" ;
previewBox . classList . remove ( "is-error" ) ;
previewBox . classList . add ( "is-preview" ) ;
previewText . innerHTML =
"<strong>" +
( p . add _mode _label || "" ) +
"</strong> · 约 <strong>" +
( p . add _amount _display != null ? p . add _amount _display : p . add _amount _raw ) +
"</strong> 张<br>" +
"加仓参考价 " +
( p . add _price _display != null ? p . add _price _display : p . add _price ) +
" · 新止损 " +
( p . new _sl _display != null ? p . new _sl _display : p . new _stop _loss ) +
"<br>" +
"合并均价 " +
p . avg _entry _after +
" · 打到止损约 " +
p . loss _at _sl _usdt +
"U(风险预算 " +
( p . risk _budget _usdt != null ? p . risk _budget _usdt : "—" ) +
"U) " ;
}
function syncFieldVisibility ( ) {
syncRollFormMode ( form , modeSel . value || "market" ) ;
resetPreview ( ) ;
}
function resetPreview ( ) {
previewOk = false ;
monitorSubmitting = false ;
clearMessageBox ( ) ;
if ( countdownTimer ) {
clearInterval ( countdownTimer ) ;
countdownTimer = null ;
}
syncSubmitButton ( ) ;
}
function formPayload ( ) {
const fd = new FormData ( form ) ;
const obj = { } ;
fd . forEach ( function ( v , k ) {
if ( v !== "" ) obj [ k ] = v ;
} ) ;
return obj ;
}
function requestPreview ( ) {
return fetch ( "/strategy/roll/preview" , {
method : "POST" ,
headers : { "Content-Type" : "application/json" , Accept : "application/json" } ,
body : JSON . stringify ( formPayload ( ) ) ,
credentials : "same-origin" ,
} ) . then ( function ( r ) {
return r . json ( ) ;
} ) ;
}
function runPreview ( ) {
resetPreview ( ) ;
if ( ! symbolSel . value ) {
showReject ( "请先选择持仓币种" ) ;
return ;
}
if ( previewBtn ) previewBtn . disabled = true ;
requestPreview ( )
. then ( function ( data ) {
if ( previewBtn ) previewBtn . disabled = false ;
if ( ! data . ok ) {
showReject ( data . msg || "预览失败" ) ;
return ;
}
const p = data . preview || { } ;
lastPreviewMode = p . add _mode || modeSel . value ;
showPreviewResult ( p ) ;
previewOk = true ;
if ( lastPreviewMode === "market" ) {
startCountdown ( 10 ) ;
} else {
syncSubmitButton ( ) ;
}
} )
. catch ( function ( ) {
if ( previewBtn ) previewBtn . disabled = false ;
showReject ( "预览请求失败,请稍后重试" ) ;
} ) ;
}
function runMonitorSubmit ( ) {
if ( monitorSubmitting ) return ;
if ( ! symbolSel . value ) {
showReject ( "请先选择持仓币种" ) ;
return ;
}
monitorSubmitting = true ;
if ( submitBtn ) submitBtn . disabled = true ;
requestPreview ( )
. then ( function ( data ) {
monitorSubmitting = false ;
if ( submitBtn && ! trendLocked ) {
submitBtn . disabled = false ;
submitBtn . removeAttribute ( "disabled" ) ;
}
if ( ! data . ok ) {
showReject ( data . msg || "无法提交监控" ) ;
return ;
}
const p = data . preview || { } ;
const modeLabel = modeSel . options [ modeSel . selectedIndex ] . text ;
const summary =
"约 " +
( p . add _amount _display != null ? p . add _amount _display : p . add _amount _raw ) +
" 张 · 触发参考价 " +
( p . add _price _display != null ? p . add _price _display : p . add _price ) +
" · 新止损 " +
( p . new _sl _display != null ? p . new _sl _display : p . new _stop _loss ) ;
if ( ! confirm ( "确认提交「" + modeLabel + "」?\n" + summary ) ) {
return ;
}
submitRollForm ( form ) ;
} )
. catch ( function ( ) {
monitorSubmitting = false ;
if ( submitBtn && ! trendLocked ) {
submitBtn . disabled = false ;
submitBtn . removeAttribute ( "disabled" ) ;
}
showReject ( "校验请求失败,请稍后重试" ) ;
} ) ;
}
function startCountdown ( sec ) {
let left = sec ;
if ( submitBtn ) submitBtn . disabled = true ;
if ( countdownEl ) {
countdownEl . style . display = "block" ;
countdownEl . textContent = "市价加仓:" + left + " 秒后可执行(修改表单将取消预览)" ;
}
countdownTimer = setInterval ( function ( ) {
left -= 1 ;
if ( left <= 0 ) {
clearInterval ( countdownTimer ) ;
countdownTimer = null ;
if ( countdownEl ) countdownEl . textContent = "可以执行市价加仓" ;
syncSubmitButton ( ) ;
return ;
}
if ( countdownEl ) countdownEl . textContent = "市价加仓:" + left + " 秒后可执行" ;
} , 1000 ) ;
}
symbolSel . addEventListener ( "change" , function ( ) {
syncDirectionLock ( ) ;
resetPreview ( ) ;
} ) ;
modeSel . addEventListener ( "change" , syncFieldVisibility ) ;
form . addEventListener ( "input" , resetPreview ) ;
form . addEventListener ( "change" , function ( e ) {
if ( e . target !== previewBtn ) resetPreview ( ) ;
} ) ;
if ( previewBtn ) previewBtn . addEventListener ( "click" , runPreview ) ;
form . addEventListener ( "submit" , function ( e ) {
e . preventDefault ( ) ;
if ( isMonitorMode ( ) ) {
runMonitorSubmit ( ) ;
return ;
}
if ( ! previewOk ) {
showReject ( "请先点击「预览」并通过校验" ) ;
return ;
}
if ( submitBtn && submitBtn . disabled ) {
showReject ( "请等待 10 秒确认倒计时结束后再执行市价加仓" ) ;
return ;
}
const modeLabel = modeSel . options [ modeSel . selectedIndex ] . text ;
if ( ! confirm ( "确认提交「" + modeLabel + "」?" ) ) {
return ;
}
submitRollForm ( form ) ;
} ) ;
syncDirectionLock ( ) ;
syncFieldVisibility ( ) ;
}
symbolSel . addEventListener ( "change" , function ( ) {
syncDirectionLock ( ) ;
resetPreview ( ) ;
} ) ;
modeSel . addEventListener ( "change" , syncFieldVisibility ) ;
form . addEventListener ( "input" , resetPreview ) ;
form . addEventListener ( "change" , function ( e ) {
if ( e . target !== previewBtn ) resetPreview ( ) ;
} ) ;
if ( previewBtn ) previewBtn . addEventListener ( "click" , runPreview ) ;
form . addEventListener ( "submit" , function ( e ) {
if ( isMonitorMode ( ) ) {
e . preventDefault ( ) ;
runMonitorSubmit ( ) ;
return ;
}
if ( ! previewOk ) {
e . preventDefault ( ) ;
showReject ( "请先点击「预览」并通过校验" ) ;
return ;
}
if ( submitBtn && submitBtn . disabled ) {
e . preventDefault ( ) ;
showReject ( "请等待 10 秒确认倒计时结束后再执行市价加仓" ) ;
return ;
}
const modeLabel = modeSel . options [ modeSel . selectedIndex ] . text ;
if ( ! confirm ( "确认提交「" + modeLabel + "」?" ) ) {
e . preventDefault ( ) ;
}
} ) ;
syncDirectionLock ( ) ;
syncFieldVisibility ( ) ;
window . initStrategyRollForm = initStrategyRollForm ;
initStrategyRollForm ( ) ;
} ) ( ) ;