Fix breakeven badge still showing after roll lowers stop loss.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -233,6 +233,7 @@ from lib.trade.order_monitor_display_lib import (
|
|||||||
apply_order_price_display_fields,
|
apply_order_price_display_fields,
|
||||||
enrich_order_display_fields,
|
enrich_order_display_fields,
|
||||||
order_monitor_tpsl_needs_sync,
|
order_monitor_tpsl_needs_sync,
|
||||||
|
stale_breakeven_armed,
|
||||||
)
|
)
|
||||||
from lib.common.wechat_notify_lib import build_wechat_rs_level_message, send_wechat_webhook
|
from lib.common.wechat_notify_lib import build_wechat_rs_level_message, send_wechat_webhook
|
||||||
from lib.hub.hub_auth import request_allowed as hub_request_allowed
|
from lib.hub.hub_auth import request_allowed as hub_request_allowed
|
||||||
@@ -6489,6 +6490,12 @@ def check_order_monitors():
|
|||||||
# 到达设定 R 倍后,按阶梯持续上移止损(本地风控层)
|
# 到达设定 R 倍后,按阶梯持续上移止损(本地风控层)
|
||||||
risk_amount = float(r["risk_amount"] or 0)
|
risk_amount = float(r["risk_amount"] or 0)
|
||||||
breakeven_armed = int(r["breakeven_armed"] or 0)
|
breakeven_armed = int(r["breakeven_armed"] or 0)
|
||||||
|
if stale_breakeven_armed(direction, trigger_price, stop_loss, breakeven_armed):
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE order_monitors SET breakeven_armed=0, breakeven_price=NULL WHERE id=?",
|
||||||
|
(pid,),
|
||||||
|
)
|
||||||
|
breakeven_armed = 0
|
||||||
trigger_rr = float(r["breakeven_rr_trigger"] or BREAKEVEN_RR_TRIGGER)
|
trigger_rr = float(r["breakeven_rr_trigger"] or BREAKEVEN_RR_TRIGGER)
|
||||||
step_r = float(r["breakeven_step_r"] or BREAKEVEN_STEP_R or 1.0)
|
step_r = float(r["breakeven_step_r"] or BREAKEVEN_STEP_R or 1.0)
|
||||||
step_r = 1.0 if step_r <= 0 else step_r
|
step_r = 1.0 if step_r <= 0 else step_r
|
||||||
|
|||||||
@@ -235,6 +235,7 @@ from lib.trade.order_monitor_display_lib import (
|
|||||||
apply_order_price_display_fields,
|
apply_order_price_display_fields,
|
||||||
enrich_order_display_fields,
|
enrich_order_display_fields,
|
||||||
order_monitor_tpsl_needs_sync,
|
order_monitor_tpsl_needs_sync,
|
||||||
|
stale_breakeven_armed,
|
||||||
)
|
)
|
||||||
from lib.common.wechat_notify_lib import build_wechat_rs_level_message, send_wechat_webhook
|
from lib.common.wechat_notify_lib import build_wechat_rs_level_message, send_wechat_webhook
|
||||||
from lib.hub.hub_auth import request_allowed as hub_request_allowed
|
from lib.hub.hub_auth import request_allowed as hub_request_allowed
|
||||||
@@ -6163,6 +6164,12 @@ def check_order_monitors():
|
|||||||
# 到达设定 R 倍后,按阶梯持续上移止损(本地风控层)
|
# 到达设定 R 倍后,按阶梯持续上移止损(本地风控层)
|
||||||
risk_amount = float(r["risk_amount"] or 0)
|
risk_amount = float(r["risk_amount"] or 0)
|
||||||
breakeven_armed = int(r["breakeven_armed"] or 0)
|
breakeven_armed = int(r["breakeven_armed"] or 0)
|
||||||
|
if stale_breakeven_armed(direction, trigger_price, stop_loss, breakeven_armed):
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE order_monitors SET breakeven_armed=0, breakeven_price=NULL WHERE id=?",
|
||||||
|
(pid,),
|
||||||
|
)
|
||||||
|
breakeven_armed = 0
|
||||||
trigger_rr = float(r["breakeven_rr_trigger"] or BREAKEVEN_RR_TRIGGER)
|
trigger_rr = float(r["breakeven_rr_trigger"] or BREAKEVEN_RR_TRIGGER)
|
||||||
step_r = float(r["breakeven_step_r"] or BREAKEVEN_STEP_R or 1.0)
|
step_r = float(r["breakeven_step_r"] or BREAKEVEN_STEP_R or 1.0)
|
||||||
step_r = 1.0 if step_r <= 0 else step_r
|
step_r = 1.0 if step_r <= 0 else step_r
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ from lib.trade.order_monitor_display_lib import (
|
|||||||
apply_order_price_display_fields,
|
apply_order_price_display_fields,
|
||||||
enrich_order_display_fields,
|
enrich_order_display_fields,
|
||||||
order_monitor_tpsl_needs_sync,
|
order_monitor_tpsl_needs_sync,
|
||||||
|
stale_breakeven_armed,
|
||||||
)
|
)
|
||||||
from lib.common.wechat_notify_lib import build_wechat_rs_level_message, send_wechat_webhook
|
from lib.common.wechat_notify_lib import build_wechat_rs_level_message, send_wechat_webhook
|
||||||
from lib.hub.hub_auth import request_allowed as hub_request_allowed
|
from lib.hub.hub_auth import request_allowed as hub_request_allowed
|
||||||
@@ -5968,6 +5969,12 @@ def check_order_monitors():
|
|||||||
# 到达设定 R 倍后,按阶梯持续上移止损(本地风控层)
|
# 到达设定 R 倍后,按阶梯持续上移止损(本地风控层)
|
||||||
risk_amount = float(r["risk_amount"] or 0)
|
risk_amount = float(r["risk_amount"] or 0)
|
||||||
breakeven_armed = int(r["breakeven_armed"] or 0)
|
breakeven_armed = int(r["breakeven_armed"] or 0)
|
||||||
|
if stale_breakeven_armed(direction, trigger_price, stop_loss, breakeven_armed):
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE order_monitors SET breakeven_armed=0, breakeven_price=NULL WHERE id=?",
|
||||||
|
(pid,),
|
||||||
|
)
|
||||||
|
breakeven_armed = 0
|
||||||
trigger_rr = float(r["breakeven_rr_trigger"] or BREAKEVEN_RR_TRIGGER)
|
trigger_rr = float(r["breakeven_rr_trigger"] or BREAKEVEN_RR_TRIGGER)
|
||||||
step_r = float(r["breakeven_step_r"] or BREAKEVEN_STEP_R or 1.0)
|
step_r = float(r["breakeven_step_r"] or BREAKEVEN_STEP_R or 1.0)
|
||||||
step_r = 1.0 if step_r <= 0 else step_r
|
step_r = 1.0 if step_r <= 0 else step_r
|
||||||
|
|||||||
@@ -844,9 +844,41 @@ function formatRrRatio(rr){
|
|||||||
return `${body}:1`;
|
return `${body}:1`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function paintBreakevenBadge(orderId, secured){
|
function resolveSlBreakevenSecured(orderId, snap){
|
||||||
|
if(!snap) return false;
|
||||||
|
const et = snap.exchange_tpsl;
|
||||||
|
const slSlot = et && et.sl ? et.sl : null;
|
||||||
|
let slPx = NaN;
|
||||||
|
if(slSlot){
|
||||||
|
const raw = slSlot.trigger_price != null ? slSlot.trigger_price : slSlot.trigger_display;
|
||||||
|
slPx = raw != null && raw !== "" ? Number(raw) : NaN;
|
||||||
|
}
|
||||||
|
if(!Number.isFinite(slPx) || slPx <= 0){
|
||||||
|
slPx = snap.stop_loss != null && snap.stop_loss !== "" ? Number(snap.stop_loss) : NaN;
|
||||||
|
}
|
||||||
|
let entry = snap.avg_entry_price != null && snap.avg_entry_price !== "" ? Number(snap.avg_entry_price) : NaN;
|
||||||
|
let direction = snap.direction ? String(snap.direction).toLowerCase() : "";
|
||||||
|
const card = document.getElementById(`order-row-${orderId}`);
|
||||||
|
if(card){
|
||||||
|
if(!direction) direction = String(card.getAttribute("data-direction") || "long").toLowerCase();
|
||||||
|
if(!Number.isFinite(entry)){
|
||||||
|
const ent = card.getAttribute("data-entry");
|
||||||
|
if(ent){
|
||||||
|
const n = Number(String(ent).replace(/,/g, ""));
|
||||||
|
if(Number.isFinite(n) && n > 0) entry = n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(Number.isFinite(slPx) && Number.isFinite(entry) && entry > 0){
|
||||||
|
if(direction === "short") return slPx <= entry;
|
||||||
|
return slPx >= entry;
|
||||||
|
}
|
||||||
|
return snap.sl_breakeven_secured === true || snap.sl_breakeven_secured === 1;
|
||||||
|
}
|
||||||
|
function paintBreakevenBadge(orderId, snap){
|
||||||
const wrap = document.getElementById(`order-be-wrap-${orderId}`);
|
const wrap = document.getElementById(`order-be-wrap-${orderId}`);
|
||||||
if(!wrap) return;
|
if(!wrap) return;
|
||||||
|
const secured = (snap && typeof snap === "object") ? resolveSlBreakevenSecured(orderId, snap) : !!snap;
|
||||||
wrap.style.display = secured ? "inline-flex" : "none";
|
wrap.style.display = secured ? "inline-flex" : "none";
|
||||||
}
|
}
|
||||||
function paintPlanTpslDisplay(orderId, snap){
|
function paintPlanTpslDisplay(orderId, snap){
|
||||||
@@ -1001,7 +1033,7 @@ function refreshPriceSnapshot(){
|
|||||||
paintLatestRiskDisplay(o.id, o);
|
paintLatestRiskDisplay(o.id, o);
|
||||||
paintContractsDisplay(o.id, o);
|
paintContractsDisplay(o.id, o);
|
||||||
paintTpProfitDisplay(o.id, o);
|
paintTpProfitDisplay(o.id, o);
|
||||||
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
paintBreakevenBadge(o.id, o);
|
||||||
if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl);
|
if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl);
|
||||||
paintPlanTpslDisplay(o.id, o);
|
paintPlanTpslDisplay(o.id, o);
|
||||||
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
||||||
@@ -1383,7 +1415,7 @@ function refreshPriceSnapshotConditional(){
|
|||||||
}
|
}
|
||||||
const rrEl = document.getElementById(`order-rr-${o.id}`);
|
const rrEl = document.getElementById(`order-rr-${o.id}`);
|
||||||
if(rrEl) rrEl.innerText = formatRrRatio(o.rr_ratio);
|
if(rrEl) rrEl.innerText = formatRrRatio(o.rr_ratio);
|
||||||
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
paintBreakevenBadge(o.id, o);
|
||||||
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
|
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
|
||||||
paintPlanTpslDisplay(o.id, o);
|
paintPlanTpslDisplay(o.id, o);
|
||||||
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
||||||
|
|||||||
@@ -1451,9 +1451,41 @@ function formatRrRatio(rr){
|
|||||||
return `${body}:1`;
|
return `${body}:1`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function paintBreakevenBadge(orderId, secured){
|
function resolveSlBreakevenSecured(orderId, snap){
|
||||||
|
if(!snap) return false;
|
||||||
|
const et = snap.exchange_tpsl;
|
||||||
|
const slSlot = et && et.sl ? et.sl : null;
|
||||||
|
let slPx = NaN;
|
||||||
|
if(slSlot){
|
||||||
|
const raw = slSlot.trigger_price != null ? slSlot.trigger_price : slSlot.trigger_display;
|
||||||
|
slPx = raw != null && raw !== "" ? Number(raw) : NaN;
|
||||||
|
}
|
||||||
|
if(!Number.isFinite(slPx) || slPx <= 0){
|
||||||
|
slPx = snap.stop_loss != null && snap.stop_loss !== "" ? Number(snap.stop_loss) : NaN;
|
||||||
|
}
|
||||||
|
let entry = snap.avg_entry_price != null && snap.avg_entry_price !== "" ? Number(snap.avg_entry_price) : NaN;
|
||||||
|
let direction = snap.direction ? String(snap.direction).toLowerCase() : "";
|
||||||
|
const card = document.getElementById(`order-row-${orderId}`);
|
||||||
|
if(card){
|
||||||
|
if(!direction) direction = String(card.getAttribute("data-direction") || "long").toLowerCase();
|
||||||
|
if(!Number.isFinite(entry)){
|
||||||
|
const ent = card.getAttribute("data-entry");
|
||||||
|
if(ent){
|
||||||
|
const n = Number(String(ent).replace(/,/g, ""));
|
||||||
|
if(Number.isFinite(n) && n > 0) entry = n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(Number.isFinite(slPx) && Number.isFinite(entry) && entry > 0){
|
||||||
|
if(direction === "short") return slPx <= entry;
|
||||||
|
return slPx >= entry;
|
||||||
|
}
|
||||||
|
return snap.sl_breakeven_secured === true || snap.sl_breakeven_secured === 1;
|
||||||
|
}
|
||||||
|
function paintBreakevenBadge(orderId, snap){
|
||||||
const wrap = document.getElementById(`order-be-wrap-${orderId}`);
|
const wrap = document.getElementById(`order-be-wrap-${orderId}`);
|
||||||
if(!wrap) return;
|
if(!wrap) return;
|
||||||
|
const secured = (snap && typeof snap === "object") ? resolveSlBreakevenSecured(orderId, snap) : !!snap;
|
||||||
wrap.style.display = secured ? "inline-flex" : "none";
|
wrap.style.display = secured ? "inline-flex" : "none";
|
||||||
}
|
}
|
||||||
function paintPlanTpslDisplay(orderId, snap){
|
function paintPlanTpslDisplay(orderId, snap){
|
||||||
@@ -1638,7 +1670,7 @@ function refreshPriceSnapshot(){
|
|||||||
paintLatestRiskDisplay(o.id, o);
|
paintLatestRiskDisplay(o.id, o);
|
||||||
paintContractsDisplay(o.id, o);
|
paintContractsDisplay(o.id, o);
|
||||||
paintTpProfitDisplay(o.id, o);
|
paintTpProfitDisplay(o.id, o);
|
||||||
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
paintBreakevenBadge(o.id, o);
|
||||||
if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl);
|
if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl);
|
||||||
paintPlanTpslDisplay(o.id, o);
|
paintPlanTpslDisplay(o.id, o);
|
||||||
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
||||||
@@ -2069,7 +2101,7 @@ function refreshPriceSnapshotConditional(){
|
|||||||
paintLatestRiskDisplay(o.id, o);
|
paintLatestRiskDisplay(o.id, o);
|
||||||
paintContractsDisplay(o.id, o);
|
paintContractsDisplay(o.id, o);
|
||||||
paintTpProfitDisplay(o.id, o);
|
paintTpProfitDisplay(o.id, o);
|
||||||
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
paintBreakevenBadge(o.id, o);
|
||||||
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
|
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
|
||||||
paintPlanTpslDisplay(o.id, o);
|
paintPlanTpslDisplay(o.id, o);
|
||||||
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
||||||
|
|||||||
@@ -507,7 +507,8 @@ def _roll_execute(cfg: dict, data: dict) -> tuple[bool, str]:
|
|||||||
|
|
||||||
live_qty = normalize_contracts_qty(live_qty)
|
live_qty = normalize_contracts_qty(live_qty)
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE order_monitors SET stop_loss=?, order_amount=? WHERE id=?",
|
"""UPDATE order_monitors SET stop_loss=?, order_amount=?,
|
||||||
|
breakeven_armed=0, breakeven_price=NULL WHERE id=?""",
|
||||||
(new_sl, live_qty, mon["id"]),
|
(new_sl, live_qty, mon["id"]),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|||||||
@@ -459,7 +459,9 @@ def _execute_pending_roll_leg(
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE order_monitors SET stop_loss=?, order_amount=? WHERE id=? AND status='active'",
|
"""UPDATE order_monitors SET stop_loss=?, order_amount=?,
|
||||||
|
breakeven_armed=0, breakeven_price=NULL
|
||||||
|
WHERE id=? AND status='active'""",
|
||||||
(sl, live_qty, mon["id"]),
|
(sl, live_qty, mon["id"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,25 @@ def tpsl_update_passes_rr_gate(
|
|||||||
return False, f"计划盈亏比 {rr_txt}:1 低于最低要求 {min_rr}:1(盈利侧保本止损不受此限)"
|
return False, f"计划盈亏比 {rr_txt}:1 低于最低要求 {min_rr}:1(盈利侧保本止损不受此限)"
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_breakeven_entry_price(entry_price: Any, avg_entry_price: Any = None) -> Optional[float]:
|
||||||
|
"""保本判断基准价:有持仓加权均价时优先(滚仓后),否则用首仓成交价."""
|
||||||
|
avg = _positive_float(avg_entry_price)
|
||||||
|
if avg is not None:
|
||||||
|
return avg
|
||||||
|
return _positive_float(entry_price)
|
||||||
|
|
||||||
|
|
||||||
|
def stale_breakeven_armed(direction: str, entry_price: Any, stop_loss: Any, breakeven_armed: Any) -> bool:
|
||||||
|
"""止损已回到亏损侧时 breakeven_armed 视为过期(如滚仓下移止损)."""
|
||||||
|
try:
|
||||||
|
armed = int(breakeven_armed or 0) != 0
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return False
|
||||||
|
if not armed:
|
||||||
|
return False
|
||||||
|
return not stop_is_profit_protecting(direction, entry_price, stop_loss)
|
||||||
|
|
||||||
|
|
||||||
def is_sl_breakeven_secured(direction: str, entry_price: Any, exchange_sl_price: Any) -> bool:
|
def is_sl_breakeven_secured(direction: str, entry_price: Any, exchange_sl_price: Any) -> bool:
|
||||||
"""
|
"""
|
||||||
交易所当前止损相对开仓成交价是否已保本.
|
交易所当前止损相对开仓成交价是否已保本.
|
||||||
@@ -290,8 +309,10 @@ def apply_order_price_display_fields(
|
|||||||
stop_loss,
|
stop_loss,
|
||||||
take_profit,
|
take_profit,
|
||||||
)
|
)
|
||||||
|
risk_entry = resolve_breakeven_entry_price(entry_price, avg_entry_price)
|
||||||
|
payload["avg_entry_price"] = risk_entry
|
||||||
payload["sl_breakeven_secured"] = sl_breakeven_from_exchange_tpsl(
|
payload["sl_breakeven_secured"] = sl_breakeven_from_exchange_tpsl(
|
||||||
direction, entry_price, exchange_tpsl
|
direction, risk_entry, exchange_tpsl
|
||||||
)
|
)
|
||||||
payload["stop_loss"] = disp_sl
|
payload["stop_loss"] = disp_sl
|
||||||
payload["take_profit"] = disp_tp
|
payload["take_profit"] = disp_tp
|
||||||
@@ -310,7 +331,6 @@ def apply_order_price_display_fields(
|
|||||||
payload["contracts"] = c
|
payload["contracts"] = c
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
pass
|
pass
|
||||||
risk_entry = _positive_float(avg_entry_price) or _positive_float(entry_price)
|
|
||||||
payload["latest_risk_amount"] = calc_latest_risk_amount(
|
payload["latest_risk_amount"] = calc_latest_risk_amount(
|
||||||
direction,
|
direction,
|
||||||
risk_entry,
|
risk_entry,
|
||||||
|
|||||||
@@ -2058,14 +2058,21 @@
|
|||||||
function isBreakevenSecured(side, entry, monitorOrder, cond, pos) {
|
function isBreakevenSecured(side, entry, monitorOrder, cond, pos) {
|
||||||
const mo = monitorOrder || {};
|
const mo = monitorOrder || {};
|
||||||
const p = pos || {};
|
const p = pos || {};
|
||||||
if (mo.sl_breakeven_secured === true || mo.sl_breakeven_secured === 1) return true;
|
|
||||||
if (p.sl_breakeven_secured === true || p.sl_breakeven_secured === 1) return true;
|
|
||||||
const { sl } = pickExTpslOrders(cond);
|
const { sl } = pickExTpslOrders(cond);
|
||||||
const trig = sl && sl.trigger_price != null ? Number(sl.trigger_price) : NaN;
|
const trig = sl && sl.trigger_price != null ? Number(sl.trigger_price) : NaN;
|
||||||
const e = Number(entry);
|
const liveEntry =
|
||||||
if (!Number.isFinite(trig) || !Number.isFinite(e)) return false;
|
p.entry_price != null && p.entry_price !== ""
|
||||||
if ((side || "long").toLowerCase() === "short") return trig <= e;
|
? Number(p.entry_price)
|
||||||
return trig >= e;
|
: mo.avg_entry_price != null && mo.avg_entry_price !== ""
|
||||||
|
? Number(mo.avg_entry_price)
|
||||||
|
: Number(entry);
|
||||||
|
if (Number.isFinite(trig) && Number.isFinite(liveEntry)) {
|
||||||
|
if ((side || "long").toLowerCase() === "short") return trig <= liveEntry;
|
||||||
|
return trig >= liveEntry;
|
||||||
|
}
|
||||||
|
if (mo.sl_breakeven_secured === true || mo.sl_breakeven_secured === 1) return true;
|
||||||
|
if (p.sl_breakeven_secured === true || p.sl_breakeven_secured === 1) return true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function breakevenBadgeHtml() {
|
function breakevenBadgeHtml() {
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ from lib.trade.order_monitor_display_lib import (
|
|||||||
is_sl_breakeven_secured,
|
is_sl_breakeven_secured,
|
||||||
monitor_open_stop_loss,
|
monitor_open_stop_loss,
|
||||||
order_monitor_tpsl_needs_sync,
|
order_monitor_tpsl_needs_sync,
|
||||||
|
resolve_breakeven_entry_price,
|
||||||
resolve_live_tpsl_prices,
|
resolve_live_tpsl_prices,
|
||||||
sl_breakeven_from_exchange_tpsl,
|
sl_breakeven_from_exchange_tpsl,
|
||||||
snapshot_rr,
|
snapshot_rr,
|
||||||
snapshot_stop_loss,
|
snapshot_stop_loss,
|
||||||
|
stale_breakeven_armed,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -106,7 +108,7 @@ def test_apply_order_price_display_fields_live_sl():
|
|||||||
)
|
)
|
||||||
assert payload["stop_loss"] == 1661
|
assert payload["stop_loss"] == 1661
|
||||||
assert payload["stop_loss_display"] == "1661.00"
|
assert payload["stop_loss_display"] == "1661.00"
|
||||||
assert payload["sl_breakeven_secured"] is True
|
assert payload["sl_breakeven_secured"] is False
|
||||||
assert payload["rr_ratio"] is not None
|
assert payload["rr_ratio"] is not None
|
||||||
assert payload["latest_risk_amount"] is not None
|
assert payload["latest_risk_amount"] is not None
|
||||||
assert payload["latest_risk_amount"] >= 0
|
assert payload["latest_risk_amount"] >= 0
|
||||||
@@ -149,3 +151,30 @@ def test_calc_latest_risk_amount_long():
|
|||||||
def test_calc_latest_risk_amount_profit_side_stop():
|
def test_calc_latest_risk_amount_profit_side_stop():
|
||||||
risk = calc_latest_risk_amount("long", 100, 101, exchange_notional=1000)
|
risk = calc_latest_risk_amount("long", 100, 101, exchange_notional=1000)
|
||||||
assert risk == 0.0
|
assert risk == 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_breakeven_entry_price_prefers_avg():
|
||||||
|
assert resolve_breakeven_entry_price(1777.39, 1777.2) == 1777.2
|
||||||
|
assert resolve_breakeven_entry_price(1777.39, None) == 1777.39
|
||||||
|
|
||||||
|
|
||||||
|
def test_roll_long_not_breakeven_with_avg_entry():
|
||||||
|
payload = {}
|
||||||
|
apply_order_price_display_fields(
|
||||||
|
payload,
|
||||||
|
direction="long",
|
||||||
|
entry_price=1777.39,
|
||||||
|
initial_stop_loss=1750,
|
||||||
|
stop_loss=1767,
|
||||||
|
take_profit=1833,
|
||||||
|
calc_rr_ratio_fn=_calc_rr,
|
||||||
|
exchange_tpsl={"sl": {"trigger_price": 1767}, "tp": {"trigger_price": 1833}},
|
||||||
|
avg_entry_price=1777.2,
|
||||||
|
)
|
||||||
|
assert payload["sl_breakeven_secured"] is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_stale_breakeven_armed_after_roll_down():
|
||||||
|
assert stale_breakeven_armed("long", 1777.39, 1767, 1) is True
|
||||||
|
assert stale_breakeven_armed("long", 1777.39, 1778, 1) is False
|
||||||
|
assert stale_breakeven_armed("long", 1777.39, 1767, 0) is False
|
||||||
|
|||||||
Reference in New Issue
Block a user