-- More information about this indicator can be found at:
-- http://fxcodebase.com/code/viewtopic.php?f=17&t=1643

--+------------------------------------------------------------------+
--|                               Copyright © 2018, Gehtsoft USA LLC | 
--|                                            http://fxcodebase.com |
--+------------------------------------------------------------------+
--|                                      Developed by : Mario Jemic  |                    
--|                                          mario.jemic@gmail.com   |
--+------------------------------------------------------------------+
--|                                 Support our efforts by donating  | 
--|                                    Paypal: https://goo.gl/9Rj74e |
--+------------------------------------------------------------------+
--|                                Patreon :  https://goo.gl/GdXWeN  |  
--|                    BitCoin : 15VCJTLaz12Amr7adHSBtL9v8XomURo9RF  |  
--|                BitCoin Cash: 1BEtS465S3Su438Kc58h2sqvVvHK9Mijtg  | 
--|           Ethereum : 0x8C110cD61538fb6d7A2B47858F0c0AaBd663068D  |  
--|                   LiteCoin : LLU8PSY2vsq7B9kRELLZQcKf5nJQrdeqwD  |  
--+------------------------------------------------------------------+

-- Indicator profile initialization routine
-- Defines indicator profile properties and indicator parameters
-- TODO: Add minimal and maximal value of numeric parameters and default color of the streams
function Init()
    indicator:name("Triangle");
    indicator:description("Triangle");
    indicator:requiredSource(core.Bar);
    indicator:type(core.Indicator);
	
	indicator.parameters:addString("k1", "Triangle", "Triangle  Y/N" , "Yes");
    indicator.parameters:addStringAlternative("k1", "Yes", "Triangle Yes" , "Yes");
	indicator.parameters:addStringAlternative("k1", "No", "Triangle No", "No");
	
		indicator.parameters:addString("k2", "Expanding Triangle", "Expanding Triangle  Y/N" , "No");
    indicator.parameters:addStringAlternative("k2", "Yes", "Expanding Triangle Yes" , "Yes");
	indicator.parameters:addStringAlternative("k2", "No", "Expanding Triangle No" , "No");
	
	
		--indicator.parameters:addString("k3", "Top", "Top  Y/N" , "No");
    --indicator.parameters:addStringAlternative("k3", "Yes", "Top Yes" , "Yes");
	--indicator.parameters:addStringAlternative("k3", "No", "Top No" , "No");
	
		--indicator.parameters:addString("k4", "Bottoms", "Bottoms  Y/N" , "No");
  -- indicator.parameters:addStringAlternative("k4", "Yes", "Bottoms Yes" , "Yes");
	--indicator.parameters:addStringAlternative("k4", "No", "Bottoms No" , "No");
	
	
	indicator.parameters:addString("k5", "Wedges", "Up Wedges  Y/N" , "No");
    indicator.parameters:addStringAlternative("k5", "Yes", "Wedges  Yes" , "Yes");
	indicator.parameters:addStringAlternative("k5", "No", "Wedges  No" , "No");
	
	indicator.parameters:addString("k6", "Down Wedges", "Wedges  Y/N" , "No");
    indicator.parameters:addStringAlternative("k6", "Yes", "Wedges  Yes" , "Yes");
	indicator.parameters:addStringAlternative("k6", "No", "Wedges  No" , "No");
	
	indicator.parameters:addInteger("x", "Max. Base / Side Ratio", "Max. Base / Side Ratio", 5);
	indicator.parameters:addInteger("y", "Max. Side / Side Ratio", "Max. Side / Side Ratio", 5);
	
	
    indicator.parameters:addInteger("frame", "Frame Size", "Frame Size", 50);
    indicator.parameters:addColor("top_color", "Color of Top", "Color of Top", core.rgb(0, 255, 0));
    indicator.parameters:addColor("bottom_color", "Color of Bottom", "Color of Bottom", core.rgb(0, 0, 255));
	indicator.parameters:addInteger("width", "Line width", "", 1, 1, 5);
    indicator.parameters:addInteger("style", "Line style", "", core.LINE_SOLID);
    indicator.parameters:setFlag("style", core.FLAG_LINE_STYLE);

    indicator.parameters:addGroup("Alerts Sound"); 
    indicator.parameters:addBoolean("ChartAlert", "Chart alert", "", true);  
    indicator.parameters:addBoolean("PlaySound", "Play Sound", "", false);  
    indicator.parameters:addBoolean("RecurrentSound", "Recurrent Sound", "", false);
    indicator.parameters:addFile("AlertSound", "Alert Sound", "", "");
    indicator.parameters:setFlag("AlertSound", core.FLAG_SOUND);
    indicator.parameters:addBoolean("SendEmail", "Send email", "", false);
    indicator.parameters:addString("Email", "Email address", "", "");
    indicator.parameters:setFlag("Email", core.FLAG_EMAIL);
end

-- Indicator instance initialization routine
-- Processes indicator parameters and creates output streams
-- TODO: Refine the first period calculation for each of the output streams.
-- TODO: Calculate all constants, create instances all subsequent indicators and load all required libraries
-- Parameters block

local first = nil;
local source = nil;
local candlesize = nil;

local k1=nil;
local k2=nil;
--local k3=nil;
--local k4=nil;
local k5=nil;
local k6=nil;
local base1=0;
local base2=0;

local LastAlertTime;


-- Streams block
local dummy = nil;

--Variable
local upy = nil;
local downy = nil;
local style, width;
-- Routine

function Signal(Msg)
   if LastAlertTime~=source:date(source:size()-1) then
    LastAlertTime=source:date(source:size()-1);
   else
    return; 
   end

  if instance.parameters.ChartAlert then
   core.host:execute("prompt", 1, "Alert" , Msg);
  end
  
  if instance.parameters.PlaySound then
   terminal:alertSound(instance.parameters.AlertSound, instance.parameters.RecurrentSound); 
  end

  if instance.parameters.SendEmail then
   terminal:alertEmail(Email, "Alert from Triangle indicator", Msg);
  end

end

function Prepare(nameOnly)
        style = instance.parameters.style;
        frame = instance.parameters.frame;
		width = instance.parameters.width;
		k1 = instance.parameters.k1;
		k2 = instance.parameters.k2;
		k3 = instance.parameters.k3;
		k4 = instance.parameters.k4;
		k5 = instance.parameters.k5;
		k6 = instance.parameters.k6;
		base1 = instance.parameters.x;
		base2 = instance.parameters.y;

    source = instance.source;
    first = source:first();
	
	local name = profile:id() .. "(" .. source:name() .. ", " .. frame .. ")";
    instance:name(name);
	
	
	if   (nameOnly) then
        return;
    end

    local s, e;
    s, e = core.getcandle(source:barSize(), core.now(), 0);
    -- remember size of the candle to calculate the date for the lines which ends after the end of the chart
    candlesize = e - s;

    upy = instance:addInternalStream(0, 0);
    downy = instance:addInternalStream(0, 0);

  
    -- add an extent to make the output streams longer than the source to be able draw into the future
    dummy = instance:addStream("D", core.Dot, name, "D", instance.parameters.top_color, first, 300);
end

local last_period = -1;

-- update
function Update(period)

    -- detect restart
    if last_period > period then
        core.host:execute("removeAll");
    end
    last_period = period;

    if period >= 6 then
        fractal(period);
    end
    if period >= frame then
        Draw(period, frame);
    end
end


--distance between two points
function Distance (x1,y1,x2,y2)
return math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
end


-- calculate fractals
function fractal(p)
    upy[p] = 0;
    downy[p] = 0;

    curr = source.high[p - 2];

    if (curr >= source.high[p - 4] and curr >= source.high[p - 3] and
        curr >= source.high[p - 1] and curr >= source.high[p]) then
        upy[p - 2] = source.high[p-2];
    end

    curr = source.low[p - 2];
    if (curr <= source.low[p - 4] and curr <= source.low[p - 3] and
        curr <= source.low[p - 1] and curr <= source.low[p]) then
        downy[p - 2] = source.low[p-2];
    end

    upy[p - 1] = 0;
    downy[p - 1] = 0;
    upy[p] = 0;
    downy[p] = 0;
end

-- get line equation coefficients
function getline(x1, y1, x2, y2)
    local a, b;

    a = ((y2 - y1) / (x2 - x1));
    b = (y1 - a * x1);
    return a, b;
end

-- get line intersection point
function intersect(a1, b1, a2, b2)
    local x, y;

    -- collinear
    if a1 == a2 then
        return nil, nil;
    end

    x = (b2 - b1) / (a1 - a2);
    y = a1 * x + b1;
    return x, y;
end

local line_id = 0;

-- draws line based on two fractals and the point of the intersection
function drawline(x1, y1, x2, y2, ix, iy, color)
    -- line's coordinates
    local lx1, lx2, ly1, ly2;

    -- case 1
    -- intesection is before x1
    if ix < x1 then
        lx1 = ix;
        ly1 = iy;
        lx2 = x2;
        ly2 = y2;
    -- intesection is between x1 and x2
    elseif ix >= x1 and ix <= x2 then
        lx1 = x1;
        ly1 = y1;
        lx2 = x2;
        ly2 = y2;
    -- intesection is after x2
    else
        lx1 = x1;
        ly1 = y1;
        lx2 = ix;
        ly2 = iy;
    end


    -- if line is out of the chart area - adjust it to the chart borders
    if lx1 < 0 or lx2 >= dummy:size() then
        local a, b;
        a, b = getline(lx1, ly1, lx2, ly2);
        if lx1 < 0 then
            lx1 = 0;
        end
        if lx2 >= dummy:size() then
            lx2 = dummy:size() - 1;
        end
        ly1 = a * lx1 + b;
        ly2 = a * lx2 + b;
    end

    -- get dates instead of number of the periods
    local date1, date2;
    date1 = source:date(lx1);
    if lx2 >= source:size() then
        date2 = source:date(source:size() - 1) + candlesize * (lx2 - source:size() + 1);
    else
        date2 = source:date(lx2);
    end

    core.host:execute("drawLine", line_id, date1, ly1, date2, ly2, color, style,width );
    line_id = line_id + 1;
end

-- find last two up and down fractals
function Draw(period, frame)
    local i, f;
    -- x (period) and y (price) coordinates of two last fractals
    local hx1, hy1, hx2, hy2;
    local lx1, ly1, lx2, ly2;

    hy1 = nil;
    hy2 = nil;
    ly1 = nil;
    ly2 = nil;
    f = 0;

    for i = period, period - frame + 1, -1 do
        if upy[i] ~= 0 then
            if hy1 == nil then
                hy1 = upy[i];
                hx1 = i;
                f = f + 1;
            elseif hy2 == nil then
                hy2 = upy[i];
                hx2 = i;
                f = f + 1;
            end
        end
        if downy[i] ~= 0 then
            if ly1 == nil then
                ly1 = downy[i];
                lx1 = i;
                f = f + 1;
            elseif ly2 == nil then
                ly2 = downy[i];
                lx2 = i;
                f = f + 1;
            end
        end
        -- all four points are found
        if f == 4 then
            break;
        end
    end


    -- if all four points has been found
    if f == 4 then
        -- intersection point
        local ix, iy = nil;
        -- a and b coefficients of the y = a * x + b line quation
        -- x2 always < x1
        local a1, b1, a2, b2;
        a1, b1 = getline(hx2, hy2, hx1, hy1);
        a2, b2 = getline(lx2, ly2, lx1, ly1);


        ix, iy = intersect(a1, b1, a2, b2);
        if ix == nil then
            -- collinear
            return ;
        else
		
		local ls = Distance (lx2,ly2,ix,iy); 
		local bs= Distance (hx2,hy2,lx2,ly2);
		local hs = Distance (hx2,hy2,ix,iy);
		
		            if  ls/bs  > base1 or  hs/bs > base1 then
					return;
					
					end
					
					
					if  bs  >  ls or bs > hs then
					return;								
					end
					
					if  ls/hs > base2 or hs/ls > base2 then
					return;								
					end
					
					
					 --Triangle 
					if  ix>hx1 and ix>lx1 and iy<=hy1 and iy >=ly1 and k1== "No"  then 
					return;
									
					end
					
					
					     --Expanding Triangle  
						 if  ix<hx1 and ix<lx1 and iy<=hy1 and iy >=ly1 and k2== "No"  then
						 return;
						      	 
						 
						 end
						 
						     --Top 
							-- if  iy>hy1 and iy>ly1 and ix >hx1 and ix<lx1  and k3== "No"  then
							 --return;
							 --end 
							 
							 
							         --Top 
								--   if  iy<hy1 and iy<ly1 and ix >hx1 and ix<lx1 and k4== "No"  then
								  -- return;
								   --end  
								      
									  
									    --Up Wedges
									   if  iy>hy1 and iy>ly1 and ix >hx1 and ix>lx1 and k5== "No"  then
								       return;
									   end  
									    
										
										 -- Down Wedges
									     if  iy<hy1 and iy<ly1 and ix >hx1 and ix>lx1 and k6== "No" then
				     				       return;
										  
							  			end
										
										   -- Expanding Down Wedges
									     if  iy<hy1 and iy<ly1 and ix <hx1 and ix<lx1  then
				     				       return;
										end
										
										  -- Expanding Up Wedges
									     if  iy>hy1 and iy>ly1 and ix <hx1 and ix<lx1  then
				     				       return;
										end
				
				                       
				                         
				
									drawline(hx2, hy2, hx1, hy1, ix, iy, instance.parameters.top_color);
									drawline(lx2, ly2, lx1, ly1, ix, iy, instance.parameters.bottom_color);

                                    if period==source:size()-1 then
                                        Signal("A new triangle has formed.");
                                    end
						
    end
end
end

function AsyncOperationFinished(cookie)

end