-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathibkr-ai.py
More file actions
293 lines (245 loc) · 15.4 KB
/
Copy pathibkr-ai.py
File metadata and controls
293 lines (245 loc) · 15.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# -*- coding: utf-8 -*-
# https://ib-insync.readthedocs.io/api.html
import yfinance as yf
from ib_insync import IB, Stock, MarketOrder
import ibapi
import importlib.metadata
from datetime import datetime, time as time1
import pandas as pd
import pandas_market_calendars as mcal
import pytz, time, random
host = '127.0.0.1'
port = 4001
spot_holding = 0 # 现货:0-正常持币,4-立刻清仓
spot_position = 1 # 现货仓位:1-100%仓位,0.9-90%仓位,1.1-110%仓位
# 定义标的和其交易参数(以金额为单位)
symbols = {
# 纳斯达克
'INTC': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NASDAQ','st':0 }, # 英特尔
# 'QQQ': { 'buy_value': 910, 'sell_value': 1050, 'trade_amount': 70, 'x': 'NASDAQ' }, # 纳斯达克100
'MARA': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NASDAQ' }, # 比特币挖矿
'RKLB': { 'buy_value': 850, 'trade_amount': 69, 'market': 'US', 'x': 'NASDAQ' }, # 火箭实验室
'SMCI': { 'buy_value': 890, 'trade_amount': 59, 'market': 'US', 'x': 'NASDAQ' }, # 超微电脑
'TQQQ': { 'buy_value': 990, 'trade_amount': 59, 'market': 'US', 'x': 'NASDAQ' }, # 三倍做多QQQ(纳斯达克100)
'TSLG': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NASDAQ' }, # 二倍做多特斯拉
'AMDL': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NASDAQ' }, # 二倍做多AMD
'CRCG': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NASDAQ' }, # 二倍做多CIRCLE
'CONL': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NASDAQ' }, # 二倍做多COIN
'NFXL': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NASDAQ' }, # 二倍做多NETFLIX
'TSMU': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NASDAQ' }, # 二倍做多台积电
'USAU': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NASDAQ' }, # 美国黄金公司
'FBL': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NASDAQ' }, # 二倍做多META
# 纽交所
'F': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NYSE' }, # 福特汽车
'GME': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NYSE' }, # 游戏驿站
'KODK': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NYSE' }, # 柯达
'AG': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NYSE' }, # 第一银矿业
# 中概股
'BEKE': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NYSE' }, # 贝壳
# 'BILI': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NYSE' }, # B站
# 'IQ': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NYSE' }, # 爱奇艺
'MNSO': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'NYSE' }, # MINISO
# ETF 指数基金
# 'GLD': { 'buy_value': 0, 'sell_value': 1020, 'trade_amount': 256, 'market': 'US', 'x': 'ARCA' }, # 黄金
# 'USO': { 'buy_value': 0, 'sell_value': 1020, 'trade_amount': 88, 'market': 'US', 'x': 'ARCA' }, # 石油
'GDXU': { 'buy_value': 1190, 'trade_amount': 299, 'market': 'US', 'x': 'ARCA' }, # 金矿3倍看涨
'KORU': { 'buy_value': 1090, 'trade_amount': 299, 'market': 'US', 'x': 'ARCA' }, # 韩国3倍看涨
'RETL': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 零售3倍看涨
'SOXL': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 半导体3倍看涨
'WEBL': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 互联网3倍看涨
'YINN': { 'buy_value': 590, 'trade_amount': 49, 'market': 'US', 'x': 'ARCA' }, # 富时中国3倍看涨
# 'WTIU': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 原油3倍看涨
'PILL': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 医药3倍看涨
'DRN': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 房地产3倍看涨
# 'JETU': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 航空公司3倍看涨
# 'OILU': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 勘探和生产3倍看涨
# 'TYD': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 7-10年债券3倍看涨
'UMDD': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 标普中盘400三倍看涨
'MEXX': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 墨西哥IMI指数三倍看涨
# 'WANT': { 'buy_value': 490, 'trade_amount': 59, 'market': 'US', 'x': 'ARCA' }, # 非必需消费品三倍看涨
'SGOL': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 黄金实物信托基金
'PSLV': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 白银实物信托基金
'MSOX': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 美国大麻二倍看涨
'SILJ': { 'buy_value': 490, 'trade_amount': 39, 'market': 'US', 'x': 'ARCA' }, # 白银二倍看涨
'SHNY': { 'buy_value': 790, 'trade_amount': 179, 'market': 'US', 'x': 'ARCA' }, # 黄金三倍看涨
'AGQ': { 'buy_value': 790, 'trade_amount': 149, 'market': 'US', 'x': 'ARCA' }, # 白银二倍看涨
}
# 仓位管理
def spot_position_init(symbols, spot_position = 1):
for symbol, values in symbols.items():
if "lead_price" in values and spot_position < 1: values["lead_price"] *= spot_position
values["buy_value"] = round(values["buy_value"] * spot_position)
values["trade_amount"] = max(round(values["trade_amount"] * spot_position), 11)
if "sell_value" in values: values["sell_value"] = round(max(values["sell_value"] * spot_position, values["buy_value"] + values["trade_amount"] * 1.99))
if "sell_valuex" in values: values["sell_valuex"] = round(max(values["sell_valuex"] * spot_position, values["buy_value"] + values["trade_amount"] * 1.99))
return symbols
if spot_position != 1: symbols = spot_position_init(symbols, spot_position)
# 设置不同市场的时区
eastern = pytz.timezone('America/New_York') # 纽约
hong_kong = pytz.timezone('Asia/Hong_Kong') # 香港
china = pytz.timezone('Asia/Shanghai') # 中国
# 获取不同市场的交易日历
nyse = mcal.get_calendar('NYSE') # 美股
hkex = mcal.get_calendar('HKEX') # 港股
# 上海证券交易所日历,用于 A 股
cndata = mcal.get_calendar('XSHG') # A股
def get_market_status(market):
"""
检查指定股票在对应市场当前是否开盘
:param market: 市场代码('US' 表示美股市场,'HK' 表示港股市场,'CN' 表示 A 股市场)
:return: True 如果市场开盘,False 如果未开盘
"""
if market == 'US': calendar = nyse; timezone = eastern
elif market == 'HK': calendar = hkex; timezone = hong_kong
elif market == 'CN': calendar = cndata; timezone = china
else: raise ValueError("市场代码无效,请使用 'US'、'HK' 或 'CN'")
current_time = datetime.now(timezone)
today = current_time.date()
schedule = calendar.schedule(start_date=today, end_date=today)
# 如果当天没有开放时间,则表示未开盘
if schedule.empty: return False
# 获取市场的开盘和收盘时间
market_open = schedule.iloc[0]['market_open'].astimezone(timezone)
market_close = schedule.iloc[0]['market_close'].astimezone(timezone)
# 检查当前时间是否在开盘时间内
if market == 'CN':
# A 股有午间休市,需要分别检查上午和下午时段
current_time_time = current_time.time()
morning_open = time1(9, 30); morning_close = time1(11, 30); afternoon_open = time1(13, 0); afternoon_close = time1(15, 0)
return ((morning_open <= current_time_time < morning_close) or (afternoon_open <= current_time_time < afternoon_close))
else: return market_open <= current_time < market_close
# 获取 ib_insync 和 ibapi 的版本信息
ib_insync_version = importlib.metadata.version('ib_insync')
ib_api_version = ibapi.__version__ if hasattr(ibapi, '__version__') else "Unknown"
print("ib_insync 版本:", ib_insync_version)
print("IB API 版本:", ib_api_version)
# 连接到 IB Gateway
ib = IB(); ib.connect(host , port, clientId=1)
def truncate(number, decimal_places, return_type="string"):
factor = 10 ** decimal_places
truncated_number = int(number * factor) / factor
if truncated_number == int(truncated_number): truncated_number = int(truncated_number)
if return_type != "string": return truncated_number
return f'{truncated_number:.{decimal_places}f}'.rstrip('0').rstrip('.')
def get_bid_ask(symbol):
""" 使用 yfinance 获取 bid 和 ask 价格 """
stock = yf.Ticker(symbol) # ; print(symbol, stock.info)
# 获取当前的买入价和卖出价
bid = stock.info['bid'] if "bid" in stock.info else 0
ask = stock.info['ask'] if "ask" in stock.info else 0
return bid, ask
def get_cash_balance():
""" 获取账户可用资金 """
account_summary = ib.accountSummary()
for item in account_summary:
if item.tag == 'CashBalance' and item.currency == 'USD':
return float(item.value)
return 0.0 # 如果未找到,返回0
# 乱序遍历持仓,低买高卖
def manage_positions(positions, symbols, market_status):
# 获取所有键,然后乱序
keys = list(symbols.keys())
random.shuffle(keys)
# 以乱序的方式遍历字典
i = 0
for symbol in keys:
i += 1
params = symbols[symbol]
buy_value = params['buy_value']
trade_amount = params['trade_amount']
# US-美股、HK-港股、CN-A股票
market = params.get('market', "US") # 默认值:US-美股
is_market_open = market_status[market]
if not is_market_open: continue
# 计算默认 sell_value,若未定义则使用默认值
sell_value = params.get('sell_value', buy_value + trade_amount * 2)
# 使用 yfinance 获取 bid 和 ask 价格(免费)
# bid_price, ask_price = get_bid_ask(symbol)
# 使用 IBKR API 获取 bid 和 ask 价格(非专业订阅:NASDAQ $1.50/月,NYSE $1.50/月,ARCA $1.50/月)
stock = Stock(symbol, 'SMART', 'USD')
ticker = ib.reqMktData(stock) # , regulatorySnapshot=False
ib.sleep(3); bid_price = ticker.bid; ask_price = ticker.ask
# 检查 bid 和 ask 价格是否有效
if not (bid_price > 0 and ask_price > 0):
print(time.strftime("%Y-%m-%d %H:%M:%S"), f"无法获取 {symbol} 的 bid 或 ask 价格。")
continue
# 计算动态交易金额:除IBKR API外,所有IBKR平台均支持小数股。
# https://www.interactivebrokers.com.hk/cn/trading/fractional-trading.php
if trade_amount <= ask_price + 1:
trade_amount = int(ask_price + 1)
sell_value = buy_value + round(trade_amount * 1.96, 0)
# print(time.strftime("%Y-%m-%d %H:%M:%S"), symbol, "buy_value", buy_value, "trade_amount", trade_amount, "sell_value", sell_value)
# 获取当前持仓数量
current_amount = 0
for position in positions:
if position.contract.symbol == symbol:
current_amount = position.position #; print(position)
break
# if current_amount <= 0: print(f"无法获取 {symbol} 的持仓数量。"); continue
bid_value = round(bid_price * current_amount, 2)
ask_value = round(ask_price * current_amount, 2)
print(time.strftime("%Y-%m-%d %H:%M:%S"), f"{i}.{symbol} 当前 bid_price: {bid_price} ask_price: {ask_price} bid_value: {bid_value}, ask_value: {ask_value}")
# 是否开盘
is_market_open = get_market_status(market)
if not is_market_open: continue # 未开盘
if not ib.isConnected(): break # 未连接
if not "st" in params: params["st"] = spot_holding # 0-正常持币,4-立刻清仓
# 判断是否需要买入
if ask_value <= buy_value:
trade_amount_ex = buy_value - ask_value + trade_amount / 2
quantity = round(max(trade_amount, trade_amount_ex) / ask_price, 0)
if quantity <= 0: print("trade_amount / ask_price <= 0"); continue
required_funds = ask_price * quantity
# 获取可用资金
cash_balance = get_cash_balance()
if required_funds <= cash_balance:
print(f"市价买入 {quantity} 股的 {symbol},价格: {ask_price},金额:{truncate(required_funds, 2)}。")
order = MarketOrder('BUY', quantity) # order = LimitOrder("BUY", quantity, ask_price)
trade = ib.placeOrder(Stock(symbol, 'SMART', 'USD'), order)
ib.waitOnUpdate(); ib.sleep(1)
# print("trade", trade)
else:
print(f"资金不足,无法买入 {symbol}。所需资金: {required_funds}, 可用资金: {cash_balance}")
# 判断是否需要卖出
elif bid_value >= sell_value:
trade_amount_ex = bid_value - sell_value + trade_amount
quantity = round(trade_amount_ex / bid_price, 0)
if quantity <= 0: print("trade_amount / bid_price <= 0"); continue
print(f"市价卖出 {quantity} 股的 {symbol},价格: {bid_price},金额:{truncate(bid_price * quantity, 2)}。")
order = MarketOrder('SELL', quantity) # order = LimitOrder("SELL", quantity, bid_price)
trade = ib.placeOrder(Stock(symbol, 'SMART', 'USD'), order)
ib.waitOnUpdate(); ib.sleep(1)
# print("trade", trade)
# 循环获取持仓,低买高卖
try:
while True:
try:
if not ib.isConnected(): ib.connect(host, port, clientId=1)
# 获取账户余额信息
account_summary = ib.accountSummary()
# 简单获取美元可用余额和总市值
available_funds = next((item.value for item in account_summary if item.tag == 'AvailableFunds' and item.currency == 'USD'), 0)
net_liquidation = next((item.value for item in account_summary if item.tag == 'NetLiquidation' and item.currency == 'USD'), 0)
cash_balance = next((item.value for item in account_summary if item.tag == 'CashBalance' and item.currency == 'USD'), 0)
print(time.strftime("%Y-%m-%d %H:%M:%S"), f"可用余额: {cash_balance} USD", f"总市值: {net_liquidation} USD")
# 检查 美股、港股、A股 是否开盘
market_status = { "US": False, "HK": False, "CN": False }
market_status["US"] = get_market_status('US')
market_status["HK"] = get_market_status('HK')
market_status["CN"] = get_market_status('CN')
print(time.strftime("%Y-%m-%d %H:%M:%S"), f"开盘状态: US: {'开盘' if market_status['US'] else '未开盘'} HK: {'开盘' if market_status['HK'] else '未开盘'} CN: {'开盘' if market_status['CN'] else '未开盘'}")
if market_status["US"] or market_status["HK"] or market_status["CN"]:
# 获取当前持仓
positions = ib.positions()
# 调用管理函数
manage_positions(positions, symbols, market_status)
except Exception as e:
print(time.strftime("%Y-%m-%d %H:%M:%S"), f"Exception:", str(e))
finally:
# 休息时间
time.sleep(300)
except KeyboardInterrupt:
print("手动停止了循环。")
finally:
# 断开连接
ib.disconnect()