-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathVP_Breakout_Bot.cs
More file actions
146 lines (129 loc) · 5.79 KB
/
VP_Breakout_Bot.cs
File metadata and controls
146 lines (129 loc) · 5.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// VProfileBreakoutBot.cs
using System;
using System.Linq;
using System.Collections.Generic;
using cAlgo.API;
using cAlgo.API.Internals;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class VProfileBreakoutBot : Robot
{
[Parameter("Symbol", DefaultValue = "")]
public string SymbolName { get; set; }
[Parameter("Buckets", DefaultValue = 50)]
public int Buckets { get; set; }
[Parameter("Risk %", DefaultValue = 1.0)]
public double RiskPercent { get; set; }
[Parameter("TP RR", DefaultValue = 2.0)]
public double TakeProfitRR { get; set; }
[Parameter("Value Area %", DefaultValue = 70)]
public int ValueAreaPercent { get; set; }
[Parameter("Only one trade", DefaultValue = true)]
public bool OnlyOne { get; set; }
private Symbol _symbol;
private double VAH, VAL, POC;
private DateTime lastProfileTime = DateTime.MinValue;
protected override void OnStart()
{
_symbol = (SymbolName == "") ? Symbol : MarketData.GetSymbol(SymbolName);
// build initial
BuildProfile();
}
protected override void OnBar()
{
// rebuild daily profile if new day
var daily = MarketData.GetBars(TimeFrame.Daily);
if (daily.Count < 2) return;
var anchor = daily.Last(1).OpenTime;
if (anchor != lastProfileTime)
{
BuildProfile();
lastProfileTime = anchor;
}
// check M15 closed bar
var bars15 = MarketData.GetBars(TimeFrame.Minute15);
if (bars15.Count < 2) return;
var lastClose = bars15.Last(1).Close;
// check positions
int posCount = Positions.FindAll("VP_Breakout", _symbol.Name).Length;
if (lastClose > VAH && (!OnlyOne || posCount==0))
TryOpen(TradeType.Buy, lastClose);
else if (lastClose < VAL && (!OnlyOne || posCount==0))
TryOpen(TradeType.Sell, lastClose);
}
private void BuildProfile()
{
var dailyBars = MarketData.GetBars(TimeFrame.Daily);
if (dailyBars.Count < 2) return;
var sessionBar = dailyBars.Last(1);
double sHigh = sessionBar.High;
double sLow = sessionBar.Low;
if (sHigh <= sLow) return;
var buckets = new double[Buckets];
var prices = new double[Buckets];
for (int i = 0; i < Buckets; i++)
{
prices[i] = sLow + (sHigh - sLow) * (i + 0.5) / Buckets;
buckets[i] = 0;
}
// iterate minute bars within the session
var minuteBars = MarketData.GetBars(TimeFrame.Minute);
var startIndex = minuteBars.FindIndex(b => b.OpenTime == sessionBar.OpenTime);
if (startIndex < 0) startIndex = minuteBars.Count - 1;
for (int i = startIndex; i < minuteBars.Count; i++)
{
var b = minuteBars[i];
if (b.OpenTime < sessionBar.OpenTime) continue;
if (b.OpenTime >= sessionBar.OpenTime + TimeSpan.FromDays(1)) break;
double price = b.Close;
int idx = (int)Math.Floor((price - sLow) / (sHigh - sLow) * Buckets);
if (idx < 0) idx = 0;
if (idx >= Buckets) idx = Buckets - 1;
buckets[idx] += b.TickVolume;
}
double total = buckets.Sum();
if (total <= 0) return;
int pocIdx = 0;
for (int i = 1; i < Buckets; i++)
if (buckets[i] > buckets[pocIdx]) pocIdx = i;
POC = prices[pocIdx];
double target = total * ValueAreaPercent / 100.0;
double sum = buckets[pocIdx];
int low = pocIdx, high = pocIdx;
while (sum < target)
{
double left = (low - 1 >= 0) ? buckets[low - 1] : -1;
double right = (high + 1 < Buckets) ? buckets[high + 1] : -1;
if (left <= 0 && right <= 0) break;
if (left > right) { low--; sum += buckets[low]; } else { high++; sum += buckets[high]; }
}
VAL = prices[low];
VAH = prices[high];
Print("VP built. POC=" + POC + " VAL=" + VAL + " VAH=" + VAH);
}
private void TryOpen(TradeType type, double price)
{
// Risk calc
double equity = Account.Balance + Account.Equity - Account.Balance; // close enough; cTrader provides better APIs if needed
equity = Account.Balance; // simple
double risk = equity * RiskPercent / 100.0;
double sl = (type == TradeType.Buy) ? VAL : VAH;
double dist = Math.Abs(price - sl);
if (dist <= Symbol.TickSize * 2) { Print("SL too close"); return; }
// approximate pip value per lot:
double pipValue = Symbol.PipValue;
if (pipValue == 0) pipValue = 1.0;
double riskPerLot = dist / Symbol.TickSize * pipValue;
double volume = risk / Math.Max(riskPerLot, 1e-6);
double volStep = Symbol.VolumeStep;
volume = Math.Max(Symbol.VolumeMin, Math.Floor(volume / volStep) * volStep);
volume = Math.Min(volume, Symbol.VolumeMax);
if (volume < Symbol.VolumeMin) { Print("volume too small"); return; }
double tp = (type==TradeType.Buy) ? price + TakeProfitRR * dist : price - TakeProfitRR * dist;
var res = ExecuteMarketOrder(type, _symbol.Name, volume, "VP_Breakout", sl, tp);
if (res.IsSuccessful) Print("Order placed: " + res.Position.Id);
else Print("Order failed: " + res.Error);
}
}
}