siglab-py 0.1.18__py3-none-any.whl → 0.1.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of siglab-py might be problematic. Click here for more details.
- siglab_py/exchanges/futubull.py +22 -2
- siglab_py/market_data_providers/futu_candles.py +95 -0
- siglab_py/ordergateway/gateway.py +5 -2
- {siglab_py-0.1.18.dist-info → siglab_py-0.1.20.dist-info}/METADATA +1 -1
- {siglab_py-0.1.18.dist-info → siglab_py-0.1.20.dist-info}/RECORD +7 -6
- {siglab_py-0.1.18.dist-info → siglab_py-0.1.20.dist-info}/WHEEL +0 -0
- {siglab_py-0.1.18.dist-info → siglab_py-0.1.20.dist-info}/top_level.txt +0 -0
siglab_py/exchanges/futubull.py
CHANGED
|
@@ -8,7 +8,9 @@ Subscribe L2 data: https://openapi.futunn.com/futu-api-doc/en/intro/authority.ht
|
|
|
8
8
|
|
|
9
9
|
Investor Protection: https://www.futuhk.com/en
|
|
10
10
|
|
|
11
|
-
Margin Trading:
|
|
11
|
+
Margin Trading:
|
|
12
|
+
https://www.futunn.com/en/learn/detail-what-is-margin-trading-62010-220679271
|
|
13
|
+
Margin rate 6.8% annual https://www.futuhk.com/en/support/topic2_417
|
|
12
14
|
|
|
13
15
|
Download Futu OpenD
|
|
14
16
|
https://www.futuhk.com/en/support/topic1_464?global_content=%7B%22promote_id%22%3A13765%2C%22sub_promote_id%22%3A10%7D
|
|
@@ -40,13 +42,14 @@ API
|
|
|
40
42
|
|
|
41
43
|
'''
|
|
42
44
|
from datetime import datetime, timedelta
|
|
45
|
+
import pytz
|
|
43
46
|
from typing import List, Dict, Union, Any, NoReturn
|
|
44
47
|
import pandas as pd
|
|
45
48
|
|
|
46
49
|
import futu as ft
|
|
47
50
|
from futu import *
|
|
48
51
|
|
|
49
|
-
from exchanges.any_exchange import AnyExchange
|
|
52
|
+
from siglab_py.exchanges.any_exchange import AnyExchange
|
|
50
53
|
|
|
51
54
|
class Futubull(AnyExchange):
|
|
52
55
|
def __init__(self, *args: Dict[str, Any]) -> None:
|
|
@@ -259,6 +262,23 @@ class Futubull(AnyExchange):
|
|
|
259
262
|
'''
|
|
260
263
|
time_key = row['time_key']
|
|
261
264
|
dt = datetime.strptime(time_key, "%Y-%m-%d %H:%M:%S")
|
|
265
|
+
|
|
266
|
+
if self.market in [ Market.HK, Market.SH, Market.SZ ]:
|
|
267
|
+
tz = pytz.timezone('Asia/Shanghai')
|
|
268
|
+
dt = tz.localize(dt)
|
|
269
|
+
elif self.market == Market.US:
|
|
270
|
+
tz = pytz.timezone('US/Eastern')
|
|
271
|
+
dt = tz.localize(dt)
|
|
272
|
+
elif self.market == Market.CA:
|
|
273
|
+
tz = pytz.timezone('US/Eastern')
|
|
274
|
+
dt = tz.localize(dt)
|
|
275
|
+
elif self.market == Market.AU:
|
|
276
|
+
tz = pytz.timezone('Australia/Sydney')
|
|
277
|
+
dt = tz.localize(dt)
|
|
278
|
+
else:
|
|
279
|
+
# @todo: HK SH SZ US AU CA FX
|
|
280
|
+
raise ValueError(f"Unsupported market {self.market}")
|
|
281
|
+
|
|
262
282
|
timestamp_ms = int(dt.timestamp() * 1000)
|
|
263
283
|
open = row['open']
|
|
264
284
|
high = row['high']
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Dict, Union
|
|
4
|
+
import asyncio
|
|
5
|
+
|
|
6
|
+
from futu import *
|
|
7
|
+
|
|
8
|
+
from siglab_py.exchanges.futubull import Futubull
|
|
9
|
+
from siglab_py.util.market_data_util import fetch_candles
|
|
10
|
+
from siglab_py.util.analytic_util import compute_candles_stats
|
|
11
|
+
|
|
12
|
+
end_date : datetime = datetime.today()
|
|
13
|
+
start_date : datetime = end_date - timedelta(days=365)
|
|
14
|
+
|
|
15
|
+
param : Dict = {
|
|
16
|
+
'symbol' : None,
|
|
17
|
+
'trdmarket' : TrdMarket.HK,
|
|
18
|
+
'security_firm' : SecurityFirm.FUTUSECURITIES,
|
|
19
|
+
'market' : Market.HK,
|
|
20
|
+
'security_type' : SecurityType.STOCK,
|
|
21
|
+
'daemon' : {
|
|
22
|
+
'host' : '127.0.0.1',
|
|
23
|
+
'port' : 11111
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
'''
|
|
28
|
+
If debugging from VSCode, launch.json:
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
"version": "0.2.0",
|
|
32
|
+
"configurations": [
|
|
33
|
+
{
|
|
34
|
+
"name": "Python Debugger: Current File",
|
|
35
|
+
"type": "debugpy",
|
|
36
|
+
"request": "launch",
|
|
37
|
+
"program": "${file}",
|
|
38
|
+
"console": "integratedTerminal",
|
|
39
|
+
"args" : [
|
|
40
|
+
"--symbol", "HK.00700",
|
|
41
|
+
"--market", "HK",
|
|
42
|
+
"--trdmarket", "HK",
|
|
43
|
+
"--security_firm", "FUTUSECURITIES",
|
|
44
|
+
"--security_type", "STOCK"
|
|
45
|
+
],
|
|
46
|
+
"env": {
|
|
47
|
+
"PYTHONPATH": "${workspaceFolder}"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
'''
|
|
53
|
+
def parse_args():
|
|
54
|
+
parser = argparse.ArgumentParser() # type: ignore
|
|
55
|
+
parser.add_argument("--symbol", help="symbol, example HK.00700", default=None)
|
|
56
|
+
|
|
57
|
+
'''
|
|
58
|
+
Enums here:
|
|
59
|
+
https://openapi.futunn.com/futu-api-doc/en/quote/quote.html#66
|
|
60
|
+
https://openapi.futunn.com/futu-api-doc/en/trade/trade.html#9434
|
|
61
|
+
'''
|
|
62
|
+
parser.add_argument("--market", help="market: HK SH SZ US AU CA FX", default=Market.HK)
|
|
63
|
+
parser.add_argument("--trdmarket", help="trdmarket: HK, HKCC, HKFUND, FUTURES, CN, CA, AU, JP, MY, SG, US, USFUND", default=TrdMarket.HK)
|
|
64
|
+
parser.add_argument("--security_firm", help="security_firm: FUTUSECURITIES (HK), FUTUINC (US), FUTUSG (SG), FUTUAU (AU)", default=SecurityFirm.FUTUSECURITIES)
|
|
65
|
+
parser.add_argument("--security_type", help="STOCK, BOND, ETF, FUTURE, WARRANT, IDX ... ", default=SecurityType.STOCK)
|
|
66
|
+
|
|
67
|
+
args = parser.parse_args()
|
|
68
|
+
param['symbol'] = args.symbol
|
|
69
|
+
param['market'] = args.market
|
|
70
|
+
param['trdmarket'] = args.trdmarket
|
|
71
|
+
param['security_firm'] = args.security_firm
|
|
72
|
+
|
|
73
|
+
async def main():
|
|
74
|
+
parse_args()
|
|
75
|
+
|
|
76
|
+
exchange = Futubull(param)
|
|
77
|
+
|
|
78
|
+
pd_candles: Union[pd.DataFrame, None] = fetch_candles(
|
|
79
|
+
start_ts=int(start_date.timestamp()),
|
|
80
|
+
end_ts=int(end_date.timestamp()),
|
|
81
|
+
exchange=exchange,
|
|
82
|
+
normalized_symbols=[ param['symbol'] ],
|
|
83
|
+
candle_size='1d'
|
|
84
|
+
)[param['symbol']]
|
|
85
|
+
|
|
86
|
+
assert pd_candles is not None
|
|
87
|
+
|
|
88
|
+
if pd_candles is not None:
|
|
89
|
+
assert len(pd_candles) > 0, "No candles returned."
|
|
90
|
+
expected_columns = {'exchange', 'symbol', 'timestamp_ms', 'open', 'high', 'low', 'close', 'volume', 'datetime_utc', 'datetime', 'year', 'month', 'day', 'hour', 'minute'}
|
|
91
|
+
assert set(pd_candles.columns) >= expected_columns, "Missing expected columns."
|
|
92
|
+
assert pd_candles['timestamp_ms'].notna().all(), "timestamp_ms column contains NaN values."
|
|
93
|
+
assert pd_candles['timestamp_ms'].is_monotonic_increasing, "Timestamps are not in ascending order."
|
|
94
|
+
|
|
95
|
+
asyncio.run(main())
|
|
@@ -463,9 +463,13 @@ async def execute_one_position(
|
|
|
463
463
|
slice_amount_in_base_ccy : float = slice.amount
|
|
464
464
|
rounded_slice_amount_in_base_ccy = slice_amount_in_base_ccy / multiplier # After devided by multiplier, rounded_slice_amount_in_base_ccy in number of contracts actually (Not in base ccy).
|
|
465
465
|
rounded_slice_amount_in_base_ccy = exchange.amount_to_precision(position.ticker, rounded_slice_amount_in_base_ccy) # type: ignore
|
|
466
|
-
rounded_slice_amount_in_base_ccy = float(rounded_slice_amount_in_base_ccy)
|
|
466
|
+
rounded_slice_amount_in_base_ccy = float(rounded_slice_amount_in_base_ccy) if rounded_slice_amount_in_base_ccy else 0
|
|
467
467
|
rounded_slice_amount_in_base_ccy = rounded_slice_amount_in_base_ccy if rounded_slice_amount_in_base_ccy>min_amount else min_amount
|
|
468
468
|
|
|
469
|
+
if rounded_slice_amount_in_base_ccy==0:
|
|
470
|
+
log(f"{position.ticker} Slice amount rounded to zero?! slice amount before rounding: {slice.amount}")
|
|
471
|
+
continue
|
|
472
|
+
|
|
469
473
|
orderbook = await exchange.fetch_order_book(symbol=position.ticker, limit=3) # type: ignore
|
|
470
474
|
if position.side=='buy':
|
|
471
475
|
asks = [ ask[0] for ask in orderbook['asks'] ]
|
|
@@ -604,7 +608,6 @@ async def execute_one_position(
|
|
|
604
608
|
await asyncio.sleep(int(param['loop_freq_ms']/1000))
|
|
605
609
|
|
|
606
610
|
|
|
607
|
-
|
|
608
611
|
# Cancel hung limit order, resend as market
|
|
609
612
|
if order_status!='closed':
|
|
610
613
|
# If no update from websocket, do one last fetch via REST
|
|
@@ -2,18 +2,19 @@ siglab_py/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
2
2
|
siglab_py/constants.py,sha256=YGNdEsWtQ99V2oltaynZTjM8VIboSfyIjDXJKSlhv4U,132
|
|
3
3
|
siglab_py/exchanges/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
siglab_py/exchanges/any_exchange.py,sha256=Y-zue75ZSmu9Ga1fONbjBGLNH5pDHQI01hCSjuLBkAk,889
|
|
5
|
-
siglab_py/exchanges/futubull.py,sha256=
|
|
5
|
+
siglab_py/exchanges/futubull.py,sha256=6M99G8ZYsIyDyoTvvleMT_WPyNYbb6q-t2KXp0Qyi6g,20422
|
|
6
6
|
siglab_py/market_data_providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
siglab_py/market_data_providers/aggregated_orderbook_provider.py,sha256=FZRobEBNRzcNGlOG3u38OVhmOZYlkNm8dVvR-S7Ii2g,23342
|
|
8
8
|
siglab_py/market_data_providers/candles_provider.py,sha256=fqHJjlECsBiBlpgyywrc4gTgxiROPNzZM8KxQBB5cOg,14139
|
|
9
9
|
siglab_py/market_data_providers/candles_ta_provider.py,sha256=uiAhbEZZdTF-YulBHpSLwabos5LHCKU91NTiTmpUc0w,12001
|
|
10
10
|
siglab_py/market_data_providers/deribit_options_expiry_provider.py,sha256=e9Ee8TmC8pXaid8-jouSLKIpuW6_JBBgwRTieI665yQ,8684
|
|
11
|
+
siglab_py/market_data_providers/futu_candles.py,sha256=hjvbEV8nIs_v5lu8sWu_8C4cNoB82LLRXTzTiz7YZUE,3526
|
|
11
12
|
siglab_py/market_data_providers/orderbooks_provider.py,sha256=olt-3LIkoyzQWfNNQRhJtKibLbkTutt_q_rCCTM7i1g,16216
|
|
12
13
|
siglab_py/market_data_providers/test_provider.py,sha256=wBLCgcWjs7FGZJXWsNyn30lkOLa_cgpuvqRakMC0wbA,2221
|
|
13
14
|
siglab_py/ordergateway/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
15
|
siglab_py/ordergateway/client.py,sha256=qh4vrCJm8iITKAl07lisZxJ3V4JkbsYlmIBMYihfUaU,9575
|
|
15
16
|
siglab_py/ordergateway/encrypt_keys_util.py,sha256=-qi87db8To8Yf1WS1Q_Cp2Ya7ZqgWlRqSHfNXCM7wE4,1339
|
|
16
|
-
siglab_py/ordergateway/gateway.py,sha256=
|
|
17
|
+
siglab_py/ordergateway/gateway.py,sha256=FoE-OUBrHJ2fdKAfvGue1yQ2P67falWoyoEGCV8ScfA,37219
|
|
17
18
|
siglab_py/ordergateway/test_ordergateway.py,sha256=_Gz2U_VqljogGWqGyNDYYls1INqUiig9veyPttfGRpg,3901
|
|
18
19
|
siglab_py/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
20
|
siglab_py/tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -25,7 +26,7 @@ siglab_py/util/analytic_util.py,sha256=QLabbEMqM4rKKH2PE_LqxIyo-BUdCRhkLybLATBIm
|
|
|
25
26
|
siglab_py/util/aws_util.py,sha256=KGmjHrr1rpnnxr33nXHNzTul4tvyyxl9p6gpwNv0Ygc,2557
|
|
26
27
|
siglab_py/util/market_data_util.py,sha256=PmGPBXJDTuK-so4qoGT27ag-_Qig7GNFV6znxkmcRW4,17252
|
|
27
28
|
siglab_py/util/retry_util.py,sha256=mxYuRFZRZoaQQjENcwPmxhxixtd1TFvbxIdPx4RwfRc,743
|
|
28
|
-
siglab_py-0.1.
|
|
29
|
-
siglab_py-0.1.
|
|
30
|
-
siglab_py-0.1.
|
|
31
|
-
siglab_py-0.1.
|
|
29
|
+
siglab_py-0.1.20.dist-info/METADATA,sha256=WjWRIUcBDUn91ZX1ulfXINyZfAFI_yQcBm1mg0MjbQ0,1097
|
|
30
|
+
siglab_py-0.1.20.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
31
|
+
siglab_py-0.1.20.dist-info/top_level.txt,sha256=AbD4VR9OqmMOGlMJLkAVPGQMtUPIQv0t1BF5xmcLJSk,10
|
|
32
|
+
siglab_py-0.1.20.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|