﻿#
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# 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.
#

from clr import AddReference
AddReference("QuantConnect.Indicators")

from QuantConnect.Indicators import *
from datetime import datetime
import decimal as d
import unittest

class IndicatorExtensionsTests(unittest.TestCase):
    def test_PipesDataUsingOfFromFirstToSecond(self):
        first = SimpleMovingAverage(2)
        second = Delay(1)

        # this is a configuration step, but returns the reference to the second for method chaining
        third = IndicatorExtensions.Of(second, first)

        data1 = IndicatorDataPoint(datetime.now(), 1)
        data2 = IndicatorDataPoint(datetime.now(), 2)
        data3 = IndicatorDataPoint(datetime.now(), 3)
        data4 = IndicatorDataPoint(datetime.now(), 4)

        # sma has one item
        first.Update(data1)
        self.assertFalse(first.IsReady)
        self.assertEqual(0, second.Current.Value)

        # sma is ready, delay will repeat this value
        first.Update(data2)
        self.assertTrue(first.IsReady)
        self.assertFalse(second.IsReady)
        self.assertEqual(1.5, second.Current.Value)

        # delay is ready, and repeats its first input
        first.Update(data3)
        self.assertTrue(second.IsReady)
        self.assertEqual(1.5, second.Current.Value)

        # now getting the delayed data
        first.Update(data4)
        self.assertEqual(2.5, second.Current.Value)

    def test_PipesDataFirstWeightedBySecond(self):
        period = 4
        value = Identity("Value")
        weight = Identity("Weight")

        third = IndicatorExtensions.WeightedBy(value, weight, period)

        data = range(1, 11)
        window = list(reversed(data))[:period]
        current = sum([ 2 * x * x for x in window ]) / float(sum(window))

        for item in data:
            value.Update(datetime.now(), 2 * item)
            weight.Update(datetime.now(), item)

        self.assertEqual(current, float(third.Current.Value))

    def test_NewDataPushesToDerivedIndicators(self):
        identity = Identity("identity")
        self.sma = SimpleMovingAverage(3)

        identity.Updated += self.identity_updated

        identity.Update(datetime.now(), 1)
        identity.Update(datetime.now(), 2)
        self.assertFalse(self.sma.IsReady)

        identity.Update(datetime.now(), 3)
        self.assertTrue(self.sma.IsReady)
        self.assertEqual(2, self.sma.Current.Value)

    def identity_updated(self, sender, consolidated):
        self.sma.Update(consolidated)

    def test_MultiChainSMA(self):
        identity = Identity("identity")
        delay = Delay(2)

        # create the SMA of the delay of the identity
        sma = IndicatorExtensions.SMA(IndicatorExtensions.Of(delay, identity), 2)

        identity.Update(datetime.now(), 1)
        self.assertTrue(identity.IsReady)
        self.assertFalse(delay.IsReady)
        self.assertFalse(sma.IsReady)

        identity.Update(datetime.now(), 2)
        self.assertTrue(identity.IsReady)
        self.assertFalse(delay.IsReady)
        self.assertFalse(sma.IsReady)

        identity.Update(datetime.now(), 3)
        self.assertTrue(identity.IsReady)
        self.assertTrue(delay.IsReady)
        self.assertFalse(sma.IsReady)

        identity.Update(datetime.now(), 4)
        self.assertTrue(identity.IsReady)
        self.assertTrue(delay.IsReady)
        self.assertTrue(sma.IsReady)

        self.assertEqual(1.5, sma.Current.Value)

    def test_MultiChainEMA(self):
        identity = Identity("identity")
        delay = Delay(2)

        # create the EMA of chained methods
        ema = IndicatorExtensions.EMA(IndicatorExtensions.Of(delay, identity), 2, d.Decimal(1))
            
        identity.Update(datetime.now(), 1)
        self.assertTrue(identity.IsReady)
        self.assertFalse(delay.IsReady)
        self.assertFalse(ema.IsReady)

        identity.Update(datetime.now(), 2)
        self.assertTrue(identity.IsReady)
        self.assertFalse(delay.IsReady)
        self.assertFalse(ema.IsReady)

        identity.Update(datetime.now(), 3)
        self.assertTrue(identity.IsReady)
        self.assertTrue(delay.IsReady)
        self.assertFalse(ema.IsReady)

        identity.Update(datetime.now(), 4)
        self.assertTrue(identity.IsReady)
        self.assertTrue(delay.IsReady)
        self.assertTrue(ema.IsReady)

        self.assertEqual(2, ema.Current.Value)

    def test_MultiChainMAX(self):
        identity = Identity("identity")
        delay = Delay(2)

        # create the MAX of the delay of the identity
        max = IndicatorExtensions.MAX(IndicatorExtensions.Of(delay, identity), 2)

        identity.Update(datetime.now(), 1)
        self.assertTrue(identity.IsReady)
        self.assertFalse(delay.IsReady)
        self.assertFalse(max.IsReady)

        identity.Update(datetime.now(), 2)
        self.assertTrue(identity.IsReady)
        self.assertFalse(delay.IsReady)
        self.assertFalse(max.IsReady)

        identity.Update(datetime.now(), 3)
        self.assertTrue(identity.IsReady)
        self.assertTrue(delay.IsReady)
        self.assertFalse(max.IsReady)

        identity.Update(datetime.now(), 4)
        self.assertTrue(identity.IsReady)
        self.assertTrue(delay.IsReady)
        self.assertTrue(max.IsReady)

        self.assertEqual(2, max.Current.Value)

    def test_MultiChainMIN(self):
        identity = Identity("identity")
        delay = Delay(2)

        # create the MAX of the delay of the identity
        min = IndicatorExtensions.MIN(IndicatorExtensions.Of(delay, identity), 2)

        identity.Update(datetime.now(), 1)
        self.assertTrue(identity.IsReady)
        self.assertFalse(delay.IsReady)
        self.assertFalse(min.IsReady)

        identity.Update(datetime.now(), 2)
        self.assertTrue(identity.IsReady)
        self.assertFalse(delay.IsReady)
        self.assertFalse(min.IsReady)

        identity.Update(datetime.now(), 3)
        self.assertTrue(identity.IsReady)
        self.assertTrue(delay.IsReady)
        self.assertFalse(min.IsReady)

        identity.Update(datetime.now(), 4)
        self.assertTrue(identity.IsReady)
        self.assertTrue(delay.IsReady)
        self.assertTrue(min.IsReady)

        self.assertEqual(1, min.Current.Value)

    def test_PlusAddsLeftAndRightAfterBothUpdated(self):
        left = Identity("left")
        right = Identity("right")
        composite = IndicatorExtensions.Plus(left, right)

        left.Update(datetime.now(), 1)
        right.Update(datetime.now(), 1)
        self.assertEqual(2, composite.Current.Value)

        left.Update(datetime.today(), 2)
        self.assertEqual(2, composite.Current.Value)

        left.Update(datetime.today(), 3)
        self.assertEqual(2, composite.Current.Value)

        right.Update(datetime.today(), 4)
        self.assertEqual(7, composite.Current.Value)

    def test_MinusSubtractsLeftAndRightAfterBothUpdated(self):
        left = Identity("left")
        right = Identity("right")
        composite = IndicatorExtensions.Minus(left, right)

        left.Update(datetime.today(), 1)
        right.Update(datetime.today(), 1)
        self.assertEqual(0, composite.Current.Value)

        left.Update(datetime.today(), 2)
        self.assertEqual(0, composite.Current.Value)

        left.Update(datetime.today(), 3)
        self.assertEqual(0, composite.Current.Value)

        right.Update(datetime.today(), 4)
        self.assertEqual(-1, composite.Current.Value)

    def test_OverDivdesLeftAndRightAfterBothUpdated(self):
        left = Identity("left")
        right = Identity("right")
        composite = IndicatorExtensions.Over(left, right)

        left.Update(datetime.today(), 1)
        right.Update(datetime.today(), 1)
        self.assertEqual(1, composite.Current.Value)

        left.Update(datetime.today(), 2)
        self.assertEqual(1, composite.Current.Value)

        left.Update(datetime.today(), 3)
        self.assertEqual(1, composite.Current.Value)

        right.Update(datetime.today(), 4)
        self.assertEqual(3.0 / 4.0, composite.Current.Value)

    def test_OverHandlesDivideByZero(self):
        left = Identity("left")
        right = Identity("right")
        composite = IndicatorExtensions.Over(left, right)
        self.updatedEventFired = False
        composite.Updated += self.composite_updated

        left.Update(datetime.today(), 1)
        self.assertFalse(self.updatedEventFired)
        right.Update(datetime.today(), 0)
        self.assertFalse(self.updatedEventFired)

        # submitting another update to right won't cause an update without corresponding update to left
        right.Update(datetime.today(), 1)
        self.assertFalse(self.updatedEventFired)
        left.Update(datetime.today(), 1)
        self.assertTrue(self.updatedEventFired)

    def composite_updated(self, sender, consolidated):
        self.updatedEventFired = True

    def test_TimesMultipliesLeftAndRightAfterBothUpdated(self):
        left = Identity("left")
        right = Identity("right")
        composite = IndicatorExtensions.Times(left, right)

        left.Update(datetime.today(), 1)
        right.Update(datetime.today(), 1)
        self.assertEqual(1, composite.Current.Value)

        left.Update(datetime.today(), 2)
        self.assertEqual(1, composite.Current.Value)

        left.Update(datetime.today(), 3)
        self.assertEqual(1, composite.Current.Value)

        right.Update(datetime.today(), 4)
        self.assertEqual(12, composite.Current.Value)


if __name__ == '__main__':
    unittest.main()