This commit is contained in:
parent
7a2dfd95e7
commit
cf54f4b98d
1 changed files with 35 additions and 7 deletions
|
|
@ -844,12 +844,35 @@ class LiveSignalTrader:
|
||||||
n_tranches = min(n_desired_tranches, max_tranches)
|
n_tranches = min(n_desired_tranches, max_tranches)
|
||||||
return total_lot, n_tranches
|
return total_lot, n_tranches
|
||||||
|
|
||||||
|
MAX_ENTRY_DEVIATION = 0.02 # 2 % vom Live-Kurs → Parse-Fehler
|
||||||
|
|
||||||
def _resolve_missing_fields(self, signal):
|
def _resolve_missing_fields(self, signal):
|
||||||
"""Same default-fill logic as the backtester's
|
"""Same default-fill logic as the backtester's
|
||||||
_resolve_informal_signals, just resolved against a live price
|
_resolve_informal_signals, just resolved against a live price
|
||||||
instead of historical bars."""
|
instead of historical bars.
|
||||||
|
|
||||||
|
Zusätzlich: ein geparster Einstieg, der weit vom Live-Kurs entfernt
|
||||||
|
ist (z.B. 2077.75 statt 4127 durch "4126.5-29"), wird verworfen und
|
||||||
|
durch den Live-Kurs ersetzt. SL/TP, die dann auf der falschen Seite
|
||||||
|
oder unplausibel weit weg liegen, werden neu aus dem Default-Plan
|
||||||
|
gebildet bzw. das Signal wird übersprungen."""
|
||||||
|
live = self._get_live_price(signal['pair'], signal['action'])
|
||||||
|
if signal['entry'] is not None and live:
|
||||||
|
dev = abs(signal['entry'] - live) / live
|
||||||
|
if dev > self.MAX_ENTRY_DEVIATION:
|
||||||
|
logger.warning(f"[{self.channel_name}] Entry {signal['entry']} weicht {dev:.0%} vom "
|
||||||
|
f"Live-Kurs {live} ab – Parse-Fehler, verwende Live-Kurs")
|
||||||
|
signal['entry'] = live
|
||||||
|
signal['tp_levels'] = [t for t in signal['tp_levels']
|
||||||
|
if abs(t - live) / live <= self.MAX_ENTRY_DEVIATION]
|
||||||
|
if signal['entry'] is not None and signal['sl'] is not None:
|
||||||
|
e, sl = signal['entry'], signal['sl']
|
||||||
|
wrong_side = (signal['action'] == 'BUY' and sl >= e) or (signal['action'] == 'SELL' and sl <= e)
|
||||||
|
if wrong_side or abs(sl - e) / e > self.MAX_ENTRY_DEVIATION:
|
||||||
|
logger.warning(f"[{self.channel_name}] SL {sl} unplausibel zu Entry {e} – Signal übersprungen")
|
||||||
|
return False
|
||||||
if signal['entry'] is None:
|
if signal['entry'] is None:
|
||||||
price = self._get_live_price(signal['pair'], signal['action'])
|
price = live or self._get_live_price(signal['pair'], signal['action'])
|
||||||
if price is None:
|
if price is None:
|
||||||
return False
|
return False
|
||||||
signal['entry'] = price
|
signal['entry'] = price
|
||||||
|
|
@ -858,6 +881,9 @@ class LiveSignalTrader:
|
||||||
if signal['sl'] is None:
|
if signal['sl'] is None:
|
||||||
dist = self.analyzer.default_sl_pips * pip
|
dist = self.analyzer.default_sl_pips * pip
|
||||||
signal['sl'] = signal['entry'] - dist if signal['action'] == 'BUY' else signal['entry'] + dist
|
signal['sl'] = signal['entry'] - dist if signal['action'] == 'BUY' else signal['entry'] + dist
|
||||||
|
signal['tp_levels'] = [t for t in signal['tp_levels']
|
||||||
|
if (signal['action'] == 'BUY' and t > signal['entry'])
|
||||||
|
or (signal['action'] == 'SELL' and t < signal['entry'])]
|
||||||
if not signal['tp_levels']:
|
if not signal['tp_levels']:
|
||||||
levels = [signal['entry'] + (d * pip if signal['action'] == 'BUY' else -d * pip)
|
levels = [signal['entry'] + (d * pip if signal['action'] == 'BUY' else -d * pip)
|
||||||
for d in self.analyzer.default_tp_ladder_pips]
|
for d in self.analyzer.default_tp_ladder_pips]
|
||||||
|
|
@ -968,8 +994,10 @@ class LiveSignalTrader:
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
positions = resp.json().get('positions', [])
|
positions = resp.json().get('positions', [])
|
||||||
ticket_set = set(int(t) for t in tickets)
|
ticket_set = set(int(t) for t in tickets)
|
||||||
fills = [float(p['price_open']) for p in positions
|
fills = [float(p.get('open_price') or p.get('price_open'))
|
||||||
if int(p.get('ticket', -1)) in ticket_set and p.get('price_open')]
|
for p in positions
|
||||||
|
if int(p.get('ticket', -1)) in ticket_set
|
||||||
|
and (p.get('open_price') or p.get('price_open'))]
|
||||||
if fills:
|
if fills:
|
||||||
avg = sum(fills) / len(fills)
|
avg = sum(fills) / len(fills)
|
||||||
logger.info(f"[LIVE] fill prices from /positions: {fills} → avg {avg:.2f}")
|
logger.info(f"[LIVE] fill prices from /positions: {fills} → avg {avg:.2f}")
|
||||||
|
|
@ -1149,7 +1177,7 @@ def main():
|
||||||
"Eliminates auto-generated SL/TP guesses but may miss fast-moving entries.")
|
"Eliminates auto-generated SL/TP guesses but may miss fast-moving entries.")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.channel_id is None:
|
if args.channel_id is None and args.channels in ('ch1', 'both') and not args.channel2_id:
|
||||||
logger.error("No channel ID given - pass --channel-id or set CHANNEL_ID in .env")
|
logger.error("No channel ID given - pass --channel-id or set CHANNEL_ID in .env")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if not (API_ID and API_HASH and PHONE_NUMBER):
|
if not (API_ID and API_HASH and PHONE_NUMBER):
|
||||||
|
|
@ -1172,7 +1200,7 @@ def main():
|
||||||
logger.info("LIVE mode auto-confirmed (non-interactive / nohup)")
|
logger.info("LIVE mode auto-confirmed (non-interactive / nohup)")
|
||||||
|
|
||||||
# Resolve which channels to actually activate
|
# Resolve which channels to actually activate
|
||||||
run_ch1 = args.channels in ('ch1', 'both')
|
run_ch1 = args.channels in ('ch1', 'both') and args.channel_id is not None
|
||||||
run_ch2 = args.channels in ('ch2', 'both') and bool(args.channel2_id)
|
run_ch2 = args.channels in ('ch2', 'both') and bool(args.channel2_id)
|
||||||
|
|
||||||
if args.channels in ('ch2', 'both') and not args.channel2_id:
|
if args.channels in ('ch2', 'both') and not args.channel2_id:
|
||||||
|
|
@ -1230,7 +1258,7 @@ def main():
|
||||||
f"using seed capital {args.initial_capital:.2f} USDT for sizing until the "
|
f"using seed capital {args.initial_capital:.2f} USDT for sizing until the "
|
||||||
f"first signal fires (will retry before each trade)")
|
f"first signal fires (will retry before each trade)")
|
||||||
|
|
||||||
client = TelegramClient('mysession', API_ID, API_HASH)
|
client = TelegramClient(os.getenv('TG_SESSION', 'mysession'), API_ID, API_HASH)
|
||||||
|
|
||||||
# --- Event handlers --------------------------------------------------------
|
# --- Event handlers --------------------------------------------------------
|
||||||
if trader is not None:
|
if trader is not None:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue