# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2013 Jason Koelker
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import collections
import itertools
import numbers
import operator

import numpy as np
from zipline.transforms import utils as transforms
from zl.indicators import utils


BUY = 'Buy'
SELL = 'Sell'


class Signal(object):
    def __init__(self, direction, high, low, bars, perfection):
        self.directin = direction
        self.high = high
        self.low = low
        self.bars = bars
        self.perfection = perfection

    def is_perfection(self, event):
        if self.direction == BUY:
            return event['low'] <= self.perfection
        return event['high'] >= self.perfection

    @utils.cached_property
    def risk_level(self):
        for bar in self.bars:
            if self.direction == BUY:
                if bar['low'] == self.low:
                    return self.low - (bar['high'] - bar['low'])

            elif self.direction == SELL:
                if bar['high'] == self.hight:
                    return self.high + (bar['high'] - bar['low'])

        assert False, "Signal extreme not found in bars!"


class Setup(object):
    __metaclass__ = transforms.TransformMeta

    def __init__(self, period=9, lookback=4, field='close_price'):
        self.period = period
        self.lookback = lookback
        self.field = field
        self.sid_windows = collections.defaultdict(self.create_window)

    def create_window(self):
        return SetupWindow(self.period, self.lookback, self.field)

    def update(self, event):
        window = self.sid_windows[event.sid]
        window.update(event)
        return window()


class SetupWindow(transforms.EventWindow):
    def __init__(self, period, lookback, field):
        window_length = period + lookback
        transforms.EventWindow.__init__(self, window_length=window_length)

        self.period = period
        self.lookback = lookback
        self.field = field
        self.signal = None

    def handle_add(self, event):
        for field in (self.field, 'high', 'low'):
            assert field in event
            assert isinstance(event[field], numbers.Number)

    def handle_remove(self, event):
        pass

    def _get_direction(self):
        values = [t[self.field] for t in self.ticks]

        if any(itertools.imap(operator.lt,
                              values[self.lookback:],
                              values[:self.period])):
            return BUY

        elif any(itertools.imap(operator.gt,
                                values[self.lookback:],
                                values[:self.period])):
            return SELL

    def _perfection(self):
        if not self.signal:
            return

        if self.signal.is_perfection(self.ticks[-1]):
            signal = self.signal
            self.signal = None
            return signal

    def __call__(self):
        if len(self.ticks) < self.window_length:
            return

        direction = self._get_direction()

        if not direction:
            return self._perfection()

        bars = self.ticks[self.lookback:]

        lowes = [bar['low'] for bar in bars]
        highs = [bar['high'] for bar in bars]

        high = np.max(highs)
        low = np.max(lowes)

        if direction == BUY:
            perfection = np.min(lowes[-4:-2])
        else:
            perfection = np.max(highs[-4:-2])

        signal = Signal(direction, high, low, bars, perfection)

        for check in bars[-2:]:
            if signal.is_perfection(check):
                return signal

        self.signal = signal

@@ -1064,18 +1064,21 @@
     def 
-is
+check
 _perfect
@@ -1225,24 +1225,149 @@
 perfection%0A%0A
+    @utils.cached_property%0A    def is_perfect(self):%0A        return any(%5Bself.check_perfection(b) for b in self.bars%5B-2:%5D%5D)%0A%0A
     @utils.c
@@ -2621,35 +2621,8 @@
 ield
-%0A        self.signal = None
 %0A%0A  
@@ -3292,228 +3292,8 @@
 LL%0A%0A
-    def _perfection(self):%0A        if not self.signal:%0A            return%0A%0A        if self.signal.is_perfection(self.ticks%5B-1%5D):%0A            signal = self.signal%0A            self.signal = None%0A            return signal%0A%0A
     
@@ -3472,27 +3472,8 @@
 turn
- self._perfection()
 %0A%0A  
@@ -3807,24 +3807,22 @@
         
-signal =
+return
  Signal(
@@ -3865,141 +3865,4 @@
 on)%0A
-%0A        for check in bars%5B-2:%5D:%0A            if signal.is_perfection(check):%0A                return signal%0A%0A        self.signal = signal%0A

Allow Setup without perfection to be valid