Ejemplo n.º 1
0
class Trading(object):
    """Trading 된 시점의 모든 데이터를 snapshot 으로 보관하는 객체
        특정시점의 모든 data를 snapshot뜨는 기능에 집중
    """
    def __init__(self, trading_type):
        self.trading_type = trading_type  # SELL, BUY (defined at constant)
        self.logger = TTlog().logger
        self.trading_core_index = [
            'timestamp', 'code', 'stock_name', 'trading_type',
            '현재가', '매매수량', '보유수량', '매매금액', '매입금액', '최대매입금액', '매입평균가',
            '평가손익', '실현손익', '누적손익', '평가금액', '수익률', '누적수익률', '평가금액변동',
            '시작잔고', '예수금', '총평가금액', '총매입금액', '총평가손익', '총손익', '추정자산',
            '총수익률', '총누적손익', '총누적수익률'
        ]

    def print_attr(self, attr_list=None):
        self.logger.debug("\n" + ("-" * 100))
        self.logger.debug("[Stock] Information : {}/{}".format(self.stock_name, self.code))
        if bool(attr_list):
            attr_val = []
            for attr in attr_list:
                val = self.__getattribute__(attr)
                log_str = "{}: {}".format(attr, val)
                if isinstance(val, float):
                    log_str = "{}: {:.2f}".format(attr, val)
                attr_val.append(log_str)
            self.logger.debug("\t".join(attr_val))
        else:
            self.logger.debug(self.__repr__())
        # pass

    def __repr__(self):
        core_index = self.trading_core_index
        log = []
        for attr in core_index:
            val = self.__getattribute__(attr)
            log_str = "{}: {}".format(attr, val)
            if isinstance(val, float):
                log_str = "{}: {:.2f}".format(attr, val)
            log.append(log_str)
        return "\t".join(log)

    def check_core_index(self):
        """Trading의 핵심 지표들이 모두 설정되었는지 check.
        누락된 지표가 있다면 Exception 발생하고 프로그램 종료

        :return:
        """
        for index in self.trading_core_index:
            self.__getattribute__(index)
Ejemplo n.º 2
0
class DBM():
    def __init__(self, dbname, host=None, port=None):
        """DBM 생성자, dbname을 인자로 받는다.

        :param str db: database name
        """
        if bool(host) and bool(port):
            self.mongo = MongoClient(host=host, port=port)
        else:
            self.mongo = MongoClient()

        self.db = self.mongo.get_database(dbname)
        self.col_table = {
            "tick1": self.db.time_series_tick1,
            "tick3": self.db.time_series_tick3,
            "tick5": self.db.time_series_tick5,
            "tick10": self.db.time_series_tick10,
            "tick30": self.db.time_series_tick30,
            "min1": self.db.time_series_min1,
            "min3": self.db.time_series_min3,
            "min5": self.db.time_series_min5,
            "min10": self.db.time_series_min10,
            "min30": self.db.time_series_min30,
            "real_condi_search": self.db.real_condi_search
        }
        self.logger = TTlog(logger_name="DB").logger

    def get_unique_data(self, col, query=None):
        """collection의 특정 필드값을 unique 하게 얻고 싶을때 사용

            ex)
            >>> cur = self.db.trading_history.distinct("stock_name", {'date': {'$gt': datetime(2018, 7, 20, 13, 0, 0)}})

        :param col: 필드명
        :param query: 특정 조건을 만족하는 doc 를 명시하고 싶은경우 query 를 사용
        :return:
        """
        #
        if bool(query):
            cur = self.db.trading_history.distinct(col, query)
        else:
            cur = self.db.trading_history.distinct(col)
        return cur

    def get_time_series_collection(self, time_unit):
        return self.col_table[time_unit]

    def check_tick_cache(self, code, date, tick="1"):
        """특정 종목의 하루단위 tick data가 DB에 있는지 확인하고,
        DB에 있으면 cur를 반환한다.

        :param code:
        :param date:
        :param tick:
        :return:
        """
        col = self.get_time_series_collection("tick" + tick)
        date = datetime(date.year, date.month, date.day, 0, 0, 0)
        cur = col.find({'code': code, 'date': date})
        if cur.count() != 0:
            return True, cur
        return False, None

    def save_force(self, col, data, search_condition):
        try:
            col.insert(data)
        except Exception as e:
            col.update(search_condition, data, upsert=True)

    def get_tick_data(self, code, date, tick="1"):
        col = self.get_time_series_collection("tick" + tick)
        base_date = datetime(date.year, date.month, date.day)
        query = {'code': code, 'date': base_date}
        cur = col.find(query)
        if cur.count() == 0:
            return []

        return cur.next()["time_series_1tick"]

    def save_tick_data(self, data, tick="1"):
        col = self.get_time_series_collection("tick" + tick)
        try:
            col.insert(data)
        except Exception as e:
            col.update({
                'date': data['date'],
                'code': data['code']
            },
                       data,
                       upsert=True)

    def get_code_list_of_rcs(self, s_date, e_date):
        cur = self.db.real_condi_search.find(
            {'date': {
                '$gte': s_date,
                '$lte': e_date
            }}, {
                'code': 1,
                '_id': 0
            })
        code_list = list({data['code'] for data in cur})
        code_list.sort()
        return code_list

    def get_code_list_condi_search_result(self, date):
        # cache
        cur = self.db.real_condi_search_cache.find({'date': date})
        if cur.count() != 0:
            self.logger.debug("hit cache (real_condi_search_cache)")
            code_list = cur.next()['code_list']
        else:
            s_date = datetime(date.year, date.month, date.day, 9, 0, 0)
            e_date = datetime(date.year, date.month, date.day, 16, 0, 0)
            code_list = self.get_code_list_of_rcs(s_date, e_date)
            self.db.real_condi_search_cache.insert({
                'date': date,
                'code_list': code_list
            })
        return code_list

    def get_condi_result(self, s_date, e_date):
        query = {"date": {"$gte": s_date, "$lte": e_date}, "event": "I"}
        cur = self.db.real_condi_search.find(query)

        # date, code, stock_name, condi_name
        condi_result = [data for data in cur]
        condi_result.sort(key=lambda x: x['date'])
        return condi_result

    def code_list_by_condi_id(self, condi_index, date):
        query = {'condi_index': condi_index, 'date': date}
        cur = self.db.real_condi_search.find(query)
        code_list = [doc['code'] for doc in cur]
        return code_list

    def already_collect_tick_data(self, code, date, tick="1"):
        cur = self.db.collect_tick_data_history.find({
            'code': code,
            'date': date,
            'tick': tick
        })
        return cur.count() != 0

    def save_collect_tick_data_history(self, code, date, tick="1"):
        self.db.collect_tick_data_history.update(
            {
                'code': code,
                'date': date,
                'tick': tick
            }, {
                'code': code,
                'date': date,
                'tick': tick
            },
            upsert=True)

    def record_collect_tick_data_status(self, status, date, tick="1"):
        self.db.collect_tick_data_status.update({
            "date": date,
            "tick": tick
        }, {
            "status": status,
            "date": date,
            "tick": tick
        },
                                                upsert=True)

    def get_collect_tick_data_status(self, date, tick="1"):
        cur = self.db.collect_tick_data_status.find({
            'date': date,
            'tick': tick
        })
        if cur.count() == 0:
            return "NEVER_STARTED"
        data = cur.next()
        return data['status']

    def get_real_condi_search_data(self, date, condi_name):
        y, m, d = date.year, date.month, date.day
        s_date = datetime(y, m, d, 9, 0, 0)
        e_date = datetime(y, m, d, 15, 30, 0)

        # need to fix (condi_name -> condi_index)
        # query = {'date': {'$gte': s_date, '$lte': e_date}, 'event': 'I', 'condi_index': condi_index}
        query = {
            'date': {
                '$gte': s_date,
                '$lte': e_date
            },
            'event': 'I',
            'condi_name': condi_name
        }
        cur = self.db.real_condi_search.find(query)\
            .sort('date', pymongo.ASCENDING)
        if cur.count() == 0:
            return []
        return list(cur)
Ejemplo n.º 3
0
class TopTrader(QMainWindow, ui):
    def __init__(self):
        super().__init__()
        # self.setupUi(self)  # load app screen
        self.logger = TTlog().logger
        self.dbm = DBM('TopTrader')
        self.mongo = MongoClient()
        self.db = self.mongo.TopTrader
        self.slack = Slack(config_manager.get_slack_token())
        self.kw = Kiwoom()
        self.login()
        self.main()

    def load_tick_data(self,
                       code,
                       stock_name,
                       date,
                       tick="1",
                       screen_no="1234"):
        """특정 종목의 하루단위 tick data를 가져오는 함수
        tick_data가 DB에 있으면 DB에서 가져오고, 없으면 kw module로부터 가져온다.

        :param code:
        :param stock_name:
        :param date:
        :param tick:
        :param screen_no:
        :return:
        """
        ret, cur = self.dbm.check_tick_cache(code, date, tick="1")

        if ret:
            tick_data = cur.next()
        else:
            base_date = datetime(date.year, date.month, date.day, 0, 0, 0)
            raw_data = self.kw.stock_price_by_tick(code,
                                                   tick=tick,
                                                   screen_no=screen_no,
                                                   date=base_date)
            tick_data = {
                'code':
                code,
                'stock_name':
                stock_name,
                'date':
                base_date,
                'time_series_1tick': [{
                    'timestamp': _d['date'],
                    '현재가': _d['현재가'],
                    '거래량': _d['거래량']
                } for _d in raw_data]
            }
        return tick_data

    def collect_tick_data(self,
                          code,
                          stock_name,
                          s_date,
                          e_date,
                          tick="1",
                          screen_no="1234"):
        """특정 종목의 일정 기간의 tick data를 DB에 저장하는 함수
        DB에 이미 값이 저장되어 있으면 저장하지 않고 skip 한다.
        s_date, e_date는 초단위까지 지정이 가능하지만, 저장은 일단위로 저장한다.
        tick은 1, 3, 5, 10, 30 선택 가능하다.

        :param code:
        :param stock_name:
        :param tick:
        :param s_date:
        :param e_date:
        :return:
        """
        s_base_date = datetime(s_date.year, s_date.month, s_date.day, 0, 0, 0)
        e_base_date = datetime(e_date.year, e_date.month, e_date.day, 0, 0, 0)
        days = (e_base_date - s_base_date).days + 1
        date_list = [s_base_date + timedelta(days=x) for x in range(0, days)]
        for base_date in date_list:
            ret, cur = self.dbm.check_tick_cache(code, base_date, tick="1")
            if ret:
                self.logger.debug(
                    "No need to save data. already has data in DB")
                continue

            raw_data = self.kw.stock_price_by_tick(code,
                                                   tick=tick,
                                                   screen_no=screen_no,
                                                   date=base_date)
            tick_data = {
                'code':
                code,
                'stock_name':
                stock_name,
                'date':
                base_date,
                'time_series_1tick': [{
                    'timestamp': _d['date'],
                    '현재가': _d['현재가'],
                    '거래량': _d['거래량']
                } for _d in raw_data]
                # 'time_series': [{'timestamp': _d['date'], '현재가': _d['현재가'], '거래량': _d['거래량']} for _d in raw_data]
            }
            # DB에 없으면 수행되는 부분이라서 insert 함수를 사용.
            self.dbm.save_tick_data(tick_data, tick="1")

    def main(self):
        # 사용자 정의 부분_S
        # 8/2일자 조건검색식으로 검출된 모든 종목의 1tick data를 수집한다.
        with open("base_date_when_collect_tick_data.txt") as f:
            year, month, day = [int(i) for i in f.read().strip().split(" ")]
        self.base_date = datetime(year, month, day)
        # 사용자 정의 부분_E

        # kospi, kosdaq 모든 종목의 코드와 종목명 정보를 불러온다.
        self.stock_info = self.kw.get_stock_basic_info()

        code_list = self.dbm.get_code_list_condi_search_result(self.base_date)
        code_list.sort()
        self.dbm.record_collect_tick_data_status("START",
                                                 self.base_date,
                                                 tick="1")
        for num, code in enumerate(code_list, 1):
            stock_name = self.stock_info[code]["stock_name"]
            if self.dbm.already_collect_tick_data(code,
                                                  self.base_date,
                                                  tick="1"):
                self.logger.debug(
                    "{}/{} - {}:{} Skip. Already saved data!".format(
                        num, len(code_list), code, stock_name))
                continue

            self.logger.debug("{}/{} - {}:{} Start !".format(
                num, len(code_list), code, stock_name))
            self.collect_tick_data(code,
                                   stock_name,
                                   self.base_date,
                                   self.base_date,
                                   tick="1",
                                   screen_no="1234")
            self.logger.debug("{}/{} - {}:{} Completed !".format(
                num, len(code_list), code, stock_name))

            self.dbm.save_collect_tick_data_history(code,
                                                    self.base_date,
                                                    tick="1")
            self.dbm.record_collect_tick_data_status("WORKING",
                                                     self.base_date,
                                                     tick="1")

        self.dbm.record_collect_tick_data_status("END",
                                                 self.base_date,
                                                 tick="1")
        self.logger.debug("save all 1tick information..")
        exit(0)  # program exit

    def login(self):
        err_code = self.kw.login()
        if err_code != 0:
            self.logger.error("Login Fail")
            exit(-1)
        self.logger.info("Login success")
Ejemplo n.º 4
0
class Account(object):
    """
    [Todo]
    구현되어야 할 api list
    -

    """
    def __init__(self, init_balance, th):

        self.시작잔고 = init_balance
        self.예수금 = init_balance
        self.총평가금액 = 0.0
        self.총매입금액 = 0.0
        self.총최대매입금액 = 0.0
        self.총평가손익 = 0.0
        self.총손익 = 0.0  # 총평가손익 + 총누적손익
        self.추정자산 = init_balance
        self.총수익률 = 0.0
        self.총누적손익 = 0.0
        self.총누적수익률 = 0.0
        self.보유주식 = {}
        self.core_index = ['시작잔고', '예수금', '총평가금액', '총최대매입금액', '총매입금액', '총평가손익', '총손익',
                           '추정자산', '총수익률', '총누적손익', '총누적수익률']

        self.core_index = ['총누적손익', '총누적수익률', '총손익', '총수익률', '총평가손익', '총평가금액', '총매입금액',
                           '추정자산', '총최대매입금액', '예수금', '시작잔고']

        self.check_core_index()

        self.dbm = DBM('TopTrader')
        self.timestamp = None
        self.trading_history = th
        self.logger = TTlog().logger

    def print_attr(self, trading_type, stock_name, code, reason, attr_list=None):
        if not cfg_mgr.ACCOUNT_MONITOR:
            return
        self.logger.debug("\n" + ("-" * 100))
        self.logger.debug("[Account] Information : {}".format(self.timestamp))
        self.logger.debug("[Trading Type] -> {}-{} ( {}/{} )".format(trading_type, reason, stock_name, code))
        if bool(attr_list):
            attr_val = []
            for attr in attr_list:
                val = self.__getattribute__(attr)
                log_str = "{}: {}".format(attr, val)
                if isinstance(val, float):
                    log_str = "{}: {:.2f}".format(attr, val)
                attr_val.append(log_str)
            self.logger.debug("\n".join(attr_val))
        else:
            self.logger.debug(self.__repr__())

    def __repr__(self):
        core_index = ['timestamp'] + self.core_index

        # log = ["{}: {}".format(attr, str(self.__getattribute__(attr))) for attr in core_index]
        log = []
        for attr in core_index:
            val = self.__getattribute__(attr)
            log_str = "{}: {}".format(attr, val)
            if isinstance(val, float):
                log_str = "{}: {:.2f}".format(attr, val)
            log.append(log_str)
        return "\t".join(log)

    def sync(self):
        """실제 계좌 정보를 불러와서 멤버 변수를 초기화 한다.

        :return:
        """

    @common.type_check
    def update_sell(self, stock: Stock, price_per_stock: int, amount: int, reason: str):
        """특정 주식을 매도했을때, 계좌정보를 업데이트

        :param code:
        :param amount:
        :return:
        """
        # stock 객체 업데이트
        stock.trading_reason = reason
        stock.update_sell(price_per_stock, amount)
        if cfg_mgr.debug_mode():
            pass
            # self.print_attr('SELL', stock.stock_name, stock.code, reason)
            # pdb.set_trace()

        # 계좌 정보 업데이트
        self.예수금 += stock.매매금액
        self.총평가금액 += stock.평가금액변동
        self.총매입금액 += stock.매입금액변동
        self.총최대매입금액 = max(self.총매입금액, self.총최대매입금액)
        self.추정자산 = self.예수금 + self.총평가금액  # 현금(예수금) + 주식(총평가금액)
        self.총누적손익 += stock.실현손익
        self.총평가손익 = float(self.총평가금액 - self.총매입금액)  # 보유한 주식의가치로 얻은 손익
        self.총손익 = self.총평가손익 + self.총누적손익  # 개념상 손익 (현금의 손익 + 가치의 손익)
        try:
            self.총수익률 = self.총평가손익 / self.총매입금액 * 100  # 보유한 주식에 대한 총 수익률
        except ZeroDivisionError as e:
            self.총수익률 = 0.0
        self.총누적수익률 = float(self.총누적손익) / self.총최대매입금액 * 100  # 실현한 수익에 대한 총 수익률

        self.sell_transaction(stock)

        if stock.보유수량 == 0:
            del self.보유주식[stock.code]
        else:
            self.보유주식[stock.code] = stock

        try:
            if cfg_mgr.debug_mode():
                self.print_attr('SELL', stock.stock_name, stock.code, reason)
                # pdb.set_trace()
        except Exception:
            pdb.set_trace()
            print("Exception")
        return ""

    def gen_trading_info(self, stock, trading_type):
        """
            self.time = ""  # 또는 체결시간?
            self.trading_type = ""  # [B]uy, [S]ell
            self.code = ""  # 종목의 코드
            self.stock_name = ""  # 종목 이름
            self.stock_price = ""  # buy, sell 시 얼마에 사고팔았는지?
            self.loss_flag = None  # 손해를 봤는지, 안봤는지 (buy 일땐 None, sell 일땐 True/False
            self.profit_amount = 0  # 손해 또는 이익을 본 금액 (sell에서만 사용)
            self.profit_rate = 0.0  # 손해 또는 이익을 본 비율 (sell에서만 사용)

        :return:
        """
        tr = Trading(trading_type)

        # 속성값이 겹치지 않게 잘 관리되어야 함.
        # 겹치는 속성이 있다면, Account속성에는 '총' 붙이도록 함.
        stock_index = stock.get_core_index()
        account_index = self.get_core_index()

        # temp code - allow to duplicate 'timestamp'
        ret = (set(stock_index) & set(account_index)) - set('timestamp')
        if bool(ret):
            self.logger.error("{} is duplicate index. program exit")
            exit(-1)

        tr = common.copy_attr(stock, tr, stock_index)
        tr = common.copy_attr(self, tr, account_index)

        return tr

    def sell_transaction(self, stock):
        """

        :return:
        """
        tr = self.gen_trading_info(stock, constant.SELL_TRADING_TYPE)
        self.trading_history.record_sell_transaction(tr)

    @common.type_check
    def update_buy(self, stock: Stock, price_per_stock: int, amount: int, reason: str):
        """특정 주식을 매수했을때, 계좌정보를 업데이트

        :param code:
        :param price_per_stock:
        :param amount:
        :return:
        """
        # stock 객체 업데이트
        stock.trading_reason = reason
        stock.update_buy(price_per_stock, amount)

        if cfg_mgr.debug_mode():
            pass
            # self.print_attr('BUY', stock.stock_name, stock.code, reason)  # need to move to util/common pkg
            # pdb.set_trace()

        # 계좌 정보 업데이트
        self.예수금 -= stock.매매금액
        self.총평가금액 += stock.평가금액변동
        self.총매입금액 += stock.매입금액변동
        self.총최대매입금액 = max(self.총매입금액, self.총최대매입금액)
        self.추정자산 = self.예수금 + self.총평가금액  # 현금(예수금) + 주식(총평가금액)
        # self.총누적손익 += self.총누적손익 --> 변동없음
        self.총평가손익 = float(self.총평가금액 - self.총매입금액)  # 보유한 주식의가치로 얻은 손익
        self.총손익 = self.총평가손익 + self.총누적손익  # 개념상 손익 (현금의 손익 + 가치의 손익)
        self.총수익률 = self.총평가손익 / self.총매입금액 * 100  # 보유한 주식에 대한 총 수익률
        self.총누적수익률 = float(self.총누적손익) / self.총최대매입금액 * 100  # 실현한 수익에 대한 총 수익률

        # 보유주식에 포함
        self.보유주식[stock.code] = stock

        self.buy_transaction(stock)
        try:
            if cfg_mgr.debug_mode():
                self.print_attr('BUY', stock.stock_name, stock.code, reason)  # need to move to util/common pkg
                # pdb.set_trace()

        except Exception:
            # pdb.set_trace()
            print("Exception")

    def buy_transaction(self, stock):
        """

        :return:
        """
        tr = self.gen_trading_info(stock, constant.BUY_TRADING_TYPE)
        self.trading_history.record_buy_transaction(tr)

    def revaluate(self):
        """주식가치가 변경(현재가 변경)되면서 계좌에 업데이트 해야 하는 정보를 모두 업데이트

            총평가금액
            추정자산 = 예수금 + 총평가금액
            총수익률 = 총평가금액 - 총매입금액 / 총매입금액 * 100
            총손익 = (총평가금액 - 총매입금액) + 실현손익
        :return:
        """
        # self.총평가금액 = 0
        for stock in self.보유주식.values():
            # if cfg_mgr.debug_mode():
            #     self.print_attr('KEEP', stock.stock_name, stock.code, 'CHANGE_PRICE')  # need to move to util/common pkg
            #     pdb.set_trace()

            # 계좌 정보 업데이트
            # self.예수금 -= stock.매매금액
            self.총평가금액 += stock.평가금액변동
            # self.총매입금액 += stock.매입금액변동
            # self.총최대매입금액 = max(self.총매입금액, self.총최대매입금액)
            self.추정자산 = self.예수금 + self.총평가금액  # 현금(예수금) + 주식(총평가금액)
            # self.총누적손익 += self.총누적손익 --> 변동없음
            self.총평가손익 = float(self.총평가금액 - self.총매입금액)  # 보유한 주식의가치로 얻은 손익
            self.총손익 = self.총평가손익 + self.총누적손익  # 개념상 손익 (현금의 손익 + 가치의 손익)
            self.총수익률 = self.총평가손익 / self.총매입금액 * 100  # 보유한 주식에 대한 총 수익률
            # self.총누적수익률 = float(self.총누적손익) / self.총최대매입금액 * 100  # 실현한 수익에 대한 총 수익률

        # if cfg_mgr.debug_mode():
        #     self.print_attr('KEEP', stock.stock_name, stock.code, 'CHANGE_PRICE')  # need to move to util/common pkg
        #     pdb.set_trace()

    def update_account_value(self, timestamp):
        """보유주식을 현 timestamp에 맞게 내부 정보를 업데이트 한다.

            변경된 현재가를 기준으로 아래 필드가 함께 업데이트 된다.
            self.수익률 = (self.현재가 - self.매입평균가) / self.매입평균가 * 100
            self.평가손익 = self.총매입금액 * self.수익률
            self.총평가금액 = self.현재가 * self.보유수량

        :param timestamp:
        :return:
        """
        self.timestamp = timestamp

        if not bool(self.보유주식):
            return

        for stock in self.보유주식.values():
            stock.update_stock_value(timestamp)

        self.revaluate()

    def add_stocks(self, code_list):
        """보유주식에 포함시킨다.

        :param code:
        :return:
        """
        for code in code_list:
            self.보유주식[code] = Stock.get_instance(code)

    def set_tick1_data(self, target_date):
        """보유한 주식(Stock)에 대해 tick1 data를 생성한다.

        :param target_date:
        :return:
        """
        for stock in self.보유주식.values():
            stock.gen_time_series_sec1(target_date)

    def get_tick_data(self, code):
        """

        :param code:
        :return:
        """
        return self.보유주식[code].tick_data

    def get_balance(self):
        """현재 잔고를 반환

        :return:
        """
        return self.balance

    def has_code(self, code):
        """해당 종목을 현재 보유중인지

        :param code:
        :return:
        """
        return code in self.보유주식

    def get_stock_count(self):
        """현재 보유중인 종목의 갯수를 반환

        :return:
        """
        return self.get_code_count()

    def get_code_count(self):
        """현재 보유중인 종목의 갯수를 반환

        :return:
        """
        return len(self.get_code_list_in_account())

    def get_code_list_in_account(self):
        """현재 보유중인 종목코드를 반환

        :return:
        """
        return self.보유주식.keys()

    def get_stock_list_in_account(self):
        """현재 보유중인 종목(Stock)을 반환

        :return:
        """
        return [stock for stock in self.보유주식.values()]

    def clear_stock(self, stock, price, reason):
        """보유한 주식중 특정 종목 일괄 청산

        :param code:
        :return:
        """
        stock.trading_reason = reason
        stock.update_sell(int(price), int(stock.보유수량))

    def all_clear_stocks(self, timestamp):
        """보유한 주식을 모두 일괄 청산

        :return:
        """
        for stock in self.보유주식.values():
            self.clear_stock(stock, stock.get_curr_price(timestamp), constant.TRADING_TIME_EXPIRED)

    def get_core_index(self):
        """Account 객체의 핵심 지표 리스트. 반드시 Account 객체의 속성값과 동기화가 되어야 함.

        :return:
        """
        return self.core_index

    def check_core_index(self):
        """계좌의 핵심 지표들이 모두 설정되었는지 check.
        누락된 지표가 있다면 Exception 발생하고 프로그램 종료

        :return:
        """
        for index in self.get_core_index():
            self.__getattribute__(index)

    def is_empty(self):
        return bool(self.보유주식)
Ejemplo n.º 5
0
class Stock(object):
    """code당 1개의 객체(singleton/code)를 생성한다.

    """
    _inst = {}

    def __init__(self, code):
        self.empty = True
        self.first_trading = True  # 첫 거래인지
        self.timestamp = None
        self.first_buy_time = None

        # if add/delete index, must update self.core_index
        self.timestamp = None
        self.code = code
        self.stock_name = cfg_mgr.STOCK_INFO[code]['stock_name']
        self.현재가 = 0
        self.매매수량 = 0
        self.보유수량 = 0
        self.매매금액 = 0
        self.매입금액 = 0
        self.최대매입금액 = 0
        self.매입평균가 = 0.0
        self.평가손익 = 0.0
        self.실현손익 = 0
        self.누적손익 = 0
        self.평가금액 = 0.0
        self.수익률 = 0.0
        self.누적수익률 = 0.0
        self.평가금액변동 = 0.0
        self.매입금액변동 = 0.0
        self.실현손익변동 = 0.0
        self.core_index = [
            'timestamp', 'code', 'stock_name', '현재가', '매매수량', '보유수량', '매매금액',
            '매입금액', '최대매입금액', '매입평균가', '평가손익', '실현손익', '누적손익', '평가금액', '수익률',
            '누적수익률', '평가금액변동', '매입금액변동', '실현손익변동'
        ]
        self.check_core_index()

        self.time_series_sec1 = None
        self.logger = TTlog().logger
        self.dbm = DBM('TopTrader')

    def print_attr(self, trading_type, attr_list=None):
        if not cfg_mgr.STOCK_MONITOR:
            return
        self.logger.debug("\n" + ("-" * 100))
        self.logger.debug("[Stock] Information : {}/{}".format(
            self.stock_name, self.code))
        self.logger.debug("[Trading Type] -> {}-{}".format(
            trading_type, self.trading_reason))
        if bool(attr_list):
            attr_val = []
            for attr in attr_list:
                val = self.__getattribute__(attr)
                log_str = "{}: {}".format(attr, val)
                if isinstance(val, float):
                    log_str = "{}: {:.2f}".format(attr, val)
                attr_val.append(log_str)
            self.logger.debug("\t".join(attr_val))
        else:
            self.logger.debug(self.__repr__())
        # pass

    def __repr__(self):
        core_index = self.core_index

        # log = ["{}: {}".format(attr, str(self.__getattribute__(attr))) for attr in core_index]
        log = []
        for attr in core_index:
            val = self.__getattribute__(attr)
            log_str = "{}: {}".format(attr, val)
            if isinstance(val, float):
                log_str = "{}: {:.2f}".format(attr, val)
            log.append(log_str)
        return "\t".join(log)

    @classmethod
    def get_instance(cls, code):
        if code not in cls._inst:
            cls._inst[code] = Stock(code)
        return cls._inst[code]

    @classmethod
    def get_new_instance(cls, code, recycle_time_series=False):
        if code in cls._inst and recycle_time_series:
            time_series_sec1 = cls._inst[code].time_series_sec1
            cls._inst[code] = Stock(code)
            cls._inst[code].time_series_sec1 = time_series_sec1
        else:
            cls._inst[code] = Stock(code)
        return cls._inst[code]

    def get_holding_period(self, timestamp=None):
        """최초 해당 주식을 보유한 시간으로부터 경과된 시간을 반환한다.

        :return:
        """
        if not bool(timestamp):
            timestamp = self.timestamp

        if not bool(self.first_buy_time) or timestamp <= self.first_buy_time:
            return timedelta(seconds=0)
        return timestamp - self.first_buy_time

    def bep(self, event, price, amount=None):
        """Backup/Evaluate/PostAction

            prev : 기존 데이터
        :return:
        """
        # Backup
        self.backup()

        # dispatch Evaluate function
        eval_fn = self.__getattribute__("evaluate_{}".format(event))
        eval_fn(**{'price': price, 'amount': amount})

        # dispatch Post function
        post_fn = self.__getattribute__("post_{}".format(event))
        post_fn(**{'price': price, 'amount': amount})

        # Update

    def backup(self):
        """중요 지표들을 모두 백업한다.
        백업 지표는 앞에 '기존' 키워드가 붙는다.

        :return:
        """
        core_index = [
            '현재가', '매매수량', '보유수량', '매매금액', '매입금액', '최대매입금액', '매입평균가', '평가손익',
            '실현손익', '누적손익', '평가금액', '수익률', '누적수익률'
        ]

        for index in core_index:
            val = self.__getattribute__(index)
            self.__setattr__('기존' + index, val)

    def evaluate_buy(self, **kwargs):
        price, amount = kwargs['price'], kwargs['amount']
        self.현재가 = price
        self.매매수량 = amount
        self.보유수량 = self.기존보유수량 + self.매매수량
        self.매매금액 = self.현재가 * self.매매수량
        self.매입금액 = self.기존매입금액 + self.매매금액
        self.매입금액변동 = self.매입금액 - self.기존매입금액
        self.최대매입금액 = max(self.최대매입금액, self.매입금액)
        self.매입평균가 = self.매입금액 / self.보유수량
        self.평가손익 = (self.현재가 - self.매입평균가) * self.보유수량
        # self.실현손익 = self.실현손익
        self.실현손익변동 = 0
        # self.누적손익 = self.누적손익
        self.평가금액 = self.현재가 * self.보유수량
        if self.보유수량 == 0:
            self.수익률 = 0.0
        else:
            self.수익률 = round((self.평가금액 - self.매입금액) / self.매입금액 * 100, 2)
        self.누적수익률 = round(float(self.누적손익) / self.최대매입금액 * 100, 2)
        self.평가금액변동 = self.평가금액 - self.기존평가금액

        if self.first_trading:
            self.first_buy_time = self.timestamp
        self.first_trading = False

    def post_buy(self, **kwargs):
        price, amount = kwargs['price'], kwargs['amount']
        core_index = [
            'timestamp', '현재가', '매입평균가', '수익률', '누적수익률', '누적손익', '매매수량',
            '보유수량', '매입금액', '최대매입금액', '평가손익', '실현손익', '평가금액', '매매금액'
        ]
        self.print_attr('BUY', core_index)
        # pdb.set_trace()

    def evaluate_sell(self, **kwargs):
        price, amount = kwargs['price'], kwargs['amount']
        self.현재가 = price
        self.매매수량 = amount
        self.보유수량 = self.기존보유수량 - self.매매수량
        self.매매금액 = self.현재가 * self.매매수량
        self.매입금액 = self.기존매입평균가 * self.보유수량
        self.매입금액변동 = self.매입금액 - self.기존매입금액
        self.최대매입금액 = max(self.최대매입금액, self.매입금액)
        # self.매입평균가 = self.매입평균가
        self.평가손익 = (self.현재가 - self.매입평균가) * self.보유수량
        self.실현손익 = (self.현재가 - self.매입평균가) * self.매매수량
        self.실현손익변동 = self.실현손익 - self.기존실현손익
        self.누적손익 += self.실현손익
        self.평가금액 = self.현재가 * self.보유수량
        # self.기존수익률 = round((self.기존평가금액 - self.기존매입금액) / self.기존매입금액 * 100, 2)
        if self.보유수량 == 0:
            self.수익률 = 0.0
        else:
            self.수익률 = round((self.평가금액 - self.매입금액) / self.매입금액 * 100, 2)
        self.누적수익률 = round(float(self.누적손익) / self.최대매입금액 * 100, 2)
        self.평가금액변동 = self.평가금액 - self.기존평가금액

        if self.보유수량 == 0:
            self.first_trading = True
            self.first_buy_time = None

    def post_sell(self, **kwargs):
        price, amount = kwargs['price'], kwargs['amount']
        core_index = [
            'timestamp', '현재가', '매입평균가', '실현손익', '기존수익률', '기존누적수익률', '누적손익',
            '매매수량', '보유수량', '매입금액', '최대매입금액', '평가손익', '평가금액', '매매금액'
        ]
        self.print_attr('SELL', core_index)
        # pdb.set_trace()

    def evaluate_change_price(self, **kwargs):
        price, amount = kwargs['price'], 0
        self.현재가 = price
        self.매매수량 = amount
        # self.보유수량 = self.기존보유수량 - self.매매수량
        self.매매금액 = 0
        # self.매입금액 = self.기존매입평균가 * self.보유수량
        # self.최대매입금액 = max(self.최대매입금액, self.매입금액)
        # self.매입평균가 = self.매입평균가
        self.평가손익 = (self.현재가 - self.매입평균가) * self.보유수량
        # self.실현손익 = (self.현재가 - self.매입평균가) * self.매매수량
        # self.누적손익 += self.실현손익
        self.평가금액 = self.현재가 * self.보유수량
        if self.보유수량 == 0:
            self.수익률 = 0.0
        else:
            self.수익률 = round((self.평가금액 - self.매입금액) / self.매입금액 * 100, 2)
        # self.누적수익률 = round(float(self.누적손익) / self.최대매입금액 * 100, 2)
        self.평가금액변동 = self.평가금액 - self.기존평가금액

    def post_change_price(self, **kwargs):
        price, amount = kwargs['price'], 0
        core_index = [
            '현재가', '매매수량', '보유수량', '매매금액', '매입금액', '최대매입금액', '매입평균가', '평가손익',
            '실현손익', '누적손익', '평가금액', '수익률', '누적수익률'
        ]
        # self.print_attr('CHANGE PRICE')

    @common.type_check
    def update_sell(self, price_per_stock: int, amount: int):
        """현재가에 amount 만큼 매도를 하여, 내부 정보를 업데이트 한다.

        :param amount:
        :return:
        """
        self.bep('sell', price_per_stock, amount)

    @common.type_check
    def update_buy(self, price_per_stock: int, amount: int):
        """해당 주식을 매수하였을 경우, 관련 지표를 업데이트

        :param price_per_stock:
        :param amount:
        :return:
        """
        self.bep('buy', price_per_stock, amount)

    def update_stock_value(self, timestamp):
        """해당 주식의 현재가가 변경되었을 경우, 관련 지표를 업데이트

        :param timestamp:
        :return:
        """
        self.timestamp = timestamp
        현재가 = self.time_series_sec1.ix[timestamp]['현재가']
        self.bep('change_price', 현재가)

    def set_strategy(self, strg):
        """

        :param strg:
        :return:
        """
        self.strg = strg

    def gen_time_series_sec1(self, target_date):
        """특정종목 특정일의 초단위 tick_data(DataFrame 객체)를 생성한다.

                timestamp                현재가  volumn
                2018-08-02 09:00:00      0.0     0.0
                2018-08-02 09:00:01  15000.0   130.0
                2018-08-02 09:00:02  15000.0    91.0
                2018-08-02 09:00:03  15500.0     0.0
                ...(생략)

        :param target_date:
        :return:
        """

        if self.time_series_sec1 is not None:
            self.logger.info(
                "{}/{} use existing gen_time_series_sec1..".format(
                    self.stock_name, self.code))
            return self.time_series_sec1

        self.logger.info("{}/{} gen_time_series_sec1..".format(
            self.stock_name, self.code))
        y, m, d = target_date.year, target_date.month, target_date.day
        s_time = datetime(y, m, d, 9, 0, 0)
        e_time = datetime(y, m, d, 15, 30, 0)
        df = pd.DataFrame(
            self.dbm.get_tick_data(self.code, target_date, tick="1"))
        ts_group = df.groupby('timestamp')
        price = pd.DataFrame(ts_group.max()['현재가'])
        index = pd.date_range(s_time, e_time, freq='S')
        price = price.reindex(index, method='ffill', fill_value=0)
        # volumn = pd.DataFrame(ts_group.sum()['거래량'])
        # volumn = volumn.reindex(index, method='ffill', fill_value=0)
        # price['volumn'] = volumn
        # index = timestamp (freq=second)
        # 현재가 = max, ffill, fill_value=0
        # 거래량 = sum, ffill, fill_value=0
        self.time_series_sec1 = price
        return self.time_series_sec1

    def get_curr_price(self, timestamp):
        """현 시점(timestamp)의 주가를 반환한다.

        :param timestamp:
        :return:
        """
        if self.time_series_sec1 is None:
            y, m, d = timestamp.year, timestamp.month, timestamp.day
            self.time_series_sec1 = self.gen_time_series_sec1(datetime(
                y, m, d))

        return self.time_series_sec1.ix[timestamp]['현재가']

    def get_core_index(self):
        """Stock객체의 핵심 지표 리스트. 반드시 Stock객체의 속성값과 동기화가 되어야 함.
        
        :return: 
        """
        return self.core_index

    def check_core_index(self):
        """

        :return:
        """
        for index in self.get_core_index():
            self.__getattribute__(index)