Design data structure for merging meeting schedules - data-structures

I was asked to design a data structure for the meeting schedules and after that to merge them. For example if person A has meeting from 9:00 AM to 10:00 AM and person B has meeting from 9:30 AM to 11:30 AM then the merged busy slot is from 9:00 AM to 11:30 AM.
I made the classes for the Person and this class has the collection of meeting objects. The Meeting class has the start time [hh:mm] in 24 hours format so that I can do the comparison easily.
class Person {
String name;
Collection<Meeting> meetings;
}
class Meeting{
int hh, mm;
int duration; // duration will be in minutes from where we can get the end time.
}
I want to know that which data structure will be most efficient for merging.
One way is to use the sorted ArrayList of meeting.
Any better design is appreciated.

As #Anonymouse suggested you can use 96 bits i.e. 12 bytes to represent a day so a 30 min meeting starting at 1:00 Am would be represented as 110000 and you can use simple | operation on all numbers.
Time O(n) Memory O(12n) byte. It would be way faster theoretically.
Given a Meeting [start time in minute, end time in minute].
Merging two meetings (Sa & Sb) into Sc when overlapping
Sc [ minimum (SA-start, SB-start), maximum (SA-end, SB-end) ] and storing merged meetings in collection. If not overlapping then you can store them separately.
We know that total minutes in a day = 24 * 60 = 1440
If you have 15 minute unit then it becomes 24 * 60 / 15 = 96 (under 1 byte)
So you need 2 byte per schedule i.e. byte start, end.
Time O(n) Memory O(2n) byte
Both approach won't work if you have to delete a meeting later. For that you would definitely to hold all original meeting schedule separately.

Since it's unrealistic to schedule meetings with timeslots less than 15 Minutes, I'd settle for ... a long. 64 bits per day, that is enough for 16 hours; I don't need that much. Or use two longs / three ints for a full day, if you want.
Merging then is an | operation. For larger timeslots, I can shift-or them, then check for unset bits as meeting start times. This highly compressed data structure will kick ass of any index, just because of the low-level operations. The CPU cache can fit the schedules of hundreds of days / users.

Calculating free slots without merging schedules:
Employee 1 availability(bounds): [9:00 to 20:00]
Employee 1 busy schedule intervals:
[[9:00, 10:30],[12:00, 13:00],[16:00, 18:00]]
Employee 2 availability(bounds) : [10:00 to 18:30]
Employee 2 busy schedule intervals: [[10:00, 11:30],[12:30,
14:30],[14:30, 15:00],[16:00, 17:00]]
Free intervals: [[11:30, 12:00],[15:00, 16:00],[18:00, 18:30]]
Program output: [11h:30m - 12h:00m] [15h:00m - 16h:00m] [18h:00m - 18h:30m]
Here is my approach: I donot merge calenders to calculate free time, instead I use TreeMap to mark busy schedule as 'B' and free time as 'F'. I take every minute into consideration within lower and upper bound. Initially, map will be updated with 'F' assuming free time is available for lower to upper bound. Later, map will be updated with busy schedule. Time complexity is O(n*2) because intervals is a list, each interval needs to be processed to update the map. Space complexity is O(n).
ScheduleFinderTest test = new ScheduleFinderTest();
test.findFreeSchedules();
public class ScheduleFinderTest {
public void findFreeSchedules() {
// Employee availability time ( 9:00 to 12:00 )
MeetingIntervals bound = new MeetingIntervals(9, 00, 20, 00);
MeetingIntervals busyInterval1 = new MeetingIntervals(9, 00, 10, 30);
MeetingIntervals busyInterval2 = new MeetingIntervals(12, 00, 13, 00);
MeetingIntervals busyInterval3 = new MeetingIntervals(16, 00, 18, 00);
List<MeetingIntervals> allBusyIntervals1 = Arrays.asList(busyInterval1, busyInterval2, busyInterval3);
Employee e1 = new Employee("John", bound, allBusyIntervals1);
// Employee availability time ( 10:00 to 18:30 )
bound = new MeetingIntervals(10, 00, 18, 30);
busyInterval1 = new MeetingIntervals(10, 00, 11, 30);
busyInterval2 = new MeetingIntervals(12, 30, 14, 30);
busyInterval3 = new MeetingIntervals(14, 30, 15, 00);
MeetingIntervals busyInterval4 = new MeetingIntervals(16, 00, 17, 00);
List<MeetingIntervals> allBusyIntervals2 = Arrays.asList(busyInterval1, busyInterval2, busyInterval3, busyInterval4);
Employee e2 = new Employee("Christiana", bound, allBusyIntervals2);
ScheduleFinder scheduleFinder = new ScheduleFinder(Arrays.asList(e1, e2));
scheduleFinder.find();
}
}
#Data
#AllArgsConstructor
class Employee {
private String name;
private MeetingIntervals bounds;
private List<MeetingIntervals> intervals;
}
#Data
#AllArgsConstructor
class MeetingIntervals {
private int fromHour;
private int fromMinutes;
private int toHour;
private int toMinutes;
}
public class ScheduleFinder {
private List<Employee> employees;
public ScheduleFinder(List<Employee> employees) {
this.employees = employees;
}
public void find() {
// sort all bound hours (from availability) and get the maximum one
Collections.sort(employees, (e1, e2) -> e2.getBounds().getFromHour() - e1.getBounds().getFromHour());
int inTimeHour = employees.get(0).getBounds().getFromHour();
int inTimeMinutes = employees.get(0).getBounds().getFromMinutes();
// sort all bound hours ( to availability ) and get the minimum one
Collections.sort(employees, (e1, e2)-> e1.getBounds().getToHour() - e2.getBounds().getToHour());
int exitTimeHour = employees.get(0).getBounds().getToHour();
int exitTimeMinutes = employees.get(0).getBounds().getToMinutes();
// initially mark the map with free time for bounds as calculated above
MeetingIntervals availableInterval = new MeetingIntervals(inTimeHour, inTimeMinutes, exitTimeHour, exitTimeMinutes);
Map<String, Character> scheduleMap = new TreeMap<>();
updateSchedule(availableInterval, scheduleMap, 'F');
System.out.println(scheduleMap);
// update the map with busy intervals
List<MeetingIntervals> allBusyIntervals = employees.stream()
.flatMap(m -> m.getIntervals().stream())
.collect(Collectors.toList());
Collections.sort(allBusyIntervals, (e1, e2) -> e1.getFromHour() - e2.getFromHour());
updateScheduleMap(allBusyIntervals, scheduleMap, 'B');
System.out.println(scheduleMap);
// print free schedules
printFreeSchedules(scheduleMap, exitTimeHour, exitTimeMinutes);
}
private void updateScheduleMap(List<MeetingIntervals> busyIntervals, Map<String, Character> scheduleMap, char marker) {
for (MeetingIntervals interval : busyIntervals) {
updateSchedule(interval, scheduleMap, marker);
}
}
private void updateSchedule(MeetingIntervals interval, Map<String, Character> map, char marker) {
int startTimeHour = interval.getFromHour();
int startTimeMinutes = interval.getFromMinutes();
int startTimeInMinutes = getTimeInMinutes( startTimeHour, startTimeMinutes);
int endTimeInMinutes = getTimeInMinutes( interval.getToHour(), interval.getToMinutes());
for (int i = 0; i < (endTimeInMinutes - startTimeInMinutes); i++) {
String key = getFormattedKey(startTimeHour, startTimeMinutes);
if (marker == 'B' && map.get(key) != null) {
map.put(key, marker);
} else if (marker == 'F' && map.get(key) == null) {
map.put(key, marker);
}
++startTimeMinutes;
if (startTimeMinutes == 60) {
startTimeMinutes = 0;
++startTimeHour;
}
}
}
private int getTimeInMinutes(int hour, int minutes) {
return ( hour * 60 ) + minutes ;
}
public String getFormattedKey(int hour, int minutes) {
StringBuilder sb = new StringBuilder();
String hourStr = hour + "";
String minutesStr = minutes + "";
if (String.valueOf(hour).length() == 1) {
hourStr = "0" + hour;
}
if (String.valueOf(minutes).length() == 1) {
minutesStr = "0" + minutes;
}
sb.append(hourStr).append("h:").append(minutesStr).append("m");
return sb.toString();
}
private void printFreeSchedules(Map<String, Character> scheduleMap, int exitTimeHour, int exitTimeMinutes) {
boolean intervalStarted = false;
boolean intervalEnded = false;
for(String k : scheduleMap.keySet()) {
if ( scheduleMap.get(k) == 'F' && intervalStarted == false) {
System.out.print("[" + k);
intervalStarted = true;
intervalEnded = false;
}
if ( scheduleMap.get(k) == 'B' && intervalStarted == true && intervalEnded == false) {
System.out.println(" - " + k + "]");
intervalEnded = true;
intervalStarted = false;
}
}
if ( intervalStarted == true && intervalEnded == false ) {
System.out.println(" - " + exitTimeHour + "h:" + exitTimeMinutes + "m]");
}
}
}

This is a classic problem of Task Scheduling.

Related

Optimum movie schedule considering duration and no overlap

I am struggling to find a good strategy to tackle this problem. I considered sorting the collection of movies, by duration, but that seems to obscure the overlap condition. Any ideas?
Another thing I was thinking about was sorting based on start month and then, approaching as an extension of this problem.
An actor is offered a set of movies for a certain (start month and
duration). You have to select the movies the actor should do so that
the actor can do the most number of movies in the year, without any
overlaps.
It is assumed that movies start on the first of a month, and end on
the last day. Months are of 30 days (should not be required?).
I wrote up some code to demonstrate a solution. The overall essence is similar to what Edward Peters mentioned. First thing is to sort the movies by their end times. Now, greedily the first option is always chosen.
Why does choosing the first option work?
In our case, our ordering A had the first movie, some a. Say there is another ordering B where the first movie is some b, such that b ≠ a. There must be an ordering (we already know about the existence of A) such that, A = {B – {b}} U {a}. Obviously, in the case of B, b would have the smallest time; since b ≠ a, duration(b) >= duration(a).
public class Optimum_Movie_Schedule {
public static void main(String[] args) {
Movie[] movies = new Movie[] {
new Movie("f", 5, 9),
new Movie("a", 1, 2),
new Movie("b", 3, 4),
new Movie("c", 0, 6),
new Movie("d", 5, 7),
new Movie("e", 8, 9)
};
// sort by endMonth; startMonth if endMonth clash
Arrays.sort(movies);
System.out.println(Arrays.toString(movies));
System.out.println("Final list: ");
System.out.print(movies[0] + " ");
int cur = 0;
for(int i = 1 ; i < movies.length ; i++) {
if(movies[i].startMonth > movies[cur].endMonth) {
System.out.print(movies[i] + " ");
cur = i;
}
}
}
static class Movie implements Comparable<Movie> {
String name;
int startMonth;
int endMonth;
int duration;
public Movie(String name, int start, int end) {
this.name = name;
this.startMonth = start;
this.endMonth = end;
this.duration = (end + 1) - start;
}
public int compareTo(Movie o) {
if(this.endMonth < o.endMonth)
return -1;
if(this.endMonth == o.endMonth)
if(this.startMonth < o.startMonth)
return -1;
return 1;
}
public String toString() {
return name + "("+ startMonth + ", " + endMonth + ")";
}
}
}
Take your list of movies, sorted by their end dates. For each movie, you want to solve "What are the maximum number of movies I could have acted in, by the end of this movie." This can be found recursively as the maximum of either the result for the most recent movie, or one plus the result for the most recent movie to end before this one began.
The first case corresponds to "I did not act in this movie, so would have been able to pursue other movies during that duration." The second case corresponds to "I acted in this movie, but was not available for any movies that did not end before this movie began."

Efficient tuple search algorithm

Given a store of 3-tuples where:
All elements are numeric ex :( 1, 3, 4) (1300, 3, 15) (1300, 3, 15) …
Tuples are removed and added frequently
At any time the store is typically under 100,000 elements
All Tuples are available in memory
The application is interactive requiring 100s of searches per second.
What are the most efficient algorithms/data structures to perform wild card (*) searches such as:
(1, *, 6) (3601, *, *) (*, 1935, *)
The aim is to have a Linda like tuple space but on an application level
Well, there are only 8 possible arrangements of wildcards, so you can easily construct 6 multi-maps and a set to serve as indices: one for each arrangement of wildcards in the query. You don't need an 8th index because the query (*,*,*) trivially returns all tuples. The set is for tuples with no wildcards; only a membership test is needed in this case.
A multimap takes a key to a set. In your example, e.g., the query (1,*,6) would consult the multimap for queries of the form (X,*,Y), which takes key <X,Y> to the set of all tuples with X in the first position and Y in third. In this case, X=1 and Y=6.
With any reasonable hash-based multimap implementation, lookups ought to be very fast. Several hundred a second ought to be easy, and several thousand per second doable (with e.g a contemporary x86 CPU).
Insertions and deletions require updating the maps and set. Again this ought to be reasonably fast, though not as fast as lookups of course. Again several hundred per second ought to be doable.
With only ~10^5 tuples, this approach ought to be fine for memory as well. You can save a bit of space with tricks, e.g. keeping a single copy of each tuple in an array and storing indices in the map/set to represent both key and value. Manage array slots with a free list.
To make this concrete, here is pseudocode. I'm going to use angle brackets <a,b,c> for tuples to avoid too many parens:
# Definitions
For a query Q <k2,k1,k0> where each of k_i is either * or an integer,
Let I(Q) be a 3-digit binary number b2|b1|b0 where
b_i=0 if k_i is * and 1 if k_i is an integer.
Let N(i) be the number of 1's in the binary representation of i
Let M(i) be a multimap taking a tuple with N(i) elements to a set
of tuples with 3 elements.
Let t be a 3 element tuple. Then T(t,i) returns a new tuple with
only the elements of t in positions where i has a 1. For example
T(<1,2,3>,0) = <> and T(<1,2,3>,6) = <2,3>
Note that function T works fine on query tuples with wildcards.
# Algorithm to insert tuple T into the database:
fun insert(t)
for i = 0 to 7
add the entry T(t,i)->t to M(i)
# Algorithm to delete tuple T from the database:
fun delete(t)
for i = 0 to 7
delete the entry T(t,i)->t from M(i)
# Query algorithm
fun query(Q)
let i = I(Q)
return M(i).lookup(T(Q, i)) # lookup failure returns empty set
Note that for simplicity, I've not shown the "optimizations" for M(0) and M(7). For M(0), the algorithm above would create a multimap taking the empty tuple to the set of all 3-tuples in the database. You can avoid this merely by treating i=0 as a special case. Similarly M(7) would take each tuple to a set containing only itself.
An "optimized" version:
fun insert(t)
for i = 1 to 6
add the entry T(t,i)->t to M(i)
add t to set S
fun delete(t)
for i = 1 to 6
delete the entry T(t,i)->t from M(i)
remove t from set S
fun query(Q)
let i = I(Q)
if i = 0, return S
elsif i = 7 return if Q\in S { Q } else {}
else return M(i).lookup(T(Q, i))
Addition
For fun, a Java implementation:
package hacking;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Random;
import java.util.Scanner;
import java.util.Set;
public class Hacking {
public static void main(String [] args) {
TupleDatabase db = new TupleDatabase();
int n = 200000;
long start = System.nanoTime();
for (int i = 0; i < n; ++i) {
db.insert(db.randomTriple());
}
long stop = System.nanoTime();
double elapsedSec = (stop - start) * 1e-9;
System.out.println("Inserted " + n + " tuples in " + elapsedSec
+ " seconds (" + (elapsedSec / n * 1000.0) + "ms per insert).");
Scanner in = new Scanner(System.in);
for (;;) {
System.out.print("Query: ");
int a = in.nextInt();
int b = in.nextInt();
int c = in.nextInt();
System.out.println(db.query(new Tuple(a, b, c)));
}
}
}
class Tuple {
static final int [] N_ONES = new int[] { 0, 1, 1, 2, 1, 2, 2, 3 };
static final int STAR = -1;
final int [] vals;
Tuple(int a, int b, int c) {
vals = new int[] { a, b, c };
}
Tuple(Tuple t, int code) {
vals = new int[N_ONES[code]];
int m = 0;
for (int k = 0; k < 3; ++k) {
if (((1 << k) & code) > 0) {
vals[m++] = t.vals[k];
}
}
}
#Override
public boolean equals(Object other) {
if (other instanceof Tuple) {
Tuple triple = (Tuple) other;
return Arrays.equals(this.vals, triple.vals);
}
return false;
}
#Override
public int hashCode() {
return Arrays.hashCode(this.vals);
}
#Override
public String toString() {
return Arrays.toString(vals);
}
int code() {
int c = 0;
for (int k = 0; k < 3; k++) {
if (vals[k] != STAR) {
c |= (1 << k);
}
}
return c;
}
Set<Tuple> setOf() {
Set<Tuple> s = new HashSet<>();
s.add(this);
return s;
}
}
class Multimap extends HashMap<Tuple, Set<Tuple>> {
#Override
public Set<Tuple> get(Object key) {
Set<Tuple> r = super.get(key);
return r == null ? Collections.<Tuple>emptySet() : r;
}
void put(Tuple key, Tuple value) {
if (containsKey(key)) {
super.get(key).add(value);
} else {
super.put(key, value.setOf());
}
}
void remove(Tuple key, Tuple value) {
Set<Tuple> set = super.get(key);
set.remove(value);
if (set.isEmpty()) {
super.remove(key);
}
}
}
class TupleDatabase {
final Set<Tuple> set;
final Multimap [] maps;
TupleDatabase() {
set = new HashSet<>();
maps = new Multimap[7];
for (int i = 1; i < 7; i++) {
maps[i] = new Multimap();
}
}
void insert(Tuple t) {
set.add(t);
for (int i = 1; i < 7; i++) {
maps[i].put(new Tuple(t, i), t);
}
}
void delete(Tuple t) {
set.remove(t);
for (int i = 1; i < 7; i++) {
maps[i].remove(new Tuple(t, i), t);
}
}
Set<Tuple> query(Tuple q) {
int c = q.code();
switch (c) {
case 0: return set;
case 7: return set.contains(q) ? q.setOf() : Collections.<Tuple>emptySet();
default: return maps[c].get(new Tuple(q, c));
}
}
Random gen = new Random();
int randPositive() {
return gen.nextInt(1000);
}
Tuple randomTriple() {
return new Tuple(randPositive(), randPositive(), randPositive());
}
}
Some output:
Inserted 200000 tuples in 2.981607358 seconds (0.014908036790000002ms per insert).
Query: -1 -1 -1
[[504, 296, 987], [500, 446, 184], [499, 482, 16], [488, 823, 40], ...
Query: 500 446 -1
[[500, 446, 184], [500, 446, 762]]
Query: -1 -1 500
[[297, 56, 500], [848, 185, 500], [556, 351, 500], [779, 986, 500], [935, 279, 500], ...
If you think of the tuples like a ip address, then a radix tree (trie) type structure might work. Radix tree is used for IP discovery.
Another way maybe to calculate use bit operations and calculate a bit hash for the tuple and in your search do bit (or, and) for quick discovery.

How can I divide 24 music albums into 6 playlists such that the running time / length is as evenly distributed as possible?

NOTE: I plan to implement this using Java, but any plain-English explanation of the steps needed in the logic is welcome and appreciated.
I'm trying to come up with a way to divide a group of 24 music albums/records into 6 playlists such that the lengths/running time all 6 playlists are as close to each other as possible.
I initially thought that maybe I could find all possible permutations of the problem and then work out a logic that will analyze which is the best division. I even created a thread to ask for help for it yesterday (I have 24 items that I need to separate into 6 sets of 4. What algorithm can I use to find all possible combinations?). However, when I got close to finding a solution, I realized that just finding all permutations of the problem will take incredibly long to run, so that approach seem impractical.
So I was wondering, is there a faster way to approach such a problem?
Given that these are the running times of the albums in question (in MM:SS format), what is the fastes way for me to find the division of albums into 6 playlists of 4 such that the lengths of each of the playlists is as close to each other as possible?
39:03
41:08
41:39
42:54
44:31
44:34
44:40
45:55
45:59
47:06
47:20
47:53
49:35
49:57
50:15
51:35
51:50
55:45
58:10
58:11
59:48
59:58
60:00
61:08
I did the math and considering the total time for all the albums, having 6 playlists that run at 200 minutes and 49 seconds would be perfect... but since the individual album lengths probably don't allow for that exact of a division, what would be the most exact possible division is my question.
NOTE: I could actually do this manually and get a close enough approximation that would suffice, but I am still really interested on how it could be done through a program.
Thanks!
I'd suggest you to use a Simulated annealing algorithm
And here is one nice solution derived by this algorithm:
[17, 16, 15, 9] 199:39
[3, 14, 10, 24] 199:50
[6, 8, 13, 21] 199:52
[1, 5, 20, 19] 199:55
[4, 23, 2, 18] 199:47
[11, 7, 22, 12] 199:51
As Steven Skiena pointed in his book ("The Algorithm Design Manual"), that it is very helpful to use Simulated annealing metaheuristic for finding acceptable solutions in real life combinatorial problems.
So, as you mentioned, you need to put 4 tracks in each of 6 albums such that all albums will have approximately the same duration.
Firstly lets consider - which property do we need to optimize?
From my point of view - the most suitable formulation would be: minimize standard deviation of durations of all albums. (But, if needed - you are free to include any other, more complex, properties).
Let's name the value of an optimized property as energy.
The main idea of algorithm
Each state of our system is characterized by some value of energy. By performing some actions over the system we can change its state (e.g. Swapping tracks between different albums).
Also, we have some abstract property called temperature.
When system is under high temperature, it is free to change its state to another state, even if the new state has a higher value of energy.
But when the temperature is small, the system tends to change its state mostly to new states with lower values of energy.
By physical analogy, the probability of changing the current state of system to a state with a higher value of energy can be limited in the same way as Boltzmann distribution defines.
Here is an illustration of how the standard deviation of durations changed while deriving the solution from above
Here is a full Java implementation of algorithm, which gives the solution from above
import java.util.Arrays;
import java.util.Random;
public class SimulatedAnnealingTracksOrdering {
public static void main(String[] args) {
int albumsCount = 6;
int tracksInAlbum = 4;
Album[] result = generateOptimalTracksOrdering(
tracksInAlbum,
albumsCount,
new Track[] {
new Track(1, "39:03"), new Track(2, "41:08"),
new Track(3, "41:39"), new Track(4, "42:54"),
new Track(5, "44:31"), new Track(6, "44:34"),
new Track(7, "44:40"), new Track(8, "45:55"),
new Track(9, "45:59"), new Track(10, "47:06"),
new Track(11, "47:20"), new Track(12, "47:53"),
new Track(13, "49:35"), new Track(14, "49:57"),
new Track(15, "50:15"), new Track(16, "51:35"),
new Track(17, "51:50"), new Track(18, "55:45"),
new Track(19, "58:10"), new Track(20, "58:11"),
new Track(21, "59:48"), new Track(22, "59:58"),
new Track(23, "60:00"), new Track(24, "61:08"),
});
for (Album album : result) {
System.out.println(album);
}
}
private static Album[] generateOptimalTracksOrdering(
int tracksInAlbum, int albumsCount, Track[] tracks) {
// Initialize current solution
Albums currentSolution =
new Albums(tracksInAlbum, albumsCount, tracks);
// Initialize energy of a current solution
double currentEnergy =
currentSolution.albumsDurationStandartDeviation();
System.out.println("Current energy is: " + currentEnergy);
// Also, we will memorize the solution with smallest value of energy
Albums bestSolution = currentSolution.clone();
double bestEnergy = currentEnergy;
// Constant, which defines the minimal value of energy
double minEnergy = 0.1;
// Initial temperature
double temperature = 150;
// We will decrease value of temperature, by multiplying on this
// coefficient
double alpha = 0.999;
// Constant, which defines minimal value of temperature
double minTemperature = 0.1;
// For each value of temperature - we will perform few probes, before
// decreasing temperature
int numberOfProbes = 100;
Random random = new Random(1);
while ((temperature > minTemperature)
&& (currentEnergy > minEnergy)) {
for (int i = 0; i < numberOfProbes; i++) {
// Generating new state
currentSolution.randomTracksPermutation();
double newEnergy =
currentSolution.albumsDurationStandartDeviation();
// As defined by Boltzmann distribution
double acceptanceProbability =
Math.exp(-(newEnergy - currentEnergy) / temperature);
// States with smaller energy - will be accepted always
if ((newEnergy < currentEnergy)
|| (random.nextDouble() < acceptanceProbability)) {
currentEnergy = newEnergy;
System.out.println("Current energy is: " + currentEnergy);
if (newEnergy < bestEnergy) {
bestSolution = currentSolution.clone();
bestEnergy = newEnergy;
}
} else {
// If state can't be accepted - rollback to previous state
currentSolution.undoLastPermutation();
}
}
// Decreasing temperature
temperature *= alpha;
}
// Return best solution
return bestSolution.getAlbums();
}
}
/**
* Container for bunch of albums
*/
class Albums {
private Random random = new Random(1);
private Album[] albums;
// These fields, are used for memorizing last permutation
// (needed for rollbacking)
private Album sourceAlbum;
private int sourceIndex;
private Album targetAlbum;
private int targetIndex;
public Albums(int tracksInAlbum, int albumsCount, Track[] tracks) {
// Put all tracks to albums
this.albums = new Album[albumsCount];
int t = 0;
for (int i = 0; i < albumsCount; i++) {
this.albums[i] = new Album(tracksInAlbum);
for (int j = 0; j < tracksInAlbum; j++) {
this.albums[i].set(j, tracks[t]);
t++;
}
}
}
/**
* Calculating standard deviations of albums durations
*/
public double albumsDurationStandartDeviation() {
double sumDuration = 0;
for (Album album : this.albums) {
sumDuration += album.getDuraion();
}
double meanDuration =
sumDuration / this.albums.length;
double sumSquareDeviation = 0;
for (Album album : this.albums) {
sumSquareDeviation +=
Math.pow(album.getDuraion() - meanDuration, 2);
}
return Math.sqrt(sumSquareDeviation / this.albums.length);
}
/**
* Performing swapping of random tracks between random albums
*/
public void randomTracksPermutation() {
this.sourceAlbum = this.pickRandomAlbum();
this.sourceIndex =
this.random.nextInt(this.sourceAlbum.getTracksCount());
this.targetAlbum = this.pickRandomAlbum();
this.targetIndex =
this.random.nextInt(this.targetAlbum.getTracksCount());
this.swapTracks();
}
public void undoLastPermutation() {
this.swapTracks();
}
private void swapTracks() {
Track sourceTrack = this.sourceAlbum.get(this.sourceIndex);
Track targetTrack = this.targetAlbum.get(this.targetIndex);
this.sourceAlbum.set(this.sourceIndex, targetTrack);
this.targetAlbum.set(this.targetIndex, sourceTrack);
}
private Album pickRandomAlbum() {
int index = this.random.nextInt(this.albums.length);
return this.albums[index];
}
public Album[] getAlbums() {
return this.albums;
}
private Albums() {
// Needed for clonning
}
#Override
protected Albums clone() {
Albums cloned = new Albums();
cloned.albums = new Album[this.albums.length];
for (int i = 0; i < this.albums.length; i++) {
cloned.albums[i] = this.albums[i].clone();
}
return cloned;
}
}
/**
* Container for tracks
*/
class Album {
private Track[] tracks;
public Album(int size) {
this.tracks = new Track[size];
}
/**
* Duration of album == sum of durations of tracks
*/
public int getDuraion() {
int acc = 0;
for (Track track : this.tracks) {
acc += track.getDuration();
}
return acc;
}
public Track get(int trackNum) {
return this.tracks[trackNum];
}
public void set(int trackNum, Track track) {
this.tracks[trackNum] = track;
}
public int getTracksCount() {
return this.tracks.length;
}
public Track[] getTracks() {
return this.tracks;
}
#Override
protected Album clone() {
Album cloned = new Album(this.tracks.length);
for (int i = 0; i < this.tracks.length; i++) {
cloned.tracks[i] = this.tracks[i];
}
return cloned;
}
/**
* Displaying duration in MM:SS format
*/
#Override
public String toString() {
int duraion = this.getDuraion();
String duration_MM_SS = (duraion / 60) + ":" + (duraion % 60);
return Arrays.toString(this.tracks) + "\t" + duration_MM_SS;
}
}
class Track {
private final int id;
private final int durationInSeconds;
public Track(int id, String duration_MM_SS) {
this.id = id;
this.durationInSeconds =
this.parseDuration(duration_MM_SS);
}
/**
* Converting MM:SS duration to seconds
*/
private int parseDuration(String duration_MM_SS) {
String[] parts = duration_MM_SS.split(":");
return (Integer.parseInt(parts[0]) * 60)
+ Integer.parseInt(parts[1]);
}
public int getDuration() {
return this.durationInSeconds;
}
public int getId() {
return this.id;
}
#Override
public String toString() {
return Integer.toString(this.id);
}
}
This is equivalent* to multiprocessor scheduling if you consider each album as a job and each playlist as a processor, and finding an optimal solution is NP-hard.
However, there are efficient algorithms which give decent but not necessarily optimal results. For example, sorting the albums by length and repeatedly adding the longest album to the shortest playlist.
If we number the albums from 1 to 24 from shortest to longest, this algorithm gives the following division.
{24, 13, 9, 6} (201:16)
{23, 14, 12, 2} (198:58)
{22, 15, 10, 4} (200:13)
{21, 16, 8, 5} (201:49)
{20, 17, 11, 3} (199:00)
{19, 18, 7, 1} (197:38)
* If we consider "evenly distributed" to mean that the length of the longest playlist is minimized.
With a more intelligent search algorithm than brute force, we don't have to go through all 1e12 possibilities. First we convert the input, enumerate all sets of four, and sort them by their proximity to the target time.
import heapq
import itertools
import re
def secondsfromstring(s):
minutes, seconds = s.split(':')
return int(minutes) * 60 + int(seconds)
def stringfromseconds(seconds):
minutes, seconds = divmod(seconds, 60)
return '{}:{:02}'.format(minutes, seconds)
# for simplicity, these have to be pairwise distinct
stringtimes = '''39:03 41:08 41:39 42:54 44:31 44:34
44:40 45:55 45:59 47:06 47:20 47:53
49:35 49:57 50:15 51:35 51:50 55:45
58:10 58:11 59:48 59:58 60:00 61:08'''
times = [secondsfromstring(s) for s in stringtimes.split()]
quads = [frozenset(quad) for quad in itertools.combinations(times, 4)]
targettime = sum(times) / 6
quads.sort(key=lambda quad: abs(sum(quad) - targettime))
Now comes a search. We keep a priority queue with partial solutions, ordered by the minimum possible maximum deviation from the target time. The priority queue lets us explore the most promising partial solutions first.
queue = [(0, frozenset(times), [])]
while True:
i, remaining, sofar = heapq.heappop(queue)
if not remaining:
for quad in sofar:
print(stringfromseconds(sum(quad)), ':',
*(stringfromseconds(time) for time in quad))
break
while i < len(quads):
quad = quads[i]
if quad.issubset(remaining):
heapq.heappush(queue, (i + 1, remaining, sofar))
heapq.heappush(queue, (i + 1, remaining - quad, sofar + [quad]))
break
i += 1
In a couple seconds, this code spits out the following optimal answer. (We got lucky, since this code is working on the slightly modified objective of minimizing the maximum deviation from the target time; with the more complicated program below, we can minimize the difference between minimum and maximum, which turns out to be the same grouping.)
199:50 : 47:06 41:39 61:08 49:57
199:52 : 44:34 45:55 59:48 49:35
199:45 : 55:45 41:08 59:58 42:54
199:53 : 44:40 47:20 60:00 47:53
199:55 : 58:10 44:31 58:11 39:03
199:39 : 51:35 50:15 51:50 45:59
The program that optimizes the max minus min objective is below. Compared to the above program, it doesn't stop after the first solution but instead waits until we start considering sets whose deviation from the target is greater than the smallest max minus min of solutions that we have found so far. Then it outputs the best.
import heapq
import itertools
import re
def secondsfromstring(s):
minutes, seconds = s.split(':')
return int(minutes) * 60 + int(seconds)
def stringfromseconds(seconds):
minutes, seconds = divmod(seconds, 60)
return '{}:{:02}'.format(minutes, seconds)
# for simplicity, these have to be pairwise distinct
stringtimes = '''39:03 41:08 41:39 42:54 44:31 44:34
44:40 45:55 45:59 47:06 47:20 47:53
49:35 49:57 50:15 51:35 51:50 55:45
58:10 58:11 59:48 59:58 60:00 61:08'''
times = [secondsfromstring(s) for s in stringtimes.split()]
quads = [frozenset(quad) for quad in itertools.combinations(times, 4)]
targettime = sum(times) / 6
quads.sort(key=lambda quad: abs(sum(quad) - targettime))
def span(solution):
quadtimes = [sum(quad) for quad in solution]
return max(quadtimes) - min(quadtimes)
candidates = []
bound = None
queue = [(0, frozenset(times), [])]
while True:
i, remaining, sofar = heapq.heappop(queue)
if not remaining:
candidates.append(sofar)
newbound = span(sofar)
if bound is None or newbound < bound: bound = newbound
if bound is not None and abs(sum(quads[i]) - targettime) >= bound: break
while i < len(quads):
quad = quads[i]
i += 1
if quad.issubset(remaining):
heapq.heappush(queue, (i, remaining, sofar))
heapq.heappush(queue, (i, remaining - quad, sofar + [quad]))
break
best = min(candidates, key=span)
for quad in best:
print(stringfromseconds(sum(quad)), ':',
*(stringfromseconds(time) for time in quad))
This can be a comment too, but it is too long, so I post it as an answer. This is a little easy-to-code improvement to hammar's solution. This algorithm gives You no optimal solution, but it finds a better one.
You can start with hammar's greedy algorithm to fill the playlists with albums. Next all You have to do is looping through them few times.
Let D the difference between the total length of playlist A and playlist B, where length(A)>length(B). Loop through playlists A and B and if you find albums x \in A and y \in B which statisfies x>y && x-y<D, swap x and y.
This gave me the following results:
{7,8,22,13} 200:8
{2,11,24,15} 199:51
{4,10,23,14} 199:57
{3,17,12,19} 199:32
{5,16,21,6} 200:28
{1,18,9,20} 198:58
For what it's worth, before stemm posted his Simulated annealing algorithm, I already had a solution which came to a close enough approximation of what I was looking for. So, just to share another (albeit relatively crude) approach, here it is:
(1) I sorted all of the individual track times from shortest to longest.
#01 - 39:03
#02 - 41:08
#03 - 41:39
#04 - 42:54
#05 - 44:31
#06 - 44:34
#07 - 44:40
#08 - 45:55
#09 - 45:59
#10 - 47:06
#11 - 47:20
#12 - 47:53
#13 - 49:35
#14 - 49:57
#15 - 50:15
#16 - 51:35
#17 - 51:50
#18 - 55:45
#19 - 58:10
#20 - 58:11
#21 - 59:48
#22 - 59:58
#23 - 60:00
#24 - 61:08
(2) I grouped into groups of 4 such that the resulting playlists would theoretically have as short a distance to each other just based on their positions on the sorted list, and that grouping would be: the shortest item + the longest item + the middle two items... then from the remaining (still sorted) list, again the shortest + the longest + the middle two items... so on and so forth until all items are grouped together.
(3) Using the resulting sets of 4 now, I iterate through all the individual albums in each of the sets, swapping out each item for every other item from the other sets. If, when swapped out, the distance of their source sets become smaller, then commit the swap. Else, skip the swap. Then I just did that repeatedly until no two albums can be swapped anymore to lessen the distance between their parent sets.
To illustrate step #3 in code:
Partitioner(){
for(int i=0;i<total.length;i++)
total[i] = 0;
// get the total time for all sets of four albums
for(int i=0;i<sets.length;i++){
for(int j=0;j<sets[i].length;j++){
total[i] = total[i] + sets[i][j];
}
}
// this method will be called recursively until no more swaps can be done
swapper();
for(int i=0;i<sets.length;i++){
for(int j=0;j<sets[i].length;j++){
System.out.println(sets[i][j]);
}
}
}
void swapper(){
int count = 0;
for(int i=0;i<sets.length;i++){ // loop through all the sets
for(int j=0;j<sets[i].length;j++){ // loop through all the album in a set
for(int k=0;k<sets.length;k++){ // for every album, loop through all the other sets for comparison
if(k!=i){ // but of course, don't compare it to its own set
for(int l=0;l<sets[k].length;l++){ // pair the album (sets[i][j]) with every album from the other sets
int parentA = total[i]; // store the total length from the parent set of album sets[i][j]
int parentB = total[k]; // store the total length from the parent set of the album being compared
int origDist = Math.abs(parentA - parentB); // measure the original distance between those two sets
int newA = total[i] - sets[i][j] + sets[k][l]; //compute new length of "parentA" if the two albums were swapped
int newB = total[k] - sets[k][l] + sets[i][j]; //compute new length of "parentB" if the two albums were swapped
int newdist = Math.abs(newA - newB); //compute new distance if the two albums were swapped
if(newdist<origDist){ // if the new resulting distance is less than the original distance, commit the swap
int temp = sets[i][j];
sets[i][j] = sets[k][l];
sets[k][l] = temp;
total[i] = newA;
total[k] = newB;
count++;
}
}
}
}
}
}
System.out.println(count);
if (count > 0 ){
swapper();
}
}

Support Resistance Algorithm - Technical analysis [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 4 months ago.
The community reviewed whether to reopen this question last month and left it closed:
Original close reason(s) were not resolved
Improve this question
I have an intra-day chart and I am trying to figure out how to calculate
support and resistance levels, anyone knows an algorithm for doing that, or a good starting point?
Yes, a very simple algorithm is to choose a timeframe, say 100 bars, then look for local turning points, or Maxima and Minima. Maxima and Minima can be computed from a smoothed closing price by using the 1st and second derivative (dy/dx and d^2y/dx). Where dy/dx = zero and d^y/dx is positive, you have a minima, when dy/dx = zero and d^2y/dx is negative, you have a maxima.
In practical terms this could be computed by iterating over your smoothed closing price series and looking at three adjacent points. If the points are lower/higher/lower in relative terms then you have a maxima, else higher/lower/higher you have a minima. You may wish to fine-tune this detection method to look at more points (say 5, 7) and only trigger if the edge points are a certain % away from the centre point. this is similar to the algorithm that the ZigZag indicator uses.
Once you have local maxima and minima, you then want to look for clusters of turning points within a certain distance of each other in the Y-Direction. this is simple. Take the list of N turning points and compute the Y-distance between it and each of the other discovered turning points. If the distance is less than a fixed constant then you have found two "close" turning points, indicating possible support/resistance.
You could then rank your S/R lines, so two turning points at $20 is less important than three turning points at $20 for instance.
An extension to this would be to compute trendlines. With the list of turning points discovered now take each point in turn and select two other points, trying to fit a straight line equation. If the equation is solvable within a certain error margin, you have a sloping trendline. If not, discard and move on to the next triplet of points.
The reason why you need three at a time to compute trendlines is any two points can be used in the straight line equation. Another way to compute trendlines would be to compute the straight line equation of all pairs of turning points, then see if a third point (or more than one) lies on the same straight line within a margin of error. If 1 or more other points does lie on this line, bingo you have calculated a Support/Resistance trendline.
No code examples sorry, I'm just giving you some ideas on how it could be done. In summary:
Inputs to the system
Lookback period L (number of bars)
Closing prices for L bars
Smoothing factor (to smooth closing price)
Error Margin or Delta (minimum distance between turning points to constitute a match)
Outputs
List of turning points, call them tPoints[] (x,y)
List of potential trendlines, each with the line equation (y = mx + c)
EDIT: Update
I recently learned a very simple indicator called a Donchian Channel, which basically plots a channel of the highest high in 20 bars, and lowest low. It can be used to plot an approximate support resistance level. But the above - Donchian Channel with turning points is cooler ^_^
I am using a much less complex algorithm in my algorithmic trading system.
Following steps are one side of the algorithm and are used for calculating support levels. Please read notes below the algorithm to understand how to calculate resistance levels.
Algorithm
Break timeseries into segments of size N (Say, N = 5)
Identify minimum values of each segment, you will have an array of minimum values from all segments = :arrayOfMin
Find minimum of (:arrayOfMin) = :minValue
See if any of the remaining values fall within range (X% of :minValue) (Say, X = 1.3%)
Make a separate array (:supportArr)
add values within range & remove these values from :arrayOfMin
also add :minValue from step 3
Calculating support (or resistance)
Take a mean of this array = support_level
If support is tested many times, then it is considered strong.
strength_of_support = supportArr.length
level_type (SUPPORT|RESISTANCE) = Now, if current price is below support then support changes role and becomes resistance
Repeat steps 3 to 7 until :arrayOfMin is empty
You will have all support/resistance values with a strength. Now smoothen these values, if any support levels are too close then eliminate one of them.
These support/resistance were calculated considering support levels search. You need perform steps 2 to 9 considering resistance levels search. Please see notes and implementation.
Notes:
Adjust the values of N & X to get more accurate results.
Example, for less volatile stocks or equity indexes use (N = 10, X = 1.2%)
For high volatile stocks use (N = 22, X = 1.5%)
For resistance, the procedure is exactly opposite (use maximum function instead of minimum)
This algorithm was purposely kept simple to avoid complexity, it can be improved to give better results.
Here's my implementation:
public interface ISupportResistanceCalculator {
/**
* Identifies support / resistance levels.
*
* #param timeseries
* timeseries
* #param beginIndex
* starting point (inclusive)
* #param endIndex
* ending point (exclusive)
* #param segmentSize
* number of elements per internal segment
* #param rangePct
* range % (Example: 1.5%)
* #return A tuple with the list of support levels and a list of resistance
* levels
*/
Tuple<List<Level>, List<Level>> identify(List<Float> timeseries,
int beginIndex, int endIndex, int segmentSize, float rangePct);
}
Main calculator class
/**
*
*/
package com.perseus.analysis.calculator.technical.trend;
import static com.perseus.analysis.constant.LevelType.RESISTANCE;
import static com.perseus.analysis.constant.LevelType.SUPPORT;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import com.google.common.collect.Lists;
import com.perseus.analysis.calculator.mean.IMeanCalculator;
import com.perseus.analysis.calculator.timeseries.ITimeSeriesCalculator;
import com.perseus.analysis.constant.LevelType;
import com.perseus.analysis.model.Tuple;
import com.perseus.analysis.model.technical.Level;
import com.perseus.analysis.model.timeseries.ITimeseries;
import com.perseus.analysis.util.CollectionUtils;
/**
* A support and resistance calculator.
*
* #author PRITESH
*
*/
public class SupportResistanceCalculator implements
ISupportResistanceCalculator {
static interface LevelHelper {
Float aggregate(List<Float> data);
LevelType type(float level, float priceAsOfDate, final float rangePct);
boolean withinRange(Float node, float rangePct, Float val);
}
static class Support implements LevelHelper {
#Override
public Float aggregate(final List<Float> data) {
return Collections.min(data);
}
#Override
public LevelType type(final float level, final float priceAsOfDate,
final float rangePct) {
final float threshold = level * (1 - (rangePct / 100));
return (priceAsOfDate < threshold) ? RESISTANCE : SUPPORT;
}
#Override
public boolean withinRange(final Float node, final float rangePct,
final Float val) {
final float threshold = node * (1 + (rangePct / 100f));
if (val < threshold)
return true;
return false;
}
}
static class Resistance implements LevelHelper {
#Override
public Float aggregate(final List<Float> data) {
return Collections.max(data);
}
#Override
public LevelType type(final float level, final float priceAsOfDate,
final float rangePct) {
final float threshold = level * (1 + (rangePct / 100));
return (priceAsOfDate > threshold) ? SUPPORT : RESISTANCE;
}
#Override
public boolean withinRange(final Float node, final float rangePct,
final Float val) {
final float threshold = node * (1 - (rangePct / 100f));
if (val > threshold)
return true;
return false;
}
}
private static final int SMOOTHEN_COUNT = 2;
private static final LevelHelper SUPPORT_HELPER = new Support();
private static final LevelHelper RESISTANCE_HELPER = new Resistance();
private final ITimeSeriesCalculator tsCalc;
private final IMeanCalculator meanCalc;
public SupportResistanceCalculator(final ITimeSeriesCalculator tsCalc,
final IMeanCalculator meanCalc) {
super();
this.tsCalc = tsCalc;
this.meanCalc = meanCalc;
}
#Override
public Tuple<List<Level>, List<Level>> identify(
final List<Float> timeseries, final int beginIndex,
final int endIndex, final int segmentSize, final float rangePct) {
final List<Float> series = this.seriesToWorkWith(timeseries,
beginIndex, endIndex);
// Split the timeseries into chunks
final List<List<Float>> segments = this.splitList(series, segmentSize);
final Float priceAsOfDate = series.get(series.size() - 1);
final List<Level> levels = Lists.newArrayList();
this.identifyLevel(levels, segments, rangePct, priceAsOfDate,
SUPPORT_HELPER);
this.identifyLevel(levels, segments, rangePct, priceAsOfDate,
RESISTANCE_HELPER);
final List<Level> support = Lists.newArrayList();
final List<Level> resistance = Lists.newArrayList();
this.separateLevels(support, resistance, levels);
// Smoothen the levels
this.smoothen(support, resistance, rangePct);
return new Tuple<>(support, resistance);
}
private void identifyLevel(final List<Level> levels,
final List<List<Float>> segments, final float rangePct,
final float priceAsOfDate, final LevelHelper helper) {
final List<Float> aggregateVals = Lists.newArrayList();
// Find min/max of each segment
for (final List<Float> segment : segments) {
aggregateVals.add(helper.aggregate(segment));
}
while (!aggregateVals.isEmpty()) {
final List<Float> withinRange = new ArrayList<>();
final Set<Integer> withinRangeIdx = new TreeSet<>();
// Support/resistance level node
final Float node = helper.aggregate(aggregateVals);
// Find elements within range
for (int i = 0; i < aggregateVals.size(); ++i) {
final Float f = aggregateVals.get(i);
if (helper.withinRange(node, rangePct, f)) {
withinRangeIdx.add(i);
withinRange.add(f);
}
}
// Remove elements within range
CollectionUtils.remove(aggregateVals, withinRangeIdx);
// Take an average
final float level = this.meanCalc.mean(
withinRange.toArray(new Float[] {}), 0, withinRange.size());
final float strength = withinRange.size();
levels.add(new Level(helper.type(level, priceAsOfDate, rangePct),
level, strength));
}
}
private List<List<Float>> splitList(final List<Float> series,
final int segmentSize) {
final List<List<Float>> splitList = CollectionUtils
.convertToNewLists(CollectionUtils.splitList(series,
segmentSize));
if (splitList.size() > 1) {
// If last segment it too small
final int lastIdx = splitList.size() - 1;
final List<Float> last = splitList.get(lastIdx);
if (last.size() <= (segmentSize / 1.5f)) {
// Remove last segment
splitList.remove(lastIdx);
// Move all elements from removed last segment to new last
// segment
splitList.get(lastIdx - 1).addAll(last);
}
}
return splitList;
}
private void separateLevels(final List<Level> support,
final List<Level> resistance, final List<Level> levels) {
for (final Level level : levels) {
if (level.getType() == SUPPORT) {
support.add(level);
} else {
resistance.add(level);
}
}
}
private void smoothen(final List<Level> support,
final List<Level> resistance, final float rangePct) {
for (int i = 0; i < SMOOTHEN_COUNT; ++i) {
this.smoothen(support, rangePct);
this.smoothen(resistance, rangePct);
}
}
/**
* Removes one of the adjacent levels which are close to each other.
*/
private void smoothen(final List<Level> levels, final float rangePct) {
if (levels.size() < 2)
return;
final List<Integer> removeIdx = Lists.newArrayList();
Collections.sort(levels);
for (int i = 0; i < (levels.size() - 1); i++) {
final Level currentLevel = levels.get(i);
final Level nextLevel = levels.get(i + 1);
final Float current = currentLevel.getLevel();
final Float next = nextLevel.getLevel();
final float difference = Math.abs(next - current);
final float threshold = (current * rangePct) / 100;
if (difference < threshold) {
final int remove = currentLevel.getStrength() >= nextLevel
.getStrength() ? i : i + 1;
removeIdx.add(remove);
i++; // start with next pair
}
}
CollectionUtils.remove(levels, removeIdx);
}
private List<Float> seriesToWorkWith(final List<Float> timeseries,
final int beginIndex, final int endIndex) {
if ((beginIndex == 0) && (endIndex == timeseries.size()))
return timeseries;
return timeseries.subList(beginIndex, endIndex);
}
}
Here are some supporting classes:
public enum LevelType {
SUPPORT, RESISTANCE
}
public class Tuple<A, B> {
private final A a;
private final B b;
public Tuple(final A a, final B b) {
super();
this.a = a;
this.b = b;
}
public final A getA() {
return this.a;
}
public final B getB() {
return this.b;
}
#Override
public String toString() {
return "Tuple [a=" + this.a + ", b=" + this.b + "]";
};
}
public abstract class CollectionUtils {
/**
* Removes items from the list based on their indexes.
*
* #param list
* list
* #param indexes
* indexes this collection must be sorted in ascending order
*/
public static <T> void remove(final List<T> list,
final Collection<Integer> indexes) {
int i = 0;
for (final int idx : indexes) {
list.remove(idx - i++);
}
}
/**
* Splits the given list in segments of the specified size.
*
* #param list
* list
* #param segmentSize
* segment size
* #return segments
*/
public static <T> List<List<T>> splitList(final List<T> list,
final int segmentSize) {
int from = 0, to = 0;
final List<List<T>> result = new ArrayList<>();
while (from < list.size()) {
to = from + segmentSize;
if (to > list.size()) {
to = list.size();
}
result.add(list.subList(from, to));
from = to;
}
return result;
}
}
/**
* This class represents a support / resistance level.
*
* #author PRITESH
*
*/
public class Level implements Serializable {
private static final long serialVersionUID = -7561265699198045328L;
private final LevelType type;
private final float level, strength;
public Level(final LevelType type, final float level) {
this(type, level, 0f);
}
public Level(final LevelType type, final float level, final float strength) {
super();
this.type = type;
this.level = level;
this.strength = strength;
}
public final LevelType getType() {
return this.type;
}
public final float getLevel() {
return this.level;
}
public final float getStrength() {
return this.strength;
}
#Override
public String toString() {
return "Level [type=" + this.type + ", level=" + this.level
+ ", strength=" + this.strength + "]";
}
}
I put together a package that implements support and resistance trendlines like what you're asking about. Here are a few examples of some examples:
import numpy as np
import pandas.io.data as pd
from matplotlib.pyplot import *
gentrends('fb', window = 1.0/3.0)
Output
That example just pulls the adjusted close prices, but if you have intraday data already loaded in you can also feed it raw data as a numpy array and it will implement the same algorithm on that data as it would if you just fed it a ticker symbol.
Not sure if this is exactly what you were looking for but hopefully this helps get you started. The code and some more explanation can be found on the GitHub page where I have it hosted: https://github.com/dysonance/Trendy
I have figured out another way of calculating Support/Resistance dynamically.
Steps:
Create a list of important price - The high and low of each candle in your range is important. Each of this prices is basically a probable SR(Support / Resistance).
Give each price a score.
Sort the prices by score and remove the ones close to each other(at a distance of x% from each other).
Print the top N prices and having a mimimum score of Y. These are your Support Resistances. It worked very well for me in ~300 different stocks.
The scoring technique
A price is acting as a strong SR if there are many candles which comes close to this but cannot cross this.
So, for each candle which are close to this price (within a distance of y% from the price), we will add +S1 to the score.
For each candle which cuts through this price, we will add -S2(negative) to the score.
This should give you a very basic idea of how to assign scores to this.
Now you have to tweak it according to your requirements.
Some tweak I made and which improved the performance a lot are as follows:
Different score for different types of cut. If the body of a candle cuts through the price, then score change is -S3 but the wick of a candle cuts through the price, the score change is -S4. Here Abs(S3) > Abs(S4) because cut by body is more significant than cut by wick.
If the candle which closes close the price but unable to cross is a high(higher than two candles on each side) or low(lower than 2 candles on each side), then add a higher score than other normal candles closing near this.
If the candle closing near this is a high or low, and the price was in a downtrend or a uptrend (at least y% move) then add a higher score to this point.
You can remove some prices from the initial list. I consider a price only if it is the highest or the lowest among N candles on both side of it.
Here is a snippet of my code.
private void findSupportResistance(List<Candle> candles, Long scripId) throws ExecutionException {
// This is a cron job, so I skip for some time once a SR is found in a stock
if(processedCandles.getIfPresent(scripId) == null || checkAlways) {
//Combining small candles to get larger candles of required timeframe. ( I have 1 minute candles and here creating 1 Hr candles)
List<Candle> cumulativeCandles = cumulativeCandleHelper.getCumulativeCandles(candles, CUMULATIVE_CANDLE_SIZE);
//Tell whether each point is a high(higher than two candles on each side) or a low(lower than two candles on each side)
List<Boolean> highLowValueList = this.highLow.findHighLow(cumulativeCandles);
String name = scripIdCache.getScripName(scripId);
Set<Double> impPoints = new HashSet<Double>();
int pos = 0;
for(Candle candle : cumulativeCandles){
//A candle is imp only if it is the highest / lowest among #CONSECUTIVE_CANDLE_TO_CHECK_MIN on each side
List<Candle> subList = cumulativeCandles.subList(Math.max(0, pos - CONSECUTIVE_CANDLE_TO_CHECK_MIN),
Math.min(cumulativeCandles.size(), pos + CONSECUTIVE_CANDLE_TO_CHECK_MIN));
if(subList.stream().min(Comparator.comparing(Candle::getLow)).get().getLow().equals(candle.getLow()) ||
subList.stream().min(Comparator.comparing(Candle::getHigh)).get().getHigh().equals(candle.getHigh())) {
impPoints.add(candle.getHigh());
impPoints.add(candle.getLow());
}
pos++;
}
Iterator<Double> iterator = impPoints.iterator();
List<PointScore> score = new ArrayList<PointScore>();
while (iterator.hasNext()){
Double currentValue = iterator.next();
//Get score of each point
score.add(getScore(cumulativeCandles, highLowValueList, currentValue));
}
score.sort((o1, o2) -> o2.getScore().compareTo(o1.getScore()));
List<Double> used = new ArrayList<Double>();
int total = 0;
Double min = getMin(cumulativeCandles);
Double max = getMax(cumulativeCandles);
for(PointScore pointScore : score){
// Each point should have at least #MIN_SCORE_TO_PRINT point
if(pointScore.getScore() < MIN_SCORE_TO_PRINT){
break;
}
//The extremes always come as a Strong SR, so I remove some of them
// I also reject a price which is very close the one already used
if (!similar(pointScore.getPoint(), used) && !closeFromExtreme(pointScore.getPoint(), min, max)) {
logger.info("Strong SR for scrip {} at {} and score {}", name, pointScore.getPoint(), pointScore.getScore());
// logger.info("Events at point are {}", pointScore.getPointEventList());
used.add(pointScore.getPoint());
total += 1;
}
if(total >= totalPointsToPrint){
break;
}
}
}
}
private boolean closeFromExtreme(Double key, Double min, Double max) {
return Math.abs(key - min) < (min * DIFF_PERC_FROM_EXTREME / 100.0) || Math.abs(key - max) < (max * DIFF_PERC_FROM_EXTREME / 100);
}
private Double getMin(List<Candle> cumulativeCandles) {
return cumulativeCandles.stream()
.min(Comparator.comparing(Candle::getLow)).get().getLow();
}
private Double getMax(List<Candle> cumulativeCandles) {
return cumulativeCandles.stream()
.max(Comparator.comparing(Candle::getLow)).get().getHigh();
}
private boolean similar(Double key, List<Double> used) {
for(Double value : used){
if(Math.abs(key - value) <= (DIFF_PERC_FOR_INTRASR_DISTANCE * value / 100)){
return true;
}
}
return false;
}
private PointScore getScore(List<Candle> cumulativeCandles, List<Boolean> highLowValueList, Double price) {
List<PointEvent> events = new ArrayList<>();
Double score = 0.0;
int pos = 0;
int lastCutPos = -10;
for(Candle candle : cumulativeCandles){
//If the body of the candle cuts through the price, then deduct some score
if(cutBody(price, candle) && (pos - lastCutPos > MIN_DIFF_FOR_CONSECUTIVE_CUT)){
score += scoreForCutBody;
lastCutPos = pos;
events.add(new PointEvent(PointEvent.Type.CUT_BODY, candle.getTimestamp(), scoreForCutBody));
//If the wick of the candle cuts through the price, then deduct some score
} else if(cutWick(price, candle) && (pos - lastCutPos > MIN_DIFF_FOR_CONSECUTIVE_CUT)){
score += scoreForCutWick;
lastCutPos = pos;
events.add(new PointEvent(PointEvent.Type.CUT_WICK, candle.getTimestamp(), scoreForCutWick));
//If the if is close the high of some candle and it was in an uptrend, then add some score to this
} else if(touchHigh(price, candle) && inUpTrend(cumulativeCandles, price, pos)){
Boolean highLowValue = highLowValueList.get(pos);
//If it is a high, then add some score S1
if(highLowValue != null && highLowValue){
score += scoreForTouchHighLow;
events.add(new PointEvent(PointEvent.Type.TOUCH_UP_HIGHLOW, candle.getTimestamp(), scoreForTouchHighLow));
//Else add S2. S2 > S1
} else {
score += scoreForTouchNormal;
events.add(new PointEvent(PointEvent.Type.TOUCH_UP, candle.getTimestamp(), scoreForTouchNormal));
}
//If the if is close the low of some candle and it was in an downtrend, then add some score to this
} else if(touchLow(price, candle) && inDownTrend(cumulativeCandles, price, pos)){
Boolean highLowValue = highLowValueList.get(pos);
//If it is a high, then add some score S1
if (highLowValue != null && !highLowValue) {
score += scoreForTouchHighLow;
events.add(new PointEvent(PointEvent.Type.TOUCH_DOWN, candle.getTimestamp(), scoreForTouchHighLow));
//Else add S2. S2 > S1
} else {
score += scoreForTouchNormal;
events.add(new PointEvent(PointEvent.Type.TOUCH_DOWN_HIGHLOW, candle.getTimestamp(), scoreForTouchNormal));
}
}
pos += 1;
}
return new PointScore(price, score, events);
}
private boolean inDownTrend(List<Candle> cumulativeCandles, Double price, int startPos) {
//Either move #MIN_PERC_FOR_TREND in direction of trend, or cut through the price
for(int pos = startPos; pos >= 0; pos-- ){
Candle candle = cumulativeCandles.get(pos);
if(candle.getLow() < price){
return false;
}
if(candle.getLow() - price > (price * MIN_PERC_FOR_TREND / 100)){
return true;
}
}
return false;
}
private boolean inUpTrend(List<Candle> cumulativeCandles, Double price, int startPos) {
for(int pos = startPos; pos >= 0; pos-- ){
Candle candle = cumulativeCandles.get(pos);
if(candle.getHigh() > price){
return false;
}
if(price - candle.getLow() > (price * MIN_PERC_FOR_TREND / 100)){
return true;
}
}
return false;
}
private boolean touchHigh(Double price, Candle candle) {
Double high = candle.getHigh();
Double ltp = candle.getLtp();
return high <= price && Math.abs(high - price) < ltp * DIFF_PERC_FOR_CANDLE_CLOSE / 100;
}
private boolean touchLow(Double price, Candle candle) {
Double low = candle.getLow();
Double ltp = candle.getLtp();
return low >= price && Math.abs(low - price) < ltp * DIFF_PERC_FOR_CANDLE_CLOSE / 100;
}
private boolean cutBody(Double point, Candle candle) {
return Math.max(candle.getOpen(), candle.getClose()) > point && Math.min(candle.getOpen(), candle.getClose()) < point;
}
private boolean cutWick(Double price, Candle candle) {
return !cutBody(price, candle) && candle.getHigh() > price && candle.getLow() < price;
}
Some Helper classes:
public class PointScore {
Double point;
Double score;
List<PointEvent> pointEventList;
public PointScore(Double point, Double score, List<PointEvent> pointEventList) {
this.point = point;
this.score = score;
this.pointEventList = pointEventList;
}
}
public class PointEvent {
public enum Type{
CUT_BODY, CUT_WICK, TOUCH_DOWN_HIGHLOW, TOUCH_DOWN, TOUCH_UP_HIGHLOW, TOUCH_UP;
}
Type type;
Date timestamp;
Double scoreChange;
public PointEvent(Type type, Date timestamp, Double scoreChange) {
this.type = type;
this.timestamp = timestamp;
this.scoreChange = scoreChange;
}
#Override
public String toString() {
return "PointEvent{" +
"type=" + type +
", timestamp=" + timestamp +
", points=" + scoreChange +
'}';
}
}
Some example of SR created by the code.
Here's a python function to find support / resistance levels
This function takes a numpy array of last traded price and returns a
list of support and resistance levels respectively. n is the number
of entries to be scanned.
def supres(ltp, n):
"""
This function takes a numpy array of last traded price
and returns a list of support and resistance levels
respectively. n is the number of entries to be scanned.
"""
from scipy.signal import savgol_filter as smooth
# converting n to a nearest even number
if n % 2 != 0:
n += 1
n_ltp = ltp.shape[0]
# smoothening the curve
ltp_s = smooth(ltp, (n + 1), 3)
# taking a simple derivative
ltp_d = np.zeros(n_ltp)
ltp_d[1:] = np.subtract(ltp_s[1:], ltp_s[:-1])
resistance = []
support = []
for i in xrange(n_ltp - n):
arr_sl = ltp_d[i:(i + n)]
first = arr_sl[:(n / 2)] # first half
last = arr_sl[(n / 2):] # second half
r_1 = np.sum(first > 0)
r_2 = np.sum(last < 0)
s_1 = np.sum(first < 0)
s_2 = np.sum(last > 0)
# local maxima detection
if (r_1 == (n / 2)) and (r_2 == (n / 2)):
resistance.append(ltp[i + ((n / 2) - 1)])
# local minima detection
if (s_1 == (n / 2)) and (s_2 == (n / 2)):
support.append(ltp[i + ((n / 2) - 1)])
return support, resistance
SRC
The best way I have found to get SR levels is with clustering. Maxima and Minima is calculated and then those values are flattened (like a scatter plot where x is the maxima and minima values and y is always 1). You then cluster these values using Sklearn.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.cluster import AgglomerativeClustering
# Calculate VERY simple waves
mx = df.High_15T.rolling( 100 ).max().rename('waves')
mn = df.Low_15T.rolling( 100 ).min().rename('waves')
mx_waves = pd.concat([mx,pd.Series(np.zeros(len(mx))+1)],axis = 1)
mn_waves = pd.concat([mn,pd.Series(np.zeros(len(mn))+-1)],axis = 1)
mx_waves.drop_duplicates('waves',inplace = True)
mn_waves.drop_duplicates('waves',inplace = True)
W = mx_waves.append(mn_waves).sort_index()
W = W[ W[0] != W[0].shift() ].dropna()
# Find Support/Resistance with clustering
# Create [x,y] array where y is always 1
X = np.concatenate((W.waves.values.reshape(-1,1),
(np.zeros(len(W))+1).reshape(-1,1)), axis = 1 )
# Pick n_clusters, I chose the sqrt of the df + 2
n = round(len(W)**(1/2)) + 2
cluster = AgglomerativeClustering(n_clusters=n,
affinity='euclidean', linkage='ward')
cluster.fit_predict(X)
W['clusters'] = cluster.labels_
# I chose to get the index of the max wave for each cluster
W2 = W.loc[W.groupby('clusters')['waves'].idxmax()]
# Plotit
fig, axis = plt.subplots()
for row in W2.itertuples():
axis.axhline( y = row.waves,
color = 'green', ls = 'dashed' )
axis.plot( W.index.values, W.waves.values )
plt.show()
Here is the PineScript code for S/Rs. It doesn't include all the logic Dr. Andrew or Nilendu discuss, but definitely a good start:
https://www.tradingview.com/script/UUUyEoU2-S-R-Barry-extended-by-PeterO/
//#version=3
study(title="S/R Barry, extended by PeterO", overlay=true)
FractalLen=input(10)
isFractal(x) => highestbars(x,FractalLen*2+1)==-FractalLen
sF=isFractal(-low), support=low, support:=sF ? low[FractalLen] : support[1]
rF=isFractal(high), resistance=high, resistance:=rF ? high[FractalLen] : resistance[1]
plot(series=support, color=sF?#00000000:blue, offset=-FractalLen)
plot(series=resistance, color=rF?#00000000:red, offset=-FractalLen)
supportprevious=low, supportprevious:=sF ? support[1] : supportprevious[1]
resistanceprevious=low, resistanceprevious:=rF ? resistance[1] : resistanceprevious[1]
plot(series=supportprevious, color=blue, style=circles, offset=-FractalLen)
plot(series=resistanceprevious, color=red, style=circles, offset=-FractalLen)
I'm not sure if it's really "Support & Resistance" detection but what about this:
function getRanges(_nums=[], _diff=1, percent=true) {
let nums = [..._nums];
nums.sort((a,b) => a-b);
const ranges = [];
for (let i=0; i<nums.length; i+=1) {
const num = nums[i];
const diff = percent ? perc(_diff, num) : _diff;
const range = nums.filter( j => isInRange(j, num-diff, num+diff) );
if (range.length) {
ranges.push(range);
nums = nums.slice(range.length);
i = -1;
}
}
return ranges;
}
function perc(percent, n) {
return n * (percent * 0.01);
}
function isInRange(n, min, max) {
return n >= min && n <= max;
}
So let's say you have an array of close prices:
const nums = [12, 14, 15, 17, 18, 19, 19, 21, 28, 29, 30, 30, 31, 32, 34, 34, 36, 39, 43, 44, 48, 48, 48, 51, 52, 58, 60, 61, 67, 68, 69, 73, 73, 75, 87, 89, 94, 95, 96, 98];
and you want to kinda split the numbers by an amount, like difference of 5 (or 5%), then you would get back a result array like this:
const ranges = getRanges(nums, 5, false) // ranges of -5 to +5
/* [
[12, 14, 15, 17]
[18, 19, 19, 21]
[28, 29, 30, 30, 31, 32]
[34, 34, 36, 39]
[43, 44, 48, 48, 48]
[51, 52]
[58, 60, 61]
[67, 68, 69]
[73, 73, 75]
[87, 89]
[94, 95, 96, 98]
]
*/
// or like
//const ranges = getRanges(nums, 5, true) // ranges of -5% to +5%
therefore the more length a range has, the more important of a support/resistance area it is.
(again: not sure if this could be classified as "Support & Resistance")
I briefly read Jacob's contribution. I think it may have some issues with the code below:
# Now the min
if min1 - window < 0:
min2 = min(x[(min1 + window):])
else:
min2 = min(x[0:(min1 - window)])
# Now find the indices of the secondary extrema
max2 = np.where(x == max2)[0][0] # find the index of the 2nd max
min2 = np.where(x == min2)[0][0] # find the index of the 2nd min
The algorithm does try to find secondary min value outside given window, but then the position corresponding to np.where(x == min2)[0][0] may lie inside the the window due to possibly duplicate values inside the window.
If you are looking for horizontal SR lines, I would rather want to know the whole distribution. But I think it is also a good assumption to just take the max of your histogram.
# python + pandas
spy["Close"][:60].plot()
hist, border = np.histogram(spy["Close"][:60].values, density=False)
sr = border[np.argmax(hist)]
plt.axhline(y=sr, color='r', linestyle='-')
You might need to tweak the bins and eventually you want to plot the whole bin not just the lower bound.
lower_bound = border[np.argmax(hist)]
upper_bound = border[np.argmax(hist) + 1]
PS the underlying "idea" is very similar to #Nilendu's solution.
Interpretations of Support & Resistance levels is very subjective. A lot of people do it different ways. […] When I am evaluating S&R from the charts, I am looking for two primary things:
Bounce off - There needs to be a visible departure (bounce off) from the horizontal line which is perceived to define the level of support or resistance.
Multiple touches - A single touch turning point is not sufficient to indicate establish support or resistance levels. Multiple touches to the same approximately level should be present, such that a horizontal line could be drawn through those turning points.

What class of algorithms reduce margin of error in continuous stream of input?

A machine is taking measurements and giving me discrete numbers continuously like so:
1 2 5 7 8 10 11 12 13 14 18
Let us say these measurements can be off by 2 points and a measurement is generated every 5 seconds. I want to ignore the measurements that may potentially be same
Like continuous 2 and 3 could be same because margin of error is 2 so how do I partition the data such that I get only distinct measurements but I would also want to handle the situation in which the measurements are continuously increasing like so:
1 2 3 4 5 6 7 8 9 10
In this case if we keep ignoring the consecutive numbers with difference of less than 2 then we might lose actual measurements.
Is there a class of algorithms for this? How would you solve this?
Just drop any number that comes 'in range of' the previous (kept) one. It should simply work.
For your increasing example:
1 is kept, 2 is dropped because it is in range of 1, 3 is dropped because it is in range of 1, then 4 is kept, 5 and 6 are dropped in range of 4, then 7 is kept, etc, so you still keep the increasing trend if it's big enough (which is what you want, right?
For the original example, you'd get 1,5,8,11,14,18 as a result.
In some lines of work, the standard way to deal with problems of this nature is by using the Kalman filter.
To quote Wikipedia:
Its [Kalman filter's] purpose is to use measurements
observed over time, containing noise
(random variations) and other
inaccuracies, and produce values that
tend to be closer to the true values
of the measurements and their
associated calculated values.
The filter itself is very easy to implement, but does require calibration.
I would have two queues:
Temporary Queue
Final Queue/List
Your first value would go into the temporary queue and in the final list. As new values come in, check to see if the new value is within the deadband of the last value in the list. If it is then add it to the temporary queue. If not then add it to the final list. If your temporary queue starts to increase in size before you get a new value outside of the deadband, then once you are outside of the deadband do a check to see if the values are monotonically increasing or decreasing the whole time. If they are always increasing or decreasing then add the contents of the queue to the final list, otherwise just add the single new value to the final list. This is the general gist of it.
Here is some code I whipped up quickly that implements a class to do what I described above:
public class MeasurementsFilter
{
private Queue<int> tempQueue = new Queue<int>();
private List<int> finalList = new List<int>();
private int deadband;
public MeasurementsFilter(int deadband)
{
this.deadband = deadband;
}
public void Reset()
{
finalList.Clear();
tempQueue.Clear();
}
public int[] FinalValues()
{
return finalList.ToArray();
}
public void AddNewValue(int value)
{
// if we are just starting then the first value always goes in the list and queue
if (tempQueue.Count == 0)
{
tempQueue.Enqueue(value);
finalList.Add(value);
}
else
{
// if the new value is within the deadband of the last value added to the final list
// then enqueue the value and wait
if ((tempQueue.Peek() - deadband <= value) && (value <= tempQueue.Peek() + deadband))
{
tempQueue.Enqueue(value);
}
// else the new value is outside of the deadband of the last value added to the final list
else
{
tempQueue.Enqueue(value);
if (QueueIsAlwaysIncreasingOrAlwaysDecreasing())
{
//dequeue first item (we already added it to the list before, but we need it for comparison purposes)
int currentItem = tempQueue.Dequeue();
while (tempQueue.Count > 0)
{
// if we are not seeing two in a row of the same (i.e. they are not duplicates of each other)
// then add the newest value to the final list
if (currentItem != tempQueue.Peek())
{
currentItem = tempQueue.Dequeue();
finalList.Add(currentItem);
}
// otherwise if we are seeing two in a row (i.e. duplicates)
// then discard the value and loop to the next value
else
{
currentItem = tempQueue.Dequeue();
}
}
// add the last item from the final list back into the queue for future deadband comparisons
tempQueue.Enqueue(finalList[finalList.Count - 1]);
}
else
{
// clear the queue and add the new value to the list and as the starting point of the queue
// for future deadband comparisons
tempQueue.Clear();
tempQueue.Enqueue(value);
finalList.Add(value);
}
}
}
}
private bool QueueIsAlwaysIncreasingOrAlwaysDecreasing()
{
List<int> queueList = new List<int>(tempQueue);
bool alwaysIncreasing = true;
bool alwaysDecreasing = true;
int tempIncreasing = int.MinValue;
int tempDecreasing = int.MaxValue;
int i = 0;
while ((alwaysIncreasing || alwaysDecreasing) && (i < queueList.Count))
{
if (queueList[i] >= tempIncreasing)
tempIncreasing = queueList[i];
else
alwaysIncreasing = false;
if (queueList[i] <= tempDecreasing)
tempDecreasing = queueList[i];
else
alwaysDecreasing = false;
i++;
}
return (alwaysIncreasing || alwaysDecreasing);
}
}
Here is some test code that you can throw into a Winform Load event or button click:
int[] values = new int[] { 1, 2, 2, 1, 4, 8, 3, 2, 1, 0, 6 };
MeasurementsFilter filter = new MeasurementsFilter(2);
for (int i = 0; i < values.Length; i++)
{
filter.AddNewValue(values[i]);
}
int[] finalValues = filter.FinalValues();
StringBuilder printValues = new StringBuilder();
for (int i = 0; i < finalValues.Length; i++)
{
printValues.Append(finalValues[i]);
printValues.Append(" ");
}
MessageBox.Show("The final values are: " + printValues);

Resources