diff --git a/.eslintrc b/.eslintrc
index 5dfd6363d2..5ec2210563 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -19,7 +19,7 @@
"rules": {
"no-unused-vars": ["error", { "vars": "all", "args": "after-used" }],
"comma-dangle": 0,
- "indent": ["error", "tab", {SwitchCase: 1}],
+ "indent": ["error", 4, {SwitchCase: 1}],
"quotes": [2, "double", "avoid-escape"],
"semi": [2, "always"],
"camelcase": [0],
diff --git a/web/app/components/Exchange/Exchange.jsx b/web/app/components/Exchange/Exchange.jsx
index 1308716ea0..168c231cc7 100644
--- a/web/app/components/Exchange/Exchange.jsx
+++ b/web/app/components/Exchange/Exchange.jsx
@@ -28,1262 +28,1262 @@ import ExchangeHeader from "./ExchangeHeader";
import Translate from "react-translate-component";
Highcharts.setOptions({
- global: {
- useUTC: false
- }
+ global: {
+ useUTC: false
+ }
});
class Exchange extends React.Component {
- constructor(props) {
- super();
-
- this.state = this._initialState(props);
-
- this._getWindowSize = debounce(this._getWindowSize.bind(this), 150);
- }
-
- _initialState(props) {
- let ws = props.viewSettings;
- let bid = {
- forSaleText: "",
- toReceiveText: "",
- priceText: "",
- for_sale: new Asset({
- asset_id: props.baseAsset.get("id"),
- precision: props.baseAsset.get("precision")
- }),
- to_receive: new Asset({
- asset_id: props.quoteAsset.get("id"),
- precision: props.quoteAsset.get("precision")
- })
- };
- bid.price = new Price({base: bid.for_sale, quote: bid.to_receive});
- let ask = {
- forSaleText: "",
- toReceiveText: "",
- priceText: "",
- for_sale: new Asset({
- asset_id: props.quoteAsset.get("id"),
- precision: props.quoteAsset.get("precision")
- }),
- to_receive: new Asset({
- asset_id: props.baseAsset.get("id"),
- precision: props.baseAsset.get("precision")
- })
- };
- ask.price = new Price({base: ask.for_sale, quote: ask.to_receive});
-
- /* Make sure the indicators objects only contains the current indicators */
- let savedIndicators = ws.get("indicators", {});
- let indicators = {};
- [["sma", true], ["ema1", false], ["ema2", false], ["smaVolume", true], ["macd", false], ["bb", false]].forEach(i => {
- indicators[i[0]] = savedIndicators[i[0]] || i[1];
- });
-
- let savedIndicatorsSettings = ws.get("indicatorSettings", {});
- let indicatorSettings = {};
- [["sma", 7], ["ema1", 20], ["ema2", 50], ["smaVolume", 30]].forEach(i => {
- indicatorSettings[i[0]] = savedIndicatorsSettings[i[0]] || i[1];
- });
-
- return {
- history: [],
- buySellOpen: ws.get("buySellOpen", true),
- bid,
- ask,
- flipBuySell: ws.get("flipBuySell", false),
- favorite: false,
- showDepthChart: ws.get("showDepthChart", false),
- leftOrderBook: ws.get("leftOrderBook", false),
- buyDiff: false,
- sellDiff: false,
- indicators,
- buySellTop: ws.get("buySellTop", true),
- buyFeeAssetIdx: ws.get("buyFeeAssetIdx", 0),
- sellFeeAssetIdx: ws.get("sellFeeAssetIdx", 0),
- indicatorSettings,
- tools: {
- fib: false,
- trendline: false
- },
- height: window.innerHeight,
- width: window.innerWidth,
- chartHeight: ws.get("chartHeight", 425),
- currentPeriod: ws.get("currentPeriod", 3600* 24 * 30 * 3) // 3 months
- };
- }
-
- static propTypes = {
- marketCallOrders: PropTypes.object.isRequired,
- activeMarketHistory: PropTypes.object.isRequired,
- viewSettings: PropTypes.object.isRequired,
- priceData: PropTypes.array.isRequired,
- volumeData: PropTypes.array.isRequired
- };
-
- static defaultProps = {
- marketCallOrders: [],
- activeMarketHistory: {},
- viewSettings: {},
- priceData: [],
- volumeData: []
- };
-
- componentDidMount() {
- let centerContainer = this.refs.center;
- if (centerContainer) {
- Ps.initialize(centerContainer);
- }
- SettingsActions.changeViewSetting.defer({
- lastMarket: this.props.quoteAsset.get("symbol") + "_" + this.props.baseAsset.get("symbol")
- });
-
- window.addEventListener("resize", this._getWindowSize, false);
- }
-
- shouldComponentUpdate(nextProps) {
- if (!nextProps.marketReady && !this.props.marketReady) {
- return false;
- }
- return true;
- };
-
- _getWindowSize() {
- let { innerHeight, innerWidth } = window;
- if (innerHeight !== this.state.height || innerWidth !== this.state.width) {
- this.setState({
- height: innerHeight,
- width: innerWidth
- });
- }
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.quoteAsset.get("symbol") !== this.props.quoteAsset.get("symbol") || nextProps.baseAsset.get("symbol") !== this.props.baseAsset.get("symbol")) {
- this.setState(this._initialState(nextProps));
-
- return SettingsActions.changeViewSetting({
- lastMarket: nextProps.quoteAsset.get("symbol") + "_" + nextProps.baseAsset.get("symbol")
- });
- }
-
- if (this.props.sub && nextProps.bucketSize !== this.props.bucketSize) {
- return this._changeBucketSize(nextProps.bucketSize);
- }
- }
-
- componentWillUnmount() {
- window.removeEventListener("resize", this._getWindowSize, false);
- }
-
- _getFee(asset) {
- let fee = utils.estimateFee("limit_order_create", [], ChainStore.getObject("2.0.0")) || 0;
- const coreFee = new Asset({
- amount: fee
- });
- if (!asset || asset.get("id") === "1.3.0") return coreFee;
-
- const cer = asset.getIn(["options", "core_exchange_rate"]).toJS();
-
- const cerBase = new Asset({
- asset_id: cer.base.asset_id,
- amount: cer.base.amount,
- precision: ChainStore.getAsset(cer.base.asset_id).get("precision")
- });
- const cerQuote = new Asset({
- asset_id: cer.quote.asset_id,
- amount: cer.quote.amount,
- precision: ChainStore.getAsset(cer.quote.asset_id).get("precision")
- });
- const cerPrice = new Price({
- base: cerBase, quote: cerQuote
- });
- const convertedFee = coreFee.times(cerPrice);
-
- return convertedFee;
- }
-
- _verifyFee(fee, sellAmount, sellBalance, coreBalance) {
- let coreFee = this._getFee();
-
- let sellSum = fee.getAmount() + sellAmount;
- if (fee.asset_id === "1.3.0") {
- if (coreFee.getAmount() <= coreBalance) {
- return "1.3.0";
- } else {
- return null;
- }
- } else {
- if (sellSum <= sellBalance) { // Sufficient balance in asset to pay fee
- return fee.asset_id;
- } else if (coreFee.getAmount() <= coreBalance && fee.asset_id !== "1.3.0") { // Sufficient balance in core asset to pay fee
- return "1.3.0";
- } else {
- return null; // Unable to pay fee in either asset
- }
- }
- }
-
- _createLimitOrderConfirm(buyAsset, sellAsset, sellBalance, coreBalance, feeAsset, type, short = true, e) {
- e.preventDefault();
- let {highestBid, lowestAsk} = this.props.marketData;
- let current = this.state[type === "sell" ? "ask" : "bid"];
-
- sellBalance = current.for_sale.clone(sellBalance ? parseInt(ChainStore.getObject(sellBalance).toJS().balance, 10) : 0);
- coreBalance = new Asset({
- amount: coreBalance ? parseInt(ChainStore.getObject(coreBalance).toJS().balance, 10) : 0
- });
-
- let fee = this._getFee(feeAsset);
-
- let feeID = this._verifyFee(fee, current.for_sale.getAmount(), sellBalance.getAmount(), coreBalance.getAmount());
- if (!feeID) {
- return notify.addNotification({
- message: "Insufficient funds to pay fees",
- level: "error"
- });
- }
-
- if (type === "buy" && lowestAsk) {
- let diff = this.state.bid.price.toReal() / lowestAsk.getPrice();
- if (diff > 1.20) {
- this.refs.buy.show();
- return this.setState({
- buyDiff: diff
- });
- }
- } else if (type === "sell" && highestBid) {
- let diff = 1 / (this.state.ask.price.toReal() / highestBid.getPrice());
- if (diff > 1.20) {
- this.refs.sell.show();
- return this.setState({
- sellDiff: diff
- });
- }
- }
-
- let isPredictionMarket = sellAsset.getIn(["bitasset", "is_prediction_market"]);
-
- if (current.for_sale.gt(sellBalance) && !isPredictionMarket) {
- return notify.addNotification({
- message: "Insufficient funds to place current. Required: " + current.for_sale.getAmount() + " " + sellAsset.get("symbol"),
- level: "error"
- });
- }
- //
- if (!(current.for_sale.getAmount() > 0 && current.to_receive.getAmount() > 0)) {
- return notify.addNotification({
- message: "Please enter a valid amount and price",
- level: "error"
- });
- }
- //
- if (type === "sell" && isPredictionMarket && short) {
- return this._createPredictionShort(feeID);
- }
-
-
-
- this._createLimitOrder(type, feeID);
- }
-
- _createLimitOrder(type, feeID) {
- let current = this.state[type === "sell" ? "ask" : "bid"];
- const order = new LimitOrderCreate({
- for_sale: current.for_sale,
- to_receive: current.to_receive,
- seller: this.props.currentAccount.get("id"),
- fee: {
- asset_id: feeID,
- amount: 0
- }
- });
-
- console.log("order:", JSON.stringify(order.toObject()));
- return MarketsActions.createLimitOrder2(order).then((result) => {
- if (result.error) {
- if (result.error.message !== "wallet locked")
- notify.addNotification({
- message: "Unknown error. Failed to place order for " + current.to_receive.getAmount({real: true}) + " " + current.to_receive.asset_id,
- level: "error"
- });
- }
- // console.log("order success");
- }).catch(e => {
- console.log("order failed:", e);
- });
- }
-
- _createPredictionShort(feeID) {
- let current = this.state.ask;
- const order = new LimitOrderCreate({
- for_sale: current.for_sale,
- to_receive: current.to_receive,
- seller: this.props.currentAccount.get("id"),
- fee: {
- asset_id: feeID,
- amount: 0
- }
- });
-
- Promise.all([
- FetchChain("getAsset", this.props.quoteAsset.getIn(["bitasset", "options", "short_backing_asset"]))
- ]).then(assets => {
- let [backingAsset] = assets;
- let collateral = new Asset({
- amount: order.amount_for_sale.getAmount(),
- asset_id: backingAsset.get("id"),
- precision: backingAsset.get("precision")
- });
-
- MarketsActions.createPredictionShort(
- order,
- collateral
- ).then(result => {
- if (result.error) {
- if (result.error.message !== "wallet locked")
- notify.addNotification({
- message: "Unknown error. Failed to place order for " + buyAssetAmount + " " + buyAsset.symbol,
- level: "error"
- });
- }
- });
- });
- }
-
- _forceBuy(type, feeAsset, sellBalance, coreBalance) {
- let current = this.state[type === "sell" ? "ask" : "bid"];
- // Convert fee to relevant asset fee and check if user has sufficient balance
- sellBalance = current.for_sale.clone(sellBalance ? parseInt(ChainStore.getObject(sellBalance).get("balance"), 10) : 0);
- coreBalance = new Asset({
- amount: coreBalance ? parseInt(ChainStore.getObject(coreBalance).toJS().balance, 10) : 0
- });
- let fee = this._getFee(feeAsset);
- let feeID = this._verifyFee(fee, current.for_sale.getAmount(), sellBalance.getAmount(), coreBalance.getAmount());
-
- if (feeID) {
- this._createLimitOrder(type, feeID);
- } else {
- console.error("Unable to pay fees, aborting limit order creation");
- }
- }
-
- _forceSell(type, feeAsset, sellBalance, coreBalance) {
- let current = this.state[type === "sell" ? "ask" : "bid"];
- // Convert fee to relevant asset fee and check if user has sufficient balance
- sellBalance = current.for_sale.clone(sellBalance ? parseInt(ChainStore.getObject(sellBalance).get("balance"), 10) : 0);
- coreBalance = new Asset({
- amount: coreBalance ? parseInt(ChainStore.getObject(coreBalance).toJS().balance, 10) : 0
- });
- let fee = this._getFee(feeAsset);
- let feeID = this._verifyFee(fee, current.for_sale.getAmount(), sellBalance.getAmount(), coreBalance.getAmount());
-
- if (feeID) {
- this._createLimitOrder(type, feeID);
- } else {
- console.error("Unable to pay fees, aborting limit order creation");
- }
- }
-
- _cancelLimitOrder(orderID, e) {
- e.preventDefault();
- let { currentAccount } = this.props;
- MarketsActions.cancelLimitOrder(
- currentAccount.get("id"),
- orderID // order id to cancel
- );
- }
-
- _changeBucketSize(size, e) {
- if (e) e.preventDefault();
- if (size !== this.props.bucketSize) {
- MarketsActions.changeBucketSize.defer(size);
- let currentSub = this.props.sub.split("_");
- MarketsActions.unSubscribeMarket.defer(currentSub[0], currentSub[1]);
- this.props.subToMarket(this.props, size);
- }
- }
-
- _changeZoomPeriod(size, e) {
- e.preventDefault();
- if (size !== this.state.currentPeriod) {
- this.setState({
- currentPeriod: size
- });
- SettingsActions.changeViewSetting({
- currentPeriod: size
- });
- }
- }
-
- _depthChartClick(base, quote, power, e) {
- e.preventDefault();
- let {bid, ask} = this.state;
-
- bid.price = new Price({
- base: this.state.bid.for_sale,
- quote: this.state.bid.to_receive,
- real: e.xAxis[0].value / power
- });
- bid.priceText = bid.price.toReal();
-
- ask.price = new Price({
- base: this.state.ask.to_receive,
- quote: this.state.ask.for_sale,
- real: e.xAxis[0].value / power
- });
- ask.priceText = ask.price.toReal();
- let newState = {
- bid,
- ask,
- depthLine: bid.price.toReal()
- };
-
- this._setForSale(bid, true) || this._setReceive(bid, true);
- this._setReceive(ask) || this._setForSale(ask);
-
- this.setState(newState);
- }
-
- _flipBuySell() {
- SettingsActions.changeViewSetting({
- flipBuySell: !this.state.flipBuySell
- });
-
- this.setState({ flipBuySell: !this.state.flipBuySell });
- }
-
- _toggleOpenBuySell() {
- SettingsActions.changeViewSetting({
- buySellOpen: !this.state.buySellOpen
- });
-
- this.setState({ buySellOpen: !this.state.buySellOpen });
- }
-
- _toggleCharts() {
- SettingsActions.changeViewSetting({
- showDepthChart: !this.state.showDepthChart
- });
-
- this.setState({ showDepthChart: !this.state.showDepthChart });
- }
-
- _moveOrderBook() {
- SettingsActions.changeViewSetting({
- leftOrderBook: !this.state.leftOrderBook
- });
-
- this.setState({ leftOrderBook: !this.state.leftOrderBook });
- }
-
- _currentPriceClick(type, price) {
- const isBid = type === "bid";
- let current = this.state[type];
- current.price = price[(isBid) ? "invert" : "clone"]();
- current.priceText = current.price.toReal();
- if (isBid) {
- this._setForSale(current, isBid) || this._setReceive(current, isBid);
- } else {
- this._setReceive(current, isBid) || this._setForSale(current, isBid);
- }
- this.forceUpdate();
- }
-
- _orderbookClick(order) {
- const isBid = order.isBid();
- /*
- * Because we are using a bid order to construct an ask and vice versa,
- * totalToReceive becomes forSale, and totalForSale becomes toReceive
- */
- let forSale = order.totalToReceive({noCache: true});
- // let toReceive = order.totalForSale({noCache: true});
- let toReceive = forSale.times(order.sellPrice());
-
- let newPrice = new Price({
- base: isBid ? toReceive : forSale,
- quote: isBid ? forSale : toReceive
- });
-
- let current = this.state[isBid ? "bid" : "ask"];
- current.price = newPrice;
- current.priceText = newPrice.toReal();
-
- let newState = { // If isBid is true, newState defines a new ask order and vice versa
- [isBid ? "ask" : "bid"]: {
- for_sale: forSale,
- forSaleText: forSale.getAmount({real: true}),
- to_receive: toReceive,
- toReceiveText: toReceive.getAmount({real: true}),
- price: newPrice,
- priceText: newPrice.toReal()
- }
- };
-
- if (isBid) {
- this._setForSale(current, isBid) || this._setReceive(current, isBid);
- } else {
- this._setReceive(current, isBid) || this._setForSale(current, isBid);
- }
- this.setState(newState);
- }
-
- _borrowQuote() {
- this.refs.borrowQuote.show();
- }
-
- _borrowBase() {
- this.refs.borrowBase.show();
- }
-
- _onSelectIndicators() {
- this.refs.indicators.show();
- }
-
- _getSettlementInfo() {
- let {lowestCallPrice, feedPrice, quoteAsset} = this.props;
-
- let showCallLimit = false;
- if (feedPrice) {
- if (feedPrice.inverted) {
- showCallLimit = lowestCallPrice <= feedPrice.toReal();
- } else {
- showCallLimit = lowestCallPrice >= feedPrice.toReal();
- }
- }
- return !!(showCallLimit && lowestCallPrice && !quoteAsset.getIn(["bitasset", "is_prediction_market"]));
- }
-
- _changeIndicator(key) {
- let indicators = cloneDeep(this.state.indicators);
- indicators[key] = !indicators[key];
- this.setState({
- indicators
- });
-
- SettingsActions.changeViewSetting({
- indicators
- });
- }
-
- _changeIndicatorSetting(key, e) {
- e.preventDefault();
- let indicatorSettings = cloneDeep(this.state.indicatorSettings);
- let value = parseInt(e.target.value, 10);
- if (isNaN(value)) {
- value = 1;
- }
- indicatorSettings[key] = value;
-
- this.setState({
- indicatorSettings: indicatorSettings
- });
-
- SettingsActions.changeViewSetting({
- indicatorSettings: indicatorSettings
- });
- }
-
- onChangeFeeAsset(type, e) {
- e.preventDefault();
- if (type === "buy") {
- this.setState({
- buyFeeAssetIdx: e.target.value
- });
-
- SettingsActions.changeViewSetting({
- "buyFeeAssetIdx": e.target.value
- });
- } else {
- this.setState({
- sellFeeAssetIdx: e.target.value
- });
-
- SettingsActions.changeViewSetting({
- "sellFeeAssetIdx": e.target.value
- });
- }
- }
-
- onChangeChartHeight({value, increase}) {
- let newHeight = value ? value : this.state.chartHeight + (increase ? 20 : -20);
- console.log("newHeight", newHeight);
- this.setState({
- chartHeight: newHeight
- });
-
- SettingsActions.changeViewSetting({
- "chartHeight": newHeight
- });
- }
-
- _getFeeAssets(quote, base, coreAsset) {
- let { currentAccount } = this.props;
-
- function addMissingAsset(target, asset) {
- if (target.indexOf(asset) === -1) {
- target.push(asset);
- }
- }
-
- let sellFeeAssets = [coreAsset, quote === coreAsset ? base : quote];
- addMissingAsset(sellFeeAssets, quote);
- addMissingAsset(sellFeeAssets, base);
- let sellFeeAsset;
-
- let buyFeeAssets = [coreAsset, base === coreAsset ? quote : base];
- addMissingAsset(buyFeeAssets, quote);
- addMissingAsset(buyFeeAssets, base);
- let buyFeeAsset;
-
- let balances = {};
-
- currentAccount.get("balances", []).filter((balance, id) => {
- return (["1.3.0", quote.get("id"), base.get("id")].indexOf(id) >= 0);
- }).forEach((balance, id) => {
- let balanceObject = ChainStore.getObject(balance);
- balances[id] = {
- balance: balanceObject ? parseInt(balanceObject.get("balance"), 10) : 0,
- fee: this._getFee(ChainStore.getAsset(id))
- };
- });
-
- // Sell asset fee
- sellFeeAssets = sellFeeAssets.filter(a => {
- if (!balances[a.get("id")]) {
- return false;
- };
- return balances[a.get("id")].balance > balances[a.get("id")].fee.getAmount();
- });
-
- if (!sellFeeAssets.length) {
- sellFeeAsset = coreAsset;
- sellFeeAssets.push(coreAsset);
- } else {
- sellFeeAsset = sellFeeAssets[Math.min(sellFeeAssets.length - 1, this.state.sellFeeAssetIdx)];
- }
-
- // Buy asset fee
- buyFeeAssets = buyFeeAssets.filter(a => {
- if (!balances[a.get("id")]) {
- return false;
- };
- return balances[a.get("id")].balance > balances[a.get("id")].fee.getAmount();
- });
-
- if (!buyFeeAssets.length) {
- buyFeeAsset = coreAsset;
- buyFeeAssets.push(coreAsset);
- } else {
- buyFeeAsset = buyFeeAssets[Math.min(buyFeeAssets.length - 1, this.state.buyFeeAssetIdx)];
- }
-
- let sellFee = this._getFee(sellFeeAsset);
- let buyFee = this._getFee(buyFeeAsset);
-
- return {
- sellFeeAsset,
- sellFeeAssets,
- sellFee,
- buyFeeAsset,
- buyFeeAssets,
- buyFee
- };
- }
-
- _toggleBuySellPosition() {
- this.setState({
- buySellTop: !this.state.buySellTop
- });
-
- SettingsActions.changeViewSetting({
- buySellTop: !this.state.buySellTop
- });
- }
-
- _setReceive(state, isBid) {
- if (state.price.isValid() && state.for_sale.hasAmount()) {
- state.to_receive = state.for_sale.times(state.price, isBid);
- state.toReceiveText = state.to_receive.getAmount({real: true}).toString();
- return true;
- }
- return false;
- }
-
- _setForSale(state, isBid) {
- if (state.price.isValid() && state.to_receive.hasAmount()) {
- state.for_sale = state.to_receive.times(state.price, isBid);
- state.forSaleText = state.for_sale.getAmount({real: true}).toString();
- return true;
- }
- return false;
- }
-
- _setPrice(state) {
- if (state.for_sale.hasAmount() && state.to_receive.hasAmount()) {
- state.price = new Price({
- base: state.for_sale,
- quote: state.to_receive
- });
- state.priceText = state.price.toReal().toString();
- return true;
- }
- return false;
- }
-
- _onInputPrice(type, e) {
- let current = this.state[type];
- const isBid = type === "bid";
- current.price = new Price({
- base: current[isBid ? "for_sale" : "to_receive"],
- quote: current[isBid ? "to_receive" : "for_sale"],
- real: parseFloat(e.target.value) || 0
- });
-
- if (isBid) {
- this._setForSale(current, isBid) || this._setReceive(current, isBid);
- } else {
- this._setReceive(current, isBid) || this._setForSale(current, isBid);
- }
-
- current.priceText = e.target.value;
- this.forceUpdate();
- }
-
- _onInputSell(type, e) {
- let current = this.state[type];
- const isBid = type === "bid";
- current.for_sale.setAmount({real: parseFloat(e.target.value) || 0});
-
- if (current.price.isValid()) {
- this._setReceive(current, isBid);
- } else {
- this._setPrice(current);
- }
-
- current.forSaleText = e.target.value;
- this.forceUpdate();
- }
-
- _onInputReceive(type, e) {
- let current = this.state[type];
- const isBid = type === "bid";
- current.to_receive.setAmount({real: parseFloat(e.target.value) || 0});
-
- if (current.price.isValid()) {
- this._setForSale(current, isBid);
- } else {
- this._setPrice(current);
- }
-
- current.toReceiveText = e.target.value;
- this.forceUpdate();
- }
-
- isMarketFrozen() {
- let {baseAsset, quoteAsset} = this.props;
-
- let baseWhiteList = baseAsset.getIn(["options", "whitelist_markets"]).toJS();
- let quoteWhiteList = quoteAsset.getIn(["options", "whitelist_markets"]).toJS();
- let baseBlackList = baseAsset.getIn(["options", "blacklist_markets"]).toJS();
- let quoteBlackList = quoteAsset.getIn(["options", "blacklist_markets"]).toJS();
-
- if (quoteWhiteList.length && quoteWhiteList.indexOf(baseAsset.get("id") === -1)) {
- return {isFrozen: true, frozenAsset: quoteAsset.get("symbol")};
- }
- if (baseWhiteList.length && baseWhiteList.indexOf(quoteAsset.get("id") === -1)) {
- return {isFrozen: true, frozenAsset: baseAsset.get("symbol")};
- }
-
- if (quoteBlackList.length && quoteBlackList.indexOf(baseAsset.get("id") !== -1)) {
- return {isFrozen: true, frozenAsset: quoteAsset.get("symbol")};
- }
- if (baseBlackList.length && baseBlackList.indexOf(quoteAsset.get("id") !== -1)) {
- return {isFrozen: true, frozenAsset: baseAsset.get("symbol")};
- }
-
- return {isFrozen: false};
- }
-
- render() {
- let { currentAccount, marketLimitOrders, marketCallOrders, marketData, activeMarketHistory,
- invertedCalls, starredMarkets, quoteAsset, baseAsset, lowestCallPrice,
- marketStats, marketReady, marketSettleOrders, bucketSize, totals,
- feedPrice, buckets } = this.props;
-
- const {combinedBids, combinedAsks, lowestAsk, highestBid,
- flatBids, flatAsks, flatCalls, flatSettles} = marketData;
-
- let {bid, ask, leftOrderBook, showDepthChart, tools, chartHeight,
- buyDiff, sellDiff, indicators, indicatorSettings, width, buySellTop} = this.state;
- const {isFrozen, frozenAsset} = this.isMarketFrozen();
-
- let base = null, quote = null, accountBalance = null, quoteBalance = null,
- baseBalance = null, coreBalance = null, quoteSymbol, baseSymbol,
- showCallLimit = false, latestPrice, changeClass;
-
-
- let isNullAccount = currentAccount.get("id") === "1.2.3";
-
- const showVolumeChart = this.props.viewSettings.get("showVolumeChart", true);
-
- if (quoteAsset.size && baseAsset.size && currentAccount.size) {
- base = baseAsset;
- quote = quoteAsset;
- baseSymbol = base.get("symbol");
- quoteSymbol = quote.get("symbol");
-
- accountBalance = currentAccount.get("balances").toJS();
-
- if (accountBalance) {
- for (let id in accountBalance) {
- if (id === quote.get("id")) {
- quoteBalance = accountBalance[id];
- }
- if (id === base.get("id")) {
- baseBalance = accountBalance[id];
- }
- if (id === "1.3.0") {
- coreBalance = accountBalance[id];
- }
- }
- }
-
- showCallLimit = this._getSettlementInfo();
- }
-
- let quoteIsBitAsset = quoteAsset.get("bitasset_data_id") ? true : false;
- let baseIsBitAsset = baseAsset.get("bitasset_data_id") ? true : false;
-
- let spread = (lowestAsk && highestBid) ? lowestAsk.getPrice() - highestBid.getPrice() : 0;
-
- // Latest price
- if (activeMarketHistory.size) {
- // Orders come in pairs, first is driver. Third entry is first of second pair.
- let latest_two = activeMarketHistory.take(3);
- let latest = latest_two.first();
- let second_latest = latest_two.last();
- let paysAsset, receivesAsset, isAsk = false;
- if (latest.pays.asset_id === base.get("id")) {
- paysAsset = base;
- receivesAsset = quote;
- isAsk = true;
- } else {
- paysAsset = quote;
- receivesAsset = base;
- }
- let flipped = base.get("id").split(".")[2] > quote.get("id").split(".")[2];
- latestPrice = market_utils.parse_order_history(latest, paysAsset, receivesAsset, isAsk, flipped);
-
- isAsk = false;
- if (second_latest) {
- if (second_latest.pays.asset_id === base.get("id")) {
- paysAsset = base;
- receivesAsset = quote;
- isAsk = true;
- } else {
- paysAsset = quote;
- receivesAsset = base;
- }
-
- let oldPrice = market_utils.parse_order_history(second_latest, paysAsset, receivesAsset, isAsk, flipped);
- changeClass = latestPrice.full === oldPrice.full ? "" : latestPrice.full - oldPrice.full > 0 ? "change-up" : "change-down";
- }
- }
-
- // Fees
- let coreAsset = ChainStore.getAsset("1.3.0");
- if (!coreAsset) {
- return null;
- }
-
- let {
- sellFeeAsset,
- sellFeeAssets,
- sellFee,
- buyFeeAsset,
- buyFeeAssets,
- buyFee
- } = this._getFeeAssets(quote, base, coreAsset);
-
- // Decimals
- let hasPrediction = base.getIn(["bitasset", "is_prediction_market"]) || quote.getIn(["bitasset", "is_prediction_market"]);
-
- let description = null;
-
- if (hasPrediction) {
- description = quoteAsset.getIn(["options", "description"]);
- description = assetUtils.parseDescription(description).main;
- }
-
- let smallScreen = false;
- if (width < 1000) {
- smallScreen = true;
- leftOrderBook = false;
- }
-
- let orderMultiplier = leftOrderBook ? 2 : 1;
-
- let buyForm = isFrozen ? null : (
-
- );
-
- let sellForm = isFrozen ? null : (
-
- );
-
- let orderBook = (
-
- );
-
- return (
-
-
- {/* Main vertical block with content */}
-
- {/* Left Column - Open Orders */}
- {leftOrderBook ? (
-
- {orderBook}
-
) : null}
-
- {/* Center Column */}
-
-
- {/* Top bar with info */}
-
{
- let tools = cloneDeep(this.state.tools);
- for (let k in tools) {
- if (k === key) {
- tools[k] = !tools[k];
- } else {
- tools[k] = false;
- }
- }
- this.setState({tools}, () => {
- this.setState({tools: {fib: false, trendline: false}});
- });
- }}
- onChangeChartHeight={this.onChangeChartHeight.bind(this)}
- chartHeight={chartHeight}
- showVolumeChart={showVolumeChart}
- onToggleVolume={() => {SettingsActions.changeViewSetting({showVolumeChart: !showVolumeChart});}}
- onChangeIndicatorSetting={this._changeIndicatorSetting.bind(this)}
- indicatorSettings={indicatorSettings}
- />
-
-
- {!showDepthChart ? (
-
- {/* Price history chart */}
-
1100 ? chartHeight : chartHeight - 125}
- leftOrderBook={leftOrderBook}
- marketReady={marketReady}
- indicators={indicators}
- indicatorSettings={indicatorSettings}
- bucketSize={bucketSize}
- latest={latestPrice}
- verticalOrderbook={leftOrderBook}
- theme={this.props.settings.get("themes")}
- zoom={this.state.currentPeriod}
- tools={tools}
- showVolumeChart={showVolumeChart}
- />
- ) : (
-
- 1100 ? this.state.chartHeight : this.state.chartHeight - 125}
- onClick={this._depthChartClick.bind(this, base, quote)}
- settlementPrice={(!hasPrediction && feedPrice) && feedPrice.toReal()}
- spread={spread}
- LCP={showCallLimit ? lowestCallPrice : null}
- leftOrderBook={leftOrderBook}
- hasPrediction={hasPrediction}
- noFrame={false}
- verticalOrderbook={leftOrderBook}
- theme={this.props.settings.get("themes")}
- />
-
)}
-
-
- {hasPrediction ?
: null}
-
- {isFrozen ?
: null}
- {buyForm}
- {sellForm}
-
-
-
- {!leftOrderBook ? orderBook : null}
-
-
-
-
-
- {marketLimitOrders.size > 0 && base && quote ? (
-
) : null}
-
-
-
- {/* Settle Orders */}
-
- {(base.get("id") === "1.3.0" || quote.get("id") === "1.3.0") ? (
-
) : null}
-
-
-
{ /* end CenterContent */}
-
-
- {/* End of Main Content Column */}
-
- {/* Right Column - Market History */}
-
- {/* Market History */}
-
-
-
- {/*
-
-
*/}
-
-
- {!isNullAccount && quoteIsBitAsset ?
-
: null}
- {!isNullAccount && baseIsBitAsset ?
-
: null}
- {/* End of Second Vertical Block */}
-
- );
- }
+ constructor(props) {
+ super();
+
+ this.state = this._initialState(props);
+
+ this._getWindowSize = debounce(this._getWindowSize.bind(this), 150);
+ }
+
+ _initialState(props) {
+ let ws = props.viewSettings;
+ let bid = {
+ forSaleText: "",
+ toReceiveText: "",
+ priceText: "",
+ for_sale: new Asset({
+ asset_id: props.baseAsset.get("id"),
+ precision: props.baseAsset.get("precision")
+ }),
+ to_receive: new Asset({
+ asset_id: props.quoteAsset.get("id"),
+ precision: props.quoteAsset.get("precision")
+ })
+ };
+ bid.price = new Price({base: bid.for_sale, quote: bid.to_receive});
+ let ask = {
+ forSaleText: "",
+ toReceiveText: "",
+ priceText: "",
+ for_sale: new Asset({
+ asset_id: props.quoteAsset.get("id"),
+ precision: props.quoteAsset.get("precision")
+ }),
+ to_receive: new Asset({
+ asset_id: props.baseAsset.get("id"),
+ precision: props.baseAsset.get("precision")
+ })
+ };
+ ask.price = new Price({base: ask.for_sale, quote: ask.to_receive});
+
+ /* Make sure the indicators objects only contains the current indicators */
+ let savedIndicators = ws.get("indicators", {});
+ let indicators = {};
+ [["sma", true], ["ema1", false], ["ema2", false], ["smaVolume", true], ["macd", false], ["bb", false]].forEach(i => {
+ indicators[i[0]] = savedIndicators[i[0]] || i[1];
+ });
+
+ let savedIndicatorsSettings = ws.get("indicatorSettings", {});
+ let indicatorSettings = {};
+ [["sma", 7], ["ema1", 20], ["ema2", 50], ["smaVolume", 30]].forEach(i => {
+ indicatorSettings[i[0]] = savedIndicatorsSettings[i[0]] || i[1];
+ });
+
+ return {
+ history: [],
+ buySellOpen: ws.get("buySellOpen", true),
+ bid,
+ ask,
+ flipBuySell: ws.get("flipBuySell", false),
+ favorite: false,
+ showDepthChart: ws.get("showDepthChart", false),
+ leftOrderBook: ws.get("leftOrderBook", false),
+ buyDiff: false,
+ sellDiff: false,
+ indicators,
+ buySellTop: ws.get("buySellTop", true),
+ buyFeeAssetIdx: ws.get("buyFeeAssetIdx", 0),
+ sellFeeAssetIdx: ws.get("sellFeeAssetIdx", 0),
+ indicatorSettings,
+ tools: {
+ fib: false,
+ trendline: false
+ },
+ height: window.innerHeight,
+ width: window.innerWidth,
+ chartHeight: ws.get("chartHeight", 425),
+ currentPeriod: ws.get("currentPeriod", 3600* 24 * 30 * 3) // 3 months
+ };
+ }
+
+ static propTypes = {
+ marketCallOrders: PropTypes.object.isRequired,
+ activeMarketHistory: PropTypes.object.isRequired,
+ viewSettings: PropTypes.object.isRequired,
+ priceData: PropTypes.array.isRequired,
+ volumeData: PropTypes.array.isRequired
+ };
+
+ static defaultProps = {
+ marketCallOrders: [],
+ activeMarketHistory: {},
+ viewSettings: {},
+ priceData: [],
+ volumeData: []
+ };
+
+ componentDidMount() {
+ let centerContainer = this.refs.center;
+ if (centerContainer) {
+ Ps.initialize(centerContainer);
+ }
+ SettingsActions.changeViewSetting.defer({
+ lastMarket: this.props.quoteAsset.get("symbol") + "_" + this.props.baseAsset.get("symbol")
+ });
+
+ window.addEventListener("resize", this._getWindowSize, false);
+ }
+
+ shouldComponentUpdate(nextProps) {
+ if (!nextProps.marketReady && !this.props.marketReady) {
+ return false;
+ }
+ return true;
+ };
+
+ _getWindowSize() {
+ let { innerHeight, innerWidth } = window;
+ if (innerHeight !== this.state.height || innerWidth !== this.state.width) {
+ this.setState({
+ height: innerHeight,
+ width: innerWidth
+ });
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.quoteAsset.get("symbol") !== this.props.quoteAsset.get("symbol") || nextProps.baseAsset.get("symbol") !== this.props.baseAsset.get("symbol")) {
+ this.setState(this._initialState(nextProps));
+
+ return SettingsActions.changeViewSetting({
+ lastMarket: nextProps.quoteAsset.get("symbol") + "_" + nextProps.baseAsset.get("symbol")
+ });
+ }
+
+ if (this.props.sub && nextProps.bucketSize !== this.props.bucketSize) {
+ return this._changeBucketSize(nextProps.bucketSize);
+ }
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener("resize", this._getWindowSize, false);
+ }
+
+ _getFee(asset) {
+ let fee = utils.estimateFee("limit_order_create", [], ChainStore.getObject("2.0.0")) || 0;
+ const coreFee = new Asset({
+ amount: fee
+ });
+ if (!asset || asset.get("id") === "1.3.0") return coreFee;
+
+ const cer = asset.getIn(["options", "core_exchange_rate"]).toJS();
+
+ const cerBase = new Asset({
+ asset_id: cer.base.asset_id,
+ amount: cer.base.amount,
+ precision: ChainStore.getAsset(cer.base.asset_id).get("precision")
+ });
+ const cerQuote = new Asset({
+ asset_id: cer.quote.asset_id,
+ amount: cer.quote.amount,
+ precision: ChainStore.getAsset(cer.quote.asset_id).get("precision")
+ });
+ const cerPrice = new Price({
+ base: cerBase, quote: cerQuote
+ });
+ const convertedFee = coreFee.times(cerPrice);
+
+ return convertedFee;
+ }
+
+ _verifyFee(fee, sellAmount, sellBalance, coreBalance) {
+ let coreFee = this._getFee();
+
+ let sellSum = fee.getAmount() + sellAmount;
+ if (fee.asset_id === "1.3.0") {
+ if (coreFee.getAmount() <= coreBalance) {
+ return "1.3.0";
+ } else {
+ return null;
+ }
+ } else {
+ if (sellSum <= sellBalance) { // Sufficient balance in asset to pay fee
+ return fee.asset_id;
+ } else if (coreFee.getAmount() <= coreBalance && fee.asset_id !== "1.3.0") { // Sufficient balance in core asset to pay fee
+ return "1.3.0";
+ } else {
+ return null; // Unable to pay fee in either asset
+ }
+ }
+ }
+
+ _createLimitOrderConfirm(buyAsset, sellAsset, sellBalance, coreBalance, feeAsset, type, short = true, e) {
+ e.preventDefault();
+ let {highestBid, lowestAsk} = this.props.marketData;
+ let current = this.state[type === "sell" ? "ask" : "bid"];
+
+ sellBalance = current.for_sale.clone(sellBalance ? parseInt(ChainStore.getObject(sellBalance).toJS().balance, 10) : 0);
+ coreBalance = new Asset({
+ amount: coreBalance ? parseInt(ChainStore.getObject(coreBalance).toJS().balance, 10) : 0
+ });
+
+ let fee = this._getFee(feeAsset);
+
+ let feeID = this._verifyFee(fee, current.for_sale.getAmount(), sellBalance.getAmount(), coreBalance.getAmount());
+ if (!feeID) {
+ return notify.addNotification({
+ message: "Insufficient funds to pay fees",
+ level: "error"
+ });
+ }
+
+ if (type === "buy" && lowestAsk) {
+ let diff = this.state.bid.price.toReal() / lowestAsk.getPrice();
+ if (diff > 1.20) {
+ this.refs.buy.show();
+ return this.setState({
+ buyDiff: diff
+ });
+ }
+ } else if (type === "sell" && highestBid) {
+ let diff = 1 / (this.state.ask.price.toReal() / highestBid.getPrice());
+ if (diff > 1.20) {
+ this.refs.sell.show();
+ return this.setState({
+ sellDiff: diff
+ });
+ }
+ }
+
+ let isPredictionMarket = sellAsset.getIn(["bitasset", "is_prediction_market"]);
+
+ if (current.for_sale.gt(sellBalance) && !isPredictionMarket) {
+ return notify.addNotification({
+ message: "Insufficient funds to place current. Required: " + current.for_sale.getAmount() + " " + sellAsset.get("symbol"),
+ level: "error"
+ });
+ }
+ //
+ if (!(current.for_sale.getAmount() > 0 && current.to_receive.getAmount() > 0)) {
+ return notify.addNotification({
+ message: "Please enter a valid amount and price",
+ level: "error"
+ });
+ }
+ //
+ if (type === "sell" && isPredictionMarket && short) {
+ return this._createPredictionShort(feeID);
+ }
+
+
+
+ this._createLimitOrder(type, feeID);
+ }
+
+ _createLimitOrder(type, feeID) {
+ let current = this.state[type === "sell" ? "ask" : "bid"];
+ const order = new LimitOrderCreate({
+ for_sale: current.for_sale,
+ to_receive: current.to_receive,
+ seller: this.props.currentAccount.get("id"),
+ fee: {
+ asset_id: feeID,
+ amount: 0
+ }
+ });
+
+ console.log("order:", JSON.stringify(order.toObject()));
+ return MarketsActions.createLimitOrder2(order).then((result) => {
+ if (result.error) {
+ if (result.error.message !== "wallet locked")
+ notify.addNotification({
+ message: "Unknown error. Failed to place order for " + current.to_receive.getAmount({real: true}) + " " + current.to_receive.asset_id,
+ level: "error"
+ });
+ }
+ // console.log("order success");
+ }).catch(e => {
+ console.log("order failed:", e);
+ });
+ }
+
+ _createPredictionShort(feeID) {
+ let current = this.state.ask;
+ const order = new LimitOrderCreate({
+ for_sale: current.for_sale,
+ to_receive: current.to_receive,
+ seller: this.props.currentAccount.get("id"),
+ fee: {
+ asset_id: feeID,
+ amount: 0
+ }
+ });
+
+ Promise.all([
+ FetchChain("getAsset", this.props.quoteAsset.getIn(["bitasset", "options", "short_backing_asset"]))
+ ]).then(assets => {
+ let [backingAsset] = assets;
+ let collateral = new Asset({
+ amount: order.amount_for_sale.getAmount(),
+ asset_id: backingAsset.get("id"),
+ precision: backingAsset.get("precision")
+ });
+
+ MarketsActions.createPredictionShort(
+ order,
+ collateral
+ ).then(result => {
+ if (result.error) {
+ if (result.error.message !== "wallet locked")
+ notify.addNotification({
+ message: "Unknown error. Failed to place order for " + buyAssetAmount + " " + buyAsset.symbol,
+ level: "error"
+ });
+ }
+ });
+ });
+ }
+
+ _forceBuy(type, feeAsset, sellBalance, coreBalance) {
+ let current = this.state[type === "sell" ? "ask" : "bid"];
+ // Convert fee to relevant asset fee and check if user has sufficient balance
+ sellBalance = current.for_sale.clone(sellBalance ? parseInt(ChainStore.getObject(sellBalance).get("balance"), 10) : 0);
+ coreBalance = new Asset({
+ amount: coreBalance ? parseInt(ChainStore.getObject(coreBalance).toJS().balance, 10) : 0
+ });
+ let fee = this._getFee(feeAsset);
+ let feeID = this._verifyFee(fee, current.for_sale.getAmount(), sellBalance.getAmount(), coreBalance.getAmount());
+
+ if (feeID) {
+ this._createLimitOrder(type, feeID);
+ } else {
+ console.error("Unable to pay fees, aborting limit order creation");
+ }
+ }
+
+ _forceSell(type, feeAsset, sellBalance, coreBalance) {
+ let current = this.state[type === "sell" ? "ask" : "bid"];
+ // Convert fee to relevant asset fee and check if user has sufficient balance
+ sellBalance = current.for_sale.clone(sellBalance ? parseInt(ChainStore.getObject(sellBalance).get("balance"), 10) : 0);
+ coreBalance = new Asset({
+ amount: coreBalance ? parseInt(ChainStore.getObject(coreBalance).toJS().balance, 10) : 0
+ });
+ let fee = this._getFee(feeAsset);
+ let feeID = this._verifyFee(fee, current.for_sale.getAmount(), sellBalance.getAmount(), coreBalance.getAmount());
+
+ if (feeID) {
+ this._createLimitOrder(type, feeID);
+ } else {
+ console.error("Unable to pay fees, aborting limit order creation");
+ }
+ }
+
+ _cancelLimitOrder(orderID, e) {
+ e.preventDefault();
+ let { currentAccount } = this.props;
+ MarketsActions.cancelLimitOrder(
+ currentAccount.get("id"),
+ orderID // order id to cancel
+ );
+ }
+
+ _changeBucketSize(size, e) {
+ if (e) e.preventDefault();
+ if (size !== this.props.bucketSize) {
+ MarketsActions.changeBucketSize.defer(size);
+ let currentSub = this.props.sub.split("_");
+ MarketsActions.unSubscribeMarket.defer(currentSub[0], currentSub[1]);
+ this.props.subToMarket(this.props, size);
+ }
+ }
+
+ _changeZoomPeriod(size, e) {
+ e.preventDefault();
+ if (size !== this.state.currentPeriod) {
+ this.setState({
+ currentPeriod: size
+ });
+ SettingsActions.changeViewSetting({
+ currentPeriod: size
+ });
+ }
+ }
+
+ _depthChartClick(base, quote, power, e) {
+ e.preventDefault();
+ let {bid, ask} = this.state;
+
+ bid.price = new Price({
+ base: this.state.bid.for_sale,
+ quote: this.state.bid.to_receive,
+ real: e.xAxis[0].value / power
+ });
+ bid.priceText = bid.price.toReal();
+
+ ask.price = new Price({
+ base: this.state.ask.to_receive,
+ quote: this.state.ask.for_sale,
+ real: e.xAxis[0].value / power
+ });
+ ask.priceText = ask.price.toReal();
+ let newState = {
+ bid,
+ ask,
+ depthLine: bid.price.toReal()
+ };
+
+ this._setForSale(bid, true) || this._setReceive(bid, true);
+ this._setReceive(ask) || this._setForSale(ask);
+
+ this.setState(newState);
+ }
+
+ _flipBuySell() {
+ SettingsActions.changeViewSetting({
+ flipBuySell: !this.state.flipBuySell
+ });
+
+ this.setState({ flipBuySell: !this.state.flipBuySell });
+ }
+
+ _toggleOpenBuySell() {
+ SettingsActions.changeViewSetting({
+ buySellOpen: !this.state.buySellOpen
+ });
+
+ this.setState({ buySellOpen: !this.state.buySellOpen });
+ }
+
+ _toggleCharts() {
+ SettingsActions.changeViewSetting({
+ showDepthChart: !this.state.showDepthChart
+ });
+
+ this.setState({ showDepthChart: !this.state.showDepthChart });
+ }
+
+ _moveOrderBook() {
+ SettingsActions.changeViewSetting({
+ leftOrderBook: !this.state.leftOrderBook
+ });
+
+ this.setState({ leftOrderBook: !this.state.leftOrderBook });
+ }
+
+ _currentPriceClick(type, price) {
+ const isBid = type === "bid";
+ let current = this.state[type];
+ current.price = price[(isBid) ? "invert" : "clone"]();
+ current.priceText = current.price.toReal();
+ if (isBid) {
+ this._setForSale(current, isBid) || this._setReceive(current, isBid);
+ } else {
+ this._setReceive(current, isBid) || this._setForSale(current, isBid);
+ }
+ this.forceUpdate();
+ }
+
+ _orderbookClick(order) {
+ const isBid = order.isBid();
+ /*
+ * Because we are using a bid order to construct an ask and vice versa,
+ * totalToReceive becomes forSale, and totalForSale becomes toReceive
+ */
+ let forSale = order.totalToReceive({noCache: true});
+ // let toReceive = order.totalForSale({noCache: true});
+ let toReceive = forSale.times(order.sellPrice());
+
+ let newPrice = new Price({
+ base: isBid ? toReceive : forSale,
+ quote: isBid ? forSale : toReceive
+ });
+
+ let current = this.state[isBid ? "bid" : "ask"];
+ current.price = newPrice;
+ current.priceText = newPrice.toReal();
+
+ let newState = { // If isBid is true, newState defines a new ask order and vice versa
+ [isBid ? "ask" : "bid"]: {
+ for_sale: forSale,
+ forSaleText: forSale.getAmount({real: true}),
+ to_receive: toReceive,
+ toReceiveText: toReceive.getAmount({real: true}),
+ price: newPrice,
+ priceText: newPrice.toReal()
+ }
+ };
+
+ if (isBid) {
+ this._setForSale(current, isBid) || this._setReceive(current, isBid);
+ } else {
+ this._setReceive(current, isBid) || this._setForSale(current, isBid);
+ }
+ this.setState(newState);
+ }
+
+ _borrowQuote() {
+ this.refs.borrowQuote.show();
+ }
+
+ _borrowBase() {
+ this.refs.borrowBase.show();
+ }
+
+ _onSelectIndicators() {
+ this.refs.indicators.show();
+ }
+
+ _getSettlementInfo() {
+ let {lowestCallPrice, feedPrice, quoteAsset} = this.props;
+
+ let showCallLimit = false;
+ if (feedPrice) {
+ if (feedPrice.inverted) {
+ showCallLimit = lowestCallPrice <= feedPrice.toReal();
+ } else {
+ showCallLimit = lowestCallPrice >= feedPrice.toReal();
+ }
+ }
+ return !!(showCallLimit && lowestCallPrice && !quoteAsset.getIn(["bitasset", "is_prediction_market"]));
+ }
+
+ _changeIndicator(key) {
+ let indicators = cloneDeep(this.state.indicators);
+ indicators[key] = !indicators[key];
+ this.setState({
+ indicators
+ });
+
+ SettingsActions.changeViewSetting({
+ indicators
+ });
+ }
+
+ _changeIndicatorSetting(key, e) {
+ e.preventDefault();
+ let indicatorSettings = cloneDeep(this.state.indicatorSettings);
+ let value = parseInt(e.target.value, 10);
+ if (isNaN(value)) {
+ value = 1;
+ }
+ indicatorSettings[key] = value;
+
+ this.setState({
+ indicatorSettings: indicatorSettings
+ });
+
+ SettingsActions.changeViewSetting({
+ indicatorSettings: indicatorSettings
+ });
+ }
+
+ onChangeFeeAsset(type, e) {
+ e.preventDefault();
+ if (type === "buy") {
+ this.setState({
+ buyFeeAssetIdx: e.target.value
+ });
+
+ SettingsActions.changeViewSetting({
+ "buyFeeAssetIdx": e.target.value
+ });
+ } else {
+ this.setState({
+ sellFeeAssetIdx: e.target.value
+ });
+
+ SettingsActions.changeViewSetting({
+ "sellFeeAssetIdx": e.target.value
+ });
+ }
+ }
+
+ onChangeChartHeight({value, increase}) {
+ let newHeight = value ? value : this.state.chartHeight + (increase ? 20 : -20);
+ console.log("newHeight", newHeight);
+ this.setState({
+ chartHeight: newHeight
+ });
+
+ SettingsActions.changeViewSetting({
+ "chartHeight": newHeight
+ });
+ }
+
+ _getFeeAssets(quote, base, coreAsset) {
+ let { currentAccount } = this.props;
+
+ function addMissingAsset(target, asset) {
+ if (target.indexOf(asset) === -1) {
+ target.push(asset);
+ }
+ }
+
+ let sellFeeAssets = [coreAsset, quote === coreAsset ? base : quote];
+ addMissingAsset(sellFeeAssets, quote);
+ addMissingAsset(sellFeeAssets, base);
+ let sellFeeAsset;
+
+ let buyFeeAssets = [coreAsset, base === coreAsset ? quote : base];
+ addMissingAsset(buyFeeAssets, quote);
+ addMissingAsset(buyFeeAssets, base);
+ let buyFeeAsset;
+
+ let balances = {};
+
+ currentAccount.get("balances", []).filter((balance, id) => {
+ return (["1.3.0", quote.get("id"), base.get("id")].indexOf(id) >= 0);
+ }).forEach((balance, id) => {
+ let balanceObject = ChainStore.getObject(balance);
+ balances[id] = {
+ balance: balanceObject ? parseInt(balanceObject.get("balance"), 10) : 0,
+ fee: this._getFee(ChainStore.getAsset(id))
+ };
+ });
+
+ // Sell asset fee
+ sellFeeAssets = sellFeeAssets.filter(a => {
+ if (!balances[a.get("id")]) {
+ return false;
+ };
+ return balances[a.get("id")].balance > balances[a.get("id")].fee.getAmount();
+ });
+
+ if (!sellFeeAssets.length) {
+ sellFeeAsset = coreAsset;
+ sellFeeAssets.push(coreAsset);
+ } else {
+ sellFeeAsset = sellFeeAssets[Math.min(sellFeeAssets.length - 1, this.state.sellFeeAssetIdx)];
+ }
+
+ // Buy asset fee
+ buyFeeAssets = buyFeeAssets.filter(a => {
+ if (!balances[a.get("id")]) {
+ return false;
+ };
+ return balances[a.get("id")].balance > balances[a.get("id")].fee.getAmount();
+ });
+
+ if (!buyFeeAssets.length) {
+ buyFeeAsset = coreAsset;
+ buyFeeAssets.push(coreAsset);
+ } else {
+ buyFeeAsset = buyFeeAssets[Math.min(buyFeeAssets.length - 1, this.state.buyFeeAssetIdx)];
+ }
+
+ let sellFee = this._getFee(sellFeeAsset);
+ let buyFee = this._getFee(buyFeeAsset);
+
+ return {
+ sellFeeAsset,
+ sellFeeAssets,
+ sellFee,
+ buyFeeAsset,
+ buyFeeAssets,
+ buyFee
+ };
+ }
+
+ _toggleBuySellPosition() {
+ this.setState({
+ buySellTop: !this.state.buySellTop
+ });
+
+ SettingsActions.changeViewSetting({
+ buySellTop: !this.state.buySellTop
+ });
+ }
+
+ _setReceive(state, isBid) {
+ if (state.price.isValid() && state.for_sale.hasAmount()) {
+ state.to_receive = state.for_sale.times(state.price, isBid);
+ state.toReceiveText = state.to_receive.getAmount({real: true}).toString();
+ return true;
+ }
+ return false;
+ }
+
+ _setForSale(state, isBid) {
+ if (state.price.isValid() && state.to_receive.hasAmount()) {
+ state.for_sale = state.to_receive.times(state.price, isBid);
+ state.forSaleText = state.for_sale.getAmount({real: true}).toString();
+ return true;
+ }
+ return false;
+ }
+
+ _setPrice(state) {
+ if (state.for_sale.hasAmount() && state.to_receive.hasAmount()) {
+ state.price = new Price({
+ base: state.for_sale,
+ quote: state.to_receive
+ });
+ state.priceText = state.price.toReal().toString();
+ return true;
+ }
+ return false;
+ }
+
+ _onInputPrice(type, e) {
+ let current = this.state[type];
+ const isBid = type === "bid";
+ current.price = new Price({
+ base: current[isBid ? "for_sale" : "to_receive"],
+ quote: current[isBid ? "to_receive" : "for_sale"],
+ real: parseFloat(e.target.value) || 0
+ });
+
+ if (isBid) {
+ this._setForSale(current, isBid) || this._setReceive(current, isBid);
+ } else {
+ this._setReceive(current, isBid) || this._setForSale(current, isBid);
+ }
+
+ current.priceText = e.target.value;
+ this.forceUpdate();
+ }
+
+ _onInputSell(type, e) {
+ let current = this.state[type];
+ const isBid = type === "bid";
+ current.for_sale.setAmount({real: parseFloat(e.target.value) || 0});
+
+ if (current.price.isValid()) {
+ this._setReceive(current, isBid);
+ } else {
+ this._setPrice(current);
+ }
+
+ current.forSaleText = e.target.value;
+ this.forceUpdate();
+ }
+
+ _onInputReceive(type, e) {
+ let current = this.state[type];
+ const isBid = type === "bid";
+ current.to_receive.setAmount({real: parseFloat(e.target.value) || 0});
+
+ if (current.price.isValid()) {
+ this._setForSale(current, isBid);
+ } else {
+ this._setPrice(current);
+ }
+
+ current.toReceiveText = e.target.value;
+ this.forceUpdate();
+ }
+
+ isMarketFrozen() {
+ let {baseAsset, quoteAsset} = this.props;
+
+ let baseWhiteList = baseAsset.getIn(["options", "whitelist_markets"]).toJS();
+ let quoteWhiteList = quoteAsset.getIn(["options", "whitelist_markets"]).toJS();
+ let baseBlackList = baseAsset.getIn(["options", "blacklist_markets"]).toJS();
+ let quoteBlackList = quoteAsset.getIn(["options", "blacklist_markets"]).toJS();
+
+ if (quoteWhiteList.length && quoteWhiteList.indexOf(baseAsset.get("id") === -1)) {
+ return {isFrozen: true, frozenAsset: quoteAsset.get("symbol")};
+ }
+ if (baseWhiteList.length && baseWhiteList.indexOf(quoteAsset.get("id") === -1)) {
+ return {isFrozen: true, frozenAsset: baseAsset.get("symbol")};
+ }
+
+ if (quoteBlackList.length && quoteBlackList.indexOf(baseAsset.get("id") !== -1)) {
+ return {isFrozen: true, frozenAsset: quoteAsset.get("symbol")};
+ }
+ if (baseBlackList.length && baseBlackList.indexOf(quoteAsset.get("id") !== -1)) {
+ return {isFrozen: true, frozenAsset: baseAsset.get("symbol")};
+ }
+
+ return {isFrozen: false};
+ }
+
+ render() {
+ let { currentAccount, marketLimitOrders, marketCallOrders, marketData, activeMarketHistory,
+ invertedCalls, starredMarkets, quoteAsset, baseAsset, lowestCallPrice,
+ marketStats, marketReady, marketSettleOrders, bucketSize, totals,
+ feedPrice, buckets } = this.props;
+
+ const {combinedBids, combinedAsks, lowestAsk, highestBid,
+ flatBids, flatAsks, flatCalls, flatSettles} = marketData;
+
+ let {bid, ask, leftOrderBook, showDepthChart, tools, chartHeight,
+ buyDiff, sellDiff, indicators, indicatorSettings, width, buySellTop} = this.state;
+ const {isFrozen, frozenAsset} = this.isMarketFrozen();
+
+ let base = null, quote = null, accountBalance = null, quoteBalance = null,
+ baseBalance = null, coreBalance = null, quoteSymbol, baseSymbol,
+ showCallLimit = false, latestPrice, changeClass;
+
+
+ let isNullAccount = currentAccount.get("id") === "1.2.3";
+
+ const showVolumeChart = this.props.viewSettings.get("showVolumeChart", true);
+
+ if (quoteAsset.size && baseAsset.size && currentAccount.size) {
+ base = baseAsset;
+ quote = quoteAsset;
+ baseSymbol = base.get("symbol");
+ quoteSymbol = quote.get("symbol");
+
+ accountBalance = currentAccount.get("balances").toJS();
+
+ if (accountBalance) {
+ for (let id in accountBalance) {
+ if (id === quote.get("id")) {
+ quoteBalance = accountBalance[id];
+ }
+ if (id === base.get("id")) {
+ baseBalance = accountBalance[id];
+ }
+ if (id === "1.3.0") {
+ coreBalance = accountBalance[id];
+ }
+ }
+ }
+
+ showCallLimit = this._getSettlementInfo();
+ }
+
+ let quoteIsBitAsset = quoteAsset.get("bitasset_data_id") ? true : false;
+ let baseIsBitAsset = baseAsset.get("bitasset_data_id") ? true : false;
+
+ let spread = (lowestAsk && highestBid) ? lowestAsk.getPrice() - highestBid.getPrice() : 0;
+
+ // Latest price
+ if (activeMarketHistory.size) {
+ // Orders come in pairs, first is driver. Third entry is first of second pair.
+ let latest_two = activeMarketHistory.take(3);
+ let latest = latest_two.first();
+ let second_latest = latest_two.last();
+ let paysAsset, receivesAsset, isAsk = false;
+ if (latest.pays.asset_id === base.get("id")) {
+ paysAsset = base;
+ receivesAsset = quote;
+ isAsk = true;
+ } else {
+ paysAsset = quote;
+ receivesAsset = base;
+ }
+ let flipped = base.get("id").split(".")[2] > quote.get("id").split(".")[2];
+ latestPrice = market_utils.parse_order_history(latest, paysAsset, receivesAsset, isAsk, flipped);
+
+ isAsk = false;
+ if (second_latest) {
+ if (second_latest.pays.asset_id === base.get("id")) {
+ paysAsset = base;
+ receivesAsset = quote;
+ isAsk = true;
+ } else {
+ paysAsset = quote;
+ receivesAsset = base;
+ }
+
+ let oldPrice = market_utils.parse_order_history(second_latest, paysAsset, receivesAsset, isAsk, flipped);
+ changeClass = latestPrice.full === oldPrice.full ? "" : latestPrice.full - oldPrice.full > 0 ? "change-up" : "change-down";
+ }
+ }
+
+ // Fees
+ let coreAsset = ChainStore.getAsset("1.3.0");
+ if (!coreAsset) {
+ return null;
+ }
+
+ let {
+ sellFeeAsset,
+ sellFeeAssets,
+ sellFee,
+ buyFeeAsset,
+ buyFeeAssets,
+ buyFee
+ } = this._getFeeAssets(quote, base, coreAsset);
+
+ // Decimals
+ let hasPrediction = base.getIn(["bitasset", "is_prediction_market"]) || quote.getIn(["bitasset", "is_prediction_market"]);
+
+ let description = null;
+
+ if (hasPrediction) {
+ description = quoteAsset.getIn(["options", "description"]);
+ description = assetUtils.parseDescription(description).main;
+ }
+
+ let smallScreen = false;
+ if (width < 1000) {
+ smallScreen = true;
+ leftOrderBook = false;
+ }
+
+ let orderMultiplier = leftOrderBook ? 2 : 1;
+
+ let buyForm = isFrozen ? null : (
+
+ );
+
+ let sellForm = isFrozen ? null : (
+
+ );
+
+ let orderBook = (
+
+ );
+
+ return (
+
+
+ {/* Main vertical block with content */}
+
+ {/* Left Column - Open Orders */}
+ {leftOrderBook ? (
+
+ {orderBook}
+
) : null}
+
+ {/* Center Column */}
+
+
+ {/* Top bar with info */}
+
{
+ let tools = cloneDeep(this.state.tools);
+ for (let k in tools) {
+ if (k === key) {
+ tools[k] = !tools[k];
+ } else {
+ tools[k] = false;
+ }
+ }
+ this.setState({tools}, () => {
+ this.setState({tools: {fib: false, trendline: false}});
+ });
+ }}
+ onChangeChartHeight={this.onChangeChartHeight.bind(this)}
+ chartHeight={chartHeight}
+ showVolumeChart={showVolumeChart}
+ onToggleVolume={() => {SettingsActions.changeViewSetting({showVolumeChart: !showVolumeChart});}}
+ onChangeIndicatorSetting={this._changeIndicatorSetting.bind(this)}
+ indicatorSettings={indicatorSettings}
+ />
+
+
+ {!showDepthChart ? (
+
+ {/* Price history chart */}
+
1100 ? chartHeight : chartHeight - 125}
+ leftOrderBook={leftOrderBook}
+ marketReady={marketReady}
+ indicators={indicators}
+ indicatorSettings={indicatorSettings}
+ bucketSize={bucketSize}
+ latest={latestPrice}
+ verticalOrderbook={leftOrderBook}
+ theme={this.props.settings.get("themes")}
+ zoom={this.state.currentPeriod}
+ tools={tools}
+ showVolumeChart={showVolumeChart}
+ />
+ ) : (
+
+ 1100 ? this.state.chartHeight : this.state.chartHeight - 125}
+ onClick={this._depthChartClick.bind(this, base, quote)}
+ settlementPrice={(!hasPrediction && feedPrice) && feedPrice.toReal()}
+ spread={spread}
+ LCP={showCallLimit ? lowestCallPrice : null}
+ leftOrderBook={leftOrderBook}
+ hasPrediction={hasPrediction}
+ noFrame={false}
+ verticalOrderbook={leftOrderBook}
+ theme={this.props.settings.get("themes")}
+ />
+
)}
+
+
+ {hasPrediction ?
: null}
+
+ {isFrozen ?
: null}
+ {buyForm}
+ {sellForm}
+
+
+
+ {!leftOrderBook ? orderBook : null}
+
+
+
+
+
+ {marketLimitOrders.size > 0 && base && quote ? (
+
) : null}
+
+
+
+ {/* Settle Orders */}
+
+ {(base.get("id") === "1.3.0" || quote.get("id") === "1.3.0") ? (
+
) : null}
+
+
+
{ /* end CenterContent */}
+
+
+ {/* End of Main Content Column */}
+
+ {/* Right Column - Market History */}
+
+ {/* Market History */}
+
+
+
+ {/*
+
+
*/}
+
+
+ {!isNullAccount && quoteIsBitAsset ?
+
: null}
+ {!isNullAccount && baseIsBitAsset ?
+
: null}
+ {/* End of Second Vertical Block */}
+
+ );
+ }
}
export default Exchange;
diff --git a/web/app/components/Exchange/PriceChartD3.jsx b/web/app/components/Exchange/PriceChartD3.jsx
index 8438430e40..9256447095 100644
--- a/web/app/components/Exchange/PriceChartD3.jsx
+++ b/web/app/components/Exchange/PriceChartD3.jsx
@@ -3,10 +3,10 @@ import { format } from "d3-format";
import { timeFormat } from "d3-time-format";
import { ChartCanvas, Chart, series, scale, coordinates, tooltip, axes,
- indicator, helper, interactive } from "react-stockcharts";
+ indicator, helper, interactive } from "react-stockcharts";
const { CandlestickSeries, BarSeries, LineSeries, AreaSeries, BollingerSeries,
- MACDSeries } = series;
+ MACDSeries } = series;
const { XAxis, YAxis } = axes;
const { fitWidth } = helper;
const { discontinuousTimeScaleProvider } = scale;
@@ -21,393 +21,393 @@ import { cloneDeep } from "lodash";
import utils from "common/utils";
class CandleStickChartWithZoomPan extends React.Component {
- constructor(props) {
- super();
-
- const pricePrecision = props.base.get("precision");
- const volumePrecision = props.quote.get("precision");
-
- const priceFormat = format(`.${pricePrecision}f`);
- const timeFormatter = timeFormat("%Y-%m-%d %H:%M");
- const volumeFormat = format(`.${volumePrecision}r`);
-
- this.state = {
- enableTrendLine: false,
- enableFib: false,
- tools: [],
- priceFormat,
- timeFormatter,
- volumeFormat,
- margin: {left: 75, right: 75, top:20, bottom: 30},
- calculators: this._getCalculators(props)
- };
-
- this.onTrendLineComplete = this.onTrendLineComplete.bind(this);
- this.onFibComplete = this.onFibComplete.bind(this);
- this.onKeyPress = this.onKeyPress.bind(this);
- }
-
- componentDidMount() {
- document.addEventListener("keyup", this.onKeyPress);
- }
- componentWillUnmount() {
- document.removeEventListener("keyup", this.onKeyPress);
- }
-
- onTrendLineComplete() {
- this.setState({
- enableTrendLine: false
- });
- }
-
- onFibComplete() {
- this.setState({
- enableFib: false
- });
- }
-
- onKeyPress(e) {
- const tools = cloneDeep(this.state.tools);
- const ref = this.refs[tools[tools.length - 1]];
- var keyCode = e.which;
- switch (keyCode) {
- case 46: { // DEL
- if (ref) ref.removeLast();
- tools.pop();
- this.setState({tools});
- break;
- }
- case 27: { // ESC
- if (ref) ref.terminate();
- this.setState({
- [enableFib]: false
- });
- break;
- }
- }
- }
-
- componentWillReceiveProps(np) {
- let tools = cloneDeep(this.state.tools);
- if (np.tools && np.tools.trendline) {
- this.setState({enableTrendLine: true});
- tools.push("enableTrendLine");
- };
- if (np.tools && np.tools.fib) {
- this.setState({enableFib: true});
- tools.push("enableFib");
- };
- this.setState({tools});
-
- if (!utils.are_equal_shallow(np.indicators, this.props.indicators) ||
- !utils.are_equal_shallow(np.indicatorSettings, this.props.indicatorSettings)) {
- this.setState({calculators: this._getCalculators(np)});
- }
- }
-
- _getThemeColors(props = this.props) {
- return colors[props.theme];
- }
-
- _getCalculators(props = this.props) {
- const { positiveColor, negativeColor } = this._getThemeColors(props);
- const { indicatorSettings } = props;
- const calculators = {};
-
- calculators.sma = sma()
- .windowSize(parseInt(indicatorSettings["sma"], 10))
- .sourcePath("close")
- .stroke("#1f77b4")
- .fill("#1f77b4")
- .merge((d, c) => {d.sma = c;})
- .accessor(d => d.sma);
-
- calculators.ema1 = ema()
- .windowSize(parseInt(indicatorSettings["ema1"], 10))
- .merge((d, c) => {d.ema1 = c;})
- .accessor(d => d.ema1);
-
- calculators.ema2 = ema()
- .windowSize(parseInt(indicatorSettings["ema2"], 10))
- .merge((d, c) => {d.ema2 = c;})
- .accessor(d => d.ema2);
-
- calculators.smaVolume = sma()
- .windowSize(parseInt(indicatorSettings["smaVolume"], 10))
- .sourcePath("volume")
- .merge((d, c) => {d.smaVolume = c;})
- .stroke("#1f77b4")
- .fill("#1f77b4")
- .accessor(d => d.smaVolume);
-
- calculators.bb = bollingerBand()
- .merge((d, c) => {d.bb = c;})
- .accessor(d => d.bb);
-
- calculators.macd = macd()
- .fast(12)
- .slow(26)
- .signal(9)
- .stroke({macd: negativeColor, signal: positiveColor})
- .merge((d, c) => {d.macd = c;})
- .accessor(d => d.macd);
-
- return calculators;
- }
-
- _renderVolumeChart(chartMultiplier) {
- const { height, indicators } = this.props;
- const { timeFormatter, volumeFormat, calculators } = this.state;
- const { axisLineColor, volumeColor, indicatorLineColor } = this._getThemeColors();
-
- return d.volume, calculators.smaVolume.accessor()]}
- height={height * 0.2}
- origin={(w, h) => [0, h - (chartMultiplier * height * 0.2)]}
- >
-
- {indicators.macd ? null : }
-
-
- {indicators.macd ? null : }
-
-
-
-
- d.volume} fill={volumeColor} />
- {indicators.smaVolume ? : null}
-
- {indicators.smaVolume ? : null}
- d.volume} fill={volumeColor} />
-
- d.volume} displayFormat={volumeFormat} fill="#0F0F0F"/>
- d.volume} displayFormat={volumeFormat} fill="#0F0F0F"/>
- {indicators.smaVolume ? : null}
- {indicators.smaVolume ? : null}
- ;
- }
-
- _renderCandleStickChart(chartMultiplier, maCalcs) {
- const { height, width, showVolumeChart, indicators } = this.props;
- const { timeFormatter, volumeFormat, priceFormat, margin, enableTrendLine,
- enableFib, calculators } = this.state;
- const { positiveColor, negativeColor, axisLineColor, indicatorLineColor } = this._getThemeColors();
-
- let gridHeight = height - margin.top - margin.bottom;
- let gridWidth = width - margin.left - margin.right;
-
- let showGrid = false;
- let yGrid = showGrid ? { innerTickSize: -1 * gridWidth, tickStrokeOpacity: 0.1 } : {};
- let xGrid = showGrid ? { innerTickSize: -1 * gridHeight, tickStrokeOpacity: 0.1 } : {};
-
- return [d.high, d.low], calculators.ema1.accessor(), calculators.ema2.accessor(), calculators.sma.accessor()]}
- padding={{ top: 10, bottom: 20 }}
- >
- {indicators.macd || showVolumeChart ? null :
- }
-
-
- {indicators.macd || showVolumeChart ? null :
- }
-
-
-
-
-
- d.close > d.open ? positiveColor : negativeColor}
- fill={d => d.close > d.open ? positiveColor : negativeColor}
- opacity={0.8}
- />
- {indicators.bb ? : null}
-
- {indicators.sma ? : null}
- {indicators.ema1 ? : null}
- {indicators.ema2 ? : null}
-
- {indicators.sma ? : null}
- {indicators.ema1 ? : null}
- {indicators.ema2 ? : null}
-
- d.close} displayFormat={priceFormat} fill={d => d.close > d.open ? positiveColor : negativeColor}
- />
- d.close} displayFormat={priceFormat} fill={d => d.close > d.open ? positiveColor : negativeColor}
- />
-
-
-
- {maCalcs.length ?
- : null}
-
- {indicators.bb ? : null}
-
- [d.high, d.low]}
- onComplete={this.onTrendLineComplete}
- stroke={axisLineColor}
- fontStroke={axisLineColor}
- />
-
-
- ;
- }
-
- render() {
- const { width, priceData, height, ratio, theme, zoom,
- indicators, showVolumeChart } = this.props;
- const { timeFormatter, enableFib, enableTrendLine, margin, calculators } = this.state;
- const themeColors = colors[theme];
- const { axisLineColor, indicatorLineColor} = themeColors;
-
- let chartMultiplier = showVolumeChart ? 1 : 0; // Used to adjust the height of the charts and their positioning
- // if (indicators.bb) calc.push(bb);
-
- // Indicator calculators
- let calc = [], maCalcs = [], tooltipIncludes = ["sma", "ema1", "ema2", "smaVolume"];
-
- // if (showVolumeChart) maCalcs.push(calculators["smaVolume"]);
-
- for (let i in indicators) {
- if (indicators[i]) {
- // Don't add volume indicators if the volume chart is off
- if (i.toLowerCase().indexOf("volume") !== -1 && !showVolumeChart) continue;
- // Add active calculators
- calc.push(calculators[i]);
- // Add calculators needing tooltips
- if (tooltipIncludes.indexOf(i) !== -1) maCalcs.push(calculators[i]);
- }
- };
- if (indicators["macd"]) chartMultiplier++;
-
- const filterDate = new Date((new Date).getTime() - zoom * 1000);
- const filteredData = zoom === "all" ? priceData : priceData.filter(a => {
- return a.date > filterDate;
- });
-
- return (
- d.date} xScaleProvider={discontinuousTimeScaleProvider}
- xExtents={[filteredData[0].date, filteredData[filteredData.length - 1].date]}
- type="hybrid"
- className="ps-child no-overflow Stockcharts__wrapper ps-must-propagate"
- drawMode={enableTrendLine || enableFib}>
- >
- {showVolumeChart ? this._renderVolumeChart(chartMultiplier)
- : }
-
- {this._renderCandleStickChart(chartMultiplier, maCalcs)}
-
- {indicators.macd ?
- [0, h - ((chartMultiplier - (showVolumeChart ? 1 : 0) ) * height * 0.2)]}
- padding={{ top: 40, bottom: 10 }} >
-
-
-
-
-
-
-
-
- : /* Need to return an empty element here, null triggers an error */}
-
-
-
- );
- }
+ constructor(props) {
+ super();
+
+ const pricePrecision = props.base.get("precision");
+ const volumePrecision = props.quote.get("precision");
+
+ const priceFormat = format(`.${pricePrecision}f`);
+ const timeFormatter = timeFormat("%Y-%m-%d %H:%M");
+ const volumeFormat = format(`.${volumePrecision}r`);
+
+ this.state = {
+ enableTrendLine: false,
+ enableFib: false,
+ tools: [],
+ priceFormat,
+ timeFormatter,
+ volumeFormat,
+ margin: {left: 75, right: 75, top:20, bottom: 30},
+ calculators: this._getCalculators(props)
+ };
+
+ this.onTrendLineComplete = this.onTrendLineComplete.bind(this);
+ this.onFibComplete = this.onFibComplete.bind(this);
+ this.onKeyPress = this.onKeyPress.bind(this);
+ }
+
+ componentDidMount() {
+ document.addEventListener("keyup", this.onKeyPress);
+ }
+ componentWillUnmount() {
+ document.removeEventListener("keyup", this.onKeyPress);
+ }
+
+ onTrendLineComplete() {
+ this.setState({
+ enableTrendLine: false
+ });
+ }
+
+ onFibComplete() {
+ this.setState({
+ enableFib: false
+ });
+ }
+
+ onKeyPress(e) {
+ const tools = cloneDeep(this.state.tools);
+ const ref = this.refs[tools[tools.length - 1]];
+ var keyCode = e.which;
+ switch (keyCode) {
+ case 46: { // DEL
+ if (ref) ref.removeLast();
+ tools.pop();
+ this.setState({tools});
+ break;
+ }
+ case 27: { // ESC
+ if (ref) ref.terminate();
+ this.setState({
+ [enableFib]: false
+ });
+ break;
+ }
+ }
+ }
+
+ componentWillReceiveProps(np) {
+ let tools = cloneDeep(this.state.tools);
+ if (np.tools && np.tools.trendline) {
+ this.setState({enableTrendLine: true});
+ tools.push("enableTrendLine");
+ };
+ if (np.tools && np.tools.fib) {
+ this.setState({enableFib: true});
+ tools.push("enableFib");
+ };
+ this.setState({tools});
+
+ if (!utils.are_equal_shallow(np.indicators, this.props.indicators) ||
+ !utils.are_equal_shallow(np.indicatorSettings, this.props.indicatorSettings)) {
+ this.setState({calculators: this._getCalculators(np)});
+ }
+ }
+
+ _getThemeColors(props = this.props) {
+ return colors[props.theme];
+ }
+
+ _getCalculators(props = this.props) {
+ const { positiveColor, negativeColor } = this._getThemeColors(props);
+ const { indicatorSettings } = props;
+ const calculators = {};
+
+ calculators.sma = sma()
+ .windowSize(parseInt(indicatorSettings["sma"], 10))
+ .sourcePath("close")
+ .stroke("#1f77b4")
+ .fill("#1f77b4")
+ .merge((d, c) => {d.sma = c;})
+ .accessor(d => d.sma);
+
+ calculators.ema1 = ema()
+ .windowSize(parseInt(indicatorSettings["ema1"], 10))
+ .merge((d, c) => {d.ema1 = c;})
+ .accessor(d => d.ema1);
+
+ calculators.ema2 = ema()
+ .windowSize(parseInt(indicatorSettings["ema2"], 10))
+ .merge((d, c) => {d.ema2 = c;})
+ .accessor(d => d.ema2);
+
+ calculators.smaVolume = sma()
+ .windowSize(parseInt(indicatorSettings["smaVolume"], 10))
+ .sourcePath("volume")
+ .merge((d, c) => {d.smaVolume = c;})
+ .stroke("#1f77b4")
+ .fill("#1f77b4")
+ .accessor(d => d.smaVolume);
+
+ calculators.bb = bollingerBand()
+ .merge((d, c) => {d.bb = c;})
+ .accessor(d => d.bb);
+
+ calculators.macd = macd()
+ .fast(12)
+ .slow(26)
+ .signal(9)
+ .stroke({macd: negativeColor, signal: positiveColor})
+ .merge((d, c) => {d.macd = c;})
+ .accessor(d => d.macd);
+
+ return calculators;
+ }
+
+ _renderVolumeChart(chartMultiplier) {
+ const { height, indicators } = this.props;
+ const { timeFormatter, volumeFormat, calculators } = this.state;
+ const { axisLineColor, volumeColor, indicatorLineColor } = this._getThemeColors();
+
+ return d.volume, calculators.smaVolume.accessor()]}
+ height={height * 0.2}
+ origin={(w, h) => [0, h - (chartMultiplier * height * 0.2)]}
+ >
+
+ {indicators.macd ? null : }
+
+
+ {indicators.macd ? null : }
+
+
+
+
+ d.volume} fill={volumeColor} />
+ {indicators.smaVolume ? : null}
+
+ {indicators.smaVolume ? : null}
+ d.volume} fill={volumeColor} />
+
+ d.volume} displayFormat={volumeFormat} fill="#0F0F0F"/>
+ d.volume} displayFormat={volumeFormat} fill="#0F0F0F"/>
+ {indicators.smaVolume ? : null}
+ {indicators.smaVolume ? : null}
+ ;
+ }
+
+ _renderCandleStickChart(chartMultiplier, maCalcs) {
+ const { height, width, showVolumeChart, indicators } = this.props;
+ const { timeFormatter, volumeFormat, priceFormat, margin, enableTrendLine,
+ enableFib, calculators } = this.state;
+ const { positiveColor, negativeColor, axisLineColor, indicatorLineColor } = this._getThemeColors();
+
+ let gridHeight = height - margin.top - margin.bottom;
+ let gridWidth = width - margin.left - margin.right;
+
+ let showGrid = false;
+ let yGrid = showGrid ? { innerTickSize: -1 * gridWidth, tickStrokeOpacity: 0.1 } : {};
+ let xGrid = showGrid ? { innerTickSize: -1 * gridHeight, tickStrokeOpacity: 0.1 } : {};
+
+ return [d.high, d.low], calculators.ema1.accessor(), calculators.ema2.accessor(), calculators.sma.accessor()]}
+ padding={{ top: 10, bottom: 20 }}
+ >
+ {indicators.macd || showVolumeChart ? null :
+ }
+
+
+ {indicators.macd || showVolumeChart ? null :
+ }
+
+
+
+
+
+ d.close > d.open ? positiveColor : negativeColor}
+ fill={d => d.close > d.open ? positiveColor : negativeColor}
+ opacity={0.8}
+ />
+ {indicators.bb ? : null}
+
+ {indicators.sma ? : null}
+ {indicators.ema1 ? : null}
+ {indicators.ema2 ? : null}
+
+ {indicators.sma ? : null}
+ {indicators.ema1 ? : null}
+ {indicators.ema2 ? : null}
+
+ d.close} displayFormat={priceFormat} fill={d => d.close > d.open ? positiveColor : negativeColor}
+ />
+ d.close} displayFormat={priceFormat} fill={d => d.close > d.open ? positiveColor : negativeColor}
+ />
+
+
+
+ {maCalcs.length ?
+ : null}
+
+ {indicators.bb ? : null}
+
+ [d.high, d.low]}
+ onComplete={this.onTrendLineComplete}
+ stroke={axisLineColor}
+ fontStroke={axisLineColor}
+ />
+
+
+ ;
+ }
+
+ render() {
+ const { width, priceData, height, ratio, theme, zoom,
+ indicators, showVolumeChart } = this.props;
+ const { timeFormatter, enableFib, enableTrendLine, margin, calculators } = this.state;
+ const themeColors = colors[theme];
+ const { axisLineColor, indicatorLineColor} = themeColors;
+
+ let chartMultiplier = showVolumeChart ? 1 : 0; // Used to adjust the height of the charts and their positioning
+ // if (indicators.bb) calc.push(bb);
+
+ // Indicator calculators
+ let calc = [], maCalcs = [], tooltipIncludes = ["sma", "ema1", "ema2", "smaVolume"];
+
+ // if (showVolumeChart) maCalcs.push(calculators["smaVolume"]);
+
+ for (let i in indicators) {
+ if (indicators[i]) {
+ // Don't add volume indicators if the volume chart is off
+ if (i.toLowerCase().indexOf("volume") !== -1 && !showVolumeChart) continue;
+ // Add active calculators
+ calc.push(calculators[i]);
+ // Add calculators needing tooltips
+ if (tooltipIncludes.indexOf(i) !== -1) maCalcs.push(calculators[i]);
+ }
+ };
+ if (indicators["macd"]) chartMultiplier++;
+
+ const filterDate = new Date((new Date).getTime() - zoom * 1000);
+ const filteredData = zoom === "all" ? priceData : priceData.filter(a => {
+ return a.date > filterDate;
+ });
+
+ return (
+ d.date} xScaleProvider={discontinuousTimeScaleProvider}
+ xExtents={[filteredData[0].date, filteredData[filteredData.length - 1].date]}
+ type="hybrid"
+ className="ps-child no-overflow Stockcharts__wrapper ps-must-propagate"
+ drawMode={enableTrendLine || enableFib}>
+ >
+ {showVolumeChart ? this._renderVolumeChart(chartMultiplier)
+ : }
+
+ {this._renderCandleStickChart(chartMultiplier, maCalcs)}
+
+ {indicators.macd ?
+ [0, h - ((chartMultiplier - (showVolumeChart ? 1 : 0) ) * height * 0.2)]}
+ padding={{ top: 40, bottom: 10 }} >
+
+
+
+
+
+
+
+
+ : /* Need to return an empty element here, null triggers an error */}
+
+
+
+ );
+ }
}
CandleStickChartWithZoomPan = fitWidth(CandleStickChartWithZoomPan);
export default class Wrapper extends React.Component {
- shouldComponentUpdate(np) {
- if (!np.priceData.length) {
- return false;
- }
- return (
- !utils.are_equal_shallow(np.priceData, this.props.priceData) ||
- !utils.are_equal_shallow(np.indicators, this.props.indicators) ||
- !utils.are_equal_shallow(np.indicatorSettings, this.props.indicatorSettings) ||
- !utils.are_equal_shallow(np.tools, this.props.tools) ||
- np.height !== this.props.height ||
- np.zoom !== this.props.zoom ||
- np.showVolumeChart !== this.props.showVolumeChart
- );
- }
-
- render() {
- if (!this.props.priceData.length) {
- return null;
- }
-
- return (
-
- );
- }
+ shouldComponentUpdate(np) {
+ if (!np.priceData.length) {
+ return false;
+ }
+ return (
+ !utils.are_equal_shallow(np.priceData, this.props.priceData) ||
+ !utils.are_equal_shallow(np.indicators, this.props.indicators) ||
+ !utils.are_equal_shallow(np.indicatorSettings, this.props.indicatorSettings) ||
+ !utils.are_equal_shallow(np.tools, this.props.tools) ||
+ np.height !== this.props.height ||
+ np.zoom !== this.props.zoom ||
+ np.showVolumeChart !== this.props.showVolumeChart
+ );
+ }
+
+ render() {
+ if (!this.props.priceData.length) {
+ return null;
+ }
+
+ return (
+
+ );
+ }
}