107 lines
4.0 KiB
Python
107 lines
4.0 KiB
Python
import time, os, requests, json, urllib3
|
|
from datetime import datetime, timedelta, timezone, time as dtime
|
|
from dotenv import load_dotenv
|
|
|
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
load_dotenv("tok.env")
|
|
|
|
TOKEN = os.getenv("TINKOFF_TOKEN")
|
|
TG_TOKEN = os.getenv("TG_TOKEN")
|
|
TG_CHAT_ID = os.getenv("TG_CHAT_ID")
|
|
ACC_ID = os.getenv("ACCOUNT_ID")
|
|
|
|
INSTRUMENT_UID = "a78b8349-a1dc-447d-9277-1d75826d089a"
|
|
BASE_URL = "https://invest-public-api.tbank.ru/rest"
|
|
STATE_FILE = "bot_state_prod.json"
|
|
|
|
s = requests.Session()
|
|
s.headers.update({"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"})
|
|
|
|
def get_price():
|
|
path = f"{BASE_URL}/tinkoff.public.invest.api.contract.v1.MarketDataService/GetLastPrices"
|
|
try:
|
|
r = s.post(path, json={"instrumentId": [INSTRUMENT_UID]}, timeout=5)
|
|
if r.status_code != 200: return None
|
|
res = r.json()
|
|
if 'lastPrices' in res and res['lastPrices']:
|
|
p = res['lastPrices'][0]['price']
|
|
return int(p.get('units', 0)) + int(p.get('nano', 0)) / 1e9
|
|
except: return None
|
|
return None
|
|
|
|
def get_atr_steps():
|
|
path = f"{BASE_URL}/tinkoff.public.invest.api.contract.v1.MarketDataService/GetCandles"
|
|
try:
|
|
now_utc = datetime.now(timezone.utc)
|
|
payload = {
|
|
"instrumentId": INSTRUMENT_UID,
|
|
"from": (now_utc - timedelta(days=2)).strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
"to": now_utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
"interval": "CANDLE_INTERVAL_HOUR"
|
|
}
|
|
r = s.post(path, json=payload, timeout=10)
|
|
candles = r.json().get('candles', [])
|
|
if len(candles) < 2: return 1.1, 1.5
|
|
|
|
def to_f(p): return int(p.get('units', 0)) + int(p.get('nano', 0)) / 1e9
|
|
trs = [to_f(c['high']) - to_f(c['low']) for c in candles[-14:]]
|
|
atr = sum(trs) / len(trs)
|
|
|
|
buy_step = max(round(atr * 0.45, 2), 0.5)
|
|
return buy_step, round(buy_step * 1.2, 2)
|
|
except: return 1.1, 1.5
|
|
|
|
def post_order(direction):
|
|
path = f"{BASE_URL}/tinkoff.public.invest.api.contract.v1.OrdersService/PostOrder"
|
|
payload = {
|
|
"instrumentId": INSTRUMENT_UID, "quantity": 1,
|
|
"direction": direction, "orderType": "ORDER_TYPE_MARKET",
|
|
"accountId": ACC_ID, "orderId": f"p_{int(time.time())}"
|
|
}
|
|
try:
|
|
r = s.post(path, json=payload, timeout=10)
|
|
return r.status_code == 200
|
|
except: return False
|
|
|
|
def run():
|
|
print("🚀 БОТ ЗАПУЩЕН")
|
|
state = {"phase": "BUY", "base_price": None}
|
|
if os.path.exists(STATE_FILE):
|
|
try:
|
|
with open(STATE_FILE, 'r') as f: state = json.load(f)
|
|
except: pass
|
|
|
|
buy_step, profit_step = get_atr_steps()
|
|
|
|
while True:
|
|
price = get_price()
|
|
if price:
|
|
if state['base_price'] is None:
|
|
state['base_price'] = price
|
|
with open(STATE_FILE, 'w') as f: json.dump(state, f)
|
|
print(f"\n🎯 База установлена: {price}")
|
|
|
|
if state['phase'] == "BUY":
|
|
target = round(state['base_price'] - buy_step, 2)
|
|
else:
|
|
target = round(state['base_price'] + profit_step, 2)
|
|
|
|
print(f"[{datetime.now().strftime('%H:%M:%S')}] Цена: {price:.2f} | Цель: {target:.2f} | {state['phase']} ", end='\r')
|
|
|
|
if state['phase'] == "BUY" and price <= target:
|
|
if post_order("ORDER_DIRECTION_BUY"):
|
|
state.update({"phase": "SELL", "base_price": price})
|
|
with open(STATE_FILE, 'w') as f: json.dump(state, f)
|
|
print(f"\n🛒 КУПЛЕНО по {price}")
|
|
|
|
elif state['phase'] == "SELL" and price >= target:
|
|
if post_order("ORDER_DIRECTION_SELL"):
|
|
state.update({"phase": "BUY", "base_price": price})
|
|
with open(STATE_FILE, 'w') as f: json.dump(state, f)
|
|
print(f"\n💰 ПРОДАНО по {price}")
|
|
|
|
time.sleep(1)
|
|
|
|
if __name__ == "__main__":
|
|
run()
|