0% found this document useful (0 votes)
143 views7 pages

Real-Time Forex Trading with Finnhub

This document outlines a real-time trading bot using Finnhub WebSocket, designed to analyze forex pairs and generate trading signals. It includes configurations for API keys, trading symbols, and machine learning model training, along with functions for data processing and Telegram integration. The bot operates by subscribing to price data, performing analysis, and sending updates to a designated chat on Telegram.

Uploaded by

kontesuji2021
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
143 views7 pages

Real-Time Forex Trading with Finnhub

This document outlines a real-time trading bot using Finnhub WebSocket, designed to analyze forex pairs and generate trading signals. It includes configurations for API keys, trading symbols, and machine learning model training, along with functions for data processing and Telegram integration. The bot operates by subscribing to price data, performing analysis, and sending updates to a designated chat on Telegram.

Uploaded by

kontesuji2021
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

# -*- coding: utf-8 -*-

# =================================================================================
# KODE BOT TRADING AI - VERSI REAL-TIME DENGAN FINNHUB WEBSOCKET
# PERINGATAN: Sangat tidak stabil jika dijalankan di Pydroid/Ponsel.
# Direkomendasikan untuk dijalankan di PC/VPS.
# =================================================================================

import os
import asyncio
import logging
import json
import nest_asyncio
import numpy as np
import pandas as pd
import finnhub
import websocket # Library: websocket-client
import threading
import time
from datetime import datetime, timezone

# Terapkan patch untuk Pydroid


nest_asyncio.apply()

from sklearn.model_selection import train_test_split


from [Link] import RandomForestClassifier
from [Link] import accuracy_score
import [Link] as genai
from openai import OpenAI
from telegram import Update
from [Link] import Application, CommandHandler, ContextTypes

# --- KONFIGURASI (GANTI DENGAN KUNCI API ANDA) ---


TELEGRAM_BOT_TOKEN = "GANTI_DENGAN_TOKEN_BOT_TELEGRAM_ANDA"
GEMINI_API_KEY = "GANTI_DENGAN_API_KEY_GEMINI_BARU_ANDA"
GROQ_API_KEY = "GANTI_DENGAN_API_KEY_GROQ_ANDA"
FINNHUB_API_KEY = "GANTI_DENGAN_API_KEY_FINNHUB_ANDA" # <-- BARU: API Key Finnhub

# --- Konfigurasi Sinyal Trading---


# Daftar pair yang akan dipantau oleh WebSocket. Finnhub butuh prefix OANDA untuk
forex.
SYMBOLS_TO_WATCH = ['OANDA:EUR_USD', 'OANDA:GBP_USD', 'OANDA:XAU_USD']
INTERVAL = 5 # Interval dalam menit untuk agregasi candle
CANDLE_HISTORY_SIZE = 500 # Jumlah candle historis yang disimpan

# --- Variabel Global untuk State Management ---


# Dictionary untuk menyimpan data candle setiap simbol
# Contoh: {'OANDA:EUR_USD': DataFrame, 'OANDA:GBP_USD': DataFrame}
candle_data = {symbol: [Link]() for symbol in SYMBOLS_TO_WATCH}
# Dictionary untuk menyimpan timestamp candle terakhir setiap simbol
last_candle_ts = {symbol: 0 for symbol in SYMBOLS_TO_WATCH}
# Dictionary untuk menyimpan hasil sinyal terakhir
latest_signals = {symbol: "Belum ada sinyal." for symbol in SYMBOLS_TO_WATCH}
# Variabel untuk menyimpan chat_id pertama yang memulai bot
TARGET_CHAT_ID = None

# --- Inisialisasi Klien API ---


try:
gemini_json_config =
[Link](response_mime_type="application/json")
[Link](api_key=GEMINI_API_KEY)
gemini_model = [Link]('gemini-1.5-flash-latest',
generation_config=gemini_json_config)
groq_client = OpenAI(api_key=GROQ_API_KEY,
base_url="[Link]
finnhub_client = [Link](api_key=FINNHUB_API_KEY)
except Exception as e:
[Link](f"Gagal menginisialisasi API: {e}")
exit()

# --- Setup Logging ---


[Link](format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
level=[Link])
[Link]("httpx").setLevel([Link])
logger = [Link](__name__)

# --- MODEL MACHINE LEARNING ---


model_ml = RandomForestClassifier(n_estimators=100, random_state=42)

async def train_and_predict(symbol: str):


"""Fungsi inti yang melatih model dan menghasilkan prediksi untuk satu
simbol."""
[Link](f"Memulai analisis untuk {symbol}...")
try:
df = candle_data[symbol].copy()
if len(df) < 50: # Butuh cukup data untuk menghitung indikator
[Link](f"Data historis untuk {symbol} tidak cukup untuk
analisis.")
return None

# Perhitungan Indikator
df['SMA_10'] = df['close'].rolling(window=10).mean()
df['SMA_30'] = df['close'].rolling(window=30).mean()
delta = df['close'].diff(1); gain = [Link](delta > 0, 0); loss = -
[Link](delta < 0, 0)
avg_gain = [Link](window=14).mean(); avg_loss =
[Link](window=14).mean()
rs = avg_gain / avg_loss; df['RSI'] = 100 - (100 / (1 + rs))
low_14, high_14 = df['low'].rolling(window=14).min(),
df['high'].rolling(window=14).max()
df['stoch_k'] = 100 * ((df['close'] - low_14) / (high_14 - low_14))
df['stoch_d'] = df['stoch_k'].rolling(window=3).mean()
ema_12, ema_26 = df['close'].ewm(span=12, adjust=False).mean(),
df['close'].ewm(span=26, adjust=False).mean()
df['macd'] = ema_12 - ema_26; df['macd_signal'] = df['macd'].ewm(span=9,
adjust=False).mean(); df['macd_hist'] = df['macd'] - df['macd_signal']
high_low = df['high'] - df['low']; high_prev_close = [Link](df['high'] -
df['close'].shift(1)); low_prev_close = [Link](df['low'] - df['close'].shift(1))
tr = [Link](high_low, [Link](high_prev_close, low_prev_close));
df['atr'] = [Link](window=14).mean()
df['future_price'] = df['close'].shift(-5); [Link](inplace=True);
df['target'] = (df['future_price'] > df['close']).astype(int)

if [Link]:
[Link](f"Data tidak cukup untuk training {symbol} setelah
dihitung.")
return None
features = ['SMA_10', 'SMA_30', 'RSI', 'stoch_k', 'stoch_d', 'macd',
'macd_hist', 'atr']
X, y = df[features], df['target']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
random_state=42, shuffle=False)
model_ml.fit(X_train, y_train)

# Prediksi pada data terakhir


last_data_point = [Link][-1:]
prediction_encoded = model_ml.predict(last_data_point[features])[0]
signal_ml = "BUY" if prediction_encoded == 1 else "SELL"
confidence_ml = max(model_ml.predict_proba(last_data_point[features])[0]) *
100

return {
"signal_ml": signal_ml,
"confidence_ml": confidence_ml,
"last_data": last_data_point
}
except Exception as e:
[Link](f"Error saat training model untuk {symbol}: {e}")
return None

async def get_ai_analysis(symbol, ml_result):


"""Meminta analisis dari Gemini dan Groq, lalu membuat kesimpulan."""
last_data = ml_result['last_data']
current_price = last_data['close'].iloc[0]

base_prompt = (f"Analisis data trading untuk {symbol} M5.


Harga={current_price:.5f}, RSI={last_data['RSI'].iloc[0]:.2f},
Stoch(K={last_data['stoch_k'].iloc[0]:.2f}, D={last_data['stoch_d'].iloc[0]:.2f}),
MACD Hist={last_data['macd_hist'].iloc[0]:.6f}, ATR={last_data['atr'].iloc[0]:.5f}.
Prediksi ML: {ml_result['signal_ml']} ({ml_result['confidence_ml']:.2f}%). Berikan
analisis dalam JSON: "
f'{{"signal": "BUY/SELL/HOLD", "entry": harga, "tp": harga,
"sl": harga, "confidence_percent": persentase, "reason": "alasan singkat"}}.
Pastikan semua nilai adalah angka.')

groq_analysis_json, gemini_analysis_json = None, None


try:
groq_response = groq_client.[Link](model="llama3-8b-8192",
messages=[{"role": "user", "content": base_prompt}], response_format={"type":
"json_object"})
groq_analysis_json = groq_response.choices[0].[Link]
except Exception as e: [Link](f"Gagal hubungi Groq: {e}")
try:
gemini_response = gemini_model.generate_content(base_prompt)
gemini_analysis_json = [Link](gemini_response.text)
except Exception as e: [Link](f"Gagal hubungi Gemini: {e}")

summary_prompt = (f"Anda manajer trading. Simpulkan 2 analisis ini menjadi 1


keputusan final. Analisis 1: {gemini_analysis_json}. Analisis 2:
{groq_analysis_json}. Data ML: {ml_result['signal_ml']}
({ml_result['confidence_ml']:.2f}%). Berikan kesimpulan dalam format JSON: "
f'{{"final_signal": "BUY/SELL/HOLD", "final_entry": harga,
"final_tp": harga, "final_sl": harga, "final_confidence_percent": persentase}}.')

final_summary_json = None
try:
summary_response = groq_client.[Link](model="llama3-8b-
8192", messages=[{"role": "user", "content": summary_prompt}],
response_format={"type": "json_object"})
final_summary_json = summary_response.choices[0].[Link]
except Exception as e: [Link](f"Gagal membuat kesimpulan: {e}")

return gemini_analysis_json, groq_analysis_json, final_summary_json

def format_final_message(symbol, gemini_json, groq_json, summary_json):


"""Memformat pesan akhir yang akan dikirim ke Telegram."""
def format_single_analysis(title, analysis_json):
if not analysis_json: return f"**{title}:**\nAnalisis tidak tersedia."
try:
data = analysis_json if isinstance(analysis_json, dict) else
[Link](analysis_json)
return (f"**{title}:**\nSinyal: **{[Link]('signal', 'N/A')}**
({[Link]('confidence_percent', 0)}%)\nTP: {[Link]('tp', 'N/A')} | SL:
{[Link]('sl', 'N/A')}\nAlasan: *{[Link]('reason', 'N/A')}*")
except: return f"**{title}:**\nAnalisis tidak valid."

final_summary_text = "**Kesimpulan Akhir:**\nTidak dapat membuat kesimpulan."


if summary_json:
try:
data = [Link](summary_json)
final_summary_text = (f"**⭐ Kesimpulan Akhir:**\n"
f"Sinyal: **{[Link]('final_signal', 'N/A')}**
({[Link]('final_confidence_percent', 0)}%)\n"
f"Entry: **{[Link]('final_entry', 'N/A')}**\n"
f"TP: {[Link]('final_tp', 'N/A')} | SL:
{[Link]('final_sl', 'N/A')}")
except: pass

return (f"🚨 **Analisis Sinyal Real-time: {[Link]('OANDA:', '')}** 🚨\n\


n"
f"🤖 **Analisis Gemini:**\n{format_single_analysis('Gemini',
gemini_json)}\n\n"
f"⚡ **Analisis Groq (Llama 3):**\n{format_single_analysis('Groq',
groq_json)}\n\n"
f"----------------------------------------\n{final_summary_text}\
n----------------------------------------\n"
f"⚠️ *Disclaimer: Selalu lakukan riset Anda sendiri.*")

# --- Logika WebSocket ---


def on_message(ws, message):
"""Fungsi ini dipanggil setiap kali ada data harga baru dari Finnhub."""
global last_candle_ts
data = [Link](message)
if data['type'] != 'trade':
return

for trade in data['data']:


symbol = trade['s']
price = trade['p']
# Konversi timestamp milidetik ke detik
ts_seconds = trade['t'] / 1000
dt_object = [Link](ts_seconds, tz=[Link])

# Tentukan bucket 5 menit untuk trade ini


current_bucket_ts = int(ts_seconds - (ts_seconds % (INTERVAL * 60)))
# Jika ini adalah candle pertama untuk simbol ini
if last_candle_ts.get(symbol, 0) == 0:
last_candle_ts[symbol] = current_bucket_ts

# Jika trade masuk ke bucket (candle) yang baru


if current_bucket_ts > last_candle_ts[symbol]:
[Link](f"Candle M{INTERVAL} baru terdeteksi untuk {symbol} pada
{dt_object}. Memicu analisis.")
last_candle_ts[symbol] = current_bucket_ts

# Jalankan analisis di thread terpisah agar tidak memblokir WebSocket


analysis_thread = [Link](target=[Link],
args=(run_full_analysis_cycle(symbol),))
analysis_thread.start()

# Update data candle saat ini (agregasi)


df = candle_data[symbol]
if not [Link]:
last_idx = [Link][-1]
[Link][last_idx, 'close'] = price
[Link][last_idx, 'high'] = max([Link][last_idx, 'high'], price)
[Link][last_idx, 'low'] = min([Link][last_idx, 'low'], price)
else: # Jika DataFrame masih kosong, buat baris pertama
new_row = {'open': price, 'high': price, 'low': price, 'close': price,
'timestamp': current_bucket_ts}
candle_data[symbol] = [Link]([df, [Link]([new_row])],
ignore_index=True)

def on_error(ws, error):


[Link](f"WebSocket Error: {error}")

def on_close(ws, close_status_code, close_msg):


[Link]("### WebSocket ditutup ###")

def on_open(ws):
[Link]("### WebSocket Terbuka ###")
for symbol in SYMBOLS_TO_WATCH:
[Link](f"Berlangganan data untuk {symbol}")
[Link]([Link]({'type':'subscribe', 'symbol':symbol}))

def run_websocket():
"""Menjalankan WebSocket dalam loop tak terbatas."""
while True:
ws = [Link](f"[Link]
on_message=on_message,
on_error=on_error,
on_close=on_close)
ws.on_open = on_open
ws.run_forever()
[Link]("Mencoba menyambung ulang WebSocket dalam 10 detik...")
[Link](10)

# --- FUNGSI BOT TELEGRAM ---


async def run_full_analysis_cycle(symbol):
"""Menjalankan seluruh siklus: train, predict, AI analysis, dan kirim pesan."""
if TARGET_CHAT_ID is None:
[Link]("Belum ada chat_id target, analisis tidak dikirim.")
return

# 1. Train model dan dapatkan prediksi


ml_result = await train_and_predict(symbol)
if not ml_result:
latest_signals[symbol] = f"Gagal menganalisis {symbol}: Prediksi ML gagal."
await [Link].send_message(TARGET_CHAT_ID,
text=latest_signals[symbol])
return

# 2. Dapatkan analisis dari AI


gemini_json, groq_json, summary_json = await get_ai_analysis(symbol, ml_result)

# 3. Format dan kirim pesan


final_message = format_final_message(symbol, gemini_json, groq_json,
summary_json)
latest_signals[symbol] = final_message # Simpan sinyal terakhir
await [Link].send_message(TARGET_CHAT_ID, text=final_message,
parse_mode='Markdown')

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:


global TARGET_CHAT_ID
user = update.effective_user
chat_id = [Link].chat_id

# Simpan chat_id pertama sebagai target untuk pengiriman sinyal otomatis


if TARGET_CHAT_ID is None:
TARGET_CHAT_ID = chat_id
[Link](f"Chat ID target telah diatur ke: {chat_id}")
await [Link].reply_text("Anda telah ditetapkan sebagai penerima
sinyal otomatis.")

await [Link].reply_html(
f"Halo {user.mention_html()}!\n\nBot sekarang berjalan dalam mode **Real-
time**.\n"
f"Sinyal akan dikirim secara otomatis setiap kali candle M{INTERVAL}
selesai untuk pair yang dipantau.\n\n"
f"<b>/signal [pair]</b> - Lihat sinyal terakhir (contoh: /signal EURUSD)."
)

async def signal_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) ->


None:
"""Mengirim sinyal terakhir yang tersimpan."""
symbol_arg = "EUR_USD" # Default
if [Link]:
symbol_arg = [Link][0].upper().replace('/', '_')

finnhub_symbol = f"OANDA:{symbol_arg}"

if finnhub_symbol in latest_signals:
await [Link].reply_text(latest_signals[finnhub_symbol],
parse_mode='Markdown')
else:
await [Link].reply_text(f"Simbol {symbol_arg} tidak dipantau.")

def fetch_initial_data():
"""Mengambil data historis awal untuk semua simbol."""
[Link]("Mengambil data historis awal...")
for symbol in SYMBOLS_TO_WATCH:
try:
# Finnhub menggunakan timestamp UNIX dalam detik
to_ts = int([Link]())
from_ts = to_ts - (CANDLE_HISTORY_SIZE * INTERVAL * 60 * 2) # Ambil
data lebih banyak untuk cadangan
res = finnhub_client.forex_candles(symbol, str(INTERVAL), from_ts,
to_ts)
if res['s'] == 'ok':
df = [Link](res)
df = [Link](columns={'o': 'open', 'h': 'high', 'l': 'low', 'c':
'close', 't': 'timestamp'})
df = df[['timestamp', 'open', 'high', 'low', 'close']]
# Ambil hanya sejumlah yang dibutuhkan
candle_data[symbol] =
[Link](CANDLE_HISTORY_SIZE).reset_index(drop=True)
[Link](f"Berhasil mengambil {len(candle_data[symbol])} candle
untuk {symbol}.")
else:
[Link](f"Gagal mengambil data awal untuk {symbol}: {res}")
except Exception as e:
[Link](f"Exception saat mengambil data awal untuk {symbol}: {e}")

# --- MAIN EXECUTION ---


def main() -> None:
global application
application = [Link]().token(TELEGRAM_BOT_TOKEN).build()

# Handler
application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("signal", signal_handler))
# ask handler bisa ditambahkan jika perlu

# 1. Ambil data historis dulu


fetch_initial_data()

# 2. Jalankan WebSocket di thread terpisah


ws_thread = [Link](target=run_websocket)
ws_thread.daemon = True # Agar thread mati saat program utama berhenti
ws_thread.start()

# 3. Jalankan bot
[Link]("Bot Telegram mulai berjalan...")
application.run_polling()

if __name__ == "__main__":
main()

You might also like