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.