Skip to content

Commit 5427e91

Browse files
committed
Support auto purchasing new stocks
1 parent 74d0f56 commit 5427e91

File tree

9 files changed

+203
-2
lines changed

9 files changed

+203
-2
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ ENV/
8989
.ropeproject
9090

9191
.idea/
92+
config/*
93+
!config/*-example.ini
9294
examples/joinquant/config/config.ini
9395
tests/config/config.ini
9496

README.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,28 @@ ShiPanE-Python-SDK
88
| 详情见:http://www.iguuu.com/e
99
| 交流QQ群:11527956 |实盘易-股票自动交易|
1010
11+
自动新股申购
12+
--------
13+
14+
一. Windows
15+
~~~~~~~~~~~~
16+
17+
- 安装 Python 3.5(建议安装 Anaconda3)
18+
- cmd 中运行:pip install shipane_sdk
19+
- cmd 中运行:explorer %UserProfile%\.shipane_sdk
20+
- 进入 config 目录;将 scheduler-example.ini 拷贝为 scheduler.ini;并修改内容(建议使用Notepad++)
21+
- 找到 python 安装目录,例如:C:\Users\[用户名]\Anaconda3
22+
- cmd 下执行(具体路径自行修改):python "C:\Users\[用户名]\Anaconda3\Scripts\shipane-scheduler.py"
23+
24+
二. Mac/Linux
25+
~~~~~~~~~~~~
26+
27+
- 安装 Python 3.5
28+
- terminal 中运行:pip install shipane_sdk
29+
- terminal 中运行:cp -n ~/.shipane_sdk/config/scheduler-example.ini ~/.shipane_sdk/config/scheduler.ini
30+
- 修改 ~/.shipane_sdk/config/scheduler.ini
31+
- terminal 中运行:shipane-scheduler.py
32+
1133
聚宽集成
1234
--------
1335

config/scheduler-example.ini

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
;
2+
; 实盘易配置
3+
;
4+
[ShiPanE]
5+
host=localhost
6+
port=8888
7+
key=
8+
9+
;
10+
; 交易客户端 client 参数别名列表
11+
;
12+
[ClientAliases]
13+
; 值可以参考实盘易安装目录下的《用户手册.txt》
14+
client1=title:monijiaoyi
15+
client2=title:guotai
16+
17+
;
18+
; 自动新股申购配置
19+
;
20+
[NewStocks]
21+
; 是否启用?
22+
enabled=false
23+
24+
; 触发时间设置,类似于 cron 表达式
25+
;
26+
; 格式为:[秒(0-59)] [分(0-59)] [时(0-23)] [星期几(0-6 或英文缩写)] [星期(1-53)] [日(1-31)] [月(1-12)] [年(四位数字)]
27+
; (星期几英文缩写:mon,tue,wed,thu,fri,sat,sun)
28+
;
29+
; 字段支持表达式:
30+
; - * 为任意单位时间触发
31+
; - */a 为每 a 个单位时间触发
32+
; - a-b 为 a 到 b 的区间触发
33+
; - a-b/c 为 a 到 b 的区间每 c 个单位时间触发
34+
;
35+
; 详见:https://apscheduler.readthedocs.io/en/v2.1.2/cronschedule.html
36+
;
37+
; 默认设置为:星期一至星期五中午十二点
38+
;
39+
schedule=0 0 12 mon-fri * * * *
40+
41+
; 需要自动新股申购的交易客户端列表,以,(半角逗号)分割
42+
; clients=client1,client2
43+
clients=client1

scripts/shipane-scheduler.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env python
2+
3+
from shipane_sdk.scheduler import Scheduler
4+
5+
Scheduler().start()

setup.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from setuptools import setup, find_packages
44
from codecs import open
55
from os import path
6+
import os.path
67

78
here = path.abspath(path.dirname(__file__))
89

@@ -44,7 +45,7 @@
4445

4546
packages=find_packages(exclude=['contrib', 'docs', 'tests']),
4647

47-
install_requires=['requests', 'six'],
48+
install_requires=['requests', 'six', 'apscheduler', 'lxml', 'cssselect', 'bs4', 'html5lib', 'pandas'],
4849

4950
extras_require={
5051
'dev': [],
@@ -54,7 +55,9 @@
5455
package_data={
5556
},
5657

57-
data_files=[],
58+
data_files=[(os.path.join(os.path.expanduser('~'), '.shipane_sdk', 'config'), ['config/scheduler-example.ini'])],
59+
60+
scripts=['scripts/shipane-scheduler.py'],
5861

5962
entry_points={
6063
'console_scripts': [

shipane_sdk/ap.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from apscheduler.triggers.cron import CronTrigger
4+
5+
6+
class APCronParser(object):
7+
@classmethod
8+
def parse(cls, expression):
9+
parts = list(reversed(expression.split()))
10+
for i in range(len(parts)):
11+
if parts[i] == '?':
12+
parts[i] = None
13+
14+
return CronTrigger(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7])
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# -*- coding: utf-8 -*-
2+
import json
3+
import logging
4+
from datetime import datetime
5+
6+
from shipane_sdk.stock import *
7+
8+
9+
class NewStockPurchaseJob(object):
10+
def __init__(self, config, client):
11+
self._log = logging.getLogger()
12+
self._config = config
13+
self._client = client
14+
15+
def __call__(self):
16+
all_client_aliases = dict(self._config.items('ClientAliases'))
17+
client_aliases = self._config.get('NewStocks', 'clients').split(',')
18+
today = datetime.strftime(datetime.today(), '%Y-%m-%d')
19+
df = StockUtils.new_stocks()
20+
df = df[(df.ipo_date == today)]
21+
for client_alias in client_aliases:
22+
client = all_client_aliases[client_alias.strip()]
23+
self._log.info(u'客户端[%s(%s)]开始新股申购', client_alias, client)
24+
for index, row in df.iterrows():
25+
try:
26+
order = {
27+
'symbol': row['code'], 'type': 'LIMIT', 'price': row['price'], 'amountProportion': 'ALL'
28+
}
29+
self._log.info(u'下单:%s', json.dumps(order))
30+
response = self._client.buy(client, **order)
31+
if response is not None:
32+
self._log.info(u'[实盘易] 回复如下:\nstatus_code: %d\ntext: %s',
33+
response.status_code, response.text)
34+
else:
35+
self._log.error('[实盘易] 未回复')
36+
except Exception as e:
37+
self._log.exception('账户[%s(%s)]申购新股[%s(%s)]失败', client_alias, client, row['name'], row['code'])
38+
self._log.info(u'客户端[%s(%s)]结束新股申购', client_alias, client)

shipane_sdk/scheduler.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# -*- coding: utf-8 -*-
2+
import codecs
3+
import logging
4+
import os
5+
import os.path
6+
import time
7+
8+
import six
9+
from apscheduler.schedulers.background import BackgroundScheduler
10+
from six.moves import configparser
11+
12+
from shipane_sdk import Client
13+
from shipane_sdk.ap import APCronParser
14+
from shipane_sdk.jobs.new_stock_purchase import NewStockPurchaseJob
15+
16+
if six.PY2:
17+
ConfigParser = configparser.RawConfigParser
18+
else:
19+
ConfigParser = configparser.ConfigParser
20+
21+
22+
class Scheduler(object):
23+
def __init__(self):
24+
logging.basicConfig(level=logging.INFO, format='%(asctime)-15s %(levelname)-6s %(message)s')
25+
self._log = logging.getLogger()
26+
27+
config_path = os.path.join(os.path.expanduser('~'), '.shipane_sdk', 'config', 'scheduler.ini')
28+
self._log.info('Config path: %s', config_path)
29+
self._config = ConfigParser()
30+
self._config.readfp(codecs.open(config_path, "r", "utf8"))
31+
32+
self._client = Client(host=self._config.get('ShiPanE', 'host'),
33+
port=self._config.get('ShiPanE', 'port'),
34+
key=self._config.get('ShiPanE', 'key'))
35+
36+
self._new_stock_purchase_job = NewStockPurchaseJob(self._config, self._client)
37+
38+
def start(self):
39+
scheduler = BackgroundScheduler()
40+
41+
if self._config.getboolean('NewStocks', 'enabled'):
42+
scheduler.add_job(self._new_stock_purchase_job,
43+
APCronParser.parse(self._config.get('NewStocks', 'schedule')))
44+
45+
scheduler.start()
46+
print('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C'))
47+
48+
try:
49+
while True:
50+
time.sleep(1)
51+
except (KeyboardInterrupt, SystemExit):
52+
scheduler.shutdown()

shipane_sdk/stock.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import lxml.html.soupparser
4+
import pandas as pd
5+
import requests
6+
7+
8+
class StockUtils(object):
9+
@staticmethod
10+
def new_stocks():
11+
url = 'http://vip.stock.finance.sina.com.cn/corp/view/vRPD_NewStockIssue.php?page=1&cngem=0&orderBy=NetDate&orderType=desc'
12+
request = requests.get(url)
13+
doc = lxml.html.soupparser.fromstring(request.content, features='html.parser')
14+
table = doc.cssselect('table#NewStockTable')[0]
15+
table.remove(table.cssselect('thead')[0])
16+
table_html = lxml.html.etree.tostring(table).decode('utf-8')
17+
df = pd.read_html(table_html, skiprows=[0, 1])[0]
18+
df = df.drop([df.columns[idx] for idx in [1, 12, 13, 14]], axis=1)
19+
df.columns = ['code', 'name', 'ipo_date', 'issue_date', 'amount', 'markets', 'price', 'pe', 'limit', 'funds',
20+
'ballot']
21+
df['code'] = df['code'].astype(str)
22+
return df

0 commit comments

Comments
 (0)