import time
import json
import datetime
import pandas as pd
import numpy as np
import os

# =========================================================
# [Part 1] 데이터 표준화 (Data Class)
# 증권사마다 주는 데이터 이름이 다르므로, 우리 봇이 쓸 공통 이름으로 정의
# =========================================================
class StockData:
	def __init__(self, code, name, price, open_p, high, low, close, volume):
		self.code = code
		self.name = name
		self.current_price = price  # 현재가
		self.open = open_p  # 시가
		self.high = high  # 고가
		self.low = low  # 저가
		self.close = close  # 종가
		self.volume = volume  # 거래량


class OrderbookData:
	def __init__(self, total_bid, total_ask):
		self.total_bid = total_bid  # 총 매수 잔량 (살 사람)
		self.total_ask = total_ask  # 총 매도 잔량 (팔 사람)


# =========================================================
# [Part 2] 증권사 연결 인터페이스 (이 부분만 채우세요!)
# =========================================================
class BrokerAPI:
	"""
	[안내] 여기에 한국투자증권(KIS)이나 다른 API 코드를 연결합니다.
	봇 로직은 이 함수들을 호출해서 데이터를 받아옵니다.
	"""

	def get_rank_list(self):
		# [To-Do] 거래대금 상위 종목 리스트 반환 (API 호출)
		# return [{"code": "005930", "price": 60000}, ...]
		return []  # 테스트용 빈 리스트

	def get_ohlcv_limit(self, code, timeframe='3m', limit=100):
		# [To-Do] 특정 종목의 캔들(OHLCV) 데이터 반환 (RSI, MA 계산용)
		# DataFrame 형태로 반환: columns=['open', 'high', 'low', 'close', 'volume']
		return pd.DataFrame()

	def get_daily_ohlc_yesterday(self, code):
		# [To-Do] 어제 일봉 데이터 반환 (Dynamic K 계산용)
		# return StockData(...)
		pass

	def get_current_data(self, code):
		# [To-Do] 현재가 데이터 반환
		pass

	def get_orderbook(self, code):
		# [To-Do] 호가창 데이터 반환
		# return OrderbookData(bid, ask)
		pass

	def buy_market_order(self, code, qty):
		# [To-Do] 시장가 매수 주문
		print(f"✅ [API] {code} {qty}주 매수 주문 전송 완료")

	def sell_market_order(self, code, qty):
		# [To-Do] 시장가 매도 주문
		print(f"✅ [API] {code} {qty}주 매도 주문 전송 완료")


# =========================================================
# [Part 3] 정훈님의 정글 서바이버 봇 (핵심 로직)
# =========================================================
class JungleSurvivorBot:
	def __init__(self, broker_api, budget=100000):
		self.api = broker_api  # 증권사 API 객체 연결
		self.budget = budget  # 운용 자금 (10만원)
		self.target_list = []  # 감시 종목 리스트
		self.portfolio = {}  # 보유 종목: {'code': {'buy_price': 1000, 'max_price': 1100, 'qty': 10}}

		# --- [전략 파라미터] ---
		self.rsi_period = 11  # RSI 기간 (남들 14보다 빠르게)
		self.rsi_sell_std = 68  # RSI 매도 기준 (70보다 조금 낮게 선취매도)
		self.trailing_gap = 0.01  # 트레일링 스탑 (고점 대비 1% 하락 시 매도)
		self.target_profit = 0.035  # 목표 수익률 (3.5%면 무조건 익절)
		self.stop_loss = -0.02  # 손절매 (-2%)

	# -----------------------------------------------------
	# [3-1] 계산기 (Indicators)
	# -----------------------------------------------------
	def calc_rsi(self, df):
		"""RSI 지표 계산"""
		delta = df['close'].diff()
		gain = (delta.where(delta > 0, 0)).rolling(window=self.rsi_period).mean()
		loss = (-delta.where(delta < 0, 0)).rolling(window=self.rsi_period).mean()
		rs = gain / loss
		return 100 - (100 / (1 + rs))

	def calc_ma(self, df, period=20):
		"""이동평균선 계산"""
		return df['close'].rolling(window=period).mean()

	def calc_dynamic_k(self, yesterday_data):
		"""
		변동성 돌파 K값 실시간 변경 (노이즈 비율 활용)
		노이즈가 심하면 K값을 높여서 진입 장벽을 높임
		"""
		if yesterday_data is None: return 0.5  # 데이터 없으면 기본값

		total_range = yesterday_data.high - yesterday_data.low
		body_range = abs(yesterday_data.open - yesterday_data.close)

		if total_range == 0: return 0.5

		noise_ratio = 1 - (body_range / total_range)
		# K값은 최소 0.3 ~ 최대 0.9 사이로 제한
		return max(0.3, min(noise_ratio, 0.9))

	# -----------------------------------------------------
	# [3-2] 타겟 선정 (Dynamic Universe)
	# -----------------------------------------------------
	def update_universe(self):
		print("\n🔄 [리스트 갱신] 실시간 주도주 스캔 중...")
		raw_list = self.api.get_rank_list()

		new_targets = []
		for item in raw_list:
			code = item['code']
			price = item['price']

			# [필터] 1. 가격 (2천원 ~ 5만원) / 10만원으로 살 수 있어야 함
			if not (2000 <= price <= 50000) or (price > self.budget):
				continue

			# [필터] 2. 프로그램 매매 추이 (API 제공 시 추가)
			# if item['prog_buy'] < 0: continue

			new_targets.append(code)

		# JSON 파일 저장 (로그용)
		with open('target_universe.json', 'w') as f:
			json.dump(new_targets, f)

		self.target_list = new_targets
		print(f"✅ 타겟 갱신 완료: {len(self.target_list)}개 종목 감시 시작")

	# -----------------------------------------------------
	# [3-3] 매수 판단 로직 (살까 말까?)
	# -----------------------------------------------------
	def check_buy(self, code):
		# 1. 데이터 수집
		df_candle = self.api.get_ohlcv_limit(code)  # 3분봉
		curr_data = self.api.get_current_data(code)
		yesterday = self.api.get_daily_ohlc_yesterday(code)
		orderbook = self.api.get_orderbook(code)
		prog_net_buy = self.api.get_program_net_buy(code)  # 프로그램 수급 확인
		if prog_net_buy < 0:
			print(f"🚫 {code} 패스: 프로그램 매도세 ({prog_net_buy})")
			return False
		if len(df_candle) < 20: return False  # 데이터 부족

		current_price = curr_data.current_price

		# 2. 지표 계산
		ma20 = self.calc_ma(df_candle, 20).iloc[-1]
		k_val = self.calc_dynamic_k(yesterday)

		# 3. 변동성 돌파 목표가 계산
		# 목표가 = 오늘 시가 + (어제 변동폭 * K)
		prev_range = yesterday.high - yesterday.low
		breakout_price = curr_data.open + (prev_range * k_val)

		print(f"🧐 {code} 분석: 현재가 {current_price} | 20선 {ma20:.0f} | 목표가 {breakout_price:.0f} (K:{k_val:.2f})")

		# --- [매수 조건 (AND)] ---
		# A. 추세: 현재가가 20일선 위에 있는가?
		cond_trend = current_price >= ma20

		# B. 변동성 돌파: 의미 있는 가격(목표가)을 넘었는가?
		cond_breakout = current_price >= breakout_price

		# C. 수급 안전판: 매수 잔량이 매도 잔량보다 2배 많은가?
		cond_safe = orderbook.total_bid >= (orderbook.total_ask * 2)

		if cond_trend and cond_breakout and cond_safe:
			print(f"🚀 [매수 신호] {code} 발견! (Trend+Breakout+Safe)")
			return True

		return False

	# -----------------------------------------------------
	# [3-4] 매도 판단 로직 (팔까 말까?)
	# -----------------------------------------------------
	def check_sell(self, code):
		if code not in self.portfolio: return False

		info = self.portfolio[code]
		buy_price = info['buy_price']
		max_price = info['max_price']  # 트레일링 스탑용 고점
		qty = info['qty']

		curr_data = self.api.get_current_data(code)
		curr_price = curr_data.current_price

		# 고점 갱신 (트레일링 스탑용)
		if curr_price > max_price:
			self.portfolio[code]['max_price'] = curr_price
			max_price = curr_price

		# 지표 계산
		df_candle = self.api.get_ohlcv_limit(code)
		rsi = self.calc_rsi(df_candle).iloc[-1]

		# 수익률 계산
		profit_rate = (curr_price - buy_price) / buy_price
		drop_from_high = (max_price - curr_price) / max_price

		print(f"👀 {code} 보유중: 수익률 {profit_rate * 100:.2f}% | 고점대비하락 {drop_from_high * 100:.2f}% | RSI {rsi:.1f}")

		# --- [매도 조건 (OR)] ---

		# 1. [목표 달성] 3.5% 먹으면 묻지도 따지지도 말고 익절
		if profit_rate >= self.target_profit:
			print("💰 목표 수익 달성! 매도!")
			return True

		# 2. [트레일링 스탑] 고점 대비 1% 빠지면 익절/청산
		if drop_from_high >= self.trailing_gap and profit_rate > 0:
			print("📉 트레일링 스탑 발동! 매도!")
			return True

		# 3. [조기 퇴근] RSI가 68 넘으면 과열이므로 선취 매도
		if rsi >= self.rsi_sell_std:
			print("🔥 RSI 과열! 조기 매도!")
			return True

		# 4. [손절] -2% 살려주세요
		if profit_rate <= self.stop_loss:
			print("😭 손절매 실행...")
			return True

		return False

	# -----------------------------------------------------
	# [3-5] 메인 루프 (실행기)
	# -----------------------------------------------------
	def run(self):
		print("🤖 정글 서바이버 봇 가동 시작!")

		# [수정 1] 봇 켜자마자 일단 유니버스(감시 종목)부터 만들고 시작!
		# 파일이 없거나 비어있을 수 있으니 강제 갱신 1회 실행
		if not os.path.exists('target_universe.json'):
			print("📂 타겟 파일이 없어서 새로 만듭니다...")
			self.update_universe()
		else:
			# 파일이 있어도 비어있는지 확인
			try:
				with open('target_universe.json', 'r') as f:
					json.load(f)
			except json.JSONDecodeError:
				print("📂 타겟 파일이 비어있어서 새로 채웁니다...")
				self.update_universe()

		while True:
			# 1. 현재 시간 체크
			now = datetime.datetime.now()

			# 2. 유니버스 갱신 (30분마다)
			if now.minute % 30 == 0 and now.second < 5:
				self.update_universe()
				time.sleep(5)

			# 3. 매도 감시 (보유 종목 먼저 체크)
			sell_list = []
			for code in list(self.portfolio.keys()):
				if self.check_sell(code):
					# 매도 실행
					qty = self.portfolio[code]['qty']
					self.api.sell_market_order(code, qty)
					sell_list.append(code)

			# 포트폴리오에서 삭제
			for code in sell_list:
				del self.portfolio[code]

			# 4. 매수 감시 (보유 종목 없을 때 or 자금 남을 때)
			if len(self.portfolio) < 3:
				# [수정 2] 읽을 때도 안전장치 추가
				try:
					with open('target_universe.json', 'r') as f:
						targets = json.load(f)
				except (FileNotFoundError, json.JSONDecodeError):
					targets = []  # 에러 나면 그냥 빈 리스트로 처리

				for code in targets:
					if code in self.portfolio: continue  # 이미 가진 건 패스

					if self.check_buy(code):
						# 매수 실행 (예: 10만원어치 계산)
						curr_price = self.api.get_current_data(code).current_price
						qty = int(self.budget / 3 / curr_price)  # 자금의 1/3 투입
						if qty > 0:
							self.api.buy_market_order(code, qty)
							# 포트폴리오 등록
							self.portfolio[code] = {
								'buy_price': curr_price,
								'max_price': curr_price,
								'qty': qty
							}
						break  # 한 턴에 하나만 산다 (서버 부하 방지)

			time.sleep(1)  # 1초 대기


# =========================================================
# 실행부
# =========================================================
if __name__ == "__main__":
	# 1. API 객체 생성 (나중에 키움/한투 코드로 교체될 부분)
	my_broker = BrokerAPI()

	# 2. 봇 생성 및 실행
	bot = JungleSurvivorBot(my_broker, budget=100000)
	bot.run()