unageanu/jiji2

View on GitHub
sites/src/js/viewmodel/chart/positions.js

Summary

Maintainability
B
4 hrs
Test Coverage
import ContainerJS          from "container-js"
import Observable           from "../../utils/observable"
import Dates                from "../../utils/dates"
import Numbers              from "../../utils/numbers"
import DateFormatter        from "../utils/date-formatter"
import Intervals            from "../../model/trading/intervals"

/**
 * 同時に表示するポジションの最大数。
 */
const maxDisplayCounts = 8;


class Slot {

  constructor(coordinateCalculator) {
    this.coordinateCalculator = coordinateCalculator;
    this.positions = [];
  }
  isVacant( position ) {
    return this.positions.findIndex(
      (item) => Slot.overlap( position, item )) === -1;
  }
  add( position ) {
    this.positions.push(position);
  }
  clear() {
    this.positions = [];
  }
  toDisplayPositions() {
    return this.positions.map((p) => {
      p.startX = this.coordinateCalculator.calculateX(p.normalizedStart);
      if (p.normalizedEnd) p.endX = this.coordinateCalculator.calculateX(p.normalizedEnd);
      return p;
    });
  }

  static overlap( a, b ) {
    return (!b.normalizedEnd || a.normalizedStart.getTime() <= b.normalizedEnd.getTime())
        && (!a.normalizedEnd || a.normalizedEnd.getTime() >= b.normalizedStart.getTime());
  }
}

export default class Positions extends Observable {

  constructor(context, coordinateCalculator, positionService) {
    super();
    this.context              = context;
    this.positionService      = positionService;
    this.coordinateCalculator = coordinateCalculator;

    this.initSlots();
  }

  initSlots() {
    this.slots = [];
    for (let i=0; i<maxDisplayCounts; i++) {
      this.slots.push(new Slot(this.coordinateCalculator));
    }
  }

  attach(slider) {
    this.slider = slider;
    this.slider.addObserver("propertyChanged", (n, e) => {
      if (e.key === "currentRange") {
        this.currentRange = e.newValue;
        this.update();
      }
    }, this);

    this.currentRange = slider.currentRange;
    this.update();
  }

  unregisterObservers() {
    this.slider.removeAllObservers(this);
    this.context.removeAllObservers(this);
  }

  update() {
    if (!this.currentRange) return;
    this.positionService.fetchWithin(
      this.currentRange.start,
      this.currentRange.end,
      this.context.backtestId
    ).then((data) => this.positions = data );
  }

  set positionsForDisplay(positions) {
    this.setProperty("positionsForDisplay", positions);
  }
  get positionsForDisplay() {
    return this.getProperty("positionsForDisplay");
  }

  set positions(data) {
    this.setProperty("positions", data);
    this.positionsForDisplay = this.calculatePositionsForDisplay(data);
  }
  get positions() {
    return this.getProperty("positions");
  }

  calculatePositionsForDisplay( positions ) {
    this.slots.forEach((s) => s.clear());
    positions.forEach((p) => {
      p.normalizedStart = this.normalizeDate(p.enteredAt);
      p.normalizedEnd   = this.normalizeDate(p.exitedAt);
      const vacancy = this.slots.find((slot) => slot.isVacant(p));
      if (vacancy) vacancy.add(p);
    });
    return this.slots.map((s) => s.toDisplayPositions());
  }

  normalizeDate(date) {
    if (!date) return null;
    return this.coordinateCalculator.normalizeDate(date);
  }
}