class QuoteAdapter:

    def get_quote(self, asset):
        raise NotImplementedError("QuoteAdapter.get_quote: You should subclass this and create an adapter.")

    def get_options(self, underlying_asset=None, expiration_date=None):
        raise NotImplementedError("QuoteAdapter.get_options: You should subclass this and create an adapter.")

    def get_expiration_dates(self, underlying_asset=None):
        raise NotImplementedError("QuoteAdapter.get_expiration_dates: You should subclass this and create an adapter.") class GoogleFinanceQuoteAdapter(QuoteAdapter):

    def __init__(self):
        self._cache = {}
    def _set_cache(self, quote):
        self._cache[quote.asset.symbol] = quote
        return quote

    def get_quote(self, asset):

        asset = asset_factory(asset)
        if self._cache.get(asset.symbol) is not None:
            return self._cache.get(asset.symbol)

        if isinstance(asset, Option):

            options = self.get_options(asset.underlying, asset.expiration_date)
            matches = [_ for _ in options if _.asset == asset]
            if len(matches) == 0:
                raise Exception("GoogleFinanceAdapter.get_quote: No quote found for {}".format(asset.symbol))
            return matches[0]

        else:

            google_quotes = getQuotes(asset.symbol)

            if google_quotes is None or len(google_quotes) == 0:
                raise Exception("GoogleFinanceAdapter.get_quote: No quote found for {}".format(asset.symbol))

            last_trade = google_quotes[0].get('LastTradeWithCurrency', None)
            if last_trade is None or last_trade == '' or last_trade == '-':
                raise Exception("GoogleFinanceAdapter.get_quote: No quote found for {}".format(asset.symbol))

            return Quote(quote_date=arrow.now().format('YYYY-MM-DD'), asset=asset, bid=float(last_trade)-0.01, ask=float(last_trade)+0.01)

    def get_expiration_dates(self, underlying_asset=None):
        oc = OptionChain('NASDAQ:' + asset_factory(underlying_asset).symbol)
        return sorted(list(set([asset_factory(_['s']).expiration_date for _ in (oc.calls + oc.puts)])))

    def get_options(self, underlying_asset=None, expiration_date=None):
        oc = OptionChain('NASDAQ:' + asset_factory(underlying_asset).symbol)
        underlying_quote = self.get_quote(underlying_asset)

        out = []
        for option in (oc.calls + oc.puts):
            if arrow.get(expiration_date).format('YYMMDD') in option['s']:
                quote = OptionQuote(quote_date=arrow.now().format('YYYY-MM-DD'),
                                asset=option['s'],
                                bid=float(option['b']) if option['b'] != '-' else None,
                                ask=float(option['a']) if option['a'] != '-' else None,
                                underlying_price = underlying_quote.price)
                self._set_cache(quote)
                out.append(quote)

        return out class AccountAdapter():

    def get_account(self, account_id:str, current_date=None):
        raise NotImplementedError("BookAdapter.get_account: subclass me!")

    def put_account(self, account:Account, current_date=None):
        raise NotImplementedError("BookAdapter.put_account: subclass me!")

    def get_account_ids(self, current_date=None):
        raise NotImplementedError("BookAdapter.get_accounts: subclass me!") class LocalFileSystemAccountAdapter():

    def __init__(self, root=None):
        if root is None: root = tempfile.gettempdir()
        if not os.path.exists(root+"/accounts/"):
            os.makedirs(root+"/accounts/")
        self.root = root

    def get_account(self, account_id: str, current_date=None):
        with open(self.root + "/accounts/" + account_id + ".pickle", 'rb') as f:
            return pickle.load(file=f)

    def has_account(self, account_id: str, current_date=None):
        try:
            pickle.load(file=self.root + "/accounts/" + account_id + ".pickle")
            return True
        except:
            return False

    def put_account(self, account: Account, current_date=None):
        with open(self.root + "/accounts/" + account.account_id + ".pickle", 'wb') as f:
            pickle.dump(account, file=f)

    def get_account_ids(self, current_date=None):
        mypath = self.root + "/accounts/"
        return [f.split(".")[0] for f in listdir(mypath) if isfile(join(mypath, f))]

    def delete_account(self, account, current_date=None):
        try:
            os.remove(self.root + "/accounts/" + account_factory(account).account_id + ".pickle")
        except:
            pass

    def delete_accounts(self, accounts, current_date=None):
        [self.delete_account(account) for account in accounts] class MarketAdapter():

    def enter_order(self, account: Account, order: Order):
        pass class PaperMarketAdapter(MarketAdapter):

    def __init__(self, quote_adapter:QuoteAdapter, estimator: Estimator = None):
        self.pending_orders = []
        self.quote_adapter = quote_adapter
        self.estimator = estimator if estimator is not None else Estimator()

    def expire_options(self, account:Account, quote_adapter:QuoteAdapter):
        close_expired_options(account=account, quote_adapter=quote_adapter, market_adapter=self)

    # fill any open orders that the broker knows about
    # # if it is possible to fill the order
    # and then cancel everything that was left
    def fill_pending_orders(self, cancel_on_failure = True):
        for pending_order in self.pending_orders:
            try:

                #actually fill the order
                fill_order(account=pending_order.account,
                        order = pending_order.order,
                        quote_adapter=self.quote_adapter,
                        estimator=pending_order.estimator)

                pending_order.order.status = 'filled'

            except Exception as e:
                pending_order.order.status = 'failed'
                print("Order failed to execute")
                print(e)
                pass

        self.pending_orders = [_ for _ in self.pending_orders if _.order.status != 'filled']

        if cancel_on_failure:
            self.pending_orders = []

    def simulate_order(self, account: Account, order: Order, estimator:Estimator=None):
        estimator = estimator if estimator is not None else self.estimator

        # since we are simulating the order, make a copy of the account and the order
        # we'll force the order against the account and check the values at the end
        account_copy = deepcopy(account)
        order_copy = deepcopy(order)

        # force fill copies of any open orders against copies of this account
        for pending_order in [_ for _ in self.pending_orders if _.account == account]:
            account_copy = fill_order(account=account_copy, order=deepcopy(pending_order.order), estimator=estimator, quote_adapter=self.quote_adapter)

        # now fill this order against a copy of the account
        fill_order(account=account_copy, order=order_copy, estimator=estimator, quote_adapter=self.quote_adapter)

        return OrderImpact(account0 = account, account1 = account_copy, order = order_copy, actual_commission = None, actual_fill_price = None)


    def enter_order(self, account: Account, order: Order, estimator:Estimator=None, auto_fill=True):
        estimator = estimator if estimator is not None else self.estimator
        self.pending_orders.append(PendingOrder(account=account, order=order, estimator=estimator))
        if auto_fill:
            self.fill_pending_orders()
        return account class Account():

    def __init__(self, positions=None, account_id:str=None):
        self.account_id = account_id if account_id is not None else 'account' + ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10))
        self.cash = 10000
        self.maintenance_margin = 0.0
        self.positions = positions if positions is not None else [] class Order(object):
    def __init__(self, legs = None, price=None, condition='market'):
        self.legs = legs if legs is not None else []
        self.status = 'open'
        self.price = 0.0
        self.condition = condition

    def duplicate(self, times=1):
        self.price *= times
        for leg in self.legs:
            leg.quantity *= times
        return self

    def buy_to_open(self, asset = None, quantity: int = None, price: float = None):
        return self.add_leg(asset=asset, quantity=quantity, price=price, order_type='bto')
    def sell_to_open(self, asset = None, quantity: int = None, price: float = None):
        return self.add_leg(asset=asset, quantity=quantity, price=price, order_type='sto')
    def buy_to_close(self, asset = None, quantity: int = None, price: float = None):
        return self.add_leg(asset=asset, quantity=quantity, price=price, order_type='btc')
    def sell_to_close(self, asset = None, quantity: int = None, price: float = None):
        return self.add_leg(asset=asset, quantity=quantity, price=price, order_type='stc')


    def add_leg(self, leg: Leg = None, asset = None, quantity: int = None, order_type: str = None,
                price: float = None):

        # if asset is a list-like object, then take the first elent in the list
        if asset and hasattr(asset, '__iter__') and not isinstance(asset, str):
            asset = asset[0] if len(asset) > 0 else None

        # if asset is a quote, replace it with the actual asset
        if asset and isinstance(asset, Quote):
            asset = asset.asset

        asset = asset_factory(asset)
        quantity = int(quantity)

        if asset is None:
            raise Exception("Order.add_leg: an asset is required")

        if leg is not None:
            if len([_.asset.symbol for _ in self.legs if _.asset == leg.asset]) > 0:
                raise Exception("Order.add_leg symbol {} already exists within this order".format(leg.asset.symbol))
            self.legs.append(leg)
            self.price = self.price + leg.price * abs(quantity) if self.price is not None and leg.price is not None else None
        else:
            if len([_.asset.symbol for _ in self.legs if _.asset == asset]) > 0:
                raise Exception("Order.addLeg symbol {} already exists within this order".format(asset.symbol))

            self.legs.append(Leg(asset=asset, quantity=quantity, order_type=order_type, price=price))
            self.price = self.price + price * abs(quantity) if self.price is not None and price is not None else None


        return self class Estimator:
    """
        THe mother of all estimators, midway between the bid and ask
    """
    def estimate(self, quote:Quote, quantity = None):

        if quote.bid is not None and quote.ask is None and quote.bid > 0.0 and quote.ask > 0.0:
            return round((quote.bid + quote.ask) / 2, 2)

        if quote.price is not None:
            return round(quote.price,2)

        raise Exception('Estimator.estimate: Cannot estimate a price if the bid or ask are None or 0.0') class Asset():

    def __init__(self, symbol: str=None, asset_type: str=None):
        self.symbol = symbol.upper()
        self.asset_type = asset_type or 'asset'
        return

    def __eq__(self, other):
        """Override the default Equals behavior"""
        if isinstance(other, self.__class__):
            return self.symbol == other.symbol
        if isinstance(other, str):
            return self.symbol == other.upper()
        return False

    def __ne__(self, other):
        """Define a non-equality test"""
        return not self.__eq__(other) def asset_factory(symbol=None):
    """
        Create the appropriate asset based on the symbol.
    :param symbol: Case-insensitive symbol for the asset being created
    :return: An object that's a subclass of Asset or None
    """

    if symbol is None:
        return None

    if isinstance(symbol, Asset):
        return symbol

    symbol = symbol.upper()

    if len(symbol) > 8:
        if 'P0' in symbol:
            return Put(symbol)
        elif 'C0' in symbol:
            return Call(symbol)
        else:
            return Option(symbol)
    else:
        return Asset(symbol) def validate_account(account):

    if account.cash < 0:
        raise Exception('validate_account: You do not have enough cash for this order.')

    if account.cash < account.maintenance_margin:
        raise Exception('validate_account: You do not have enough cash to cover the margin requirement.')

    return account
from .adapters.quotes import QuoteAdapter
from .adapters.quotes import GoogleFinanceQuoteAdapter
from .adapters.accounts import AccountAdapter
from .adapters.accounts import LocalFileSystemAccountAdapter
from .adapters.markets import MarketAdapter
from .adapters.markets.PaperMarketAdapter import PaperMarketAdapter
from .accounts import Account
from .orders import Order
from .estimators import Estimator
from .assets import Asset, asset_factory
from .logic.validate_account import validate_account
"""

    PaperBroker

    Instantiate this with:
        from paperbroker import PaperBroker
        broker = PaperBroker()

    There is minimal logic here

"""





class PaperBroker():


    def __init__(self, quote_adapter:QuoteAdapter=None, account_adapter:AccountAdapter=None, market_adapter:MarketAdapter=None):