PageSourceSearch

https://trade.choicetrade.com/js/classes.js

js choicetrade.com collected 2026-09-24 12:33:34 UTC 56,243 bytes, 1,692 lines download raw bytes

1/**
2 * ExtObject provides a way to implement inheritance in the JavaScript.
3 * This was added just not to include extra libraries like Prototype,
4 * because from one hand they have duplicated methods with jQuery,
5 * but jQuery from other hand does not provide ways to extend objects
6 * in class-based style.
7 *
8 * This object is advised to be used in the following way.
9 * If you need to create a new 'class', declare a new function
10 * (empty in general case) and assign its prototype a new value:
11 * function NewObject () {};
12 * NewObject.prototype = (new ExtObject()).extend(someExtraProperties);
13 * In this example someExtraProperties is an object, which will extend
14 * a created ExtObject instance with its properties and this new constructed
15 * object will act as a prototype for NewObject.
16 *
17 * When it's required to perform initialization of some instance-relative
18 * properties, you need to ovveride init fucntion of the ExtObject prototype:
19 * NewObject.prototype = (new ExtObject()).extend({
20 *      init: function () {
21 *          this.boolVar = true;
22 *          return ExtObject.prototype.init.call(this);
23 *      }
24 *  }
25 * );
26 * If you only want to extend an init function, you can also declare this way:
27 * NewObject.prototype = (new ExtObject()).extend(
28 *  function () {
29 *      this.boolVar = true;
30 *      return this;
31 *  }
32 * );
33 *
34 * Pay attention, that you need to manually call for parent initializer.
35 * If you don't want parent properties to go with child, you are expected
36 * to simply return referce to this (see the second variant). But in this case,
37 * you'll lost all properties of the parent class.
38 *
39 * All child classes are expected to be used in the following way:
40 *  - create new instance;
41 *  - then initiate it.
42 * This was done to avoid copying of instance-relative properties of
43 * prototypes. In other words, here prototypes are subject to be keepers of
44 * static properties and functions. Here is an expamle:
45 * var newObject = (new Object()).init();
46 *
47 */
48function ExtObject() {
49}
50;
51ExtObject.prototype = {
52    init:function () {
53        return this;
54    },
55    extend:function (extender) {
56        if (typeof(extender) === "function") {
57            this.init = extender;
58        } else {
59            $.extend(this, extender);
60        }
61        return this;
62    },
63    constructor:ExtObject
64};
65
66function EventDispatcher() {
67}
68EventDispatcher.prototype = (new ExtObject()).extend({
69    init:function () {
70        this.handlers = {};
71        return ExtObject.prototype.init.call(this);
72    },
73    addEventListener:function (type, handler) {
74
75        var addOneTypeListener = function (type, handler) {
76            if (typeof(handler) === "object") {
77                if (typeof(handler["on" + type]) !== "function") {
78                    throw new Error("There is no supported methods");
79                }
80            }
81            if (typeof(this.handlers[type]) === "undefined") {
82                this.handlers[type] = {};
83            }
84            if (!handler.$$guid) {
85                handler.$$guid = this.addEventListener.guid++;
86            }
87            this.handlers[type][handler.$$guid] = handler;
88        }
89
90        if (type instanceof Array) {
91            for (var t in type) {
92                addOneTypeListener.call(this, type[t], handler);
93            }
94        } else if (typeof(type) == 'string') {
95            addOneTypeListener.call(this, type, handler);
96        } else {
97            throw new Error('Type should be a name or array of names');
98        }
99    },
100    removeEventListener:function (type, handler) {
101
102        var removeOneTypeListener = function (type, handler) {
103            if (typeof(handler.$$guid) !== "undefined" &&
104                typeof(this.handlers[type]) !== "undefined") {
105                delete this.handlers[type][handler.$$guid];
106            }
107        }
108
109        if (type instanceof Array) {
110            for (var t in type) {
111                removeOneTypeListener.call(this, type[t], handler);
112            }
113        } else if (typeof(type) == 'string') {
114            removeOneTypeListener.call(this, type, handler);
115        } else {
116            throw new Error('Type should be a name or array of names');
117        }
118    },
119    removeAllEventListeners:function (type) {
120        if (typeof (type) === "undefined") {
121            for (type in this.handlers) {
122                delete this.handlers[type];
123            }
124        } else {
125            delete this.handlers[type];
126        }
127    },
128    dispatchEvent:function (type, data) {
129        if (typeof(this.handlers[type]) !== "undefined") {
130            for (var guid in this.handlers[type]) {
131                var event = {
132                    type:type,
133                    data:data,
134                    target:this
135                };
136                var handler = this.handlers[type][guid];
137                if (typeof(handler) === "function") {
138                    handler(event);
139                } else if (typeof(handler) === "object") {
140                    handler["on" + type](event);
141                }
142            }
143        }
144    },
145    _propertiesToWatch:[],
146    addPropertyWatcher:function (handler) {
147        for (var i in this._propertiesToWatch) {
148            this.addEventListener(this._propertiesToWatch[i] + 'change', handler);
149        }
150    },
151    removePropertyWatcher:function (handler) {
152        for (var i in this._propertiesToWatch) {
153            this.removeEventListener(this._propertiesToWatch[i] + 'change', handler);
154        }
155    },
156    _changeProperty:function (property, value, force) {
157        if (typeof(force) == 'undefined') {
158            force = false;
159        }
160        if (typeof(property) === "undefined" ||
161            typeof(this["_" + property]) === "undefined") {
162            throw new Error("There is no property with this name");
163        } else if (value != null && (this["_" + property] != value || force)) {
164            this["_" + property] = value;
165            if ($.inArray(property, this._propertiesToWatch) >= 0) {
166                return this.dispatchEvent(property + 'change', value);
167            }
168        } else {
169            return this["_" + property];
170        }
171    },
172    constructor:EventDispatcher
173});
174EventDispatcher.prototype.addEventListener.guid = 0;
175
176function OptionPriceController() {
177}
178OptionPriceController.prototype = (new EventDispatcher()).extend({
179    is_debit: true,
180    range: [],
181    _action: '',
182    init:function (form) {
183        this.form = form;
184
185        this.price = form.find(":input[name*=price]");
186        this.priceView = form.find(":input[name*=display_prc]");
187        this.priceView.after("<a href='#' class='credit_mark debit_dr'></a>");
188        this.creditMark = form.find("a.credit_mark");
189        this.slider = form.find(".slider");
190
191        var self = this;
192
193        this.price.change(function () {
194            self.updateView();
195        });
196        this.priceView.change(function () {
197            self.priceViewChangeHandler();
198        });
199        this.creditMark.click(function () {
200            self.creditMarkClickHandler();
201        });
202        this.slider.slider({
203            min:0,
204            max:10,
205            step:0.01,
206            slide: function(evt, ui) {
207                self.setPrice(ui.value.toFixed(2));
208            }
209        });
210
211        this.priceView.width(this.priceView.width() - this.creditMark.width());
212        this.setPrice(0.00);
213
214        return EventDispatcher.prototype.init.call(this);
215    },
216    getPrice:function () {
217        return Number(this.price.val());
218    },
219    updateView:function () {
220        if (this.priceView && this.creditMark && this.slider) {
221            var priceValue = this.getPrice();
222            if (this._action != 'quantity_change') this.priceView.val("$" + ctNumberFormat(Math.abs(priceValue)));
223
224            this.slider.slider('option', 'value', priceValue);
225            this.updateRange();
226
227            this.creditMark.toggleClass('credit_cr', !this.is_debit);
228            this.creditMark.toggleClass('debit_dr', this.is_debit);
229        }
230    },
231    setPrice:function (value) {
232        this.price.val(value);
233        this.is_debit = value > 0;
234        this.updateView();
235        this.form.trigger("dataChanged");
236    },
237    priceViewChangeHandler:function () {
238        var priceString = this.priceView.val();
239        var dollarSignPosition = priceString.search(/\u0024/);
240        if (dollarSignPosition >= 0 && dollarSignPosition < (priceString.length - 1)) {
241            priceString = priceString.substr(dollarSignPosition + 1);
242        }
243        var priceValue = Number(priceString);
244        if (isNaN(priceValue)) {
245            priceValue = 0.00;
246        }
247        if (!this.is_debit) {
248            priceValue = (-1) * priceValue;
249        }
250        this.setPrice(priceValue);
251    },
252    creditMarkClickHandler:function () {
253        this.setPrice((-1) * this.getPrice());
254        this.dispatchEvent("crdb_changed", { is_debit: this.getPrice() > 0 });
255    },
256    setRange: function(v1, v2) {
257        this.range = [v1, v2];
258        this.updateRange();
259    },
260    updateRange: function() {
261        var range_min = Math.min(this.range[0], this.range[1]);
262        var range_max = Math.max(this.range[0], this.range[1]);
263
264        this.slider.slider('option', {
265            min: (this.is_debit ? range_min : -range_max),
266            max: (this.is_debit ? range_max : -range_min)
267        })
268    },
269    constructor:OptionPriceController
270});
271
272function Quote() {
273}
274Quote.prototype = (new EventDispatcher()).extend({
275    init:function (symbol, params) {
276        this.symbol = symbol;
277
278        if(!params) {
279            params = {};
280        }
281
282        this._bid = parseFloat(params.bid) || 0;
283        this._ask = parseFloat(params.ask) || 0;
284        this._last = parseFloat(params.last) || 0;
285        this._close = parseFloat(params.close) || 0;
286
287        return EventDispatcher.prototype.init.call(this);
288    },
289    sub:function () {
290        return this.symbol.replace(/ /g, '');
291    },
292    bid:function (value) {
293        return this._changeProperty('bid', value);
294    },
295    ask:function (value) {
296        return this._changeProperty('ask', value);
297    },
298    last:function (value) {
299        return this._changeProperty('last', value);
300    },
301    close:function (value) {
302        return this._changeProperty('close', value);
303    },
304    change:function () {
305        return this._last - this._close;
306    },
307    changePct:function () {
308        if (this._close == 0.00) {
309            return 0.00;
310        } else {
311            return this.change() / this._close;
312        }
313    },
314    update:function (data, force) {
315        if (typeof(force) === 'undefined') {
316            force = false;
317        }
318        for (var p in this._propertiesToWatch) {
319            var property = this._propertiesToWatch[p];
320            if (typeof(data[property]) != 'undefined') {
321                var value = parseFloat(data[property]);
322                if (isNaN(value)) value = 0.00;
323                this._changeProperty(property, value, force);
324            }
325        }
326    },
327    _propertiesToWatch:['bid', 'ask', 'last', 'close'],
328    constructor:Quote
329});
330
331function ComplexQuote() {
332}
333ComplexQuote.prototype = (new Quote()).extend({
334    init:function (symbol, params) {
335        this._mid = this._cmpx = 0.00;
336        this.legs = new Array();
337        this.tagLink = params.tagline;
338        this.updateRule = params.updateRule;
339        var subQuotes = extractComplexOptionSymbols(symbol);
340        var contracts = params.contracts && params.contracts.split('|');
341        var i = 0;
342        for (var q in subQuotes) {
343            var leg = {
344                bs:subQuotes[q].bs,
345                quantity:contracts[i],
346                symbol:subQuotes[q].symbol,
347                quote:null
348            };
349            this.legs.push(leg);
350            i++;
351        }
352        return Quote.prototype.init.call(this, symbol);
353    },
354    mid:function (value) {
355        return this._changeProperty('mid', value);
356    },
357    cmpx:function (value) {
358        return this._changeProperty('cmpx', value);
359    },
360    calculate:function () {
361        var ask, bid, last, close;
362        ask = bid = last = close = 0.00;
363        var bidSum = 0;
364        var askSum = 0;
365        var contractsCount = [];
366        var totalQuantity = 0;
367        if (this.legs[0].quantity)
368            var minQquantity = parseInt(this.legs[0].quantity);
369
370        for (var i in this.legs)
371            contractsCount.push(Number(this.legs[i].quantity));
372        var gdValue = gcd_array(contractsCount);
373
374        for (var i in this.legs) {
375            var leg = this.legs[i];
376            bidSum += calcLegBidAsk('bid', leg.bs, leg.quote, parseInt(leg.quantity));
377            askSum += calcLegBidAsk('ask', leg.bs, leg.quote, parseInt(leg.quantity));
378            totalQuantity += parseInt(leg.quantity);
379            minQquantity = Math.min(minQquantity, parseInt(leg.quantity));
380            switch (leg.bs) {
381                case 'B' :
382                    /*ask += leg.quote.ask();
383                     bid += leg.quote.bid();*/
384                    last += leg.quote.last() * Math.ceil(parseInt(leg.quantity) / gdValue);
385                    close += leg.quote.close() * Math.ceil(parseInt(leg.quantity) / gdValue);
386                    break;
387                case 'S' :
388                    /* ask -= leg.quote.bid();
389                     bid -= leg.quote.ask();*/
390                    last -= leg.quote.last() * Math.ceil(parseInt(leg.quantity) / gdValue);
391                    close -= leg.quote.close() * Math.ceil(parseInt(leg.quantity) / gdValue);
392                    break;
393            }
394        }
395
396        var totalAsk = Math.abs(bidSum / (totalQuantity / (totalQuantity / minQquantity)));
397        var totalBid = Math.abs(askSum / (totalQuantity / (totalQuantity / minQquantity)));
398
399        ask = totalBid > totalAsk ? totalBid : totalAsk;
400        bid = totalBid > totalAsk ? totalAsk : totalBid;
401        this.update({
402            ask:ask,
403            bid:bid,
404            last:last,
405            close:Math.abs(close)
406        });
407
408        this.mid(Number(midFunc(bid, ask)).toFixed(2));
409        this.cmpx(ask + bid > 0 ? Number(Math.abs(ask).toFixed(2)) : Number(Math.abs(bid).toFixed(2)));
410
411        if(!this.tagLink)
412        {
413            return;
414        }
415        
416        var source = this.tagLink.text();
417
418        if(this.updateRule == "last")
419        {
420            var newString = source.replace(/-?\d{1,}\.\d{2,}/ig, last.toFixed(2));
421        }
422        else
423        {
424            var newValue = 0;
425            if (source.indexOf("Credit") != -1 || source.indexOf("Cr") != -1)
426                newValue = totalBid > totalAsk ? totalAsk : totalBid;
427            else newValue = totalBid > totalAsk ? totalBid : totalAsk;
428
429            var newString = source.replace(/-?\d{1,}\.\d{2,}/ig, newValue.toFixed(2));
430        }
431
432        this.tagLink.html(newString);
433    },
434    _propertiesToWatch:['bid', 'ask', 'last', 'close', 'mid', 'cmpx'],
435    constructor:ComplexQuote
436});
437
438function QuoteStorage() {
439    this.quotes = new Array();
440}
441QuoteStorage.prototype = {
442    store:function (symbol, params) {
443        symbol = symbol.toUpperCase();
444        var sub = symbol.replace(/ /g, '');
445        var quote = this.quotes[sub];
446        if (!quote) {
447            var complex = isComplexOptionSymbol(symbol);
448            if (complex) {
449                quote = (new ComplexQuote()).init(symbol, params);
450                var legs = quote.legs;
451                for (var l in legs) {
452                    var leg = legs[l];
453                    leg.quote = this.store(leg.symbol).quote;
454                    leg.quote.addPropertyWatcher(function (event) {
455                        quote.calculate();
456                    });
457                }
458            } else {
459                quote = (new Quote()).init(symbol, params);
460            }
461            this.quotes[sub] = quote;
462        }
463        return {
464            quote:quote,
465            sub:sub
466        };
467    },
468    publish:function (publisher) {
469        var subsToPublish = new Array();
470        for (var sub in this.quotes) {
471            var quote = this.quotes[sub];
472            if (!isComplexOptionSymbol(quote.symbol)) {
473                subsToPublish.push(sub);
474            }
475        }
476        publisher.publish(subsToPublish);
477    },
478    remove:function (symbol, removeListeners) {
479        if (typeof(removeListeners) === 'undefined') {
480            removeListeners = true;
481        }
482        symbol = symbol.toUpperCase();
483        var sub = symbol.replace(/ /g, '');
484        var quote = this.quotes[sub];
485        if (typeof(quote) !== "undefined") {
486            if (removeListeners) {
487                quote.removeAllEventListeners();
488            }
489            delete this.quotes[sub];
490        }
491    },
492    removeAll:function (removeListeners) {
493        for (var sub in this.quotes) {
494            this.remove(sub, removeListeners);
495        }
496    },
497    constructor:QuoteStorage
498};
499
500function QuoteViewController(element, quote) {
501    this.views = this.findViews(element, ['ask', 'bid', 'last', 'change']);
502    this.views['changePct'] = $(element).find('.change .percent:first');
503    quote.addPropertyWatcher(this);
504    this.quote = quote;
505    this.isShort = $(element).hasClass('ui-queue-leg-row short');
506}
507QuoteViewController.prototype = {
508    findViews:function (element, properties) {
509        var result = new Array();
510        for (var p in properties) {
511            var property = properties[p];
512            var css = '.' + property + ' .data:first';
513            result[property] = $(element).find(css);
514        }
515        return result;
516    },
517    createLabel:function (data) {
518        var sign = this.isShort ? (-1) : 1;
519        return createDollarLabel(data * sign);
520    },
521    onaskchange:function (event) {
522        this.views['ask'].text(this.createLabel(event.data));
523    },
524    onbidchange:function (event) {
525        this.views['bid'].text(this.createLabel(event.data));
526    },
527    onmidchange:function (event) {
528    },
529    oncmpxchange:function (event) {
530    },
531    onlastchange:function (event) {
532        this.views['last'].text(this.createLabel(event.data));
533        this.updateChange();
534    },
535    onclosechange:function (event) {
536        this.updateChange();
537    },
538    updateChange:function () {
539        var change = this.quote.change();
540        var changePct = '(' + createPercentLabel(this.quote.changePct()) + ')';
541        var changePctView = this.views['changePct'];
542        changePctView.text(changePct);
543        if (change >= 0) {
544            changePctView.removeClass('down');
545            if (change > 0) {
546                changePctView.addClass('up');
547            } else {
548                changePctView.removeClass('up');
549            }
550        } else if (change < 0) {
551            changePctView.removeClass('up').addClass('down');
552        }
553        var changeText = xor(this.isShort, change < 0) ? '-$' : '$';
554        changeText += Math.abs(change).toFixed(2);
555        this.views['change'].text(changeText);
556    },
557    clear:function () {
558        for (var v in this.views) {
559            delete this.views[v];
560        }
561        this.views.splice(0);
562        this.quote.removePropertyWatcher(this);
563    },
564    constructor:QuoteViewController
565};
566
567function Position() {
568}
569Position.prototype = (new EventDispatcher()).extend({
570    init:function (symbol) {
571        EventDispatcher.prototype.init.call(this);
572        this._last = this.qty = this.cost = this.realized = 0.00;
573        this.symbol = symbol;
574        this.isOption = isOptionSymbol(symbol);
575        return this;
576    },
577    sub:function () {
578        return this.symbol.replace(/ /g, '');
579    },
580    last:function (value) {
581        return this._changeProperty('last', value);
582    },
583    chg:function () {
584        return (this._last - this.cost) * sign(this.qty);
585    },
586    chg_p:function () {
587
588        //attempt to clear NaN percents
589        var percent = this.chg() / this.cost;
590        if(isNaN(percent)||percent==Infinity){
591            percent = 0;
592        }
593        //percent = Math.round(percent * 100) / 100
594        return percent;
595    },
596    cost_b:function () {
597        return this.qty * this.cost * (this.isOption ? (this.is_mini? 10 : 100.0) : 1.0);
598    },
599    mkt_v:function () {
600        return this.qty * this._last * (this.isOption ? (this.is_mini? 10 : 100.0) : 1.0);
601    },
602    unrealized:function () {
603        return this.chg() * Math.abs(this.qty) * (this.isOption ? (this.is_mini? 10 : 100.0) : 1.0);
604    },
605    total:function () {
606        return this.realized + this.unrealized();
607    },
608    update:function (data, force) {
609        if (typeof(force) == 'undefined') {
610            force = false;
611        }
612        for (var p in this._propertiesToWatch) {
613            var property = this._propertiesToWatch[p];
614            if (typeof(data[property]) != 'undefined') {
615                var value = parseFloat(data[property]);
616                this._changeProperty(property, value, force);
617            }
618        }
619    },
620    _propertiesToWatch:['last'],
621    constructor:Position
622});
623
624function PositionStorage() {
625    this.positions = new Array();
626}
627PositionStorage.prototype = {
628    store:function (symbol) {
629        var sub = this.sub(symbol);
630        var position = this.positions[sub] = (new Position()).init(symbol);
631        return {
632            position:position,
633            sub:sub
634        };
635    },
636    publish:function (publisher) {
637        var subsToPublish = new Array();
638        for (var sub in this.positions) {
639            subsToPublish.push(sub);
640        }
641        publisher.publish(subsToPublish);
642    },
643    remove:function (symbol, removeListeners) {
644        if (typeof(removeListeners) === 'undefined') {
645            removeListeners = true;
646        }
647        var sub = this.sub(symbol);
648        var position = this.positions[sub];
649        if (typeof(quote) !== "undefined") {
650            if (removeListeners) {
651                position.removeAllEventListeners();
652            }
653            delete this.quotes[sub];
654        }
655    },
656    removeAll:function (removeListeners) {
657        for (var sub in this.positions) {
658            this.remove(sub, removeListeners);
659        }
660    },
661    sub:function (symbol) {
662        symbol = symbol.toUpperCase();
663        return symbol.replace(/ /g, '');
664    },
665    constructor:PositionStorage
666};
667
668function PositionViewController(element, position) {
669    this.views = this.findViews(element, ['last', 'chg', 'chg_p', 'cost_b', 'mkt_v',
670        'unrealized', 'total']);
671    position.addPropertyWatcher(this);
672    this.position = position;
673}
674PositionViewController.prototype = {
675    findViews:function (element, properties) {
676        var result = new Array();
677        for (var p in properties) {
678            var property = properties[p];
679            result[property] = $(element).find('.' + property);
680        }
681        return result;
682    },
683    onlastchange:function () {
684        this.update();
685    },
686    update:function () {
687        this.updateLabels({
688            last:createDollarLabel,
689            cost_b:createDollarLabel,
690            mkt_v:createDollarLabel,
691            unrealized:createDollarLabel,
692            total:createDollarLabel
693        });
694        if (this.position.qty == 0) {
695            this.hideLabels(['chg', 'chg_p']);
696        } else {
697            this.updateLabels({
698                chg:createDollarLabel,
699                chg_p:createPercentLabel
700            });
701        }
702        this.styleLabels(['chg', 'chg_p', 'unrealized', 'total']);
703    },
704    updateLabels:function (labels) {
705        for (var l in labels) {
706            var labelFunction = labels[l];
707            var value = this.position[l]();
708            this.views[l].text(labelFunction(value));
709        }
710    },
711    hideLabels:function (labels) {
712        for (var l in labels) {
713            this.views[labels[l]].text('-');
714        }
715    },
716    styleLabels:function (labels) {
717        for (var l in labels) {
718            var label = labels[l];
719            var view = this.views[label];
720            var value = this.position[label]();
721            view.removeClass('up down').addClass(
722                value < 0 ? 'down' : (value > 0 ? 'up' : ''));
723        }
724    },
725    clear:function () {
726        for (var v in this.views) {
727            delete this.views[v];
728        }
729        this.views.splice(0);
730        this.position.removePropertyWatcher(this);
731    },
732    constructor:PositionViewController
733};
734
735function PositionTotalsViewController(element, storage) {
736    this.findViews(element);
737    this.storage = storage;
738}
739PositionTotalsViewController.VIEWS = [
740    {
741        index:0,
742        name:'cost_b'
743    },
744    {
745        index:1,
746        name:'mkt_v'
747    },
748    {
749        index:2,
750        name:'unrealized'
751    },
752    {
753        index:4,
754        name:'total'
755    }
756];
757PositionTotalsViewController.prototype = {
758    findViews:function (element) {
759        this.views = {};
760        var elements = element.find('td.data');
761        for (var v in PositionTotalsViewController.VIEWS) {
762            var view = PositionTotalsViewController.VIEWS[v];
763            this.views[view.name] = elements[view.index];
764        }
765    },
766    update:function () {
767        var totals = {};
768        for (var t in this.views) {
769            totals[t] = 0.00;
770        }
771        for (var p in this.storage.positions) {
772            for (var r in totals) {
773                totals[r] += this.storage.positions[p][r]();
774            }
775        }
776        for (var v in this.views) {
777            $(this.views[v]).text(createDollarLabel(totals[v]));
778        }
779    },
780    constructor:PositionTotalsViewController
781};
782
783function PriceAutoUpdate() {
784}
785PriceAutoUpdate.prototype = (new EventDispatcher()).extend({
786    init:function (element) {
787        EventDispatcher.prototype.init.call(this);
788
789        var priceAutoUpdate = this;
790
791        this.field = element.find('input#StockAutoUpdate');
792        this.value = $.parseJSON(this.field.val());
793        if (this.value == null) {
794            this.value = {
795                rule:'off',
796                enabled:false
797            };
798        }
799        this.value.enabled = false;
800        this.mark = element.find('a.auto_mark');
801        this.mark.click(function () {
802            priceAutoUpdate.toggleEnabled();
803            return false;
804        });
805        this.orderId = element.find('input#StockOrderId').val();
806        this._updateMark();
807        return this;
808    },
809    _updateMark:function () {
810        var title = 'Price is ';
811        if (this.value.enabled) {
812            this.mark.addClass('enabled');
813            this.mark.parent().find('#StockPrice').css('background-color', '#D9D8D8');
814            title = 'This icon will be gold if the global auto-updating feature is set in "Manage Account" ' +
815                'indicating that prices in WatchPlus are being automatically updated according to a user\'s data entitlements. ' +
816                'Click this icon to turn auto-updating "off" for the one order.  When off, the price entered for this order will remain static.';
817            /* title += 'dynamically changed on ' + this.rule() +
818             ' price update. Click to disable auto-updating for this staged order.';*/
819        } else {
820            this.mark.parent().find('#StockPrice').css('background-color', 'white');
821            this.mark.removeClass('enabled');
822            title += 'controlled manually. Click to dynamically change it on ' +
823                this.rule() + ' price update.';
824        }
825        this.mark.attr('title', title);
826    },
827    _updateField:function () {
828        this.field.val($.toJSON(this.value));
829    },
830    _saveStateToServer:function (state) {
831        var updateUrl = "/queue/saveAutoUpdateState";
832        $.post(updateUrl, {
833            orderId:this.orderId,
834            state:state ? 1 : 0
835        });
836    },
837    rule:function () {
838        return this.value.rule;
839    },
840    eventType:function () {
841        return this.value.rule + 'change';
842    },
843    enabled:function ($value) {
844        if ($value == undefined) {
845            return this.value.enabled;
846        } else {
847            this.value.enabled = $value;
848            this._updateMark();
849            this._updateField();
850            this._saveStateToServer($value);
851            this.dispatchEvent('enabledchange', this.value.enabled);
852        }
853    },
854    toggleEnabled:function () {
855        this.enabled(!this.value.enabled);
856    },
857    constructor:PriceAutoUpdate
858});
859
860function OrderViewController(element, quote) {
861
862    var controller = this;
863    var changeListener = function (event) {
864        var price = parseFloat(event.data).toFixed(2);
865        controller._change(price);
866    };
867
868    var quickSave = element.find('form.quickSave');
869    var autoUpdate = (new PriceAutoUpdate()).init(quickSave);
870
871    if (autoUpdate.rule()) {
872
873        this.order = element.find('code.order');
874        this.json = $.parseJSON(this.order.text());
875        this.jsonSign = sign(this.json.limit_price);
876
877        var description = element.find('div.description');
878        this.tagline = description.find('div.tagline');
879        this.price = description.find('input#StockPrice');
880        this.jsonInput = description.find('input#StockOrderJson');
881
882        var tradeNow = element.find('form.tradeNow');
883        this.limitPrice = element.hasClass('option') ?
884            tradeNow.find('input#OptionLimitPrice') :
885            tradeNow.find('input#StockLimitPrice');
886        this.limitPriceSign = sign(parseFloat(this.limitPrice.val()));
887
888        var eventType = autoUpdate.eventType();
889        var updateQuoteSubscription = function () {
890            controller.price.removeAttr('readonly');
891            quote.removeEventListener(eventType, changeListener);
892            if (autoUpdate.enabled()) {
893                controller.price.attr('readonly', 'readonly');
894                controller.price.val(quote[autoUpdate.rule()]().toFixed(2));
895                quote.addEventListener(eventType, changeListener);
896            }
897        };
898
899        autoUpdate.addEventListener('enabledchange', updateQuoteSubscription);
900        updateQuoteSubscription();
901    }
902}
903OrderViewController.prototype = {
904    _change:function (value) {
905        this._changeTagline(value);
906        this._changeJson(value);
907        this._changePrice(value);
908    },
909    _changeTagline:function (value) {
910        var regexp = /-?\d{1,}\.\d{2,}/ig;
911        var source = this.tagline.text();
912        this.tagline.text(source.replace(regexp, parseFloat(value).toFixed(2)));
913    },
914    _changeJson:function (value) {
915        this.json.limit_price = value * this.jsonSign;
916        var jsonString = $.toJSON(this.json);
917        this.order.text(jsonString);
918        this.jsonInput.val(jsonString);
919    },
920    _changePrice:function (value) {
921        this.price.val(value);
922        this.limitPrice.val(value * this.limitPriceSign);
923    },
924    constructor:OrderViewController
925};
926
927function WSStub() {
928}
929WSStub.prototype = (new EventDispatcher()).extend({
930    init:function () {
931        return EventDispatcher.prototype.init.call(this);
932    },
933    connect:function () {
934    },
935    disconnect:function () {
936    },
937    publish:function (subs) {
938    },
939    constructor:WSStub
940});
941
942function SioConnector() {
943}
944SioConnector.prototype = (new EventDispatcher()).extend({
945    init:function (url) {
946        this.url = url;
947        return EventDispatcher.prototype.init.call(this);
948    },
949    connect:function () {
950        var connector = this;
951        this.client = io.connect(this.url, {
952            'secure':true,
953            'reconnect':true,
954            'reconnection delay':500,
955            'connect timeout':2000,
956            'force new connection':true,
957            'max reconnection attempts':200
958        });
959        this.client.on('connect', function () {
960            connector.connected = true;
961            connector.dispatchEvent('connect', {
962                connected:connector.connected
963            });
964        });
965        this.client.on('message', function (msg) {
966            msg.symbol = msg.channel; // For compatibility with jWS format
967            connector.dispatchEvent('data', msg);
968        })
969    },
970    disconnect:function () {
971        if(this.client === undefined) return false;
972        this.client.removeAllListeners("message");
973        this.client.removeAllListeners("connect");
974        this.client.disconnect();
975    },
976    publish:function (subs) {
977        this.subs = subs;
978        for (var s in subs) {
979        
980        //@do not subscribe to options since we get it from DTN L4
981       //if (subs[s].substring(0,1)=="@"){
982       // break;
983       //}
984       //      this.client.emit('join', subs[s]);
985        }
986    },
987    constructor:SioConnector
988});
989
990function JwsConnector() {
991}
992JwsConnector.prototype = (new EventDispatcher()).extend({
993    init:function (options) {
994        this.subs = [];
995        this.connected = false;
996        this.urls = options.urls;
997        this.channel = new jws.jWebSocketJSONClient();
998        this.channel.setReliabilityOptions(jws.RO_ON);
999        return EventDispatcher.prototype.init.call(this);
1000    },
1001    connect:function () {
1002        try {
1003            this.jwsHandler = (new JwsHandler()).init({
1004                urls:this.urls,
1005                channel:this.channel
1006            });
1007            this.jwsHandler.addEventListener(['login', 'start', 'data'], this);
1008            this.channel.logon(this.urls.next(),
1009                this.jwsToken, '', this.jwsHandler);
1010        } catch (ex) {
1011            alert(ex);
1012        }
1013    },
1014    disconnect:function () {
1015        if (this.channel) {
1016            this.channel.stopKeepAlive();
1017            this.channel.close({
1018                timeout:3000
1019            });
1020        }
1021    },
1022    onlogin:function (event) {
1023        this.connected = event.data;
1024        this.dispatchEvent('connect', {
1025            connected:this.connected,
1026            channel:this.channel
1027        });
1028    },
1029    onstart:function (event) {
1030        var sub = event.data.channel;
1031        if ($.inArray(sub, this.subs) >= 0) {
1032            this.channel.channelSubscribe(sub, 'access');
1033        }
1034    },
1035    ondata:function (event) {
1036        this.dispatchEvent('data', event.data);
1037    },
1038    publish:function (subs) {
1039        this.subs = subs;
1040        for (var s in subs) {
1041            this.channel.channelPublish('control_req', subs[s]);
1042        }
1043    },
1044    constructor:JwsConnector
1045});
1046
1047function JwsHandler() {
1048}
1049JwsHandler.prototype = (new EventDispatcher()).extend({
1050    init:function (options) {
1051        this.urls = options.urls;
1052        this.channel = options.channel;
1053        return EventDispatcher.prototype.init.call(this);
1054    },
1055    OnWelcome:function (evt) {
1056    },
1057    OnMessage:function (evt) {
1058        var data = $.parseJSON(evt.data);
1059        switch (typeof(data.reqType) === 'undefined' ? data.type : data.reqType) {
1060            case 'login' :
1061                this.channel.channelSubscribe('control_res', 'control');
1062                this.channel.channelAuth('control_req',
1063                    'control', 'control');
1064                this.dispatchEvent('login', true);
1065                break;
1066            case 'subscribe' :
1067                // received subscribe acknowledgement
1068                break;
1069            case 'data' :
1070                var payload = $.parseJSON(data.data);
1071                if (payload.reqType === 'start' && payload.code == 0) {
1072                    this.dispatchEvent('start', payload);
1073                } else {
1074                    this.dispatchEvent('data', payload);
1075                }
1076                break;
1077        }
1078    },
1079    OnReconnecting:function (evt) {
1080        this.channel.fURL = this.urls.next();
1081    },
1082    OnOpen:function (evt) {
1083    },
1084    OnClose:function (evt) {
1085    },
1086    constructor:JwsHandler
1087});
1088
1089
1090function QueuedFormSubmitter() {
1091}
1092QueuedFormSubmitter.prototype = (new ExtObject()).extend({
1093    init:function () {
1094        this.queue = new Array();
1095        return ExtObject.prototype.init.call(this);
1096    },
1097    submit:function (id, form, callback) {
1098        if (typeof(this.queue[id]) == 'undefined') {
1099            this.queue[id] = 'ready';
1100        }
1101        if (this.queue[id] == 'ready') {
1102            var self = this;
1103            this.queue[id] = 'request';
1104            form.ajaxSubmit({
1105                dataType:'html',
1106                success:function (html) {
1107                    if (typeof(self.queue[id]) == 'function') {
1108                        var newCallback = self.queue[id];
1109                        self.queue[id] = 'ready';
1110                        self.submit(id, form, newCallback);
1111                    } else if (typeof(self.queue[id]) != 'undefined') {
1112                        self.queue[id] = 'ready';
1113                        callback(html);
1114                    }
1115                }
1116            });
1117        } else {
1118            this.queue[id] = callback;
1119        }
1120    },
1121    deque:function (id) {
1122        delete this.queue[id];
1123    },
1124    constructor:QueuedFormSubmitter
1125});
1126
1127function NewsWidget() {
1128}
1129NewsWidget.overlayContent = new Array();
1130NewsWidget.prototype = (new ExtObject()).extend({
1131    _showMessage:function (message, result) {
1132        return $('<span/>').addClass('message').
1133            html(message).appendTo(result);
1134    },
1135    _showError:function (message, result) {
1136        return this._showMessage(message, result).addClass('error');
1137    },
1138
1139    _loadTwitter:function (url, result, isSSL) {
1140
1141        var widget = this;
1142
1143        $.ajax({
1144            data:{},
1145            dataType:'json',
1146            error:function (jqXHR, textStatus, errorThrown) {
1147                result.empty();
1148                if (jqXHR.readyState != 0 && jqXHR.status != 0) {
1149                    widget._showError(textStatus, result);
1150                }
1151            },
1152            success:function (data, textStatus, jqXHR) {
1153                result.empty();
1154                if (data.error) {
1155                    widget._showError(data.error, result);
1156                } else if (data instanceof Array) {
1157                    var noTweets = true;
1158
1159                    for (var d in data) {
1160                        var item = data[d];
1161                        if (item.results.length > 0) {
1162                            noTweets = false;
1163                            for (var i in item.results) {
1164                                result.append(createTwitterPost(item.results[i], isSSL));
1165                                result.addClass('loaded');
1166                            }
1167                        }
1168                    }
1169
1170                    if (noTweets) {
1171                        widget._showMessage(
1172                            'No recent tweets were reported for this symbol.',
1173                            result);
1174                    }
1175                } else if (data instanceof Object) {
1176                    if (data.messages.length == 0) {
1177                        widget._showMessage(
1178                            'No recent tweets were reported for this symbol.',
1179                            result);
1180                    } else {
1181                        for (var r in data.messages) {
1182                            result.append(createTwitterPost(data.messages[r], isSSL));
1183                            result.addClass('loaded');
1184                        }
1185                    }
1186                }
1187            },
1188            url:url
1189        });
1190    },
1191    _loadMidnight:function (url, result, hideBody) {
1192
1193        var widget = this;
1194
1195        if (typeof(hideBody) == 'undefined') {
1196            hideBody = false;
1197        }
1198
1199        function parseDates(source) {
1200            $(source).find('span.date').each(function (index, item) {
1201                var dateString = moment($(item).text()).
1202                    format('MMM DD, YYYY hh:mm a');
1203                $(item).text(dateString);
1204            });
1205        }
1206
1207        function createOverlayContent(source) {
1208
1209            var id = $(source).attr('id');
1210            var overlayContent = $(source).find('div.overlay-content');
1211            if (overlayContent.length > 0) {
1212                var content = $('<div/>').addClass(
1213                    'mt').addClass('overlay').html(
1214                    overlayContent.html());
1215                var cpText = $(content).find('span.copyright').text().replace(/=+/, '');
1216                $(content).find('span.copyright').text(cpText);
1217                NewsWidget.overlayContent[id] = content;
1218            }
1219            $(source).find('a.title').click(function () {
1220                $.params.overlay.find("#overlay-content").html(
1221                    NewsWidget.overlayContent[id]);
1222                $.params.overlay.data("overlay").load();
1223            });
1224        }
1225
1226        $.ajax({
1227            dataType:'html',
1228            error:function (jqXHR, textStatus, errorThrown) {
1229                result.empty();
1230                if (jqXHR.readyState != 0 && jqXHR.status != 0) {
1231                    widget._showError(textStatus, result);
1232                }
1233            },
1234            success:function (data, textStatus, jqXHR) {
1235                result.html(data);
1236                parseDates(result);
1237                result.find('div.mt').each(function (index, item) {
1238                    if (hideBody) {
1239                        createOverlayContent(item);
1240                        $(item).find('div.overlay-content').hide();
1241                    }
1242                });
1243                result.addClass('loaded');
1244            },
1245            url:url
1246        });
1247    },
1248    constructor:NewsWidget
1249});
1250
1251function PriceDensityCalculator(sigma, mean, time, ir, annir) {
1252    this.sigma = sigma;
1253    this.mean = mean;
1254    this.factor = 1 / (sigma * Math.sqrt(2.0 * Math.PI));
1255    this.dss = 2.0 * Math.pow(sigma, 2);
1256    this.carg = sigma * Math.sqrt(2.0);
1257    this.irAdd = time * ir;
1258    this.ir = annir;
1259}
1260PriceDensityCalculator.prototype = {
1261    dencity:function (price) {
1262        if (price <= 0) return 0.00;
1263        var num = Math.log(price / this.mean) + this.irAdd;
1264        var exp = Math.exp(-(Math.pow(num, 2) / this.dss + this.ir));
1265        return this.factor / price * exp;
1266    },
1267    cumItem:function (value) {
1268        return (Math.log(value / this.mean) + this.irAdd) / this.carg;
1269    },
1270    cumulative:function (value) {
1271        return value <= 0.00 ? -0.50 :
1272            (value == Number.POSITIVE_INFINITY ? 0.5 :
1273                0.5 * erf(this.cumItem(value)));
1274    },
1275    probability:function (a, b) {
1276        return this.cumulative(b) - this.cumulative(a);
1277    },
1278    constructor:PriceDensityCalculator
1279};
1280
1281function Interval(a, b, legs, lot) {
1282    this.a = a;
1283    this.b = b;
1284    this.m = (a + (b == Number.POSITIVE_INFINITY ? (a + 2.00) : b)) / 2.00;
1285    this.k = 0;
1286    for (var l in legs) {
1287        var leg = legs[l];
1288        this.k += leg.qqtyf(this.m) * (leg.isOption ? lot : 1);
1289    }
1290}
1291;
1292Interval.prototype = {
1293    values:function () {
1294        return [
1295            {
1296                p:this.a,
1297                v:this.av
1298            },
1299            {
1300                p:this.b,
1301                v:this.bv
1302            }
1303        ];
1304    },
1305    cross:function (target) {
1306        var candidate = this.a + (target - this.av) / this.k;
1307        return between(candidate, [this.a, this.b]) ? candidate : false;
1308    },
1309    constructor:Interval
1310};
1311
1312function PLeg(leg) {
1313    var bs = covertTxToBs(Number(leg.tx));
1314    this.isOption = isOptionSymbol(leg.symbol);
1315    if (this.isOption) {
1316        this.cp = extractCp(leg.symbol);
1317        this.exp = moment(extractExpirationDate(leg.symbol));
1318        this.strikePrice = parseFloat(extractStrike(leg.symbol));
1319        this.qqtyf = PLeg.createQQtyFunction(this.cp);
1320    } else {
1321        this.qqtyf = function () {
1322            return this.qty
1323        };
1324    }
1325    this.price = parseFloat(bs == 'S' ? -leg.quote.bid : leg.quote.ask);
1326    this.qty = (bs == 'S' ? -1 : 1) * Number(leg.qty);
1327    this.income = PLeg.createIncomeFunction(this.isOption);
1328    this.tincome = PLeg.createTIncomeFunction(this.isOption);
1329}
1330PLeg.prototype = {
1331    firstExpiration:function (val) {
1332        this.expDiff = this.exp.diff(moment(val), 'days');
1333    },
1334    constructor:PLeg
1335};
1336PLeg.createIncomeFunction = function (isOption) {
1337    if (isOption) {
1338        return function (stockPrice) {
1339            var qqty = this.qqtyf(stockPrice);
1340            return stockPrice == Number.POSITIVE_INFINITY ?
1341                (qqty > 0 ? Number.POSITIVE_INFINITY :
1342                    (qqty < 0 ? Number.NEGATIVE_INFINITY : 0)) :
1343                (stockPrice - this.strikePrice) * qqty;
1344        };
1345    } else {
1346        return function (stockPrice) {
1347            return stockPrice * this.qty;
1348        }
1349    }
1350};
1351PLeg.createTIncomeFunction = function (isOption) {
1352    if (isOption) {
1353        return function (stockPrice, volatility, exp, ir) {
1354            var args = new WhaleyCalculationArguments(
1355                stockPrice, this.strikePrice, this.price, volatility, ir,
1356                exp + this.expDiff, (this.cp == 'call' ? 0 : 1), 0, 0, 1, false);
1357            return Whaley.calculateTheoretical(args).Theoretical * this.qty;
1358        };
1359    } else {
1360        return function (stockPrice) {
1361            return stockPrice * this.qty;
1362        }
1363    }
1364};
1365PLeg.createQQtyFunction = function (cp) {
1366    switch (cp) {
1367        case 'call':
1368            return function (stockPrice) {
1369                return stockPrice > this.strikePrice ? this.qty : 0;
1370            }
1371        case 'put':
1372            return function (stockPrice) {
1373                return stockPrice < this.strikePrice ? -this.qty : 0;
1374            }
1375    }
1376};
1377
1378function PLCalculator(data) {
1379    this.legs = new Array();
1380    for (var d in data.legs) {
1381        this.legs.push(new PLeg(data.legs[d]));
1382    }
1383    this.mean = parseFloat(data.underlying.quote.last);
1384    this.tdays = parseFloat(data.tdays);
1385    this.annir = parseFloat(data.ir);
1386    this.ir = Math.log(1 + this.annir / this.tdays);
1387    this.lot = parseFloat(data.lot);
1388    this.value = parseFloat(data.value);
1389    this._analyse();
1390    this._calculate();
1391}
1392PLCalculator.prototype = {
1393    _analyse:function () {
1394        var l, leg, calc = this;
1395        // Strikes & Expirations
1396        var firstExpiration = Number.MAX_VALUE;
1397        this.strikes = new Array();
1398        this.expirationsAreEqual = true;
1399        for (l in this.legs) {
1400            leg = this.legs[l];
1401            if (leg.isOption) {
1402                this.strikes.push(leg.strikePrice);
1403                firstExpiration = Math.min(leg.exp.valueOf(), firstExpiration);
1404            }
1405        }
1406        this.days = moment(firstExpiration).diff(moment(), 'days');
1407        for (l in this.legs) {
1408            leg = this.legs[l];
1409            if (leg.isOption) {
1410                leg.firstExpiration(firstExpiration);
1411                this.expirationsAreEqual =
1412                    this.expirationsAreEqual && leg.exp.valueOf() == firstExpiration;
1413            }
1414        }
1415
1416        this.strikes.sort(compare);
1417        // Ranges
1418        this.intervals = new Array();
1419        var a = 0;
1420        for (var s in this.strikes) {
1421            this.intervals.push(
1422                new Interval(a, this.strikes[s], this.legs, this.lot));
1423            a = this.strikes[s];
1424        }
1425        this.intervals.push(
1426            new Interval(a, Number.POSITIVE_INFINITY, this.legs, this.lot));
1427        this.plf = function (stockPrice) {
1428            var income = 0;
1429            for (var l in calc.legs) {
1430                var leg = calc.legs[l];
1431                income += leg.income(stockPrice) * (leg.isOption ? calc.lot : 1);
1432            }
1433            return income - calc.value;
1434        }
1435    },
1436    mainPlf:function () {
1437        return this.expirationsAreEqual ? this.plf : this.tplFunction;
1438    },
1439    _analyseIntervals:function () {
1440        var plf = this.mainPlf();
1441        var firstStrike = this.strikes[0];
1442        var lastStrike = this.strikes[this.strikes.length - 1];
1443        var strikeDistance = lastStrike == firstStrike ? 20 : lastStrike - firstStrike;
1444        var extender = strikeDistance * (this.expirationsAreEqual ? 0.45 : 0.55);
1445        var left = firstStrike - extender;
1446        var right = lastStrike + extender;
1447        this.breakevens = new Array();
1448        var minimum = {
1449            p:0,
1450            v:Number.POSITIVE_INFINITY
1451        };
1452        var maximum = {
1453            p:0,
1454            v:Number.NEGATIVE_INFINITY
1455        };
1456        var breakeven;
1457        for (var n in this.intervals) {
1458            var interval = this.intervals[n];
1459            interval.av = plf(interval.a);
1460            interval.bv = plf(interval.b);
1461            if (this.expirationsAreEqual) {
1462                if (interval.k != 0) {
1463                    breakeven = interval.cross(0);
1464                    if (breakeven) {
1465                        this.breakevens.push({
1466                            p:breakeven,
1467                            t:sign(interval.k)
1468                        });
1469                    }
1470                }
1471            } else {
1472                var cross = findIntersection(0, interval.a, interval.b, 0.005, plf);
1473                if (cross) {
1474                    for (var c in cross) {
1475                        breakeven = cross[c].x;
1476                        this.breakevens.push({
1477                            p:breakeven,
1478                            t:sign(cross[c].k)
1479                        });
1480                    }
1481                }
1482            }
1483            var values = interval.values();
1484            for (var v in values) {
1485                var value = values[v];
1486                if (value.v > maximum.v) {
1487                    maximum = value;
1488                }
1489                if (value.v < minimum.v) {
1490                    minimum = value;
1491                }
1492            }
1493        }
1494        if (this.breakevens.length > 0) {
1495            var leftBe = this.breakevens[0].p;
1496            var rightBe = this.breakevens[this.breakevens.length - 1].p;
1497            if (left >= leftBe) {
1498                left = leftBe - extender;
1499            }
1500            if (right <= rightBe) {
1501                right = rightBe + extender;
1502            }
1503        }
1504        this.range = {
1505            left:{
1506                x:left > 0 ? Math.floor(left) : 0
1507            },
1508            right:{
1509                x:Math.ceil(right)
1510            }
1511        }
1512        this.extrema = {
1513            minimum:minimum,
1514            maximum:maximum
1515        };
1516    },
1517    analyseProfit:function (targetProfit) {
1518        var plf = this.mainPlf();
1519        var t, target, targets = new Array();
1520        for (var n in this.intervals) {
1521            var interval = this.intervals[n];
1522            interval.av = plf(interval.a);
1523            interval.bv = plf(interval.b);
1524            if (this.expirationsAreEqual) {
1525                if (interval.k == 0) {
1526                    if (interval.av >= targetProfit) {
1527                        targets.push({
1528                            a:interval.a,
1529                            b:interval.b
1530                        });
1531                    }
1532                } else {
1533                    target = interval.cross(targetProfit);
1534                    if (target) {
1535                        if (interval.k > 0) {
1536                            targets.push({
1537                                a:target,
1538                                b:interval.b
1539                            });
1540                        } else if (interval.k < 0) {
1541                            targets.push({
1542                                a:interval.a,
1543                                b:target
1544                            });
1545                        }
1546                    }
1547                }
1548            } else {
1549                target = findIntersection(targetProfit, interval.a, interval.b, 0.005, plf);
1550                if (target) {
1551                    for (t in target) {
1552                        if (target[t].k > 0) {
1553                            targets.push({
1554                                a:target[t].x,
1555                                b:interval.b
1556                            });
1557                        } else if (target[t].k < 0) {
1558                            targets.push({
1559                                a:interval.a,
1560                                b:target[t].x
1561                            });
1562                        }
1563                    }
1564                } else {
1565                    if (interval.av >= targetProfit) {
1566                        targets.push({
1567                            a:interval.a,
1568                            b:interval.b
1569                        });
1570                    }
1571                }
1572            }
1573        }
1574
1575        // Combine target intervals, which are neighbors
1576        var ctargets = new Array();
1577        var tl = targets.length;
1578        if (tl > 0) {
1579            t = 0;
1580            var ta = targets[t].a;
1581            var tb = targets[t].b;
1582            for (t = 1; t < tl; t++) {
1583                if (tb == targets[t].a) {
1584                    tb = targets[t].b;
1585                } else {
1586                    ctargets.push({
1587                        a:ta,
1588                        b:tb
1589                    });
1590                    ta = targets[t].a;
1591                    tb = targets[t].b;
1592                }
1593            }
1594            ctargets.push({
1595                a:ta,
1596                b:tb
1597            });
1598        }
1599        return ctargets;
1600    },
1601    /**
1602     *  Add a point (x, y pair), to a specified array.
1603     *  @param {Array} array - Array to add points
1604     *  @param {Number} x - Absciss value of the point added
1605     *  @param {Function} func - Function to calculate ordinate value
1606     *  @param {Function} adjuster - Function to adjust ordinate value,
1607     *  calculated by the func function (e.g. round it)
1608     */
1609    addPoint:function (array, x, func, adjuster) {
1610        var y = adjuster(func(x));
1611        array.push({
1612            x:x,
1613            y:y
1614        });
1615        return y;
1616    },
1617    _calculate:function () {
1618        var calc = this;
1619        if (this.expirationsAreEqual) {
1620            this.expiration = [];
1621            this._analyseIntervals();
1622            var addExpPoint = function (price) {
1623                return calc.addPoint(calc.expiration, price, function (price) {
1624                    return calc.plf(price);
1625                }, function (num) {
1626                    return parseFloat(num.toFixed(2));
1627                });
1628            };
1629            this.range.left.e = addExpPoint(this.range.left.x);
1630            for (var s in this.strikes) {
1631                addExpPoint(this.strikes[s]);
1632            }
1633            this.range.right.e = addExpPoint(this.range.right.x);
1634        }
1635    },
1636    calculateTPL:function (days, volatility, targetProfit) {
1637        var calc = this;
1638        var theoretical = [];
1639        /*var density = [];*/
1640        var dtd = Math.sqrt(this.days / this.tdays);
1641        var sigma = volatility * dtd / 100;
1642        var pdCalculator = new PriceDensityCalculator(
1643            sigma, this.mean, days, this.ir, this.annir);
1644        this.tplFunction = function (stockPrice) {
1645            var income = 0;
1646            for (var l in calc.legs) {
1647                var leg = calc.legs[l];
1648                income += leg.tincome(stockPrice, volatility, days, calc.ir) * (leg.isOption ? calc.lot : 1);
1649            }
1650            return income - calc.value;
1651        };
1652
1653        if (!this.expirationsAreEqual) {
1654            this._analyseIntervals();
1655        }
1656
1657        var addTheoPoint = function (price) {
1658            return calc.addPoint(theoretical, price, function (price) {
1659                return calc.tplFunction(price);
1660            }, function (num) {
1661                return parseFloat(num.toFixed(2));
1662            });
1663        };
1664        /*
1665         var addDensPoint = function (price) {
1666         return calc.addPoint(density, price, function (price) {
1667         return pdCalculator.dencity(price);
1668         }, function (num) {
1669         return parseFloat(num.toFixed(3));
1670         });
1671         };
1672         */
1673        for (var i = this.range.left.x; i <= this.range.right.x; i += 0.5) {
1674            addTheoPoint(i);
1675            /*addDensPoint(i);*/
1676        }
1677        var targets = this.analyseProfit(targetProfit);
1678        var probability = 0;
1679        for (var t in targets) {
1680            probability += pdCalculator.probability(
1681                targets[t].a, targets[t].b);
1682        }
1683        return {
1684            theoretical:theoretical,
1685            /*density: density,*/
1686            probability:probability,
1687            targetProfit:targetProfit,
1688            targets:targets
1689        };
1690    },
1691    constructor:PLCalculator
1692};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.