Cluster

package com.tegl.commercial.trade.marketagent.analyzer;

import com.tegl.commercial.trade.marketagent.entities.MarketOrder;

import com.tegl.commercial.trade.marketagent.entities.SS;

import com.tegl.commercial.trade.marketagent.navigation.Navigator;

import com.tegl.commercial.trade.marketagent.navigation.Path;

import com.tegl.commercial.trade.marketagent.util.MAC;

import com.tegl.commercial.trade.marketagent.util.MAUtil;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import java.util.*;

/**

*

*

*/

public class Cluster implements Comparable{

private Log log = LogFactory.getLog(this.getClass());

private static int counter = 0;

int id = 0;

// SS.name : ArrayList-MarketOrders

private HashMap cluster = new HashMap();

private int proximity;

private int diameter;

float interestLimit;

int type;

private double score = -1.1;

private ArrayList rejectionsByProximity = new ArrayList();

// List of market orders which were passed into the constructors

// of a cluster. The reason these are important is because later

// clusters will never have been shown these, even tho they could

// be relevant.

private static ArrayList INITIAL_CLUSTER_MOS = new ArrayList();

private int stackDepth = 0;

public static final int BUY_ORDER_CLUSTER = 0;

public static final int SELL_ORDER_CLUSTER = 1;

private Cluster(int proximity, int diameter, float interestLimit, int type, MarketOrder mo, Map neighbors) {

this.proximity = proximity;

this.diameter = diameter;

this.interestLimit = interestLimit;

this.type = type;

id = ++counter;

ArrayList mos = new ArrayList();

mos.add(mo);

cluster.put(mo.systemName, mos);

addNeighbors(neighbors);

if (log.isDebugEnabled()) {

log.debug("new Cluster: " + this);

}

}

static Cluster newCluster(MarketOrder marketOrder, int proximity, int diameter, float interestLimit, int type, Map systemsInProx) {

Cluster c = new Cluster(proximity, diameter, interestLimit, type, marketOrder, systemsInProx);

c.addInitialClusterMos(marketOrder);

return c;

}

static Cluster newCluster(MarketOrder marketOrder, int clusterProximity, int diameter, float interestLimit, int type) {

return newCluster(marketOrder, clusterProximity, diameter, interestLimit, type, null);

}

private void addInitialClusterMos(MarketOrder marketOrder) {

for (int i = 0; i < INITIAL_CLUSTER_MOS.size(); i++) {

MarketOrder mo = (MarketOrder) INITIAL_CLUSTER_MOS.get(i);

try {

// We don't really care how this method call turns out.

// We just want to try to add previous market orders that

// this guy never saw.

addMarketOrder(mo);

} catch (ClusterException e) {

continue; // move on to the next one.

}

}

// Finally add THIS one to the list, if it was passed in:

if (marketOrder != null) {

INITIAL_CLUSTER_MOS.add(marketOrder);

}

}

/**

* Once this method is called, it will continue to return the same value.<p>

*

* This cluster contains n systems. Each system has n MarketOrders. Each MarketOrder

* has a score based on it's quantity and price (relative to the interestLimit).<br>

* The total score of this cluster is the sum total of every Market Order's score:

* <pre>

* score(mo) = (mo.price - interestLimit) * quantity</pre>

* The sign is reversed, if the cluster contains sell orders.

*

* @return the total score of this cluster

*/

public double getScore() {

if (score != -1.1) { // We've already calculated it.

return score;

}

Set clusterKeys = cluster.keySet();

ArrayList mos = null;

for (Iterator i = clusterKeys.iterator(); i.hasNext();) {

mos = (ArrayList) cluster.get(i.next());

for (int j = 0; j < mos.size(); j++) {

MarketOrder mo = (MarketOrder) mos.get(j);

if (type == BUY_ORDER_CLUSTER) {

score += (d(mo.price) - d(interestLimit)) * d(mo.quantity);

}

else if (type == SELL_ORDER_CLUSTER) {

score += (d(interestLimit) - d(mo.price)) * d(mo.quantity);

}

}

}

return score;

}

/**

* Attempts to add a marketOrder (and it's system) to this cluster.<br>

* It will be evaluated based on proximity and diameter settings, and

* either rejected or accepted.<br>

* If rejected, a map of systems<->marketOrders that were in proximity will be returned.

*

* @param mo

* @return

* <ul>

* <li>null if the order was accepted into the cluster </li>

* <li>A map of systems<->marketOrders to which the added system was in proximity if it

* was rejected due to diameter failure</li>

* </ul>

* @throws ClusterException if the attempt was rejected based due to proximity failure

*/

public Map addMarketOrder(MarketOrder mo) throws ClusterException {

if (stackDepth > 0) {

log.debug("> stackDepth = " + stackDepth);

}

SS moSS = SS.ss(mo.ssid);

ArrayList mosForSystem;

// ===================================================================================================

// If this is the first, one, accept it.

// ===================================================================================================

if (cluster.size() == 0) {

mosForSystem = new ArrayList();

mosForSystem.add(mo);

cluster.put(moSS.getName(), mosForSystem);

return null; // accepted

}

// ===================================================================================================

// Check if we've already seen this system and accepted it. If so, add this market order to

// our cluster, and return null.

// ===================================================================================================

mosForSystem = (ArrayList) cluster.get(moSS.getName());

if (mosForSystem != null) {

if (log.isDebugEnabled()) {

track("Accept Prior", mo); // Only if debug enabled

}

if (!mosForSystem.contains(mo)) { // Don't add duplicate mos. Each is unique

mosForSystem.add(mo);

}

return null; // accepted

}

// ===================================================================================================

// Check his proximity to the systems already in the cluster.

// If he is not in proximity of ANY of the other systems in the cluster, he's outta here.

// Either way, save off the list of systems to which he is near - so that means, go through the WHOLE

// cluster. Don't stop when you've found the first match.

// ===================================================================================================

boolean inProximity = false;

Map sysInProx = new HashMap();

Set clusterKeys = cluster.keySet();

for (Iterator i = clusterKeys.iterator(); i.hasNext();) {

String sysName = (String) i.next();

SS ss = (SS) SS.ss(sysName);

Path path = Navigator.findPath(moSS, ss, proximity, 0.0f, 1.0f); // Null if not in proximity

if (path != null) {

sysInProx.put(ss.getName(), cluster.get(ss.getName()));

inProximity = true;

}

}

if (!inProximity) {

track("Rejected (proximity)", mo); // Only if debug enabled

rejectionsByProximity.add(mo); // These are checked later, if a new system is added.

throw new ClusterException();

}

// ===================================================================================================

// If he is not within the diameter of ALL of the other systems in the cluster, he's outta here - but

// return that list of systems to which he is near.

// ===================================================================================================

// If the lists are the same size, we've DEFINITELY got a match, because that means he's within prox

// range of ALL systems in the cluster.

// Add the mo to the list of mo's stored for this system, and return null.

boolean clusterMate = false;

if ((sysInProx.size() == cluster.size())) {

clusterMate = true;

}

// So, if some are in question, go through the systems in the cluster again, and see if they're within

// diameter distance.

else {

clusterMate = true; // Set to true by default

for (Iterator i = clusterKeys.iterator(); i.hasNext();) {

String sysName = (String) i.next();

SS ss = (SS) SS.ss(sysName);

Path path = Navigator.findPath(moSS, ss, diameter, 0.0f, 1.0f); // Null if not in diameter

// If we encounter ANY that are not within range, we're done.

if (path == null) {

track("Rejected (diameter)", mo); // Only if debug enabled

clusterMate = false;

break;

}

}

}

// ===================================================================================================

// Here is the money. We've decided that, on all counts, this guy is a clustermate. Don't ask what

// that is.

// That also means that we should check to see if previous proximity-failures are now within prox

// distance of this new system. If so, we should try to add them. This is a recursive process.

// ===================================================================================================

if (clusterMate) {

track("Placing", mo); // Only if debug enabled

mosForSystem = new ArrayList();

mosForSystem.add(mo);

cluster.put(moSS.getName(), mosForSystem);

if (rejectionsByProximity.contains(mo)) {

rejectionsByProximity.remove(mo);

track("AcceptRej", mo); // Only if debug enabled

}

else {

track("Accepted", mo); // Only if debug enabled

}

log.debug("Looking thru previous rejections...");

for (int i = 0; i < rejectionsByProximity.size(); i++) {

MarketOrder previouslyRejectedOrder = (MarketOrder) rejectionsByProximity.get(i);

SS rejectedSS = SS.ss(previouslyRejectedOrder.ssid);

Path path = Navigator.findPath(moSS, rejectedSS, proximity, 0.0f, 1.0f); // Null if not in proximity

// If it's now within range, try to add it:

if (path != null) {

stackDepth++;

addMarketOrder(previouslyRejectedOrder);

stackDepth--;

log.debug("< stackDepth = " + stackDepth);

}

}

return null;

}

// Otherwise, the market order doesn't belong in this cluster. Get a map of what this guy IS in

// proximity to, and return it.

else {

return sysInProx;

}

}

MarketOrder[] getMarketOrders() {

ArrayList mos = null;

ArrayList allMos = new ArrayList();

Set clusterKeys = cluster.keySet();

for (Iterator i = clusterKeys.iterator(); i.hasNext();) {

mos = (ArrayList) cluster.get(i.next());

allMos.addAll(mos);

}

return (MarketOrder[]) allMos.toArray(new MarketOrder[0]);

}

MarketOrder[] getMarketOrdersFor(String systemName) {

ArrayList mos = (ArrayList) cluster.get(systemName);

return (MarketOrder[]) mos.toArray(new MarketOrder[0]);

}

int getMarketOrderSize() {

Set clusterKeys = cluster.keySet();

int total = 0;

for (Iterator i = clusterKeys.iterator(); i.hasNext();) {

String systemNameKey = (String) i.next();

ArrayList moList = (ArrayList) cluster.get(systemNameKey);

total += moList.size();

}

return total;

}

SS[] getSystems() {

Set clusterKeys = cluster.keySet();

ArrayList systems = new ArrayList();

for (Iterator i = clusterKeys.iterator(); i.hasNext();) {

String sysName = (String) i.next();

systems.add(SS.ss(sysName));

}

return (SS[]) systems.toArray(new SS[systems.size()]);

}

MarketOrder getMarketOrder(int marketOrderId) {

Set clusterKeys = cluster.keySet();

for (Iterator i = clusterKeys.iterator(); i.hasNext();) {

String sysName = (String) i.next();

ArrayList mos = (ArrayList) cluster.get(sysName);

for (int j = 0; j < mos.size(); j++) {

MarketOrder mo = (MarketOrder) mos.get(j);

if (mo.orderId == marketOrderId) {

return mo;

}

}

}

return null;

}

public String toString() {

if (!log.isDebugEnabled()) {

return "" + id + " (Debug off)";

}

StringBuffer buf = new StringBuffer(id + MAC.I);

buf.append(proximity + MAC.I + diameter + MAC.I);

Set systems = cluster.keySet();

for (Iterator i = systems.iterator(); i.hasNext();) {

String sysName = (String) i.next();

ArrayList mos = (ArrayList) cluster.get(sysName);

buf.append(MAUtil.padTrimRight(sysName, 10) + " " + mos.size() + MAC.I);

}

return buf.toString();

}

int getSize() {

return cluster.size();

}

public int getProximity() {

return proximity;

}

public int getDiameter() {

return diameter;

}

public int getType() {

return type;

}

public float getInterestLimit() {

return interestLimit;

}

private void addNeighbors(Map systemsInProx) {

if (systemsInProx != null) {

cluster.putAll(systemsInProx);

}

}

private double d(float f) {

return new Float(f).doubleValue();

}

private double d(int i) {

return new Integer(i).doubleValue();

}

public boolean equals(Object obj) {

if (!(obj instanceof Cluster)) {

return false;

}

// If they're not the same size, they're different.

Cluster other = (Cluster) obj;

if (getSize() != other.getSize()) {

return false;

}

// If they are, check thru all their systems

Set systems = cluster.keySet();

Set otherSystems = other.cluster.keySet();

boolean identical = true;

for (Iterator i = systems.iterator(); i.hasNext();) {

String system = (String) i.next();

if (otherSystems.contains(system)) {

continue;

}

identical = false;

break;

}

return identical;

}

public int hashCode() {

StringBuffer buf = new StringBuffer();

Set systems = cluster.keySet();

for (Iterator i = systems.iterator(); i.hasNext();) {

String sysName = (String) i.next();

buf.append(sysName);

}

return buf.toString().hashCode();

}

public int compareTo(Object o) {

Cluster c = (Cluster) o;

if (getScore() > c.getScore()) {

return -1; // Backwards to ease sorting, which in a Set is ascending

}

else if (getScore() < c.getScore()) {

return 1;

}

else /*if (getScore() == c.getScore())*/ {

return 0;

}

}

// ==========================================================================================

// Debug stuff

// ==========================================================================================

private static int zcounter = 0;

private void track(String note, MarketOrder mo) {

if (!log.isDebugEnabled()) {

return;

}

note = rejectionsByProximity.contains(mo) ? note + "(R)" : note;

long time = (mo != null)? mo.exportDate.getTime() : 11111111111l;

log.debug(MAUtil.padRight(Integer.toString(++zcounter), 4) + " " + MAUtil.padRight(note, 15) + id + MAC.I + mo + MAC.I + time);

}

}