/
QAUser.py
executable file
·599 lines (512 loc) · 17.9 KB
/
QAUser.py
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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
# coding:utf-8
#
# The MIT License (MIT)
#
# Copyright (c) 2016-2019 yutiansut/QUANTAXIS
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import pandas as pd
import datetime
import uuid
from pymongo import ASCENDING, DESCENDING
from QUANTAXIS.QAARP.QAPortfolio import QA_Portfolio
from QUANTAXIS.QAUtil.QALogs import QA_util_log_info
from QUANTAXIS.QAUtil.QARandom import QA_util_random_with_topic
from QUANTAXIS.QAUtil.QASetting import QA_Setting, DATABASE
from QUANTAXIS.QAUtil.QADate_trade import QA_util_get_next_day, QA_util_get_real_date
class QA_User():
"""QA_User
User-->Portfolio-->Account/Strategy
user ==> username / user_cookie
||
portfolio ==> portfolio_cookie
||
accounts ==> account_cookie
:::::::::::::::::::::::::::::::::::::::::::::::::
:: :: Portfolio 1 -- Account/Strategy 1 ::
:: USER :: -- Account/Strategy 2 ::
:: :: Portfolio 2 -- Account/Strategy 3 ::
:::::::::::::::::::::::::::::::::::::::::::::::::
:: 需要增加对于QA_USER的支持
USER作为一个单位实体, 可以自由创建 组合Portfolio (需要被记录),修改 组合Portfolio
@yutiansut
2018/05/08
@jerryw 添加注释,和 🛠todo list
2018/05/16
@royburns 1.根据指定的user_cookie创建user; 2.添加对应的测试代码; 3.添加注释
2018/05/18
"""
def __init__(
self,
user_cookie=None,
username='defalut',
phone='defalut',
level='l1',
utype='guests',
password='default',
coins=10000,
wechat_id=None,
money=0,
*args,
**kwargs
):
"""[summary]
Keyword Arguments:
user_cookie {[type]} -- [description] (default: {None}) 随机初始化 user_cookie 的值 Acc+4数字id+4位大小写随机
username {str} -- [description] (default: {'defalut'})
phone {str} -- [description] (default: {'defalut'})
level {str} -- [description] (default: {'l1'})
utype {str} -- [description] (default: {'guests'})
password {str} -- [description] (default: {'default'})
coins {int} -- [description] (default: {10000})
关于积分系统:
积分系统用于订阅策略, 取消订阅策略是不会增加积分的
"""
#self.setting = QA_Setting()
self.client = DATABASE.user
## user_cookie/ username / wechat_id
self.client.create_index(
[
("user_cookie",
ASCENDING),
("username",
ASCENDING),
("wechat_id",
ASCENDING)
],
unique=True
)
self.portfolio_list = {}
# ==============================
self.phone = phone
self.level = level
self.utype = utype
self.password = password
self.username = username
self.wechat_id = wechat_id
if wechat_id is not None:
if self.username == 'default':
"""基于web的初始化
"""
self.username = wechat_id
self.password = 'admin'
else:
"""
另一种 无 WECHATID 的模式, 适合本地python的调试
@yutiansut
"""
if self.username == 'default':
"""基于web的初始化
"""
self.username = 'admin'
self.password = 'admin'
self.user_cookie = QA_util_random_with_topic(
'USER'
) if user_cookie is None else user_cookie
self.coins = coins # 积分
self.money = money # 钱
# ==============================
self._subscribed_strategy = {}
self._subscribed_code = []
self._signals = [] # 预期收到的信号
self._cash = []
self._history = []
# ===============================
self.coins_history = []
self.coins_history_headers = [
'cost_coins',
'strategy_id',
'start',
'last',
'strategy_uuid',
'event'
]
self.sync()
def __repr__(self):
return '< QA_USER {} with {} portfolio: {} >'.format(
self.user_cookie,
len(self.portfolio_list),
self.portfolio_list
)
def __getitem__(self, portfolio_cookie: str):
"""获取user下的portfolio
Arguments:
portfolio_cookie {str} -- [description]
Returns:
[type] -- [description]
"""
try:
return self.get_portfolio(portfolio_cookie)
except:
return None
@property
def table(self):
return pd.concat(
[self.get_portfolio(po).table for po in self.portfolio_list],
axis=1
)
def add_coins(self, coins):
"""积分充值
Arguments:
coins {[type]} -- [description]
"""
self.coins += int(coins)
@property
def coins_table(self):
return pd.DataFrame(
self.coins_history,
columns=self.coins_history_headers
)
def subscribe_strategy(
self,
strategy_id: str,
last: int,
today=datetime.date.today(),
cost_coins=10
):
"""订阅一个策略
会扣减你的积分
Arguments:
strategy_id {str} -- [description]
last {int} -- [description]
Keyword Arguments:
today {[type]} -- [description] (default: {datetime.date.today()})
cost_coins {int} -- [description] (default: {10})
"""
if self.coins > cost_coins:
order_id = str(uuid.uuid1())
self._subscribed_strategy[strategy_id] = {
'lasttime':
last,
'start':
str(today),
'strategy_id':
strategy_id,
'end':
QA_util_get_next_day(
QA_util_get_real_date(str(today),
towards=1),
last
),
'status':
'running',
'uuid':
order_id
}
self.coins -= cost_coins
self.coins_history.append(
[
cost_coins,
strategy_id,
str(today),
last,
order_id,
'subscribe'
]
)
return True, order_id
else:
# return QAERROR.
return False, 'Not Enough Coins'
def unsubscribe_stratgy(self, strategy_id):
"""取消订阅某一个策略
Arguments:
strategy_id {[type]} -- [description]
"""
today = datetime.date.today()
order_id = str(uuid.uuid1())
if strategy_id in self._subscribed_strategy.keys():
self._subscribed_strategy[strategy_id]['status'] = 'canceled'
self.coins_history.append(
[0,
strategy_id,
str(today),
0,
order_id,
'unsubscribe']
)
@property
def subscribed_strategy(self):
"""历史(包含正在订阅的)策略
Returns:
[type] -- [description]
"""
return pd.DataFrame(list(self._subscribed_strategy.values()))
@property
def subscribing_strategy(self):
"""订阅一个策略
Returns:
[type] -- [description]
"""
res = self.subscribed_strategy.assign(
remains=self.subscribed_strategy.end.apply(
lambda x: pd.Timestamp(x) - pd.Timestamp(datetime.date.today())
)
)
#res['left'] = res['end_time']
# res['remains']
res.assign(
status=res['remains'].apply(
lambda x: 'running'
if x > datetime.timedelta(days=0) else 'timeout'
)
)
return res.query('status=="running"')
def change_wechatid(self, id):
"""修改wechat
Arguments:
id {[type]} -- [description]
"""
self.wechat_id = id
def sub_code(self, code):
"""关注的品种
"""
self._subscribed_code.append(code)
@property
def subscribed_code(self):
"""
关注的品种
Returns:
[type] -- [description]
"""
return list(set(self._subscribed_code))
def new_portfolio(self, portfolio_cookie=None):
'''
根据 self.user_cookie 创建一个 portfolio
:return:
如果存在 返回 新建的 QA_Portfolio
如果已经存在 返回 这个portfolio
'''
if portfolio_cookie not in self.portfolio_list:
self.portfolio_list.append(portfolio_cookie)
return QA_Portfolio(
user_cookie=self.user_cookie,
portfolio_cookie=portfolio_cookie
)
else:
print(
" prortfolio with user_cookie ",
self.user_cookie,
" already exist!!"
)
return self.get_portfolio(portfolio_cookie)
def get_account(self, portfolio_cookie: str, account_cookie: str):
"""直接从二级目录拿到account
Arguments:
portfolio_cookie {str} -- [description]
account_cookie {str} -- [description]
Returns:
[type] -- [description]
"""
# QA_Portfolio(
# user_cookie=self.user_cookie,
# portfolio_cookie=item
# )
try:
return self.get_portfolio(portfolio_cookie).get_account(account_cookie)
except:
return None
def get_portfolio(self, portfolio_cookie: str):
'''
'get a portfolio'
从 portfolio_list dict字典中 根据 portfolio key 获取
:param portfolio: QA_Portfolio类型
:return: QA_Portfolio类型
'''
# return self.portfolio_list[portfolio]
# fix here use cookie as key to find value in dict
return QA_Portfolio(user_cookie=self.user_cookie, portfolio_cookie= portfolio_cookie)
def generate_simpleaccount(self):
"""make a simple account with a easier way
如果当前user中没有创建portfolio, 则创建一个portfolio,并用此portfolio创建一个account
如果已有一个或多个portfolio,则使用第一个portfolio来创建一个account
"""
if len(self.portfolio_list) < 1:
po = self.new_portfolio()
else:
po = self.get_portfolio(self.portfolio_list[0])
ac = po.new_account()
return ac, po
def register_account(self, account, portfolio_cookie=None):
'''
注册一个account到portfolio组合中
account 也可以是一个策略类,实现其 on_bar 方法
:param account: 被注册的account
:return:
'''
# 查找 portfolio
if len(self.portfolio_list) < 1:
po = self.new_portfolio()
elif portfolio_cookie is not None:
po = self.get_portfolio(portfolio_cookie)
else:
po = self.get_portfolio(self.portfolio_list[0])
# 把account 添加到 portfolio中去
po.add_account(account)
return (po, account)
@property
def message(self):
return {
'user_cookie': self.user_cookie,
'username': self.username,
'password': self.password,
'wechat_id': self.wechat_id,
'phone': self.phone,
'level': self.level,
'utype': self.utype,
'coins': self.coins,
'coins_history': self.coins_history,
'money': self.money,
'subuscribed_strategy': self._subscribed_strategy,
'subscribed_code': self.subscribed_code,
'portfolio_list': self.portfolio_list,
'lastupdatetime': str(datetime.datetime.now())
}
def save(self):
"""
将QA_USER的信息存入数据库
ATTENTION:
在save user的时候, 需要同时调用 user/portfolio/account链条上所有的实例化类 同时save
"""
if self.wechat_id is not None:
self.client.update(
{'wechat_id': self.wechat_id},
{'$set': self.message},
upsert=True
)
else:
self.client.update(
{
'username': self.username,
'password': self.password
},
{'$set': self.message},
upsert=True
)
# user ==> portfolio 的存储
# account的存储在 portfolio.save ==> account.save 中
# for portfolio in list(self.portfolio_list.values()):
# portfolio.save()
def sync(self):
"""基于账户/密码去sync数据库
"""
if self.wechat_id is not None:
res = self.client.find_one({'wechat_id': self.wechat_id})
else:
res = self.client.find_one(
{
'username': self.username,
'password': self.password
}
)
if res is None:
if self.client.find_one({'username': self.username}) is None:
self.client.insert_one(self.message)
return self
else:
raise RuntimeError('账户名已存在且账户密码不匹配')
else:
self.reload(res)
return self
# @property
# def node_view(self):
# links = [
# {
# 'source': self.username,
# 'target': item
# } for item in self.portfolio_list.keys()
# ]
# data = [{'name': self.username, 'symbolSize': 100, 'value': 1}]
# for port in self.portfolio_list.values():
# links.extend(port.node_view['links'])
# data.append(
# {
# 'name': port.portfolio_cookie,
# 'symbolSize': 80,
# 'value': 2
# }
# )
# for acc in port.accounts.values():
# data.append(
# {
# 'name': acc.account_cookie,
# 'symbolSize': 50,
# 'value': 3
# }
# )
# return {
# 'node_name':
# self.username,
# 'sub_node':
# [portfolio.node_view for portfolio in self.portfolio_list.values()],
# 'links':
# links,
# 'data':
# data
# }
def reload(self, message):
"""恢复方法
Arguments:
message {[type]} -- [description]
"""
self.phone = message.get('phone')
self.level = message.get('level')
self.utype = message.get('utype')
self.coins = message.get('coins')
self.wechat_id = message.get('wechat_id')
self.coins_history = message.get('coins_history')
self.money = message.get('money')
self._subscribed_strategy = message.get('subuscribed_strategy')
self._subscribed_code = message.get('subscribed_code')
self.username = message.get('username')
self.password = message.get('password')
self.user_cookie = message.get('user_cookie')
#
self.portfolio_list = list(set([
item['portfolio_cookie'] for item in DATABASE.portfolio.find(
{'user_cookie': self.user_cookie},
{
'portfolio_cookie': 1,
'_id': 0
}
)
]))
# portfolio_list = message.get('portfolio_list')
# if len(portfolio_list) > 0:
# self.portfolio_list = dict(
# zip(
# portfolio_list,
# [
# QA_Portfolio(
# user_cookie=self.user_cookie,
# portfolio_cookie=item
# ) for item in portfolio_list
# ]
# )
# )
# else:
# self.portfolio_list = {}
if __name__ == '__main__':
# 测试不对
user = QA_User(user_cookie='user_admin')
folio = user.new_portfolio('folio_admin')
ac1 = user.get_portfolio(folio).new_account('account_admin')
print(user)
print(user.get_portfolio(folio))
print(user.get_portfolio(folio).get_account(ac1))