init
This commit is contained in:
@@ -35,7 +35,7 @@
|
||||
- 단/중/장기 이동평균선(MA5/20/60)
|
||||
- 거래량 MA5
|
||||
- **추가 지표**: 스토캐스틱, OBV, ATR, MFI
|
||||
5. **매수 후보 판정** (`check_buy_signals`)
|
||||
5. **매수 후보 판정** (`check_signals`)
|
||||
- *아래 새로운 "매수 후보 전략" 섹션 참조*
|
||||
6. **알림 발송** (`send_*_telegram_message`)
|
||||
multiprocessing Pool을 이용해 다중 메시지를 병렬로 전송합니다.
|
||||
|
||||
@@ -52,7 +52,7 @@ class MonitorStock (Monitor):
|
||||
continue
|
||||
print(f" - {US_STOCKS[symbol]} ({symbol}): {recent_data['Close'].iloc[-1]:.2f}")
|
||||
message_list.append(
|
||||
self.format_message('US', symbol, US_STOCKS[symbol], recent_data['Close'].iloc[-1], recent_data['buy_signal'].iloc[-1])
|
||||
self.format_message('US', symbol, US_STOCKS[symbol], recent_data['Close'].iloc[-1], recent_data['signal'].iloc[-1])
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error processing data for {symbol}: {str(e)}")
|
||||
@@ -78,7 +78,7 @@ class MonitorStock (Monitor):
|
||||
continue
|
||||
print(f" - {KR_ETFS[symbol]} ({symbol}): {recent_data['Close'].iloc[-1]:.2f}")
|
||||
message_list.append(
|
||||
self.format_message('KR', symbol, KR_ETFS[symbol], recent_data['Close'].iloc[-1], recent_data['buy_signal'].iloc[-1])
|
||||
self.format_message('KR', symbol, KR_ETFS[symbol], recent_data['Close'].iloc[-1], recent_data['signal'].iloc[-1])
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error processing data for {symbol}: {str(e)}")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ADA": {
|
||||
"datetime": "2025-08-14T22:41:28.363958",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"APE": {
|
||||
"datetime": "2025-08-09T14:22:02.089619",
|
||||
"buy_signal": "movingaverage"
|
||||
"signal": "movingaverage"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ARB": {
|
||||
"datetime": "2025-08-14T22:43:59.078775",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"BONK": {
|
||||
"datetime": "2025-08-14T22:41:42.247356",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ENA": {
|
||||
"datetime": "2025-08-16T01:03:31.916209",
|
||||
"buy_signal": "deviation240"
|
||||
"signal": "deviation240"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"HBAR": {
|
||||
"datetime": "2025-08-14T21:37:21.575425",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"KAIA": {
|
||||
"datetime": "2025-08-14T22:42:27.079125",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"LINK": {
|
||||
"datetime": "2025-08-14T22:42:38.780771",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ONDO": {
|
||||
"datetime": "2025-08-14T22:04:53.097618",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"PENGU": {
|
||||
"datetime": "2025-08-16T07:53:40.994785",
|
||||
"buy_signal": "deviation240"
|
||||
"signal": "deviation240"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"PEPE": {
|
||||
"datetime": "2025-08-14T22:06:10.012326",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"SAND": {
|
||||
"datetime": "2025-08-14T22:05:08.098364",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"SEI": {
|
||||
"datetime": "2025-08-14T21:36:00.600483",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"SHIB": {
|
||||
"datetime": "2025-08-14T22:05:20.734073",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"STORJ": {
|
||||
"datetime": "2025-08-14T23:32:08.979598",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"SUI": {
|
||||
"datetime": "2025-08-14T21:36:14.758922",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"UXLINK": {
|
||||
"datetime": "2025-08-14T22:05:36.242448",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"VIRTUAL": {
|
||||
"datetime": "2025-08-16T21:02:23.634183",
|
||||
"buy_signal": "deviation1440"
|
||||
"signal": "deviation1440"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"WLD": {
|
||||
"datetime": "2025-08-14T22:43:33.737340",
|
||||
"buy_signal": "fall_6p"
|
||||
"signal": "fall_6p"
|
||||
}
|
||||
}
|
||||
124
simulation.py
124
simulation.py
@@ -27,7 +27,7 @@ class Simulation:
|
||||
if current_data.get('point', 0) == 1:
|
||||
return {
|
||||
'alert': True,
|
||||
'details': f"매수신호: {current_data.get('buy_signal', 'unknown')}"
|
||||
'details': f"매수신호: {current_data.get('signal', 'unknown')}"
|
||||
}
|
||||
return {'alert': False, 'details': "매수신호 없음"}
|
||||
|
||||
@@ -99,6 +99,10 @@ class Simulation:
|
||||
|
||||
def run_simulation(self, symbol: str, interval_minutes: int, days: int = 30):
|
||||
data = self.fetch_price_history(symbol, interval_minutes)
|
||||
|
||||
inverseData = self.monitor.inverse_data(data)
|
||||
inverseData = self.monitor.check_point(symbol, inverseData, simulation=True)
|
||||
|
||||
data = self.monitor.calculate_technical_indicators(data)
|
||||
data = self.monitor.check_point(symbol, data, simulation=True)
|
||||
print(f"데이터 기간: {data.index[0]} ~ {data.index[-1]}")
|
||||
@@ -108,10 +112,10 @@ class Simulation:
|
||||
if data['point'].iloc[i] == 1:
|
||||
alerts.append((data.index[i], data['Close'].iloc[i]))
|
||||
print(f"\n총 매수 신호 수: {len(alerts)}")
|
||||
ma_signals = len(data[(data['point'] == 1) & (data['buy_signal'] == 'movingaverage')])
|
||||
dev40_signals = len(data[(data['point'] == 1) & (data['buy_signal'] == 'deviation40')])
|
||||
dev240_signals = len(data[(data['point'] == 1) & (data['buy_signal'] == 'deviation240')])
|
||||
dev1440_signals = len(data[(data['point'] == 1) & (data['buy_signal'] == 'deviation1440')])
|
||||
ma_signals = len(data[(data['point'] == 1) & (data['signal'] == 'movingaverage')])
|
||||
dev40_signals = len(data[(data['point'] == 1) & (data['signal'] == 'deviation40')])
|
||||
dev240_signals = len(data[(data['point'] == 1) & (data['signal'] == 'deviation240')])
|
||||
dev1440_signals = len(data[(data['point'] == 1) & (data['signal'] == 'deviation1440')])
|
||||
print(f" - MA 신호: {ma_signals}")
|
||||
print(f" - Dev40 신호: {dev40_signals}")
|
||||
print(f" - Dev240 신호: {dev240_signals}")
|
||||
@@ -188,34 +192,7 @@ class Simulation:
|
||||
ax1.plot([date, date], [low, high], color='black', linewidth=1)
|
||||
normal_patches.append(ax1.lines[-1]) # 추가: 선도 저장
|
||||
|
||||
# ----------------- 하이킨아시 캔들스틱 -----------------
|
||||
# 하이킨아시 OHLC 계산
|
||||
ha_df = pd.DataFrame(index=data.index, columns=['Open', 'High', 'Low', 'Close'])
|
||||
ha_df['Close'] = (data['Open'] + data['High'] + data['Low'] + data['Close']) / 4
|
||||
# 첫 번째 HA Open 값 계산
|
||||
ha_df.loc[ha_df.index[0], 'Open'] = (data['Open'].iloc[0] + data['Close'].iloc[0]) / 2
|
||||
# 이후 HA Open 값은 이전 HA 캔들의 (Open+Close)/2
|
||||
for i in range(1, len(ha_df)):
|
||||
prev_open = ha_df.loc[ha_df.index[i-1], 'Open']
|
||||
prev_close = ha_df.loc[ha_df.index[i-1], 'Close']
|
||||
ha_df.loc[ha_df.index[i], 'Open'] = (prev_open + prev_close) / 2
|
||||
# High/Low 계산
|
||||
ha_df['High'] = pd.concat([ha_df['Open'], ha_df['Close'], data['High']], axis=1).max(axis=1)
|
||||
ha_df['Low'] = pd.concat([ha_df['Open'], ha_df['Close'], data['Low']], axis=1).min(axis=1)
|
||||
|
||||
ha_patches = []
|
||||
for i, (idx, row) in enumerate(ha_df.iterrows()):
|
||||
date = mdates.date2num(idx)
|
||||
open_price, high, low, close = row['Open'], row['High'], row['Low'], row['Close']
|
||||
color = 'red' if close >= open_price else 'blue'
|
||||
body_height = abs(close - open_price)
|
||||
body_bottom = min(open_price, close)
|
||||
rect = plt.Rectangle((date - 0.3, body_bottom), 0.6, body_height,
|
||||
facecolor=color, edgecolor='black', alpha=0.7, visible=False)
|
||||
ax1.add_patch(rect)
|
||||
ha_patches.append(rect)
|
||||
line = ax1.plot([date, date], [low, high], color='black', linewidth=1, visible=False)[0]
|
||||
ha_patches.append(line)
|
||||
# ----------------- 하이킨아시 캔들스틱 (제거됨) -----------------
|
||||
|
||||
# 메인 차트 (가격, 이동평균선, 볼린저 밴드)
|
||||
line_close = ax1.plot(data.index, data["Close"], label="종가", color="black", linewidth=1.5, alpha=0.8)[0]
|
||||
@@ -236,7 +213,7 @@ class Simulation:
|
||||
|
||||
# 매수 포인트를 신호 유형별로 다르게 표시 (수직선 포함)
|
||||
# 이동평균선 기반 매수 포인트
|
||||
ma_points = data[(data['point'] == 1) & (data['buy_signal'] == 'movingaverage')]
|
||||
ma_points = data[(data['point'] == 1) & (data['signal'] == 'movingaverage')]
|
||||
scatter_ma_points = None
|
||||
if len(ma_points) > 0:
|
||||
scatter_ma_points = ax1.scatter(ma_points.index, ma_points['Close'], color='red', s=150, zorder=10, label='MA 매수 포인트', marker='o')
|
||||
@@ -244,7 +221,7 @@ class Simulation:
|
||||
ax1.axvline(x=time, color='red', linestyle='-', alpha=0.5, linewidth=1)
|
||||
|
||||
# Deviation40 기반 매수 포인트
|
||||
dev40_points = data[(data['point'] == 1) & (data['buy_signal'] == 'deviation40')]
|
||||
dev40_points = data[(data['point'] == 1) & (data['signal'] == 'deviation40')]
|
||||
scatter_dev40_points = None
|
||||
if len(dev40_points) > 0:
|
||||
scatter_dev40_points = ax1.scatter(dev40_points.index, dev40_points['Close'],
|
||||
@@ -254,7 +231,7 @@ class Simulation:
|
||||
ax1.axvline(x=time, color='red', linestyle='--', alpha=0.5, linewidth=1)
|
||||
|
||||
# Deviation240 기반 매수 포인트
|
||||
dev240_points = data[(data['point'] == 1) & (data['buy_signal'] == 'deviation240')]
|
||||
dev240_points = data[(data['point'] == 1) & (data['signal'] == 'deviation240')]
|
||||
scatter_dev240_points = None
|
||||
if len(dev240_points) > 0:
|
||||
scatter_dev240_points = ax1.scatter(dev240_points.index, dev240_points['Close'],
|
||||
@@ -264,7 +241,7 @@ class Simulation:
|
||||
ax1.axvline(x=time, color='blue', linestyle='--', alpha=0.5, linewidth=1)
|
||||
|
||||
# Deviation1440 기반 매수 포인트
|
||||
dev1440_points = data[(data['point'] == 1) & (data['buy_signal'] == 'deviation1440')]
|
||||
dev1440_points = data[(data['point'] == 1) & (data['signal'] == 'deviation1440')]
|
||||
scatter_dev1440_points = None
|
||||
if len(dev1440_points) > 0:
|
||||
scatter_dev1440_points = ax1.scatter(dev1440_points.index, dev1440_points['Close'],
|
||||
@@ -321,9 +298,6 @@ class Simulation:
|
||||
bbox=dict(boxstyle="round,pad=0.3", facecolor='lightblue', alpha=0.8),
|
||||
fontsize=10, ha='left', va='top', picker=True)
|
||||
# 하이킨아시 토글 버튼 추가
|
||||
ha_button = ax1.text(0.06, 0.98, 'HA', transform=ax1.transAxes,
|
||||
bbox=dict(boxstyle="round,pad=0.3", facecolor='orange', alpha=0.8),
|
||||
fontsize=10, ha='left', va='top', picker=True)
|
||||
|
||||
# --- 범례 생성 및 인터랙티브 토글 ---
|
||||
# 캔들 및 지표만 범례에 표시 (일반/하이킨아시 토글은 HA 버튼으로 제공)
|
||||
@@ -349,15 +323,50 @@ class Simulation:
|
||||
if scatter_dev1440_points is not None:
|
||||
plot_lines.append(scatter_dev1440_points)
|
||||
|
||||
# --------- 매도 포인트(인버스 데이터) 표시 ---------
|
||||
# Deviation40 기반 매도 포인트
|
||||
sell_dev40_points = inverseData[(inverseData['point'] == 1) & (inverseData['signal'] == 'deviation40')]
|
||||
scatter_sell_dev40_points = None
|
||||
if len(sell_dev40_points) > 0:
|
||||
scatter_sell_dev40_points = ax1.scatter(sell_dev40_points.index,
|
||||
data.loc[sell_dev40_points.index, 'Close'],
|
||||
facecolors='none', edgecolors='orange', marker='v', linewidth=2,
|
||||
s=200, zorder=10, label='Dev40 매도 포인트')
|
||||
for time in sell_dev40_points.index:
|
||||
ax1.axvline(x=time, color='orange', linestyle='--', alpha=0.5, linewidth=1)
|
||||
|
||||
# -------- 매도 포인트 마우스 오버 --------
|
||||
if scatter_sell_dev40_points is not None:
|
||||
cursor_sell_dev40 = mplcursors.cursor(scatter_sell_dev40_points, hover=True)
|
||||
cursor_sell_dev40.connect("add", lambda sel: sel.annotation.set_text(
|
||||
f'Dev40 매도신호\n날짜: {matplotlib.dates.num2date(sel.target[0]).replace(tzinfo=None).strftime("%Y-%m-%d %H:%M")}\n가격: {sel.target[1]:.2f}'
|
||||
))
|
||||
cursor_sell_dev40.connect("remove", lambda sel: sel.annotation.set_visible(False))
|
||||
|
||||
# -------- plot_lines 업데이트 --------
|
||||
if scatter_sell_dev40_points is not None:
|
||||
plot_lines.append(scatter_sell_dev40_points)
|
||||
|
||||
# 기존 범례 제거 후 새로 생성하여 매도 항목 포함
|
||||
if legend is not None:
|
||||
legend.remove()
|
||||
legend = ax1.legend(loc='upper left', bbox_to_anchor=(0.02, 0.85), fontsize=10)
|
||||
# 새 legend_handles 추출
|
||||
if hasattr(legend, "legend_handles"):
|
||||
legend_handles = legend.legend_handles
|
||||
elif hasattr(legend, "legendHandles"):
|
||||
legend_handles = legend.legendHandles
|
||||
else:
|
||||
legend_handles = legend.get_lines()
|
||||
|
||||
# lined 사전에 새 핸들 매핑 추가
|
||||
for leg_handle, orig in zip(legend_handles[:len(plot_lines)], plot_lines):
|
||||
leg_handle.set_picker(True)
|
||||
lined[leg_handle] = orig
|
||||
|
||||
# 하이킨아시/일반 토글 상태 변수
|
||||
ha_mode = False
|
||||
|
||||
def on_pick(event):
|
||||
nonlocal ha_mode
|
||||
leg_handle = event.artist
|
||||
# 홈 버튼 동작
|
||||
if leg_handle == home_button:
|
||||
@@ -370,21 +379,6 @@ class Simulation:
|
||||
)
|
||||
fig.canvas.draw_idle()
|
||||
return
|
||||
# 하이킨아시 토글
|
||||
if leg_handle == ha_button:
|
||||
ha_mode = not ha_mode
|
||||
if ha_mode:
|
||||
for p in normal_patches:
|
||||
p.set_visible(False)
|
||||
for p in ha_patches:
|
||||
p.set_visible(True)
|
||||
else:
|
||||
for p in normal_patches:
|
||||
p.set_visible(True)
|
||||
for p in ha_patches:
|
||||
p.set_visible(False)
|
||||
fig.canvas.draw_idle()
|
||||
return
|
||||
# 선 토글 처리
|
||||
orig = lined.get(leg_handle)
|
||||
if orig is None:
|
||||
@@ -501,14 +495,18 @@ if __name__ == "__main__":
|
||||
sim.run_simulation(symbol, interval, days)
|
||||
else:
|
||||
data = sim.fetch_price_history(symbol, interval, days)
|
||||
|
||||
inverseData = sim.monitor.inverse_data(data)
|
||||
inverseData = sim.monitor.check_point(symbol, inverseData, simulation=True)
|
||||
|
||||
data = sim.monitor.calculate_technical_indicators(data)
|
||||
data = sim.monitor.check_point(symbol, data, simulation=True)
|
||||
total_buy_signals = len(data[data['point'] == 1])
|
||||
ma_signals = len(data[(data['point'] == 1) & (data['buy_signal'] == 'movingaverage')])
|
||||
dev40_signals = len(data[(data['point'] == 1) & (data['buy_signal'] == 'deviation40')])
|
||||
dev240_signals = len(data[(data['point'] == 1) & (data['buy_signal'] == 'deviation240')])
|
||||
dev1440_signals = len(data[(data['point'] == 1) & (data['buy_signal'] == 'deviation1440')])
|
||||
print(f"총 매수 신호: {total_buy_signals}")
|
||||
total_signals = len(data[data['point'] == 1])
|
||||
ma_signals = len(data[(data['point'] == 1) & (data['signal'] == 'movingaverage')])
|
||||
dev40_signals = len(data[(data['point'] == 1) & (data['signal'] == 'deviation40')])
|
||||
dev240_signals = len(data[(data['point'] == 1) & (data['signal'] == 'deviation240')])
|
||||
dev1440_signals = len(data[(data['point'] == 1) & (data['signal'] == 'deviation1440')])
|
||||
print(f"총 매수 신호: {total_signals}")
|
||||
print(f" - MA 신호: {ma_signals}")
|
||||
print(f" - Dev40 신호: {dev40_signals}")
|
||||
print(f" - Dev240 신호: {dev240_signals}")
|
||||
|
||||
Reference in New Issue
Block a user