1#!/usr/bin/env python
2# -*- coding: utf-8; py-indent-offset:4 -*-
3###############################################################################
4#
5# Copyright (C) 2015, 2016, 2017 Daniel Rodriguez
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19#
20###############################################################################
21from __future__ import (absolute_import, division, print_function,
22                        unicode_literals)
23
24import collections
25
26from backtrader.metabase import MetaParams
27from backtrader.utils.py3 import with_metaclass
28
29
30class MetaSingleton(MetaParams):
31    '''Metaclass to make a metaclassed class a singleton'''
32    def __init__(cls, name, bases, dct):
33        super(MetaSingleton, cls).__init__(name, bases, dct)
34        cls._singleton = None
35
36    def __call__(cls, *args, **kwargs):
37        if cls._singleton is None:
38            cls._singleton = (
39                super(MetaSingleton, cls).__call__(*args, **kwargs))
40
41        return cls._singleton
42
43
44class Store(with_metaclass(MetaSingleton, object)):
45    '''Base class for all Stores'''
46
47    _started = False
48
49    params = ()
50
51    def getdata(self, *args, **kwargs):
52        '''Returns ``DataCls`` with args, kwargs'''
53        data = self.DataCls(*args, **kwargs)
54        data._store = self
55        return data
56
57    @classmethod
58    def getbroker(cls, *args, **kwargs):
59        '''Returns broker with *args, **kwargs from registered ``BrokerCls``'''
60        broker = cls.BrokerCls(*args, **kwargs)
61        broker._store = cls
62        return broker
63
64    BrokerCls = None  # broker class will autoregister
65    DataCls = None  # data class will auto register
66
67    def start(self, data=None, broker=None):
68        if not self._started:
69            self._started = True
70            self.notifs = collections.deque()
71            self.datas = list()
72            self.broker = None
73
74        if data is not None:
75            self._cerebro = self._env = data._env
76            self.datas.append(data)
77
78            if self.broker is not None:
79                if hasattr(self.broker, 'data_started'):
80                    self.broker.data_started(data)
81
82        elif broker is not None:
83            self.broker = broker
84
85    def stop(self):
86        pass
87
88    def put_notification(self, msg, *args, **kwargs):
89        self.notifs.append((msg, args, kwargs))
90
91    def get_notifications(self):
92        '''Return the pending "store" notifications'''
93        self.notifs.append(None)  # put a mark / threads could still append
94        return [x for x in iter(self.notifs.popleft, None)]
95