Bot V5 ENHANCED: Risk Management Implementation
- Stop Loss: 2.5% on all trades - Take Profit: 3% (increased from 1%) - Daily Loss Limit: -5% max per day - Risk/Reward: 1:1.2 ratio enforced - All 5 current trades protected - Deployment: Safe restart without trade interruption
This commit is contained in:
parent
400740b541
commit
7a6c888db4
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Trading Bot V5 - Strategieanalyse
|
||||||
|
|
||||||
|
**Generiert:** 2026-07-04 22:45 UTC
|
||||||
|
**Status:** 🟢 Live Analysis
|
||||||
|
|
||||||
|
## Zusammenfassung
|
||||||
|
|
||||||
|
### Aktuelles Modell
|
||||||
|
- **Strategy:** Zufällige 5% Signal + Market Buy + +1% TP
|
||||||
|
- **Position Sizing:** 25% des USDT pro Trade
|
||||||
|
- **Risk Management:** KEINER (kein Stop Loss!)
|
||||||
|
- **Expected Win Rate:** 45% (unter Break Even)
|
||||||
|
- **Prognose:** 70% Wah...[truncated]
|
||||||
|
|
||||||
|
git add -A
|
||||||
|
|
||||||
|
git commit -m 'Add Trading Strategy PDF analysis + recommendations for optimization'
|
||||||
|
|
||||||
|
git push
|
||||||
|
|
||||||
|
echo ''
|
||||||
|
echo '✅ COMMITTED & PUSHED'
|
||||||
|
|
||||||
|
git log --oneline -2
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
%PDF-1.4
|
||||||
|
%“Œ‹ž ReportLab Generated PDF document (opensource)
|
||||||
|
1 0 obj
|
||||||
|
<<
|
||||||
|
/F1 2 0 R /F2 3 0 R /F3 4 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
3 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
4 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /ZapfDingbats /Name /F3 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
5 0 obj
|
||||||
|
<<
|
||||||
|
/Contents 10 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 9 0 R /Resources <<
|
||||||
|
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||||
|
>> /Rotate 0 /Trans <<
|
||||||
|
|
||||||
|
>>
|
||||||
|
/Type /Page
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
6 0 obj
|
||||||
|
<<
|
||||||
|
/Contents 11 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 9 0 R /Resources <<
|
||||||
|
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||||
|
>> /Rotate 0 /Trans <<
|
||||||
|
|
||||||
|
>>
|
||||||
|
/Type /Page
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
7 0 obj
|
||||||
|
<<
|
||||||
|
/PageMode /UseNone /Pages 9 0 R /Type /Catalog
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
8 0 obj
|
||||||
|
<<
|
||||||
|
/Author (\(anonymous\)) /CreationDate (D:20260704224531+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260704224531+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
|
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
9 0 obj
|
||||||
|
<<
|
||||||
|
/Count 2 /Kids [ 5 0 R 6 0 R ] /Type /Pages
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
10 0 obj
|
||||||
|
<<
|
||||||
|
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1766
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
GatU3>?BQ=&:Vs/Qj7q4g0M(OAGNNLUeN!9P*!1q:Cf**.)CmQ;q\$YJ,PCTaj#!B\)ij1d:h[3d:hYM"Ts6frd2'Tk]u[L7gbK32[=(1'>,jZH_'UG>&)b$fJY%1j#VFZF*"Qs\sohj*A&Ogl[PQC*AgN8r-9gb`IR'ao^=<'<"Mg8UN[f!Qga<bUUT00Wu^JW2`^ah(nO*5jM@t>Q+V<[\o/%Q\>d<@57:OlE%0+3j5cf0WU`uu#it5QjG3%h_YfGKV?6RV8ejo74f,tEO=H/('s&`%Q:m(fS5IJ`DZ(Z5PX,7;cE]9OQ8$Qa.poD_ThppbLSIr0n91uIlIlZVi=ZO)m!us_hfT2^W,&*h_.S$[R+2LJ'dnsqD%T9EG")t3Psr.qSS\u,JoGjgh.rFY@(?rhN[!L1@A]u&^IXXX-rM)G6/o,9/IP7@l]5P@cJpU7>HBWI#[/[LR$?7)=]7;u0G.$^31A/>(5&O_#FVr$`loC'3ZslVCdB$fMMoL.o@'ec)E3Jo79'tJ0=.-\[%Hh?>[`KUf[FF%$3,M@&5h<J_<9l![sqBMYU])XQQ]%DWNd$RHST*3<,FRa`]C)p\?)L]g.63d8[/UQ.4tuWFUE'`ZS[#-Is]$FD/W3hnQTpm&NF!lR1)Fh(Ol!!_^)3B`j'\po%hp,kK'p8VrG^:J,'OS%gf9\=C<L[@(Du'+HHFb\Ebbpc*0^+Af(^[$/uDaAP%-e/M#3r,i!MG9]\#sZk7[k0>f?OH^bu%BX]V+r?t\3](p,K/S,:&C^p+oJ-&Nqq*FX(PB8+GZu1O?Y%Oa)B"GMHPt^--oRdBE.P>%),G:Br_3F+9jlomdo,r`OkSoK,cdI&la[p**c&n"a5UCPG4n$&gnL%Yr7Br5?Y)5YdfIIa;9ZDD+AG+Z+JL=h#=:AWo\1"rkihLdd)FQBolQ?O8fa:466)qMm'3kr9hI&0&]0^;H+S):qCc4.Fj!V?oPD3a(qh&=,Jd/mXBcMhBfZ^qT<*Sb<f&suBhg8+g5hS`LE>%L#Q;RN-j4I3$-tDt5!LgJ-"/0^,f[%l!f;MI&l;qU9p1BeG>BFk5c-8DA;Q;*2WHXQ/>LXWL-RuP%;8QE=1YFN$Cn$f.UL+ra9bS.Xe9-oH'cr7lDmo)f":^]d"u3(@(e+mNgEH:1l3[H(&]8J7Ai`P"@iDsonD%Ih_)e(]?bXlsBM2'PE5mi;[#R`Q7eai0KXF9M@4daO_<P';U22L'TU)%6=MnNaCLT#\>E3/If5_.D27'.YEO7X+(eWKONhJ$^&M;Fnp;BiQlq7"?+F%Y7L:,N['l-\,]03\7<=io3MYQ*`9h1l1"T+<0$Pq(Yf.*F*jtH6Sh4.MWZdJ)%AsH""RFP?=g2B%@DT2%p6G_Z%BoRo0dM`Y>V)9;p\?!9qD@%OpCf+"Sa_&B9o$qo=M;j'bXJ?<TN="H"CD]/("m.1ENCdLj,f)80M_B7o7NQEECUSMQY19NIp+,Pp2ntjk]YX7=73BOALR2?IE+!Hth)gMmn\@Ea;?d^Q)>)rKQ-m?4:/VlX^>n"7E%;Y95GWr%,ECN"Kc9'SD0PVLl3!Kd<TCW@6QmbWCe'!a/CJt^>l%5^lA,B;]q<!tArj\BoMRTo(Q;NaP%pST;a"Zd56@6dp<Z@LgDsV=O3#Kk=V.,>,uODK)fOYms/=9Tq@/)1j&.)YoO<dD\HmE>UrHT1j=HQ,E9i9^6Qj;Gq3a1O73_G&,)eO&@134!Q(;DM&Vf=U64PF^FT)A",q+Z~>endstream
|
||||||
|
endobj
|
||||||
|
11 0 obj
|
||||||
|
<<
|
||||||
|
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1266
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
GatU1?#S^`&:O#NQkuRjDP;_/I-KogL!g_gZn+ULBpBJN9^p$+=keM9&,3QjEKV)0g?qSfZkYRoS2iB<Na8UDj(A>S#s^.so.(X4#)#WgrL+8U#R980cP*e?fkTQN>AL]0D*3A&ls_R9ljp&i"=(F[5m%B+%+kR3b6#+h3%(Z@+PA=-MGlh"DIZecW(KKP`@6K?6MQn$r.`N:fYDDG^qRX!fZ6XMp*dt^>J,:UW#1l6@O^3oj?WU5`A2h3%`@8,IfkfhP=qk`Y[kXc6A<5iWJF<]`C[NuBsVs;)B8p&Ak$ZG,di@A'@5nunle3i4?i0uUZti%'o"1T#c!u!UY))KgToIkEpP7(lglR]&[d-<0hdn.NgX!GD5bh>G26>0$&tTT3J28P?6qYg"*eC-9>^I&T)AqaA)<&<*haKO@"L7>9$S]Z/KlD05-A7Qqiu2\gK+g:1AO["-;lY\,@JN_R>=.jq$P3sUj*FYFO,Ud9S&!/dCR\_7C.JjT\o23rNLbb%^3,u6NM)4!V^WKqQS5\4)JT7+<8+lKJW4!-M/Ng'$apJ.O6c=2@LlrU-o;\O"*lKo)T:JEa(,_cA&iA9=`'\rb9pADG/NKUe>b5IqO*KY7?]Xq%$H<]K0kC(1hm@^Vj;c'.*'t4B$R5W@'^H9^mIkL]r6%`LQ5lVIl7eKJ3R2Eh2JNh:km0-3G<jY`PCR9\G#L4^k>^_YtF5p<=K!mRE!-[`U#YPT*X4=[JI4,d[sp,UeCp@YuL[!B46LU$s;QpC3&.<;oi@UPmmmX6FSX13lq8m2CWbkJd-N)uANOA(\mcBPjXO[reOo#?uSRiJMV:o9$AV<YH;P#8XaG@HgfLZ!k`O9ip.36=E0[%n+kM$m'CBU!KR4q4.41fMNgYKF9\F)2P7hEk+<^Q]"mEP2TLqE5'+1%?]?!V`*>o]\;l&/&0R/UPf/l2GiTaYspO+"VU\`&^TonF<A#as64si>SNKu?arVO'_O,C>YuTeIl9#7*n5oDHo/X[ah><a-s;2iD.sOdNuL,=fWjmuVg.WQE.&<.^E3tLdqk!Z.Lk>rY<:@fh,_2jM!#SgoRW=!0f)(%Y>g)(;"rB;o/lWSW!#DoM-*<#1MrMNYC8ds9>)*?h+DS^<kCm!-`B.>a9)oA5)`d#Y;Qq:Ob+F1!ehojRa'K64LQm5a:_r&s&,!86LI5X&?TinNRnaVpQN:Z%]XqVPS"Mt1M*FeBM)F),oa+j2qb=>L$@D)!Ld$<c.F@7Mr8p__TDiN>^n@~>endstream
|
||||||
|
endobj
|
||||||
|
xref
|
||||||
|
0 12
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000061 00000 n
|
||||||
|
0000000112 00000 n
|
||||||
|
0000000219 00000 n
|
||||||
|
0000000331 00000 n
|
||||||
|
0000000414 00000 n
|
||||||
|
0000000618 00000 n
|
||||||
|
0000000822 00000 n
|
||||||
|
0000000890 00000 n
|
||||||
|
0000001170 00000 n
|
||||||
|
0000001235 00000 n
|
||||||
|
0000003093 00000 n
|
||||||
|
trailer
|
||||||
|
<<
|
||||||
|
/ID
|
||||||
|
[<f38c85f690d10dd14d4ba2c47b023399><f38c85f690d10dd14d4ba2c47b023399>]
|
||||||
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
|
/Info 8 0 R
|
||||||
|
/Root 7 0 R
|
||||||
|
/Size 12
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
4451
|
||||||
|
%%EOF
|
||||||
343
src/main_ml.py
343
src/main_ml.py
|
|
@ -1,173 +1,220 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os, asyncio, aiohttp, logging, random
|
"""
|
||||||
from datetime import datetime
|
Trading Bot V5 ENHANCED - Mit kritischen Risk Management Fixes
|
||||||
|
Implementiert: SL, TP Anpassung, Daily Limit, R:R Ratio
|
||||||
|
"""
|
||||||
|
import os, asyncio, logging, random, json, time
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from binance.client import Client
|
from binance.client import Client
|
||||||
from decimal import Decimal
|
from binance.exceptions import BinanceAPIException
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
with open("/home/marc/bot-deploy/.env") as f:
|
# Load config
|
||||||
env = {}
|
env = {}
|
||||||
|
with open('/home/marc/bot-deploy/.env') as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
k, _, v = line.partition("=")
|
k, _, v = line.partition('=')
|
||||||
env[k.strip()] = v.strip()
|
env[k.strip()] = v.strip()
|
||||||
|
|
||||||
class Bot:
|
class TradingBotV5Enhanced:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.binance = Client(env.get("BINANCE_API_KEY_LIVE"), env.get("BINANCE_API_SECRET_LIVE"))
|
self.binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
|
||||||
self.current_trades = {}
|
self.state_file = '/home/marc/bot-deploy/trades.json'
|
||||||
self.completed_trades = []
|
self.load_state()
|
||||||
self.balance = {}
|
|
||||||
self.trades_today = 0
|
# NEW: Risk Management Settings
|
||||||
self.daily_pnl = 0.0
|
self.STOP_LOSS_PERCENT = 2.5 # 2.5% SL (-2.5%)
|
||||||
self.dashboard = "http://localhost:7000/api/update"
|
self.TAKE_PROFIT_PERCENT = 3.0 # 3.0% TP (+3%) - was +1%
|
||||||
logger.info("🤖 Bot initialized")
|
self.DAILY_LOSS_LIMIT = 5.0 # Max -5% daily
|
||||||
|
self.MIN_RISK_REWARD = 1.5 # Min R:R ratio
|
||||||
def get_balance(self):
|
self.MAX_POSITION_PERCENT = 25 # Max 25% per trade
|
||||||
try:
|
|
||||||
acc = self.binance.get_account()
|
logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)")
|
||||||
self.balance = {}
|
|
||||||
for a in acc["balances"]:
|
def load_state(self):
|
||||||
free, locked = float(a["free"]), float(a["locked"])
|
if os.path.exists(self.state_file):
|
||||||
if free + locked > 0:
|
with open(self.state_file) as f:
|
||||||
self.balance[a["asset"]] = {"free": free, "locked": locked, "total": free+locked}
|
self.state = json.load(f)
|
||||||
logger.info(f"💰 Balance updated: USDT")
|
else:
|
||||||
except Exception as e:
|
self.state = {'current': {}, 'completed': [], 'daily_start_balance': 0}
|
||||||
logger.error(f"Balance error: {e}")
|
|
||||||
|
def save_state(self):
|
||||||
def place_buy(self, pair):
|
with open(self.state_file, 'w') as f:
|
||||||
try:
|
json.dump(self.state, f, indent=2)
|
||||||
usdt_free = self.balance.get("USDT", {}).get("free", 0)
|
|
||||||
if usdt_free < 5:
|
def check_and_place_sl_orders(self, pair, qty, entry_price):
|
||||||
return None
|
"""
|
||||||
|
NEW: Automatically place Stop Loss orders for existing positions
|
||||||
# Use 25% per trade
|
SL = Entry - 2.5%
|
||||||
qty_usdt = usdt_free * 0.25
|
"""
|
||||||
|
sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
|
||||||
ticker = self.binance.get_symbol_ticker(symbol=pair)
|
|
||||||
price = float(ticker["price"])
|
|
||||||
|
|
||||||
# Get symbol info for filters
|
|
||||||
info = self.binance.get_symbol_info(pair)
|
|
||||||
filters = {f["filterType"]: f for f in info["filters"]}
|
|
||||||
|
|
||||||
# LOT_SIZE check
|
|
||||||
if "LOT_SIZE" in filters:
|
|
||||||
lot = filters["LOT_SIZE"]
|
|
||||||
min_qty = float(lot["minQty"])
|
|
||||||
step = float(lot["stepSize"])
|
|
||||||
|
|
||||||
# Calculate quantity
|
|
||||||
qty_calc = qty_usdt / price
|
|
||||||
|
|
||||||
# Round down to step
|
|
||||||
qty = round(qty_calc / step) * step
|
|
||||||
|
|
||||||
if qty < min_qty or qty <= 0:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
qty = float(round(qty_usdt / price, 6))
|
|
||||||
|
|
||||||
# Format as string to avoid scientific notation
|
|
||||||
qty_str = f"{qty:.8f}".rstrip("0").rstrip(".")
|
|
||||||
|
|
||||||
try:
|
|
||||||
order = self.binance.order_market_buy(symbol=pair, quantity=qty_str)
|
|
||||||
logger.info(f"🟢 BUY: {pair} x{qty_str}")
|
|
||||||
|
|
||||||
self.current_trades[pair] = {
|
|
||||||
"qty": float(qty_str),
|
|
||||||
"buy_price": price,
|
|
||||||
"buy_time": datetime.now().isoformat(),
|
|
||||||
"order_id": order["orderId"]
|
|
||||||
}
|
|
||||||
self.trades_today += 1
|
|
||||||
return order
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Buy {pair} error: {e}")
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"place_buy error: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def check_tp(self):
|
|
||||||
remove = []
|
|
||||||
for pair in list(self.current_trades.keys()):
|
|
||||||
try:
|
|
||||||
trade = self.current_trades[pair]
|
|
||||||
ticker = self.binance.get_symbol_ticker(symbol=pair)
|
|
||||||
current = float(ticker["price"])
|
|
||||||
|
|
||||||
profit_pct = (current / trade["buy_price"]) - 1
|
|
||||||
|
|
||||||
if profit_pct >= 0.01:
|
|
||||||
logger.info(f"🎯 TP HIT: {pair} +{profit_pct*100:.2f}%")
|
|
||||||
|
|
||||||
sell = self.binance.order_market_sell(symbol=pair, quantity=trade["qty"])
|
|
||||||
sell_price = float(sell["fills"][0]["price"]) if sell.get("fills") else current
|
|
||||||
profit = (sell_price - trade["buy_price"]) * trade["qty"]
|
|
||||||
|
|
||||||
self.completed_trades.append({
|
|
||||||
"pair": pair,
|
|
||||||
"buy_price": trade["buy_price"],
|
|
||||||
"sell_price": sell_price,
|
|
||||||
"qty": trade["qty"],
|
|
||||||
"profit_usd": profit,
|
|
||||||
"profit_pct": profit_pct,
|
|
||||||
"buy_time": trade["buy_time"],
|
|
||||||
"sell_time": datetime.now().isoformat()
|
|
||||||
})
|
|
||||||
|
|
||||||
self.daily_pnl += profit
|
|
||||||
remove.append(pair)
|
|
||||||
except Exception as e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for p in remove:
|
|
||||||
del self.current_trades[p]
|
|
||||||
|
|
||||||
async def send_dashboard(self):
|
|
||||||
try:
|
try:
|
||||||
state = {
|
# Check if already has SL order
|
||||||
"current_trades": self.current_trades,
|
orders = self.binance.get_open_orders(symbol=pair)
|
||||||
"completed_trades": self.completed_trades[-20:],
|
has_sl = any(o['side'] == 'SELL' and float(o['price']) < entry_price for o in orders)
|
||||||
"balance": self.balance,
|
|
||||||
"trades_today": self.trades_today,
|
if not has_sl:
|
||||||
"daily_pnl": self.daily_pnl,
|
# Place SL order
|
||||||
"total_pnl": self.daily_pnl,
|
order = self.binance.order_limit_sell(
|
||||||
"wins_today": len([t for t in self.completed_trades if t.get("profit_usd", 0) > 0]),
|
symbol=pair,
|
||||||
"losses_today": len([t for t in self.completed_trades if t.get("profit_usd", 0) < 0]),
|
quantity=qty,
|
||||||
"last_update": datetime.now().isoformat()
|
price=round(sl_price, 8)
|
||||||
}
|
)
|
||||||
async with aiohttp.ClientSession() as s:
|
logger.info(f"🛡️ Stop Loss set: {pair} {qty} @ ${sl_price:.4f}")
|
||||||
async with s.post(self.dashboard, json=state, timeout=2) as r:
|
return True
|
||||||
pass
|
except Exception as e:
|
||||||
|
logger.error(f"SL Error {pair}: {e}")
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def place_buy(self, pair):
|
||||||
|
"""Place market buy with Risk Management checks"""
|
||||||
|
try:
|
||||||
|
# Get balance
|
||||||
|
balance = self.binance.get_account()
|
||||||
|
usdt_free = float([a['free'] for a in balance['balances'] if a['asset'] == 'USDT'][0])
|
||||||
|
|
||||||
|
# NEW: Daily loss check
|
||||||
|
daily_loss = self.calculate_daily_loss()
|
||||||
|
if daily_loss <= -self.DAILY_LOSS_LIMIT:
|
||||||
|
logger.warning(f"⛔ Daily loss limit hit: {daily_loss:.2f}% (limit: -{self.DAILY_LOSS_LIMIT}%)")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Calculate position size (25% of USDT)
|
||||||
|
qty_usdt = usdt_free * (self.MAX_POSITION_PERCENT / 100)
|
||||||
|
|
||||||
|
if qty_usdt < 10: # Binance minimum
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get current price
|
||||||
|
ticker = self.binance.get_symbol_info(pair)
|
||||||
|
price = float(self.binance.get_ticker(symbol=pair)['lastPrice'])
|
||||||
|
|
||||||
|
# Calculate quantity with LOT_SIZE filter
|
||||||
|
lot_filter = next(f for f in ticker['filters'] if f['filterType'] == 'LOT_SIZE')
|
||||||
|
step_size = float(lot_filter['stepSize'])
|
||||||
|
qty = float(int(qty_usdt / price / step_size) * step_size)
|
||||||
|
|
||||||
|
if qty < float(lot_filter['minQty']):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Place market buy
|
||||||
|
order = self.binance.order_market_buy(symbol=pair, quantity=qty)
|
||||||
|
logger.info(f"🟢 BUY: {pair} x{qty:.6f} @ ${price:.4f}")
|
||||||
|
|
||||||
|
# NEW: Auto-place Stop Loss
|
||||||
|
self.check_and_place_sl_orders(pair, qty, price)
|
||||||
|
|
||||||
|
return order
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Buy Error {pair}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def check_take_profit(self):
|
||||||
|
"""NEW: Check and close at +3% TP with SL protection"""
|
||||||
|
try:
|
||||||
|
balance = self.binance.get_account()
|
||||||
|
|
||||||
|
for pair in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']:
|
||||||
|
ticker = self.binance.get_ticker(symbol=pair)
|
||||||
|
current_price = float(ticker['lastPrice'])
|
||||||
|
|
||||||
|
# Check if we have open trade
|
||||||
|
if pair in self.state['current']:
|
||||||
|
entry_price = self.state['current'][pair]['buy_price']
|
||||||
|
gain_percent = (current_price - entry_price) / entry_price * 100
|
||||||
|
|
||||||
|
# TP at +3%
|
||||||
|
if gain_percent >= self.TAKE_PROFIT_PERCENT:
|
||||||
|
qty = self.state['current'][pair]['qty']
|
||||||
|
try:
|
||||||
|
order = self.binance.order_market_sell(symbol=pair, quantity=qty)
|
||||||
|
profit_usd = (current_price - entry_price) * qty
|
||||||
|
logger.info(f"💰 TP HIT: {pair} +{gain_percent:.2f}% = ${profit_usd:.2f}")
|
||||||
|
|
||||||
|
# Record completion
|
||||||
|
self.state['completed'].append({
|
||||||
|
'pair': pair,
|
||||||
|
'qty': qty,
|
||||||
|
'buy_price': entry_price,
|
||||||
|
'sell_price': current_price,
|
||||||
|
'profit_percent': gain_percent,
|
||||||
|
'profit_usd': profit_usd
|
||||||
|
})
|
||||||
|
del self.state['current'][pair]
|
||||||
|
self.save_state()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"TP sell error {pair}: {e}")
|
||||||
|
|
||||||
|
# SL at -2.5% (auto-cancelled by limit order but check anyway)
|
||||||
|
elif gain_percent <= -self.STOP_LOSS_PERCENT:
|
||||||
|
qty = self.state['current'][pair]['qty']
|
||||||
|
try:
|
||||||
|
order = self.binance.order_market_sell(symbol=pair, quantity=qty)
|
||||||
|
loss_usd = (current_price - entry_price) * qty
|
||||||
|
logger.warning(f"🛑 SL HIT: {pair} {gain_percent:.2f}% = ${loss_usd:.2f}")
|
||||||
|
|
||||||
|
self.state['completed'].append({
|
||||||
|
'pair': pair,
|
||||||
|
'qty': qty,
|
||||||
|
'buy_price': entry_price,
|
||||||
|
'sell_price': current_price,
|
||||||
|
'profit_percent': gain_percent,
|
||||||
|
'profit_usd': loss_usd
|
||||||
|
})
|
||||||
|
del self.state['current'][pair]
|
||||||
|
self.save_state()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"SL sell error {pair}: {e}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"TP check error: {e}")
|
||||||
|
|
||||||
|
def calculate_daily_loss(self):
|
||||||
|
"""Calculate daily loss percentage"""
|
||||||
|
try:
|
||||||
|
if not self.state['completed']:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
today_trades = [t for t in self.state['completed']
|
||||||
|
if datetime.fromisoformat(t.get('timestamp', datetime.now().isoformat())).date() == datetime.now().date()]
|
||||||
|
|
||||||
|
daily_loss = sum(t.get('profit_usd', 0) for t in today_trades)
|
||||||
|
|
||||||
|
balance = self.binance.get_account()
|
||||||
|
portfolio = sum(float(a['free']) for a in balance['balances'])
|
||||||
|
|
||||||
|
loss_percent = (daily_loss / portfolio * 100) if portfolio > 0 else 0
|
||||||
|
return loss_percent
|
||||||
except:
|
except:
|
||||||
pass
|
return 0
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
logger.info("🎯 Bot started")
|
"""Main trading loop"""
|
||||||
|
logger.info("🚀 Trading Bot V5 ENHANCED started (SL+TP+DailyLimit)")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
self.get_balance()
|
# Check exits first (TP/SL)
|
||||||
self.check_tp()
|
self.check_take_profit()
|
||||||
|
|
||||||
pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
|
# Generate signal (5% probability)
|
||||||
|
if random.random() < 0.05:
|
||||||
|
pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
|
||||||
|
for pair in pairs:
|
||||||
|
if pair not in self.state['current']:
|
||||||
|
self.place_buy(pair)
|
||||||
|
|
||||||
for pair in pairs:
|
|
||||||
if pair not in self.current_trades and random.random() < 0.05:
|
|
||||||
logger.info(f"🟢 Signal: {pair}")
|
|
||||||
self.place_buy(pair)
|
|
||||||
|
|
||||||
await self.send_dashboard()
|
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Run error: {e}")
|
logger.error(f"Loop error: {e}")
|
||||||
await asyncio.sleep(10)
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
bot = Bot()
|
bot = TradingBotV5Enhanced()
|
||||||
asyncio.run(bot.run())
|
asyncio.run(bot.run())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue