kwcli 0.1.0__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.
Files changed (62) hide show
  1. kiwoom/__init__.py +47 -0
  2. kiwoom/_data/kiwoom_api_spec.json +66372 -0
  3. kiwoom/core/__init__.py +6 -0
  4. kiwoom/core/auth.py +349 -0
  5. kiwoom/core/client.py +244 -0
  6. kiwoom/core/errors.py +177 -0
  7. kiwoom/core/platform_paths.py +68 -0
  8. kiwoom/core/profiles.py +143 -0
  9. kiwoom/core/runtime.py +153 -0
  10. kiwoom/core/secrets.py +217 -0
  11. kiwoom/core/settings.py +64 -0
  12. kiwoom/core/token_store.py +186 -0
  13. kiwoom/core/types.py +28 -0
  14. kiwoom/core/ws_client.py +262 -0
  15. kiwoom/realtime/__init__.py +30 -0
  16. kiwoom/realtime/decoders.py +171 -0
  17. kiwoom/realtime/events.py +98 -0
  18. kiwoom/realtime/packets.py +65 -0
  19. kiwoom/realtime/schemas.py +76 -0
  20. kiwoom/realtime/stream.py +213 -0
  21. kiwoom/specs.py +272 -0
  22. kiwoom_cli/README.md +671 -0
  23. kiwoom_cli/__init__.py +9 -0
  24. kiwoom_cli/__main__.py +5 -0
  25. kiwoom_cli/argument_maps.py +561 -0
  26. kiwoom_cli/arguments.py +147 -0
  27. kiwoom_cli/auth_context.py +64 -0
  28. kiwoom_cli/banner.py +125 -0
  29. kiwoom_cli/commands/__init__.py +1 -0
  30. kiwoom_cli/commands/auth.py +406 -0
  31. kiwoom_cli/commands/groups.py +281 -0
  32. kiwoom_cli/commands/mapped.py +74 -0
  33. kiwoom_cli/commands/orders.py +158 -0
  34. kiwoom_cli/commands/spec.py +98 -0
  35. kiwoom_cli/commands/stocks.py +100 -0
  36. kiwoom_cli/commands/streams.py +470 -0
  37. kiwoom_cli/doctor.py +324 -0
  38. kiwoom_cli/errors.py +24 -0
  39. kiwoom_cli/executor/__init__.py +33 -0
  40. kiwoom_cli/executor/condition.py +374 -0
  41. kiwoom_cli/executor/rest.py +132 -0
  42. kiwoom_cli/executor/waits.py +34 -0
  43. kiwoom_cli/executor/websocket.py +131 -0
  44. kiwoom_cli/main.py +203 -0
  45. kiwoom_cli/maps/README.md +99 -0
  46. kiwoom_cli/maps/api_commands.csv +209 -0
  47. kiwoom_cli/maps/arguments.csv +731 -0
  48. kiwoom_cli/maps/order_confirmation_commands.csv +13 -0
  49. kiwoom_cli/maps/order_confirmation_fields.csv +71 -0
  50. kiwoom_cli/maps/order_price_policies.csv +47 -0
  51. kiwoom_cli/maps/order_value_labels.csv +28 -0
  52. kiwoom_cli/maps/positional_arguments.csv +21 -0
  53. kiwoom_cli/order_confirmation.py +167 -0
  54. kiwoom_cli/output.py +136 -0
  55. kiwoom_cli/registry.py +95 -0
  56. kiwoom_cli/safety.py +30 -0
  57. kiwoom_cli/setup.py +533 -0
  58. kwcli-0.1.0.dist-info/METADATA +215 -0
  59. kwcli-0.1.0.dist-info/RECORD +62 -0
  60. kwcli-0.1.0.dist-info/WHEEL +4 -0
  61. kwcli-0.1.0.dist-info/entry_points.txt +2 -0
  62. kwcli-0.1.0.dist-info/licenses/LICENSE.md +36 -0
@@ -0,0 +1,13 @@
1
+ command_path,kind,message
2
+ kiwoomcli domestic orders buy,주식 매수,주문은 아직 전송되지 않았습니다. 실제 전송하려면 같은 명령에 --confirm을 붙여 다시 실행하세요.
3
+ kiwoomcli domestic orders sell,주식 매도,주문은 아직 전송되지 않았습니다. 실제 전송하려면 같은 명령에 --confirm을 붙여 다시 실행하세요.
4
+ kiwoomcli domestic orders modify,주식 정정,정정주문은 아직 전송되지 않았습니다. 실제 전송하려면 같은 명령에 --confirm을 붙여 다시 실행하세요.
5
+ kiwoomcli domestic orders cancel,주식 취소,취소주문은 아직 전송되지 않았습니다. 실제 전송하려면 같은 명령에 --confirm을 붙여 다시 실행하세요.
6
+ kiwoomcli domestic orders credit-buy,신용 매수,주문은 아직 전송되지 않았습니다. 실제 전송하려면 같은 명령에 --confirm을 붙여 다시 실행하세요.
7
+ kiwoomcli domestic orders credit-sell,신용 매도,주문은 아직 전송되지 않았습니다. 실제 전송하려면 같은 명령에 --confirm을 붙여 다시 실행하세요.
8
+ kiwoomcli domestic orders credit-modify,신용 정정,정정주문은 아직 전송되지 않았습니다. 실제 전송하려면 같은 명령에 --confirm을 붙여 다시 실행하세요.
9
+ kiwoomcli domestic orders credit-cancel,신용 취소,취소주문은 아직 전송되지 않았습니다. 실제 전송하려면 같은 명령에 --confirm을 붙여 다시 실행하세요.
10
+ kiwoomcli domestic orders gold-buy,금현물 매수,금현물 주문은 아직 전송되지 않았습니다. 실제 전송하려면 같은 명령에 --confirm을 붙여 다시 실행하세요.
11
+ kiwoomcli domestic orders gold-sell,금현물 매도,금현물 주문은 아직 전송되지 않았습니다. 실제 전송하려면 같은 명령에 --confirm을 붙여 다시 실행하세요.
12
+ kiwoomcli domestic orders gold-modify,금현물 정정,금현물 정정주문은 아직 전송되지 않았습니다. 실제 전송하려면 같은 명령에 --confirm을 붙여 다시 실행하세요.
13
+ kiwoomcli domestic orders gold-cancel,금현물 취소,금현물 취소주문은 아직 전송되지 않았습니다. 실제 전송하려면 같은 명령에 --confirm을 붙여 다시 실행하세요.
@@ -0,0 +1,71 @@
1
+ command_path,output_key,label,arg_name,value_label_group,include_if_empty,empty_display
2
+ kiwoomcli domestic orders buy,kind,구분,__kind__,,Y,
3
+ kiwoomcli domestic orders buy,exchange,시장,exchange,,Y,
4
+ kiwoomcli domestic orders buy,code,종목,code,,Y,
5
+ kiwoomcli domestic orders buy,quantity,수량,qty,,Y,
6
+ kiwoomcli domestic orders buy,order_type,유형,order_type,order_type,Y,
7
+ kiwoomcli domestic orders buy,price,가격,price,,Y,없음
8
+ kiwoomcli domestic orders sell,kind,구분,__kind__,,Y,
9
+ kiwoomcli domestic orders sell,exchange,시장,exchange,,Y,
10
+ kiwoomcli domestic orders sell,code,종목,code,,Y,
11
+ kiwoomcli domestic orders sell,quantity,수량,qty,,Y,
12
+ kiwoomcli domestic orders sell,order_type,유형,order_type,order_type,Y,
13
+ kiwoomcli domestic orders sell,price,가격,price,,Y,없음
14
+ kiwoomcli domestic orders modify,kind,구분,__kind__,,Y,
15
+ kiwoomcli domestic orders modify,exchange,시장,exchange,,Y,
16
+ kiwoomcli domestic orders modify,code,종목,code,,Y,
17
+ kiwoomcli domestic orders modify,original_order_id,원주문번호,order_id,,Y,
18
+ kiwoomcli domestic orders modify,quantity,정정수량,qty,,Y,
19
+ kiwoomcli domestic orders modify,price,정정가격,price,,Y,
20
+ kiwoomcli domestic orders cancel,kind,구분,__kind__,,Y,
21
+ kiwoomcli domestic orders cancel,exchange,시장,exchange,,Y,
22
+ kiwoomcli domestic orders cancel,code,종목,code,,Y,
23
+ kiwoomcli domestic orders cancel,original_order_id,원주문번호,order_id,,Y,
24
+ kiwoomcli domestic orders cancel,quantity,취소수량,qty,,Y,
25
+ kiwoomcli domestic orders credit-buy,kind,구분,__kind__,,Y,
26
+ kiwoomcli domestic orders credit-buy,exchange,시장,exchange,,Y,
27
+ kiwoomcli domestic orders credit-buy,code,종목,code,,Y,
28
+ kiwoomcli domestic orders credit-buy,quantity,수량,qty,,Y,
29
+ kiwoomcli domestic orders credit-buy,order_type,유형,order_type,order_type,Y,
30
+ kiwoomcli domestic orders credit-buy,price,가격,price,,Y,없음
31
+ kiwoomcli domestic orders credit-buy,condition_price,조건가격,condition_price,,N,
32
+ kiwoomcli domestic orders credit-sell,kind,구분,__kind__,,Y,
33
+ kiwoomcli domestic orders credit-sell,exchange,시장,exchange,,Y,
34
+ kiwoomcli domestic orders credit-sell,code,종목,code,,Y,
35
+ kiwoomcli domestic orders credit-sell,quantity,수량,qty,,Y,
36
+ kiwoomcli domestic orders credit-sell,order_type,유형,order_type,order_type,Y,
37
+ kiwoomcli domestic orders credit-sell,price,가격,price,,Y,없음
38
+ kiwoomcli domestic orders credit-sell,credit_deal,신용거래,credit_deal,credit_deal,Y,
39
+ kiwoomcli domestic orders credit-sell,loan_date,대출일,loan_date,,N,
40
+ kiwoomcli domestic orders credit-sell,condition_price,조건가격,condition_price,,N,
41
+ kiwoomcli domestic orders credit-modify,kind,구분,__kind__,,Y,
42
+ kiwoomcli domestic orders credit-modify,exchange,시장,exchange,,Y,
43
+ kiwoomcli domestic orders credit-modify,code,종목,code,,Y,
44
+ kiwoomcli domestic orders credit-modify,original_order_id,원주문번호,order_id,,Y,
45
+ kiwoomcli domestic orders credit-modify,quantity,정정수량,qty,,Y,
46
+ kiwoomcli domestic orders credit-modify,price,정정가격,price,,Y,
47
+ kiwoomcli domestic orders credit-modify,condition_price,정정조건가격,condition_price,,N,
48
+ kiwoomcli domestic orders credit-cancel,kind,구분,__kind__,,Y,
49
+ kiwoomcli domestic orders credit-cancel,exchange,시장,exchange,,Y,
50
+ kiwoomcli domestic orders credit-cancel,code,종목,code,,Y,
51
+ kiwoomcli domestic orders credit-cancel,original_order_id,원주문번호,order_id,,Y,
52
+ kiwoomcli domestic orders credit-cancel,quantity,취소수량,qty,,Y,
53
+ kiwoomcli domestic orders gold-buy,kind,구분,__kind__,,Y,
54
+ kiwoomcli domestic orders gold-buy,code,종목,code,,Y,
55
+ kiwoomcli domestic orders gold-buy,quantity,수량,qty,,Y,
56
+ kiwoomcli domestic orders gold-buy,order_type,유형,order_type,order_type,Y,
57
+ kiwoomcli domestic orders gold-buy,price,가격,price,,Y,
58
+ kiwoomcli domestic orders gold-sell,kind,구분,__kind__,,Y,
59
+ kiwoomcli domestic orders gold-sell,code,종목,code,,Y,
60
+ kiwoomcli domestic orders gold-sell,quantity,수량,qty,,Y,
61
+ kiwoomcli domestic orders gold-sell,order_type,유형,order_type,order_type,Y,
62
+ kiwoomcli domestic orders gold-sell,price,가격,price,,Y,
63
+ kiwoomcli domestic orders gold-modify,kind,구분,__kind__,,Y,
64
+ kiwoomcli domestic orders gold-modify,code,종목,code,,Y,
65
+ kiwoomcli domestic orders gold-modify,original_order_id,원주문번호,order_id,,Y,
66
+ kiwoomcli domestic orders gold-modify,quantity,정정수량,qty,,Y,
67
+ kiwoomcli domestic orders gold-modify,price,정정가격,price,,Y,
68
+ kiwoomcli domestic orders gold-cancel,kind,구분,__kind__,,Y,
69
+ kiwoomcli domestic orders gold-cancel,original_order_id,원주문번호,order_id,,Y,
70
+ kiwoomcli domestic orders gold-cancel,code,종목,code,,Y,
71
+ kiwoomcli domestic orders gold-cancel,quantity,취소수량,qty,,Y,
@@ -0,0 +1,47 @@
1
+ command_path,order_type,price_option,price_policy,condition_price_option,condition_price_policy,description
2
+ kiwoomcli domestic orders buy,limit,--price,required,,,지정가 매수는 주문단가가 필요합니다.
3
+ kiwoomcli domestic orders buy,market,--price,forbidden,,,시장가 매수는 주문단가를 보내지 않습니다.
4
+ kiwoomcli domestic orders sell,limit,--price,required,,,지정가 매도는 주문단가가 필요합니다.
5
+ kiwoomcli domestic orders sell,market,--price,forbidden,,,시장가 매도는 주문단가를 보내지 않습니다.
6
+ kiwoomcli domestic orders credit-buy,limit,--price,required,,,지정가 신용매수는 주문단가가 필요합니다.
7
+ kiwoomcli domestic orders credit-buy,market,--price,forbidden,,,시장가 신용매수는 주문단가를 보내지 않습니다.
8
+ kiwoomcli domestic orders credit-buy,conditional-limit,--price,required,,,조건부지정가는 주문단가가 필요합니다.
9
+ kiwoomcli domestic orders credit-buy,after-hours-close,--price,forbidden,,,장마감후시간외는 별도 주문단가를 보내지 않습니다.
10
+ kiwoomcli domestic orders credit-buy,pre-open,--price,forbidden,,,장시작전시간외는 별도 주문단가를 보내지 않습니다.
11
+ kiwoomcli domestic orders credit-buy,after-hours-single,--price,required,,,시간외단일가는 주문단가가 필요합니다.
12
+ kiwoomcli domestic orders credit-buy,best-limit,--price,forbidden,,,최유리지정가는 주문단가를 보내지 않습니다.
13
+ kiwoomcli domestic orders credit-buy,top-priority,--price,forbidden,,,최우선지정가는 주문단가를 보내지 않습니다.
14
+ kiwoomcli domestic orders credit-buy,limit-ioc,--price,required,,,보통 IOC는 주문단가가 필요합니다.
15
+ kiwoomcli domestic orders credit-buy,market-ioc,--price,forbidden,,,시장가 IOC는 주문단가를 보내지 않습니다.
16
+ kiwoomcli domestic orders credit-buy,best-ioc,--price,forbidden,,,최유리 IOC는 주문단가를 보내지 않습니다.
17
+ kiwoomcli domestic orders credit-buy,limit-fok,--price,required,,,보통 FOK는 주문단가가 필요합니다.
18
+ kiwoomcli domestic orders credit-buy,market-fok,--price,forbidden,,,시장가 FOK는 주문단가를 보내지 않습니다.
19
+ kiwoomcli domestic orders credit-buy,best-fok,--price,forbidden,,,최유리 FOK는 주문단가를 보내지 않습니다.
20
+ kiwoomcli domestic orders credit-buy,stop-limit,--price,required,--condition-price,required,스톱지정가는 주문단가와 조건단가가 필요합니다.
21
+ kiwoomcli domestic orders credit-buy,mid,--price,forbidden,,,중간가는 주문단가를 보내지 않습니다.
22
+ kiwoomcli domestic orders credit-buy,mid-ioc,--price,forbidden,,,중간가 IOC는 주문단가를 보내지 않습니다.
23
+ kiwoomcli domestic orders credit-buy,mid-fok,--price,forbidden,,,중간가 FOK는 주문단가를 보내지 않습니다.
24
+ kiwoomcli domestic orders credit-sell,limit,--price,required,,,지정가 신용매도는 주문단가가 필요합니다.
25
+ kiwoomcli domestic orders credit-sell,market,--price,forbidden,,,시장가 신용매도는 주문단가를 보내지 않습니다.
26
+ kiwoomcli domestic orders credit-sell,conditional-limit,--price,required,,,조건부지정가는 주문단가가 필요합니다.
27
+ kiwoomcli domestic orders credit-sell,after-hours-close,--price,forbidden,,,장마감후시간외는 별도 주문단가를 보내지 않습니다.
28
+ kiwoomcli domestic orders credit-sell,pre-open,--price,forbidden,,,장시작전시간외는 별도 주문단가를 보내지 않습니다.
29
+ kiwoomcli domestic orders credit-sell,after-hours-single,--price,required,,,시간외단일가는 주문단가가 필요합니다.
30
+ kiwoomcli domestic orders credit-sell,best-limit,--price,forbidden,,,최유리지정가는 주문단가를 보내지 않습니다.
31
+ kiwoomcli domestic orders credit-sell,top-priority,--price,forbidden,,,최우선지정가는 주문단가를 보내지 않습니다.
32
+ kiwoomcli domestic orders credit-sell,limit-ioc,--price,required,,,보통 IOC는 주문단가가 필요합니다.
33
+ kiwoomcli domestic orders credit-sell,market-ioc,--price,forbidden,,,시장가 IOC는 주문단가를 보내지 않습니다.
34
+ kiwoomcli domestic orders credit-sell,best-ioc,--price,forbidden,,,최유리 IOC는 주문단가를 보내지 않습니다.
35
+ kiwoomcli domestic orders credit-sell,limit-fok,--price,required,,,보통 FOK는 주문단가가 필요합니다.
36
+ kiwoomcli domestic orders credit-sell,market-fok,--price,forbidden,,,시장가 FOK는 주문단가를 보내지 않습니다.
37
+ kiwoomcli domestic orders credit-sell,best-fok,--price,forbidden,,,최유리 FOK는 주문단가를 보내지 않습니다.
38
+ kiwoomcli domestic orders credit-sell,stop-limit,--price,required,--condition-price,required,스톱지정가는 주문단가와 조건단가가 필요합니다.
39
+ kiwoomcli domestic orders credit-sell,mid,--price,forbidden,,,중간가는 주문단가를 보내지 않습니다.
40
+ kiwoomcli domestic orders credit-sell,mid-ioc,--price,forbidden,,,중간가 IOC는 주문단가를 보내지 않습니다.
41
+ kiwoomcli domestic orders credit-sell,mid-fok,--price,forbidden,,,중간가 FOK는 주문단가를 보내지 않습니다.
42
+ kiwoomcli domestic orders gold-buy,limit,--price,required,,,금현물 지정가 매수는 주문단가가 필요합니다.
43
+ kiwoomcli domestic orders gold-buy,limit-ioc,--price,required,,,금현물 IOC 매수는 주문단가가 필요합니다.
44
+ kiwoomcli domestic orders gold-buy,limit-fok,--price,required,,,금현물 FOK 매수는 주문단가가 필요합니다.
45
+ kiwoomcli domestic orders gold-sell,limit,--price,required,,,금현물 지정가 매도는 주문단가가 필요합니다.
46
+ kiwoomcli domestic orders gold-sell,limit-ioc,--price,required,,,금현물 IOC 매도는 주문단가가 필요합니다.
47
+ kiwoomcli domestic orders gold-sell,limit-fok,--price,required,,,금현물 FOK 매도는 주문단가가 필요합니다.
@@ -0,0 +1,28 @@
1
+ group,value,label
2
+ order_type,limit,지정가
3
+ order_type,market,시장가
4
+ order_type,conditional-limit,조건부지정가
5
+ order_type,after-hours-close,장마감후시간외
6
+ order_type,pre-open,장시작전시간외
7
+ order_type,after-hours-single,시간외단일가
8
+ order_type,best-limit,최유리지정가
9
+ order_type,top-priority,최우선지정가
10
+ order_type,limit-ioc,보통(IOC)
11
+ order_type,market-ioc,시장가(IOC)
12
+ order_type,best-ioc,최유리(IOC)
13
+ order_type,limit-fok,보통(FOK)
14
+ order_type,market-fok,시장가(FOK)
15
+ order_type,best-fok,최유리(FOK)
16
+ order_type,stop-limit,스톱지정가
17
+ order_type,mid,중간가
18
+ order_type,mid-ioc,중간가(IOC)
19
+ order_type,mid-fok,중간가(FOK)
20
+ credit_deal,financing,융자
21
+ credit_deal,financing-all,융자합
22
+ order_type,vwap-limit,VWAP지정가
23
+ order_type,twap-limit,TWAP지정가
24
+ order_type,loc,LOC
25
+ order_type,moc,MOC
26
+ order_type,vwap-market,VWAP시장가
27
+ order_type,twap-market,TWAP시장가
28
+ order_type,stop,STOP
@@ -0,0 +1,21 @@
1
+ command_path,canonical_form,positional_form,position,name,dest,option,status,reason
2
+ kiwoomcli setup,kiwoomcli setup --alias <alias> --mode <mode>,kiwoomcli setup <alias> --mode <mode>,1,alias,alias,--alias,allow,setup/login target alias is the primary operand
3
+ kiwoomcli auth login,kiwoomcli auth login --alias <alias> --mode <mode>,kiwoomcli auth login <alias> --mode <mode>,1,alias,alias,--alias,allow,auth login target alias is the primary operand
4
+ kiwoomcli auth status,kiwoomcli auth status --profile <alias>,kiwoomcli auth status <alias>,1,alias,profile,--profile,allow,profile status naturally targets one alias
5
+ kiwoomcli auth refresh,kiwoomcli auth refresh --profile <alias>,kiwoomcli auth refresh <alias>,1,alias,profile,--profile,allow,profile refresh naturally targets one alias
6
+ kiwoomcli auth revoke,kiwoomcli auth revoke --profile <alias>,kiwoomcli auth revoke <alias>,1,alias,profile,--profile,allow,profile token revoke naturally targets one alias
7
+ kiwoomcli auth clear,kiwoomcli auth clear --profile <alias> --all,kiwoomcli auth clear <alias> --all,1,alias,profile,--profile,allow,profile credential cleanup naturally targets one alias
8
+ kiwoomcli domestic stocks info,kiwoomcli domestic stocks info --code <code>,kiwoomcli domestic stocks info <code>,1,code,code,--code,candidate,common single-stock lookup shorthand
9
+ kiwoomcli domestic quotes price,kiwoomcli domestic quotes price --code <code>,kiwoomcli domestic quotes price <code>,1,code,code,--code,candidate,common single-stock quote shorthand
10
+ kiwoomcli domestic orderbooks list,kiwoomcli domestic orderbooks list --code <code>,kiwoomcli domestic orderbooks list <code>,1,code,code,--code,candidate,common single-stock orderbook shorthand
11
+ kiwoomcli domestic candles daily,kiwoomcli domestic candles daily --code <code>,kiwoomcli domestic candles daily <code>,1,code,code,--code,candidate,common single-stock chart shorthand
12
+ kiwoomcli domestic streams trades,kiwoomcli domestic streams trades --code <code>,kiwoomcli domestic streams trades <code>,1,code,code,--code,candidate,common single-stock realtime shorthand
13
+ kiwoomcli domestic streams conditions-search,kiwoomcli domestic streams conditions-search [--seq <seq>] --exchange <exchange>,defer,1,seq,seq,--seq,defer,seq is optional because the command internally loads saved conditions with CNSRLST
14
+ kiwoomcli domestic streams conditions-subscribe,kiwoomcli domestic streams conditions-subscribe [--seq <seq>] --exchange <exchange>,defer,1,seq,seq,--seq,defer,seq is optional because the command internally loads saved conditions with CNSRLST
15
+ kiwoomcli domestic streams conditions-unsubscribe,kiwoomcli domestic streams conditions-unsubscribe [--seq <seq>],defer,1,seq,seq,--seq,defer,one-shot clear is same-session only; positional shorthand would hide that constraint
16
+ kiwoomcli domestic orders buy,kiwoomcli domestic orders buy --code <code> --qty <qty> --price <price> --order-type <type>,defer,1,code,code,--code,defer,write-like order shorthand is deferred for safety and ambiguity
17
+ kiwoomcli domestic orders sell,kiwoomcli domestic orders sell --code <code> --qty <qty> --price <price> --order-type <type>,defer,1,code,code,--code,defer,write-like order shorthand is deferred for safety and ambiguity
18
+ kiwoomcli domestic orders modify,kiwoomcli domestic orders modify --code <code> --order-id <order-id> --qty <qty> --price <price>,defer,1,code,code,--code,defer,modify shorthand is deferred because order-id qty price order can be confused
19
+ kiwoomcli domestic orders modify,kiwoomcli domestic orders modify --code <code> --order-id <order-id> --qty <qty> --price <price>,defer,2,order-id,order_id,--order-id,defer,modify shorthand is deferred because order-id qty price order can be confused
20
+ kiwoomcli domestic orders cancel,kiwoomcli domestic orders cancel --code <code> --order-id <order-id> --qty <qty>,defer,1,code,code,--code,defer,cancel shorthand is deferred because order-id and quantity mistakes are high impact
21
+ kiwoomcli domestic orders cancel,kiwoomcli domestic orders cancel --code <code> --order-id <order-id> --qty <qty>,defer,2,order-id,order_id,--order-id,defer,cancel shorthand is deferred because order-id and quantity mistakes are high impact
@@ -0,0 +1,167 @@
1
+ """Order confirmation output for confirm-gated domestic order commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import csv
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from kiwoom_cli.errors import CliInternalError
12
+ from kiwoom_cli.output import format_payload
13
+ from kiwoom_cli.registry import CommandDefinition
14
+
15
+ MAP_ROOT = Path(__file__).resolve().parent / "maps"
16
+ COMMANDS_PATH = MAP_ROOT / "order_confirmation_commands.csv"
17
+ FIELDS_PATH = MAP_ROOT / "order_confirmation_fields.csv"
18
+ VALUE_LABELS_PATH = MAP_ROOT / "order_value_labels.csv"
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class OrderConfirmationCommand:
23
+ command_path: str
24
+ kind: str
25
+ message: str
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class OrderConfirmationField:
30
+ command_path: str
31
+ output_key: str
32
+ label: str
33
+ arg_name: str
34
+ value_label_group: str
35
+ include_if_empty: bool
36
+ empty_display: str
37
+
38
+
39
+ def build_order_confirmation(
40
+ definition: CommandDefinition,
41
+ args: argparse.Namespace,
42
+ ) -> dict[str, Any]:
43
+ command = get_order_confirmation_command(definition.command_path)
44
+ if command is None:
45
+ raise CliInternalError(
46
+ f"{definition.command_path}: 주문 확인 출력 매핑이 없습니다."
47
+ )
48
+ fields = get_order_confirmation_fields(definition.command_path)
49
+ value_labels = load_value_labels()
50
+ order: dict[str, str] = {}
51
+ display_rows: list[dict[str, str]] = []
52
+ for field in fields:
53
+ value = _field_value(
54
+ field, command=command, args=args, value_labels=value_labels
55
+ )
56
+ if value == "" and not field.include_if_empty:
57
+ continue
58
+ display_value = field.empty_display if value == "" else value
59
+ order[field.output_key] = display_value
60
+ display_rows.append(
61
+ {
62
+ "key": field.output_key,
63
+ "label": field.label,
64
+ "value": display_value,
65
+ }
66
+ )
67
+ return {
68
+ "message": command.message,
69
+ "order": order,
70
+ "_display_rows": display_rows,
71
+ }
72
+
73
+
74
+ def format_order_confirmation(payload: dict[str, Any], *, output_format: str) -> str:
75
+ if output_format == "pretty":
76
+ return _format_order_confirmation_pretty(payload)
77
+ machine_payload = {
78
+ "message": payload["message"],
79
+ "order": payload["order"],
80
+ }
81
+ return format_payload(machine_payload, output_format=output_format)
82
+
83
+
84
+ def load_order_confirmation_commands(
85
+ map_path: Path | None = None,
86
+ ) -> list[OrderConfirmationCommand]:
87
+ resolved_path = map_path or COMMANDS_PATH
88
+ with resolved_path.open(encoding="utf-8", newline="") as file:
89
+ return [
90
+ OrderConfirmationCommand(
91
+ command_path=row["command_path"],
92
+ kind=row["kind"],
93
+ message=row["message"],
94
+ )
95
+ for row in csv.DictReader(file)
96
+ ]
97
+
98
+
99
+ def get_order_confirmation_command(
100
+ command_path: str,
101
+ ) -> OrderConfirmationCommand | None:
102
+ for row in load_order_confirmation_commands():
103
+ if row.command_path == command_path:
104
+ return row
105
+ return None
106
+
107
+
108
+ def load_order_confirmation_fields(
109
+ map_path: Path | None = None,
110
+ ) -> list[OrderConfirmationField]:
111
+ resolved_path = map_path or FIELDS_PATH
112
+ with resolved_path.open(encoding="utf-8", newline="") as file:
113
+ return [_field_from_row(row) for row in csv.DictReader(file)]
114
+
115
+
116
+ def get_order_confirmation_fields(command_path: str) -> list[OrderConfirmationField]:
117
+ return [
118
+ field
119
+ for field in load_order_confirmation_fields()
120
+ if field.command_path == command_path
121
+ ]
122
+
123
+
124
+ def load_value_labels(map_path: Path | None = None) -> dict[tuple[str, str], str]:
125
+ resolved_path = map_path or VALUE_LABELS_PATH
126
+ with resolved_path.open(encoding="utf-8", newline="") as file:
127
+ return {
128
+ (row["group"], row["value"]): row["label"] for row in csv.DictReader(file)
129
+ }
130
+
131
+
132
+ def _field_from_row(row: dict[str, str]) -> OrderConfirmationField:
133
+ return OrderConfirmationField(
134
+ command_path=row["command_path"],
135
+ output_key=row["output_key"],
136
+ label=row["label"],
137
+ arg_name=row["arg_name"],
138
+ value_label_group=row["value_label_group"],
139
+ include_if_empty=row["include_if_empty"] == "Y",
140
+ empty_display=row["empty_display"],
141
+ )
142
+
143
+
144
+ def _field_value(
145
+ field: OrderConfirmationField,
146
+ *,
147
+ command: OrderConfirmationCommand,
148
+ args: argparse.Namespace,
149
+ value_labels: dict[tuple[str, str], str],
150
+ ) -> str:
151
+ if field.arg_name == "__kind__":
152
+ value = command.kind
153
+ else:
154
+ value = getattr(args, field.arg_name, "")
155
+ if value is None:
156
+ value = ""
157
+ value = str(value)
158
+ if value and field.value_label_group:
159
+ return value_labels.get((field.value_label_group, value), value)
160
+ return value
161
+
162
+
163
+ def _format_order_confirmation_pretty(payload: dict[str, Any]) -> str:
164
+ lines = [payload["message"], "", "주문 확인:"]
165
+ for row in payload["_display_rows"]:
166
+ lines.append(f"- {row['label']}: {row['value']}")
167
+ return "\n".join(lines)
kiwoom_cli/output.py ADDED
@@ -0,0 +1,136 @@
1
+ """Output formatting for CLI command results."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any, Iterable
7
+
8
+ from kiwoom.core.types import KiwoomResponse
9
+ from kiwoom_cli.errors import CliInternalError
10
+
11
+
12
+ OUTPUT_FORMATS = ("pretty", "json", "jsonl", "yaml")
13
+ ACCOUNT_IDENTIFIER_FIELDS = frozenset(
14
+ {
15
+ "acctNo",
16
+ "acctno",
17
+ "acct_no",
18
+ "account_no",
19
+ "account_number",
20
+ "acnt_no",
21
+ "acntNo",
22
+ "acntno",
23
+ "acnt_num",
24
+ "cano",
25
+ }
26
+ )
27
+ # Order numbers are operational identifiers the user needs to manage their own
28
+ # orders (e.g. read an `ord_no` from a buy/list response and pass it to
29
+ # modify/cancel), so they are NOT redacted. Account numbers are sensitive PII
30
+ # and stay redacted.
31
+ ACCOUNT_REDACT_FIELDS = ACCOUNT_IDENTIFIER_FIELDS
32
+
33
+
34
+ def format_response(
35
+ response: KiwoomResponse,
36
+ *,
37
+ output_format: str = "pretty",
38
+ redact_fields: Iterable[str] = (),
39
+ ) -> str:
40
+ return format_payload(response.body, output_format=output_format, redact_fields=redact_fields)
41
+
42
+
43
+ def format_payload(
44
+ payload: Any,
45
+ *,
46
+ output_format: str = "pretty",
47
+ redact_fields: Iterable[str] = (),
48
+ ) -> str:
49
+ payload = _redact_payload(payload, set(redact_fields))
50
+
51
+ if output_format == "pretty":
52
+ return json.dumps(payload, ensure_ascii=False, indent=2)
53
+ if output_format == "json":
54
+ return json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
55
+ if output_format == "jsonl":
56
+ return _to_jsonl(payload)
57
+ if output_format == "yaml":
58
+ return _to_yaml(payload)
59
+ raise CliInternalError(f"지원하지 않는 출력 형식입니다: {output_format}")
60
+
61
+
62
+ def _to_jsonl(payload: Any) -> str:
63
+ rows = payload if isinstance(payload, list) else [payload]
64
+ return "\n".join(
65
+ json.dumps(row, ensure_ascii=False, separators=(",", ":")) for row in rows
66
+ )
67
+
68
+
69
+ def _redact_payload(value: Any, redact_fields: set[str]) -> Any:
70
+ if not redact_fields:
71
+ return value
72
+ if isinstance(value, dict):
73
+ return {
74
+ key: _redact_sensitive_value(item)
75
+ if _should_redact_key(key, redact_fields)
76
+ else _redact_payload(item, redact_fields)
77
+ for key, item in value.items()
78
+ }
79
+ if isinstance(value, list):
80
+ return [_redact_payload(item, redact_fields) for item in value]
81
+ return value
82
+
83
+
84
+ def _should_redact_key(key: Any, redact_fields: set[str]) -> bool:
85
+ text = str(key)
86
+ return text in redact_fields or text.lower() in redact_fields
87
+
88
+
89
+ def _redact_sensitive_value(value: Any) -> Any:
90
+ if isinstance(value, dict):
91
+ return {key: _redact_sensitive_value(item) for key, item in value.items()}
92
+ if isinstance(value, list):
93
+ return [_redact_sensitive_value(item) for item in value]
94
+ return _redact_value(value)
95
+
96
+
97
+ def _redact_value(value: Any) -> Any:
98
+ if value is None:
99
+ return None
100
+ text = str(value)
101
+ if len(text) <= 4:
102
+ return "****"
103
+ return f"{'*' * (len(text) - 4)}{text[-4:]}"
104
+
105
+
106
+ def _to_yaml(value: Any, *, indent: int = 0) -> str:
107
+ prefix = " " * indent
108
+ if isinstance(value, dict):
109
+ lines: list[str] = []
110
+ for key, item in value.items():
111
+ if isinstance(item, (dict, list)):
112
+ lines.append(f"{prefix}{key}:")
113
+ lines.append(_to_yaml(item, indent=indent + 2))
114
+ else:
115
+ lines.append(f"{prefix}{key}: {_yaml_scalar(item)}")
116
+ return "\n".join(lines)
117
+ if isinstance(value, list):
118
+ lines = []
119
+ for item in value:
120
+ if isinstance(item, (dict, list)):
121
+ lines.append(f"{prefix}-")
122
+ lines.append(_to_yaml(item, indent=indent + 2))
123
+ else:
124
+ lines.append(f"{prefix}- {_yaml_scalar(item)}")
125
+ return "\n".join(lines)
126
+ return f"{prefix}{_yaml_scalar(value)}"
127
+
128
+
129
+ def _yaml_scalar(value: Any) -> str:
130
+ if value is None:
131
+ return "null"
132
+ if isinstance(value, bool):
133
+ return "true" if value else "false"
134
+ if isinstance(value, (int, float)):
135
+ return str(value)
136
+ return json.dumps(str(value), ensure_ascii=False)
kiwoom_cli/registry.py ADDED
@@ -0,0 +1,95 @@
1
+ """Load curated CLI command mappings."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import csv
6
+ from dataclasses import dataclass
7
+ from functools import lru_cache
8
+ from pathlib import Path
9
+
10
+ from kiwoom_cli.errors import CliInternalError
11
+
12
+
13
+ MAP_PATH = Path(__file__).resolve().parent / "maps" / "api_commands.csv"
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class CommandDefinition:
18
+ api_id: str
19
+ api_name: str
20
+ cli_group: str
21
+ cli_command: str
22
+ command_path: str
23
+ status: str
24
+ coverage_status: str
25
+ safety_policy: str
26
+ method: str
27
+ path: str
28
+ required_body_fields: tuple[str, ...]
29
+
30
+
31
+ def load_command_definitions(map_path: Path | None = None) -> list[CommandDefinition]:
32
+ resolved_path = map_path or MAP_PATH
33
+ if map_path is None:
34
+ return list(_load_default_command_definitions())
35
+ return _load_command_definitions_from_path(resolved_path)
36
+
37
+
38
+ @lru_cache(maxsize=1)
39
+ def _load_default_command_definitions() -> tuple[CommandDefinition, ...]:
40
+ return tuple(_load_command_definitions_from_path(MAP_PATH))
41
+
42
+
43
+ def _load_command_definitions_from_path(resolved_path: Path) -> list[CommandDefinition]:
44
+ with resolved_path.open(encoding="utf-8", newline="") as file:
45
+ return [_definition_from_row(row) for row in csv.DictReader(file)]
46
+
47
+
48
+ def get_implemented_command(group: str, command: str) -> CommandDefinition:
49
+ matches = [
50
+ definition
51
+ for definition in load_command_definitions()
52
+ if definition.cli_group == group
53
+ and definition.cli_command == command
54
+ and definition.status == "implemented"
55
+ ]
56
+ if not matches:
57
+ raise CliInternalError(f"구현된 명령을 찾을 수 없습니다: kiwoom {group} {command}")
58
+ if len(matches) > 1:
59
+ api_ids = ", ".join(definition.api_id for definition in matches)
60
+ raise CliInternalError(f"구현된 명령 매핑이 중복됩니다: kiwoom {group} {command} ({api_ids})")
61
+ return matches[0]
62
+
63
+
64
+ def get_command_by_path(command_path: str) -> CommandDefinition:
65
+ matches = [
66
+ definition
67
+ for definition in load_command_definitions()
68
+ if definition.command_path == command_path
69
+ ]
70
+ if not matches:
71
+ raise CliInternalError(f"명령 매핑을 찾을 수 없습니다: {command_path}")
72
+ if len(matches) > 1:
73
+ api_ids = ", ".join(definition.api_id for definition in matches)
74
+ raise CliInternalError(f"명령 경로 매핑이 중복됩니다: {command_path} ({api_ids})")
75
+ return matches[0]
76
+
77
+
78
+ def _definition_from_row(row: dict[str, str]) -> CommandDefinition:
79
+ return CommandDefinition(
80
+ api_id=row["api_id"],
81
+ api_name=row["api_name"],
82
+ cli_group=row["cli_group"],
83
+ cli_command=row["cli_command"],
84
+ command_path=row["command_path"],
85
+ status=row["status"],
86
+ coverage_status=row["coverage_status"],
87
+ safety_policy=row["safety_policy"],
88
+ method=row["method"],
89
+ path=row["path"],
90
+ required_body_fields=_split_fields(row["required_body_fields"]),
91
+ )
92
+
93
+
94
+ def _split_fields(value: str) -> tuple[str, ...]:
95
+ return tuple(part.strip() for part in value.split(";") if part.strip())
kiwoom_cli/safety.py ADDED
@@ -0,0 +1,30 @@
1
+ """Safety policy checks for CLI command execution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from kiwoom_cli.errors import CliInputError, CliInternalError
6
+ from kiwoom_cli.registry import CommandDefinition
7
+
8
+
9
+ def enforce_before_request(definition: CommandDefinition, *, confirm: bool = False) -> None:
10
+ if definition.coverage_status == "planned":
11
+ raise CliInternalError(f"{definition.command_path}: 아직 실행 정책과 명령 UX가 확정되지 않은 명령입니다.")
12
+
13
+ if definition.safety_policy in {"read", "account_read", "auth_write"}:
14
+ return
15
+
16
+ if definition.coverage_status == "preview-only":
17
+ raise CliInternalError(f"{definition.command_path}: 미리보기 전용 명령이라 실제 쓰기 요청을 전송할 수 없습니다.")
18
+
19
+ if definition.safety_policy == "order_write" and not confirm:
20
+ raise CliInputError(f"{definition.command_path}: 실제 주문 전송에는 --confirm 옵션이 필요합니다.")
21
+
22
+ if definition.safety_policy == "review_required":
23
+ raise CliInternalError(f"{definition.command_path}: 실행 전에 정책 검토가 필요한 명령입니다.")
24
+
25
+
26
+ def enforce_before_preview(definition: CommandDefinition) -> None:
27
+ if definition.safety_policy != "order_write":
28
+ raise CliInternalError(f"{definition.command_path}: 요청 미리보기를 지원하지 않는 명령입니다.")
29
+ if definition.coverage_status not in {"preview-only", "guarded"}:
30
+ raise CliInternalError(f"{definition.command_path}: 현재 적용 상태에서는 요청 미리보기를 사용할 수 없습니다.")